mirror of
https://github.com/Infisical/infisical.git
synced 2025-07-05 04:29:09 +00:00
Compare commits
141 Commits
patch-mult
...
infisical/
Author | SHA1 | Date | |
---|---|---|---|
ed5044a102 | |||
ec7fe013fd | |||
a26ad6cfb0 | |||
dd0399d12e | |||
04456fe996 | |||
2605987289 | |||
7edcf5ff90 | |||
3947e3dabf | |||
fe6e5e09ac | |||
068eb9246d | |||
3472be480a | |||
df71ecffa0 | |||
68818beb38 | |||
e600b68684 | |||
ef03e9bf3b | |||
d3932d8f08 | |||
7057d399bc | |||
c63d57f086 | |||
a9ce3789b0 | |||
023a0d99ab | |||
5aadc41a4a | |||
4f38352765 | |||
cf5e367aba | |||
a70043b80d | |||
b94db5d674 | |||
bd6a89fa9a | |||
81513e4a75 | |||
a28b458653 | |||
9977329741 | |||
cd030b0370 | |||
6c86db7d4e | |||
d48e7eca2d | |||
30f3dac35f | |||
0e5f0eefc1 | |||
2a005d2654 | |||
42425d91d5 | |||
a0770baff2 | |||
f101366bce | |||
76c468ecc7 | |||
dcf315a524 | |||
f8a4b6365c | |||
e27d273e8f | |||
30dc2d0fcb | |||
12e217d200 | |||
a3a1c9d2e5 | |||
0f266ebe9e | |||
506e0b1342 | |||
579948ea6d | |||
958ad8236a | |||
b06b8294e9 | |||
cb9dabe03f | |||
9197530b43 | |||
1eae7d0c30 | |||
cc8119766a | |||
928d5a5240 | |||
32dd478894 | |||
c3f7c1d46b | |||
89644703a0 | |||
d20b897f28 | |||
70e022826e | |||
b7f5fa2cec | |||
7b444e91a8 | |||
7626dbb96e | |||
869be3c273 | |||
9a2355fe63 | |||
3929a82099 | |||
40e5c6ef66 | |||
6c95e75d0d | |||
d6c9e6db75 | |||
76f87a7708 | |||
366f03080d | |||
dfdd8e95f9 | |||
87df5a2749 | |||
c4797ea060 | |||
6e011a0b52 | |||
05ed00834a | |||
38b0edf510 | |||
56b9506b39 | |||
ae34e015db | |||
7c42768cd8 | |||
b4a9e0e62d | |||
30606093f4 | |||
16862a3b33 | |||
e800a455c4 | |||
ba0de6afcf | |||
bfc82105bd | |||
c8a3252c1a | |||
0bba1801b9 | |||
a61e92c49c | |||
985116c6f2 | |||
9945d249d6 | |||
8bc9a5efcd | |||
8329cbf299 | |||
9138ab8ed7 | |||
cf9169ad6f | |||
69b76aea64 | |||
c9a95023be | |||
9db5be1c91 | |||
a1b41ca454 | |||
6c252b4bfb | |||
aafddaa856 | |||
776f464bee | |||
104b0d6c60 | |||
e696bff004 | |||
d9c4c332ea | |||
120e482c6f | |||
7c9c65312b | |||
8a46cbd08f | |||
fa05639592 | |||
4c0e04528e | |||
7fe7056af4 | |||
2bd9ad0137 | |||
ee152f2d20 | |||
f21a13f388 | |||
68a30f4212 | |||
4d830f1d1a | |||
cd6caab508 | |||
ab093dfc85 | |||
b8e9417466 | |||
4eb08c64d4 | |||
d76760fa9c | |||
4d8f94a9dc | |||
abd8d6aa8a | |||
9117067ab5 | |||
3a1168c7e8 | |||
88a4fb84e6 | |||
a1e8f45a86 | |||
04dca9432d | |||
920b9a7dfa | |||
8fc4fd64f8 | |||
24f7ecc548 | |||
a5ca96f2df | |||
505ccdf8ea | |||
3897bd70fa | |||
4479e626c7 | |||
6640b55504 | |||
85f024c814 | |||
531fa634a2 | |||
772dd464f5 | |||
877b9a409e | |||
104a91647c |
@ -47,7 +47,7 @@ jobs:
|
||||
- name: Wait for container to be stable and check logs
|
||||
run: |
|
||||
SECONDS=0
|
||||
r HEALTHY=0
|
||||
HEALTHY=0
|
||||
while [ $SECONDS -lt 60 ]; do
|
||||
if docker ps | grep infisical-api | grep -q healthy; then
|
||||
echo "Container is healthy."
|
||||
|
@ -22,6 +22,9 @@ jobs:
|
||||
CLI_TESTS_SERVICE_TOKEN: ${{ secrets.CLI_TESTS_SERVICE_TOKEN }}
|
||||
CLI_TESTS_PROJECT_ID: ${{ secrets.CLI_TESTS_PROJECT_ID }}
|
||||
CLI_TESTS_ENV_SLUG: ${{ secrets.CLI_TESTS_ENV_SLUG }}
|
||||
CLI_TESTS_USER_EMAIL: ${{ secrets.CLI_TESTS_USER_EMAIL }}
|
||||
CLI_TESTS_USER_PASSWORD: ${{ secrets.CLI_TESTS_USER_PASSWORD }}
|
||||
CLI_TESTS_INFISICAL_VAULT_FILE_PASSPHRASE: ${{ secrets.CLI_TESTS_INFISICAL_VAULT_FILE_PASSPHRASE }}
|
||||
|
||||
goreleaser:
|
||||
runs-on: ubuntu-20.04
|
||||
@ -56,7 +59,7 @@ jobs:
|
||||
- uses: goreleaser/goreleaser-action@v4
|
||||
with:
|
||||
distribution: goreleaser-pro
|
||||
version: latest
|
||||
version: v1.26.2-pro
|
||||
args: release --clean
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GO_RELEASER_GITHUB_TOKEN }}
|
||||
|
10
.github/workflows/run-cli-tests.yml
vendored
10
.github/workflows/run-cli-tests.yml
vendored
@ -20,7 +20,12 @@ on:
|
||||
required: true
|
||||
CLI_TESTS_ENV_SLUG:
|
||||
required: true
|
||||
|
||||
CLI_TESTS_USER_EMAIL:
|
||||
required: true
|
||||
CLI_TESTS_USER_PASSWORD:
|
||||
required: true
|
||||
CLI_TESTS_INFISICAL_VAULT_FILE_PASSPHRASE:
|
||||
required: true
|
||||
jobs:
|
||||
test:
|
||||
defaults:
|
||||
@ -43,5 +48,8 @@ jobs:
|
||||
CLI_TESTS_SERVICE_TOKEN: ${{ secrets.CLI_TESTS_SERVICE_TOKEN }}
|
||||
CLI_TESTS_PROJECT_ID: ${{ secrets.CLI_TESTS_PROJECT_ID }}
|
||||
CLI_TESTS_ENV_SLUG: ${{ secrets.CLI_TESTS_ENV_SLUG }}
|
||||
CLI_TESTS_USER_EMAIL: ${{ secrets.CLI_TESTS_USER_EMAIL }}
|
||||
CLI_TESTS_USER_PASSWORD: ${{ secrets.CLI_TESTS_USER_PASSWORD }}
|
||||
INFISICAL_VAULT_FILE_PASSPHRASE: ${{ secrets.CLI_TESTS_INFISICAL_VAULT_FILE_PASSPHRASE }}
|
||||
|
||||
run: go test -v -count=1 ./test
|
||||
|
@ -0,0 +1,21 @@
|
||||
import { Knex } from "knex";
|
||||
|
||||
import { TableName } from "../schemas";
|
||||
|
||||
export async function up(knex: Knex): Promise<void> {
|
||||
const hasPitVersionLimitColumn = await knex.schema.hasColumn(TableName.Project, "pitVersionLimit");
|
||||
await knex.schema.alterTable(TableName.Project, (tb) => {
|
||||
if (!hasPitVersionLimitColumn) {
|
||||
tb.integer("pitVersionLimit").notNullable().defaultTo(10);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
export async function down(knex: Knex): Promise<void> {
|
||||
const hasPitVersionLimitColumn = await knex.schema.hasColumn(TableName.Project, "pitVersionLimit");
|
||||
await knex.schema.alterTable(TableName.Project, (tb) => {
|
||||
if (hasPitVersionLimitColumn) {
|
||||
tb.dropColumn("pitVersionLimit");
|
||||
}
|
||||
});
|
||||
}
|
@ -16,7 +16,8 @@ export const ProjectsSchema = z.object({
|
||||
createdAt: z.date(),
|
||||
updatedAt: z.date(),
|
||||
version: z.number().default(1),
|
||||
upgradeStatus: z.string().nullable().optional()
|
||||
upgradeStatus: z.string().nullable().optional(),
|
||||
pitVersionLimit: z.number().default(10)
|
||||
});
|
||||
|
||||
export type TProjects = z.infer<typeof ProjectsSchema>;
|
||||
|
@ -362,6 +362,7 @@ export const registerScimRouter = async (server: FastifyZodProvider) => {
|
||||
const groups = await req.server.services.scim.listScimGroups({
|
||||
orgId: req.permission.orgId,
|
||||
startIndex: req.query.startIndex,
|
||||
filter: req.query.filter,
|
||||
limit: req.query.count
|
||||
});
|
||||
|
||||
|
@ -1,6 +1,7 @@
|
||||
import { nanoid } from "nanoid";
|
||||
import { z } from "zod";
|
||||
|
||||
import { removeTrailingSlash } from "@app/lib/fn";
|
||||
import { readLimit, writeLimit } from "@app/server/config/rateLimiter";
|
||||
import { verifyAuth } from "@app/server/plugins/auth/verify-auth";
|
||||
import { sapPubSchema } from "@app/server/routes/sanitizedSchemas";
|
||||
@ -19,7 +20,11 @@ export const registerSecretApprovalPolicyRouter = async (server: FastifyZodProvi
|
||||
workspaceId: z.string(),
|
||||
name: z.string().optional(),
|
||||
environment: z.string(),
|
||||
secretPath: z.string().optional().nullable(),
|
||||
secretPath: z
|
||||
.string()
|
||||
.optional()
|
||||
.nullable()
|
||||
.transform((val) => (val ? removeTrailingSlash(val) : val)),
|
||||
approvers: z.string().array().min(1),
|
||||
approvals: z.number().min(1).default(1)
|
||||
})
|
||||
@ -63,7 +68,11 @@ export const registerSecretApprovalPolicyRouter = async (server: FastifyZodProvi
|
||||
name: z.string().optional(),
|
||||
approvers: z.string().array().min(1),
|
||||
approvals: z.number().min(1).default(1),
|
||||
secretPath: z.string().optional().nullable()
|
||||
secretPath: z
|
||||
.string()
|
||||
.optional()
|
||||
.nullable()
|
||||
.transform((val) => (val ? removeTrailingSlash(val) : val))
|
||||
})
|
||||
.refine((data) => data.approvals <= data.approvers.length, {
|
||||
path: ["approvals"],
|
||||
@ -157,7 +166,7 @@ export const registerSecretApprovalPolicyRouter = async (server: FastifyZodProvi
|
||||
querystring: z.object({
|
||||
workspaceId: z.string().trim(),
|
||||
environment: z.string().trim(),
|
||||
secretPath: z.string().trim()
|
||||
secretPath: z.string().trim().transform(removeTrailingSlash)
|
||||
}),
|
||||
response: {
|
||||
200: z.object({
|
||||
|
@ -77,7 +77,7 @@ type TLdapConfigServiceFactoryDep = {
|
||||
>;
|
||||
userAliasDAL: Pick<TUserAliasDALFactory, "create" | "findOne">;
|
||||
permissionService: Pick<TPermissionServiceFactory, "getOrgPermission">;
|
||||
licenseService: Pick<TLicenseServiceFactory, "getPlan">;
|
||||
licenseService: Pick<TLicenseServiceFactory, "getPlan" | "updateSubscriptionOrgMemberCount">;
|
||||
};
|
||||
|
||||
export type TLdapConfigServiceFactory = ReturnType<typeof ldapConfigServiceFactory>;
|
||||
@ -510,6 +510,7 @@ export const ldapConfigServiceFactory = ({
|
||||
return newUserAlias;
|
||||
});
|
||||
}
|
||||
await licenseService.updateSubscriptionOrgMemberCount(organization.id);
|
||||
|
||||
const user = await userDAL.transaction(async (tx) => {
|
||||
const newUser = await userDAL.findOne({ id: userAlias.userId }, tx);
|
||||
|
@ -50,7 +50,7 @@ type TSamlConfigServiceFactoryDep = {
|
||||
orgMembershipDAL: Pick<TOrgMembershipDALFactory, "create">;
|
||||
orgBotDAL: Pick<TOrgBotDALFactory, "findOne" | "create" | "transaction">;
|
||||
permissionService: Pick<TPermissionServiceFactory, "getOrgPermission">;
|
||||
licenseService: Pick<TLicenseServiceFactory, "getPlan">;
|
||||
licenseService: Pick<TLicenseServiceFactory, "getPlan" | "updateSubscriptionOrgMemberCount">;
|
||||
tokenService: Pick<TAuthTokenServiceFactory, "createTokenForUser">;
|
||||
smtpService: Pick<TSmtpService, "sendMail">;
|
||||
};
|
||||
@ -449,6 +449,7 @@ export const samlConfigServiceFactory = ({
|
||||
return newUser;
|
||||
});
|
||||
}
|
||||
await licenseService.updateSubscriptionOrgMemberCount(organization.id);
|
||||
|
||||
const isUserCompleted = Boolean(user.isAccepted);
|
||||
const providerAuthToken = jwt.sign(
|
||||
|
@ -18,6 +18,20 @@ export const buildScimUserList = ({
|
||||
};
|
||||
};
|
||||
|
||||
export const parseScimFilter = (filterToParse: string | undefined) => {
|
||||
if (!filterToParse) return {};
|
||||
const [parsedName, parsedValue] = filterToParse.split("eq").map((s) => s.trim());
|
||||
|
||||
let attributeName = parsedName;
|
||||
if (parsedName === "userName") {
|
||||
attributeName = "email";
|
||||
} else if (parsedName === "displayName") {
|
||||
attributeName = "name";
|
||||
}
|
||||
|
||||
return { [attributeName]: parsedValue.replace(/"/g, "") };
|
||||
};
|
||||
|
||||
export const buildScimUser = ({
|
||||
orgMembershipId,
|
||||
username,
|
||||
|
@ -30,7 +30,7 @@ import { UserAliasType } from "@app/services/user-alias/user-alias-types";
|
||||
import { TLicenseServiceFactory } from "../license/license-service";
|
||||
import { OrgPermissionActions, OrgPermissionSubjects } from "../permission/org-permission";
|
||||
import { TPermissionServiceFactory } from "../permission/permission-service";
|
||||
import { buildScimGroup, buildScimGroupList, buildScimUser, buildScimUserList } from "./scim-fns";
|
||||
import { buildScimGroup, buildScimGroupList, buildScimUser, buildScimUserList, parseScimFilter } from "./scim-fns";
|
||||
import {
|
||||
TCreateScimGroupDTO,
|
||||
TCreateScimTokenDTO,
|
||||
@ -184,18 +184,6 @@ export const scimServiceFactory = ({
|
||||
status: 403
|
||||
});
|
||||
|
||||
const parseFilter = (filterToParse: string | undefined) => {
|
||||
if (!filterToParse) return {};
|
||||
const [parsedName, parsedValue] = filterToParse.split("eq").map((s) => s.trim());
|
||||
|
||||
let attributeName = parsedName;
|
||||
if (parsedName === "userName") {
|
||||
attributeName = "email";
|
||||
}
|
||||
|
||||
return { [attributeName]: parsedValue.replace(/"/g, "") };
|
||||
};
|
||||
|
||||
const findOpts = {
|
||||
...(startIndex && { offset: startIndex - 1 }),
|
||||
...(limit && { limit })
|
||||
@ -204,7 +192,7 @@ export const scimServiceFactory = ({
|
||||
const users = await orgDAL.findMembership(
|
||||
{
|
||||
[`${TableName.OrgMembership}.orgId` as "id"]: orgId,
|
||||
...parseFilter(filter)
|
||||
...parseScimFilter(filter)
|
||||
},
|
||||
findOpts
|
||||
);
|
||||
@ -391,7 +379,7 @@ export const scimServiceFactory = ({
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
await licenseService.updateSubscriptionOrgMemberCount(org.id);
|
||||
return { user, orgMembership };
|
||||
});
|
||||
|
||||
@ -557,7 +545,7 @@ export const scimServiceFactory = ({
|
||||
return {}; // intentionally return empty object upon success
|
||||
};
|
||||
|
||||
const listScimGroups = async ({ orgId, startIndex, limit }: TListScimGroupsDTO) => {
|
||||
const listScimGroups = async ({ orgId, startIndex, limit, filter }: TListScimGroupsDTO) => {
|
||||
const plan = await licenseService.getPlan(orgId);
|
||||
if (!plan.groups)
|
||||
throw new BadRequestError({
|
||||
@ -580,7 +568,8 @@ export const scimServiceFactory = ({
|
||||
|
||||
const groups = await groupDAL.findGroups(
|
||||
{
|
||||
orgId
|
||||
orgId,
|
||||
...(filter && parseScimFilter(filter))
|
||||
},
|
||||
{
|
||||
offset: startIndex - 1,
|
||||
|
@ -66,6 +66,7 @@ export type TDeleteScimUserDTO = {
|
||||
|
||||
export type TListScimGroupsDTO = {
|
||||
startIndex: number;
|
||||
filter?: string;
|
||||
limit: number;
|
||||
orgId: string;
|
||||
};
|
||||
|
@ -4,6 +4,7 @@ import picomatch from "picomatch";
|
||||
import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service";
|
||||
import { ProjectPermissionActions, ProjectPermissionSub } from "@app/ee/services/permission/project-permission";
|
||||
import { BadRequestError } from "@app/lib/errors";
|
||||
import { removeTrailingSlash } from "@app/lib/fn";
|
||||
import { containsGlobPatterns } from "@app/lib/picomatch";
|
||||
import { TProjectEnvDALFactory } from "@app/services/project-env/project-env-dal";
|
||||
import { TProjectMembershipDALFactory } from "@app/services/project-membership/project-membership-dal";
|
||||
@ -207,7 +208,8 @@ export const secretApprovalPolicyServiceFactory = ({
|
||||
return sapPolicies;
|
||||
};
|
||||
|
||||
const getSecretApprovalPolicy = async (projectId: string, environment: string, secretPath: string) => {
|
||||
const getSecretApprovalPolicy = async (projectId: string, environment: string, path: string) => {
|
||||
const secretPath = removeTrailingSlash(path);
|
||||
const env = await projectEnvDAL.findOne({ slug: environment, projectId });
|
||||
if (!env) throw new BadRequestError({ message: "Environment not found" });
|
||||
|
||||
|
@ -81,8 +81,7 @@ export const secretSnapshotServiceFactory = ({
|
||||
const folder = await folderDAL.findBySecretPath(projectId, environment, path);
|
||||
if (!folder) throw new BadRequestError({ message: "Folder not found" });
|
||||
|
||||
const count = await snapshotDAL.countOfSnapshotsByFolderId(folder.id);
|
||||
return count;
|
||||
return snapshotDAL.countOfSnapshotsByFolderId(folder.id);
|
||||
};
|
||||
|
||||
const listSnapshots = async ({
|
||||
|
@ -1,3 +1,4 @@
|
||||
/* eslint-disable no-await-in-loop */
|
||||
import { Knex } from "knex";
|
||||
|
||||
import { TDbClient } from "@app/db";
|
||||
@ -11,6 +12,7 @@ import {
|
||||
} from "@app/db/schemas";
|
||||
import { DatabaseError } from "@app/lib/errors";
|
||||
import { ormify, selectAllTableCols, sqlNestRelationships } from "@app/lib/knex";
|
||||
import { logger } from "@app/lib/logger";
|
||||
|
||||
export type TSnapshotDALFactory = ReturnType<typeof snapshotDALFactory>;
|
||||
|
||||
@ -325,12 +327,152 @@ export const snapshotDALFactory = (db: TDbClient) => {
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Prunes excess snapshots from the database to ensure only a specified number of recent snapshots are retained for each folder.
|
||||
*
|
||||
* This function operates in three main steps:
|
||||
* 1. Pruning snapshots from root/non-versioned folders.
|
||||
* 2. Pruning snapshots from versioned folders.
|
||||
* 3. Removing orphaned snapshots that do not belong to any existing folder or folder version.
|
||||
*
|
||||
* The function processes snapshots in batches, determined by the `PRUNE_FOLDER_BATCH_SIZE` constant,
|
||||
* to manage the large datasets without overwhelming the DB.
|
||||
*
|
||||
* Steps:
|
||||
* - Fetch a batch of folder IDs.
|
||||
* - For each batch, use a Common Table Expression (CTE) to rank snapshots within each folder by their creation date.
|
||||
* - Identify and delete snapshots that exceed the project's point-in-time version limit (`pitVersionLimit`).
|
||||
* - Repeat the process for versioned folders.
|
||||
* - Finally, delete orphaned snapshots that do not have an associated folder.
|
||||
*/
|
||||
const pruneExcessSnapshots = async () => {
|
||||
const PRUNE_FOLDER_BATCH_SIZE = 10000;
|
||||
|
||||
try {
|
||||
let uuidOffset = "00000000-0000-0000-0000-000000000000";
|
||||
// cleanup snapshots from root/non-versioned folders
|
||||
// eslint-disable-next-line no-constant-condition, no-unreachable-loop
|
||||
while (true) {
|
||||
const folderBatch = await db(TableName.SecretFolder)
|
||||
.where("id", ">", uuidOffset)
|
||||
.where("isReserved", false)
|
||||
.orderBy("id", "asc")
|
||||
.limit(PRUNE_FOLDER_BATCH_SIZE)
|
||||
.select("id");
|
||||
|
||||
const batchEntries = folderBatch.map((folder) => folder.id);
|
||||
|
||||
if (folderBatch.length) {
|
||||
try {
|
||||
logger.info(`Pruning snapshots in [range=${batchEntries[0]}:${batchEntries[batchEntries.length - 1]}]`);
|
||||
await db(TableName.Snapshot)
|
||||
.with("snapshot_cte", (qb) => {
|
||||
void qb
|
||||
.from(TableName.Snapshot)
|
||||
.whereIn(`${TableName.Snapshot}.folderId`, batchEntries)
|
||||
.select(
|
||||
"folderId",
|
||||
`${TableName.Snapshot}.id as id`,
|
||||
db.raw(
|
||||
`ROW_NUMBER() OVER (PARTITION BY ${TableName.Snapshot}."folderId" ORDER BY ${TableName.Snapshot}."createdAt" DESC) AS row_num`
|
||||
)
|
||||
);
|
||||
})
|
||||
.join(TableName.SecretFolder, `${TableName.SecretFolder}.id`, `${TableName.Snapshot}.folderId`)
|
||||
.join(TableName.Environment, `${TableName.Environment}.id`, `${TableName.SecretFolder}.envId`)
|
||||
.join(TableName.Project, `${TableName.Project}.id`, `${TableName.Environment}.projectId`)
|
||||
.join("snapshot_cte", "snapshot_cte.id", `${TableName.Snapshot}.id`)
|
||||
.whereNull(`${TableName.SecretFolder}.parentId`)
|
||||
.whereRaw(`snapshot_cte.row_num > ${TableName.Project}."pitVersionLimit"`)
|
||||
.delete();
|
||||
} catch (err) {
|
||||
logger.error(
|
||||
`Failed to prune snapshots from root/non-versioned folders in range ${batchEntries[0]}:${
|
||||
batchEntries[batchEntries.length - 1]
|
||||
}`
|
||||
);
|
||||
} finally {
|
||||
uuidOffset = batchEntries[batchEntries.length - 1];
|
||||
}
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// cleanup snapshots from versioned folders
|
||||
uuidOffset = "00000000-0000-0000-0000-000000000000";
|
||||
// eslint-disable-next-line no-constant-condition
|
||||
while (true) {
|
||||
const folderBatch = await db(TableName.SecretFolderVersion)
|
||||
.select("folderId")
|
||||
.distinct("folderId")
|
||||
.where("folderId", ">", uuidOffset)
|
||||
.orderBy("folderId", "asc")
|
||||
.limit(PRUNE_FOLDER_BATCH_SIZE);
|
||||
|
||||
const batchEntries = folderBatch.map((folder) => folder.folderId);
|
||||
|
||||
if (folderBatch.length) {
|
||||
try {
|
||||
logger.info(`Pruning snapshots in range ${batchEntries[0]}:${batchEntries[batchEntries.length - 1]}`);
|
||||
await db(TableName.Snapshot)
|
||||
.with("snapshot_cte", (qb) => {
|
||||
void qb
|
||||
.from(TableName.Snapshot)
|
||||
.whereIn(`${TableName.Snapshot}.folderId`, batchEntries)
|
||||
.select(
|
||||
"folderId",
|
||||
`${TableName.Snapshot}.id as id`,
|
||||
db.raw(
|
||||
`ROW_NUMBER() OVER (PARTITION BY ${TableName.Snapshot}."folderId" ORDER BY ${TableName.Snapshot}."createdAt" DESC) AS row_num`
|
||||
)
|
||||
);
|
||||
})
|
||||
.join(
|
||||
TableName.SecretFolderVersion,
|
||||
`${TableName.SecretFolderVersion}.folderId`,
|
||||
`${TableName.Snapshot}.folderId`
|
||||
)
|
||||
.join(TableName.Environment, `${TableName.Environment}.id`, `${TableName.SecretFolderVersion}.envId`)
|
||||
.join(TableName.Project, `${TableName.Project}.id`, `${TableName.Environment}.projectId`)
|
||||
.join("snapshot_cte", "snapshot_cte.id", `${TableName.Snapshot}.id`)
|
||||
.whereRaw(`snapshot_cte.row_num > ${TableName.Project}."pitVersionLimit"`)
|
||||
.delete();
|
||||
} catch (err) {
|
||||
logger.error(
|
||||
`Failed to prune snapshots from versioned folders in range ${batchEntries[0]}:${
|
||||
batchEntries[batchEntries.length - 1]
|
||||
}`
|
||||
);
|
||||
} finally {
|
||||
uuidOffset = batchEntries[batchEntries.length - 1];
|
||||
}
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// cleanup orphaned snapshots (those that don't belong to an existing folder and folder version)
|
||||
await db(TableName.Snapshot)
|
||||
.whereNotIn("folderId", (qb) => {
|
||||
void qb
|
||||
.select("folderId")
|
||||
.from(TableName.SecretFolderVersion)
|
||||
.union((qb1) => void qb1.select("id").from(TableName.SecretFolder));
|
||||
})
|
||||
.delete();
|
||||
} catch (error) {
|
||||
throw new DatabaseError({ error, name: "SnapshotPrune" });
|
||||
}
|
||||
};
|
||||
|
||||
return {
|
||||
...secretSnapshotOrm,
|
||||
findById,
|
||||
findLatestSnapshotByFolderId,
|
||||
findRecursivelySnapshots,
|
||||
countOfSnapshotsByFolderId,
|
||||
findSecretSnapshotDataById
|
||||
findSecretSnapshotDataById,
|
||||
pruneExcessSnapshots
|
||||
};
|
||||
};
|
||||
|
@ -677,6 +677,8 @@ export const INTEGRATION = {
|
||||
secretAWSTag: "The tags for AWS secrets.",
|
||||
kmsKeyId: "The ID of the encryption key from AWS KMS.",
|
||||
shouldDisableDelete: "The flag to disable deletion of secrets in AWS Parameter Store.",
|
||||
shouldMaskSecrets: "Specifies if the secrets synced from Infisical to Gitlab should be marked as 'Masked'.",
|
||||
shouldProtectSecrets: "Specifies if the secrets synced from Infisical to Gitlab should be marked as 'Protected'.",
|
||||
shouldEnableDelete: "The flag to enable deletion of secrets"
|
||||
}
|
||||
},
|
||||
|
@ -39,7 +39,9 @@ const envSchema = z
|
||||
HTTPS_ENABLED: zodStrBool,
|
||||
// smtp options
|
||||
SMTP_HOST: zpStr(z.string().optional()),
|
||||
SMTP_SECURE: zodStrBool,
|
||||
SMTP_IGNORE_TLS: zodStrBool.default("false"),
|
||||
SMTP_REQUIRE_TLS: zodStrBool.default("true"),
|
||||
SMTP_TLS_REJECT_UNAUTHORIZED: zodStrBool.default("true"),
|
||||
SMTP_PORT: z.coerce.number().default(587),
|
||||
SMTP_USERNAME: zpStr(z.string().optional()),
|
||||
SMTP_PASSWORD: zpStr(z.string().optional()),
|
||||
@ -153,13 +155,20 @@ export const initEnvConfig = (logger: Logger) => {
|
||||
return envCfg;
|
||||
};
|
||||
|
||||
export const formatSmtpConfig = () => ({
|
||||
host: envCfg.SMTP_HOST,
|
||||
port: envCfg.SMTP_PORT,
|
||||
auth:
|
||||
envCfg.SMTP_USERNAME && envCfg.SMTP_PASSWORD
|
||||
? { user: envCfg.SMTP_USERNAME, pass: envCfg.SMTP_PASSWORD }
|
||||
: undefined,
|
||||
secure: envCfg.SMTP_SECURE,
|
||||
from: `"${envCfg.SMTP_FROM_NAME}" <${envCfg.SMTP_FROM_ADDRESS}>`
|
||||
});
|
||||
export const formatSmtpConfig = () => {
|
||||
return {
|
||||
host: envCfg.SMTP_HOST,
|
||||
port: envCfg.SMTP_PORT,
|
||||
auth:
|
||||
envCfg.SMTP_USERNAME && envCfg.SMTP_PASSWORD
|
||||
? { user: envCfg.SMTP_USERNAME, pass: envCfg.SMTP_PASSWORD }
|
||||
: undefined,
|
||||
secure: envCfg.SMTP_PORT === 465,
|
||||
from: `"${envCfg.SMTP_FROM_NAME}" <${envCfg.SMTP_FROM_ADDRESS}>`,
|
||||
ignoreTLS: envCfg.SMTP_IGNORE_TLS,
|
||||
requireTLS: envCfg.SMTP_REQUIRE_TLS,
|
||||
tls: {
|
||||
rejectUnauthorized: envCfg.SMTP_TLS_REJECT_UNAUTHORIZED
|
||||
}
|
||||
};
|
||||
};
|
||||
|
@ -5,7 +5,6 @@ import { createTransport } from "nodemailer";
|
||||
|
||||
import { formatSmtpConfig, getConfig } from "@app/lib/config/env";
|
||||
import { logger } from "@app/lib/logger";
|
||||
import { getTlsOption } from "@app/services/smtp/smtp-service";
|
||||
import { getServerCfg } from "@app/services/super-admin/super-admin-service";
|
||||
|
||||
type BootstrapOpt = {
|
||||
@ -44,7 +43,7 @@ export const bootstrapCheck = async ({ db }: BootstrapOpt) => {
|
||||
console.info("Testing smtp connection");
|
||||
|
||||
const smtpCfg = formatSmtpConfig();
|
||||
await createTransport({ ...smtpCfg, ...getTlsOption(smtpCfg.host, smtpCfg.secure) })
|
||||
await createTransport(smtpCfg)
|
||||
.verify()
|
||||
.then(async () => {
|
||||
console.info("SMTP successfully connected");
|
||||
|
@ -824,6 +824,9 @@ export const registerRoutes = async (
|
||||
const dailyResourceCleanUp = dailyResourceCleanUpQueueServiceFactory({
|
||||
auditLogDAL,
|
||||
queueService,
|
||||
secretVersionDAL,
|
||||
secretFolderVersionDAL: folderVersionDAL,
|
||||
snapshotDAL,
|
||||
identityAccessTokenDAL,
|
||||
secretSharingDAL
|
||||
});
|
||||
|
@ -334,6 +334,44 @@ export const registerProjectRouter = async (server: FastifyZodProvider) => {
|
||||
}
|
||||
});
|
||||
|
||||
server.route({
|
||||
method: "PUT",
|
||||
url: "/:workspaceSlug/version-limit",
|
||||
config: {
|
||||
rateLimit: writeLimit
|
||||
},
|
||||
schema: {
|
||||
params: z.object({
|
||||
workspaceSlug: z.string().trim()
|
||||
}),
|
||||
body: z.object({
|
||||
pitVersionLimit: z.number().min(1).max(100)
|
||||
}),
|
||||
response: {
|
||||
200: z.object({
|
||||
message: z.string(),
|
||||
workspace: ProjectsSchema
|
||||
})
|
||||
}
|
||||
},
|
||||
onRequest: verifyAuth([AuthMode.JWT]),
|
||||
handler: async (req) => {
|
||||
const workspace = await server.services.project.updateVersionLimit({
|
||||
actorId: req.permission.id,
|
||||
actor: req.permission.type,
|
||||
actorAuthMethod: req.permission.authMethod,
|
||||
actorOrgId: req.permission.orgId,
|
||||
pitVersionLimit: req.body.pitVersionLimit,
|
||||
workspaceSlug: req.params.workspaceSlug
|
||||
});
|
||||
|
||||
return {
|
||||
message: "Successfully changed workspace version limit",
|
||||
workspace
|
||||
};
|
||||
}
|
||||
});
|
||||
|
||||
server.route({
|
||||
method: "GET",
|
||||
url: "/:workspaceId/integrations",
|
||||
|
@ -231,7 +231,7 @@ export const authSignupServiceFactory = ({
|
||||
|
||||
const accessToken = jwt.sign(
|
||||
{
|
||||
authMethod: AuthMethod.EMAIL,
|
||||
authMethod: authMethod || AuthMethod.EMAIL,
|
||||
authTokenType: AuthTokenType.ACCESS_TOKEN,
|
||||
userId: updateduser.info.id,
|
||||
tokenVersionId: tokenSession.id,
|
||||
@ -244,7 +244,7 @@ export const authSignupServiceFactory = ({
|
||||
|
||||
const refreshToken = jwt.sign(
|
||||
{
|
||||
authMethod: AuthMethod.EMAIL,
|
||||
authMethod: authMethod || AuthMethod.EMAIL,
|
||||
authTokenType: AuthTokenType.REFRESH_TOKEN,
|
||||
userId: updateduser.info.id,
|
||||
tokenVersionId: tokenSession.id,
|
||||
|
@ -1921,13 +1921,13 @@ const syncSecretsGitLab = async ({
|
||||
return allEnvVariables;
|
||||
};
|
||||
|
||||
const metadata = IntegrationMetadataSchema.parse(integration.metadata);
|
||||
const allEnvVariables = await getAllEnvVariables(integration?.appId as string, accessToken);
|
||||
const getSecretsRes: GitLabSecret[] = allEnvVariables
|
||||
.filter((secret: GitLabSecret) => secret.environment_scope === integration.targetEnvironment)
|
||||
.filter((gitLabSecret) => {
|
||||
let isValid = true;
|
||||
|
||||
const metadata = z.record(z.any()).parse(integration.metadata);
|
||||
if (metadata.secretPrefix && !gitLabSecret.key.startsWith(metadata.secretPrefix)) {
|
||||
isValid = false;
|
||||
}
|
||||
@ -1947,8 +1947,8 @@ const syncSecretsGitLab = async ({
|
||||
{
|
||||
key,
|
||||
value: secrets[key].value,
|
||||
protected: false,
|
||||
masked: false,
|
||||
protected: Boolean(metadata.shouldProtectSecrets),
|
||||
masked: Boolean(metadata.shouldMaskSecrets),
|
||||
raw: false,
|
||||
environment_scope: integration.targetEnvironment
|
||||
},
|
||||
@ -1965,7 +1965,9 @@ const syncSecretsGitLab = async ({
|
||||
`${gitLabApiUrl}/v4/projects/${integration?.appId}/variables/${existingSecret.key}?filter[environment_scope]=${integration.targetEnvironment}`,
|
||||
{
|
||||
...existingSecret,
|
||||
value: secrets[existingSecret.key].value
|
||||
value: secrets[existingSecret.key].value,
|
||||
protected: Boolean(metadata.shouldProtectSecrets),
|
||||
masked: Boolean(metadata.shouldMaskSecrets)
|
||||
},
|
||||
{
|
||||
headers: {
|
||||
|
@ -31,5 +31,7 @@ export const IntegrationMetadataSchema = z.object({
|
||||
.describe(INTEGRATION.CREATE.metadata.secretAWSTag),
|
||||
kmsKeyId: z.string().optional().describe(INTEGRATION.CREATE.metadata.kmsKeyId),
|
||||
shouldDisableDelete: z.boolean().optional().describe(INTEGRATION.CREATE.metadata.shouldDisableDelete),
|
||||
shouldEnableDelete: z.boolean().optional().describe(INTEGRATION.CREATE.metadata.shouldEnableDelete)
|
||||
shouldEnableDelete: z.boolean().optional().describe(INTEGRATION.CREATE.metadata.shouldEnableDelete),
|
||||
shouldMaskSecrets: z.boolean().optional().describe(INTEGRATION.CREATE.metadata.shouldMaskSecrets),
|
||||
shouldProtectSecrets: z.boolean().optional().describe(INTEGRATION.CREATE.metadata.shouldProtectSecrets)
|
||||
});
|
||||
|
@ -29,6 +29,8 @@ export type TCreateIntegrationDTO = {
|
||||
}[];
|
||||
kmsKeyId?: string;
|
||||
shouldDisableDelete?: boolean;
|
||||
shouldMaskSecrets?: boolean;
|
||||
shouldProtectSecrets?: boolean;
|
||||
shouldEnableDelete?: boolean;
|
||||
};
|
||||
} & Omit<TProjectPermission, "projectId">;
|
||||
|
@ -336,6 +336,7 @@ export const orgServiceFactory = ({
|
||||
return org;
|
||||
});
|
||||
|
||||
await licenseService.updateSubscriptionOrgMemberCount(organization.id);
|
||||
return organization;
|
||||
};
|
||||
|
||||
|
@ -39,6 +39,7 @@ import {
|
||||
TToggleProjectAutoCapitalizationDTO,
|
||||
TUpdateProjectDTO,
|
||||
TUpdateProjectNameDTO,
|
||||
TUpdateProjectVersionLimitDTO,
|
||||
TUpgradeProjectDTO
|
||||
} from "./project-types";
|
||||
|
||||
@ -133,7 +134,8 @@ export const projectServiceFactory = ({
|
||||
name: workspaceName,
|
||||
orgId: organization.id,
|
||||
slug: projectSlug || slugify(`${workspaceName}-${alphaNumericNanoId(4)}`),
|
||||
version: ProjectVersion.V2
|
||||
version: ProjectVersion.V2,
|
||||
pitVersionLimit: 10
|
||||
},
|
||||
tx
|
||||
);
|
||||
@ -406,6 +408,35 @@ export const projectServiceFactory = ({
|
||||
return updatedProject;
|
||||
};
|
||||
|
||||
const updateVersionLimit = async ({
|
||||
actor,
|
||||
actorId,
|
||||
actorOrgId,
|
||||
actorAuthMethod,
|
||||
pitVersionLimit,
|
||||
workspaceSlug
|
||||
}: TUpdateProjectVersionLimitDTO) => {
|
||||
const project = await projectDAL.findProjectBySlug(workspaceSlug, actorOrgId);
|
||||
if (!project) {
|
||||
throw new BadRequestError({
|
||||
message: "Project not found"
|
||||
});
|
||||
}
|
||||
|
||||
const { hasRole } = await permissionService.getProjectPermission(
|
||||
actor,
|
||||
actorId,
|
||||
project.id,
|
||||
actorAuthMethod,
|
||||
actorOrgId
|
||||
);
|
||||
|
||||
if (!hasRole(ProjectMembershipRole.Admin))
|
||||
throw new BadRequestError({ message: "Only admins are allowed to take this action" });
|
||||
|
||||
return projectDAL.updateById(project.id, { pitVersionLimit });
|
||||
};
|
||||
|
||||
const updateName = async ({
|
||||
projectId,
|
||||
actor,
|
||||
@ -501,6 +532,7 @@ export const projectServiceFactory = ({
|
||||
getAProject,
|
||||
toggleAutoCapitalization,
|
||||
updateName,
|
||||
upgradeProject
|
||||
upgradeProject,
|
||||
updateVersionLimit
|
||||
};
|
||||
};
|
||||
|
@ -43,6 +43,11 @@ export type TToggleProjectAutoCapitalizationDTO = {
|
||||
autoCapitalization: boolean;
|
||||
} & TProjectPermission;
|
||||
|
||||
export type TUpdateProjectVersionLimitDTO = {
|
||||
pitVersionLimit: number;
|
||||
workspaceSlug: string;
|
||||
} & Omit<TProjectPermission, "projectId">;
|
||||
|
||||
export type TUpdateProjectNameDTO = {
|
||||
name: string;
|
||||
} & TProjectPermission;
|
||||
|
@ -1,13 +1,19 @@
|
||||
import { TAuditLogDALFactory } from "@app/ee/services/audit-log/audit-log-dal";
|
||||
import { TSnapshotDALFactory } from "@app/ee/services/secret-snapshot/snapshot-dal";
|
||||
import { logger } from "@app/lib/logger";
|
||||
import { QueueJobs, QueueName, TQueueServiceFactory } from "@app/queue";
|
||||
|
||||
import { TIdentityAccessTokenDALFactory } from "../identity-access-token/identity-access-token-dal";
|
||||
import { TSecretVersionDALFactory } from "../secret/secret-version-dal";
|
||||
import { TSecretFolderVersionDALFactory } from "../secret-folder/secret-folder-version-dal";
|
||||
import { TSecretSharingDALFactory } from "../secret-sharing/secret-sharing-dal";
|
||||
|
||||
type TDailyResourceCleanUpQueueServiceFactoryDep = {
|
||||
auditLogDAL: Pick<TAuditLogDALFactory, "pruneAuditLog">;
|
||||
identityAccessTokenDAL: Pick<TIdentityAccessTokenDALFactory, "removeExpiredTokens">;
|
||||
secretVersionDAL: Pick<TSecretVersionDALFactory, "pruneExcessVersions">;
|
||||
secretFolderVersionDAL: Pick<TSecretFolderVersionDALFactory, "pruneExcessVersions">;
|
||||
snapshotDAL: Pick<TSnapshotDALFactory, "pruneExcessSnapshots">;
|
||||
secretSharingDAL: Pick<TSecretSharingDALFactory, "pruneExpiredSharedSecrets">;
|
||||
queueService: TQueueServiceFactory;
|
||||
};
|
||||
@ -17,6 +23,9 @@ export type TDailyResourceCleanUpQueueServiceFactory = ReturnType<typeof dailyRe
|
||||
export const dailyResourceCleanUpQueueServiceFactory = ({
|
||||
auditLogDAL,
|
||||
queueService,
|
||||
snapshotDAL,
|
||||
secretVersionDAL,
|
||||
secretFolderVersionDAL,
|
||||
identityAccessTokenDAL,
|
||||
secretSharingDAL
|
||||
}: TDailyResourceCleanUpQueueServiceFactoryDep) => {
|
||||
@ -25,6 +34,9 @@ export const dailyResourceCleanUpQueueServiceFactory = ({
|
||||
await auditLogDAL.pruneAuditLog();
|
||||
await identityAccessTokenDAL.removeExpiredTokens();
|
||||
await secretSharingDAL.pruneExpiredSharedSecrets();
|
||||
await snapshotDAL.pruneExcessSnapshots();
|
||||
await secretVersionDAL.pruneExcessVersions();
|
||||
await secretFolderVersionDAL.pruneExcessVersions();
|
||||
logger.info(`${QueueName.DailyResourceCleanUp}: queue task completed`);
|
||||
});
|
||||
|
||||
|
@ -62,5 +62,32 @@ export const secretFolderVersionDALFactory = (db: TDbClient) => {
|
||||
}
|
||||
};
|
||||
|
||||
return { ...secretFolderVerOrm, findLatestFolderVersions, findLatestVersionByFolderId };
|
||||
const pruneExcessVersions = async () => {
|
||||
try {
|
||||
await db(TableName.SecretFolderVersion)
|
||||
.with("folder_cte", (qb) => {
|
||||
void qb
|
||||
.from(TableName.SecretFolderVersion)
|
||||
.select(
|
||||
"id",
|
||||
"folderId",
|
||||
db.raw(
|
||||
`ROW_NUMBER() OVER (PARTITION BY ${TableName.SecretFolderVersion}."folderId" ORDER BY ${TableName.SecretFolderVersion}."createdAt" DESC) AS row_num`
|
||||
)
|
||||
);
|
||||
})
|
||||
.join(TableName.Environment, `${TableName.Environment}.id`, `${TableName.SecretFolderVersion}.envId`)
|
||||
.join(TableName.Project, `${TableName.Project}.id`, `${TableName.Environment}.projectId`)
|
||||
.join("folder_cte", "folder_cte.id", `${TableName.SecretFolderVersion}.id`)
|
||||
.whereRaw(`folder_cte.row_num > ${TableName.Project}."pitVersionLimit"`)
|
||||
.delete();
|
||||
} catch (error) {
|
||||
throw new DatabaseError({
|
||||
error,
|
||||
name: "Secret Folder Version Prune"
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
return { ...secretFolderVerOrm, findLatestFolderVersions, findLatestVersionByFolderId, pruneExcessVersions };
|
||||
};
|
||||
|
@ -1,4 +1,6 @@
|
||||
/* eslint-disable no-await-in-loop */
|
||||
import { AxiosError } from "axios";
|
||||
|
||||
import { getConfig } from "@app/lib/config/env";
|
||||
import { decryptSymmetric128BitHexKeyUTF8 } from "@app/lib/crypto";
|
||||
import { daysToMillisecond, secondsToMillis } from "@app/lib/dates";
|
||||
@ -570,11 +572,14 @@ export const secretQueueFactory = ({
|
||||
isSynced: true
|
||||
});
|
||||
} catch (err: unknown) {
|
||||
logger.info("Secret integration sync error:", err);
|
||||
logger.info("Secret integration sync error: %o", err);
|
||||
const message =
|
||||
err instanceof AxiosError ? JSON.stringify((err as AxiosError)?.response?.data) : (err as Error)?.message;
|
||||
|
||||
await integrationDAL.updateById(integration.id, {
|
||||
lastSyncJobId: job.id,
|
||||
lastUsed: new Date(),
|
||||
syncMessage: (err as Error)?.message,
|
||||
syncMessage: message,
|
||||
isSynced: false
|
||||
});
|
||||
}
|
||||
|
@ -952,15 +952,49 @@ export const secretServiceFactory = ({
|
||||
});
|
||||
|
||||
const decryptedSecrets = secrets.map((el) => decryptSecretRaw(el, botKey));
|
||||
const decryptedImports = (imports || [])?.map(({ secrets: importedSecrets, ...el }) => ({
|
||||
...el,
|
||||
secrets: importedSecrets.map((sec) =>
|
||||
const processedImports = (imports || [])?.map(({ secrets: importedSecrets, ...el }) => {
|
||||
const decryptedImportSecrets = importedSecrets.map((sec) =>
|
||||
decryptSecretRaw(
|
||||
{ ...sec, environment: el.environment, workspace: projectId, secretPath: el.secretPath },
|
||||
botKey
|
||||
)
|
||||
)
|
||||
}));
|
||||
);
|
||||
|
||||
// secret-override to handle duplicate keys from different import levels
|
||||
// this prioritizes secret values from direct imports
|
||||
const importedKeys = new Set<string>();
|
||||
const importedEntries = decryptedImportSecrets.reduce(
|
||||
(
|
||||
accum: {
|
||||
secretKey: string;
|
||||
secretPath: string;
|
||||
workspace: string;
|
||||
environment: string;
|
||||
secretValue: string;
|
||||
secretComment: string;
|
||||
version: number;
|
||||
type: string;
|
||||
_id: string;
|
||||
id: string;
|
||||
user: string | null | undefined;
|
||||
skipMultilineEncoding: boolean | null | undefined;
|
||||
}[],
|
||||
sec
|
||||
) => {
|
||||
if (!importedKeys.has(sec.secretKey)) {
|
||||
importedKeys.add(sec.secretKey);
|
||||
return [...accum, sec];
|
||||
}
|
||||
return accum;
|
||||
},
|
||||
[]
|
||||
);
|
||||
|
||||
return {
|
||||
...el,
|
||||
secrets: importedEntries
|
||||
};
|
||||
});
|
||||
|
||||
if (expandSecretReferences) {
|
||||
const expandSecrets = interpolateSecrets({
|
||||
@ -1029,12 +1063,12 @@ export const secretServiceFactory = ({
|
||||
await batchSecretsExpand(decryptedSecrets);
|
||||
|
||||
// expand imports by batch
|
||||
await Promise.all(decryptedImports.map((decryptedImport) => batchSecretsExpand(decryptedImport.secrets)));
|
||||
await Promise.all(processedImports.map((processedImport) => batchSecretsExpand(processedImport.secrets)));
|
||||
}
|
||||
|
||||
return {
|
||||
secrets: decryptedSecrets,
|
||||
imports: decryptedImports
|
||||
imports: processedImports
|
||||
};
|
||||
};
|
||||
|
||||
|
@ -111,8 +111,37 @@ export const secretVersionDALFactory = (db: TDbClient) => {
|
||||
}
|
||||
};
|
||||
|
||||
const pruneExcessVersions = async () => {
|
||||
try {
|
||||
await db(TableName.SecretVersion)
|
||||
.with("version_cte", (qb) => {
|
||||
void qb
|
||||
.from(TableName.SecretVersion)
|
||||
.select(
|
||||
"id",
|
||||
"folderId",
|
||||
db.raw(
|
||||
`ROW_NUMBER() OVER (PARTITION BY ${TableName.SecretVersion}."secretId" ORDER BY ${TableName.SecretVersion}."createdAt" DESC) AS row_num`
|
||||
)
|
||||
);
|
||||
})
|
||||
.join(TableName.SecretFolder, `${TableName.SecretFolder}.id`, `${TableName.SecretVersion}.folderId`)
|
||||
.join(TableName.Environment, `${TableName.Environment}.id`, `${TableName.SecretFolder}.envId`)
|
||||
.join(TableName.Project, `${TableName.Project}.id`, `${TableName.Environment}.projectId`)
|
||||
.join("version_cte", "version_cte.id", `${TableName.SecretVersion}.id`)
|
||||
.whereRaw(`version_cte.row_num > ${TableName.Project}."pitVersionLimit"`)
|
||||
.delete();
|
||||
} catch (error) {
|
||||
throw new DatabaseError({
|
||||
error,
|
||||
name: "Secret Version Prune"
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
return {
|
||||
...secretVersionOrm,
|
||||
pruneExcessVersions,
|
||||
findLatestVersionMany,
|
||||
bulkUpdate,
|
||||
findLatestVersionByFolderId,
|
||||
|
@ -41,21 +41,8 @@ export enum SmtpHost {
|
||||
Office365 = "smtp.office365.com"
|
||||
}
|
||||
|
||||
export const getTlsOption = (host?: SmtpHost | string, secure?: boolean) => {
|
||||
if (!secure) return { secure: false };
|
||||
if (!host) return { secure: true };
|
||||
|
||||
if ((host as SmtpHost) === SmtpHost.Sendgrid) {
|
||||
return { secure: true, port: 465 }; // more details here https://nodemailer.com/smtp/
|
||||
}
|
||||
if (host.includes("amazonaws.com")) {
|
||||
return { tls: { ciphers: "TLSv1.2" } };
|
||||
}
|
||||
return { requireTLS: true, tls: { ciphers: "TLSv1.2" } };
|
||||
};
|
||||
|
||||
export const smtpServiceFactory = (cfg: TSmtpConfig) => {
|
||||
const smtp = createTransport({ ...cfg, ...getTlsOption(cfg.host, cfg.secure) });
|
||||
const smtp = createTransport(cfg);
|
||||
const isSmtpOn = Boolean(cfg.host);
|
||||
|
||||
const sendMail = async ({ substitutions, recipients, template, subjectLine }: TSmtpSendMail) => {
|
||||
|
@ -21,6 +21,7 @@ type TUserServiceFactoryDep = {
|
||||
| "findOneUserAction"
|
||||
| "createUserAction"
|
||||
| "findUserEncKeyByUserId"
|
||||
| "delete"
|
||||
>;
|
||||
userAliasDAL: Pick<TUserAliasDALFactory, "find" | "insertMany">;
|
||||
orgMembershipDAL: Pick<TOrgMembershipDALFactory, "find" | "insertMany">;
|
||||
@ -85,7 +86,7 @@ export const userServiceFactory = ({
|
||||
tx
|
||||
);
|
||||
|
||||
// check if there are users with the same email.
|
||||
// check if there are verified users with the same email.
|
||||
const users = await userDAL.find(
|
||||
{
|
||||
email,
|
||||
@ -134,6 +135,15 @@ export const userServiceFactory = ({
|
||||
);
|
||||
}
|
||||
} else {
|
||||
await userDAL.delete(
|
||||
{
|
||||
email,
|
||||
isAccepted: false,
|
||||
isEmailVerified: false
|
||||
},
|
||||
tx
|
||||
);
|
||||
|
||||
// update current user's username to [email]
|
||||
await userDAL.updateById(
|
||||
user.id,
|
||||
|
1
cli/.gitignore
vendored
1
cli/.gitignore
vendored
@ -1,3 +1,4 @@
|
||||
.infisical.json
|
||||
dist/
|
||||
agent-config.test.yaml
|
||||
.test.env
|
@ -3,7 +3,9 @@ module github.com/Infisical/infisical-merge
|
||||
go 1.21
|
||||
|
||||
require (
|
||||
github.com/bradleyjkemp/cupaloy/v2 v2.8.0
|
||||
github.com/charmbracelet/lipgloss v0.5.0
|
||||
github.com/creack/pty v1.1.21
|
||||
github.com/denisbrodbeck/machineid v1.0.1
|
||||
github.com/fatih/semgroup v1.2.0
|
||||
github.com/gitleaks/go-gitdiff v0.8.0
|
||||
@ -29,7 +31,6 @@ require (
|
||||
require (
|
||||
github.com/alessio/shellescape v1.4.1 // indirect
|
||||
github.com/asaskevich/govalidator v0.0.0-20200907205600-7a23bdc65eef // indirect
|
||||
github.com/bradleyjkemp/cupaloy/v2 v2.8.0 // indirect
|
||||
github.com/chzyer/readline v1.5.1 // indirect
|
||||
github.com/danieljoos/wincred v1.2.0 // indirect
|
||||
github.com/davecgh/go-spew v1.1.1 // indirect
|
||||
|
@ -74,6 +74,8 @@ github.com/coreos/go-systemd/v22 v22.3.2/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSV
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
|
||||
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
|
||||
github.com/creack/pty v1.1.21 h1:1/QdRyBaHHJP61QkWMXlOIBfsgdDeeKfK8SYVUWJKf0=
|
||||
github.com/creack/pty v1.1.21/go.mod h1:MOBLtS5ELjhRRrroQr9kyvTxUAFNvYEK993ew/Vr4O4=
|
||||
github.com/danieljoos/wincred v1.2.0 h1:ozqKHaLK0W/ii4KVbbvluM91W2H3Sh0BncbUNPS7jLE=
|
||||
github.com/danieljoos/wincred v1.2.0/go.mod h1:FzQLLMKBFdvu+osBrnFODiv32YGwCfx0SkRa/eYHgec=
|
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
|
@ -4,6 +4,8 @@ import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"os"
|
||||
|
||||
"github.com/Infisical/infisical-merge/packages/config"
|
||||
)
|
||||
|
||||
func GetHomeDir() (string, error) {
|
||||
@ -21,7 +23,7 @@ func WriteToFile(fileName string, dataToWrite []byte, filePerm os.FileMode) erro
|
||||
return nil
|
||||
}
|
||||
|
||||
func CheckIsConnectedToInternet() (ok bool) {
|
||||
_, err := http.Get("http://clients3.google.com/generate_204")
|
||||
func ValidateInfisicalAPIConnection() (ok bool) {
|
||||
_, err := http.Get(fmt.Sprintf("%v/status", config.INFISICAL_URL))
|
||||
return err == nil
|
||||
}
|
||||
|
@ -307,32 +307,33 @@ func FilterSecretsByTag(plainTextSecrets []models.SingleEnvironmentVariable, tag
|
||||
}
|
||||
|
||||
func GetAllEnvironmentVariables(params models.GetAllSecretsParameters, projectConfigFilePath string) ([]models.SingleEnvironmentVariable, error) {
|
||||
isConnected := CheckIsConnectedToInternet()
|
||||
var secretsToReturn []models.SingleEnvironmentVariable
|
||||
// var serviceTokenDetails api.GetServiceTokenDetailsResponse
|
||||
var errorToReturn error
|
||||
|
||||
if params.InfisicalToken == "" && params.UniversalAuthAccessToken == "" {
|
||||
if isConnected {
|
||||
log.Debug().Msg("GetAllEnvironmentVariables: Connected to internet, checking logged in creds")
|
||||
|
||||
if projectConfigFilePath == "" {
|
||||
RequireLocalWorkspaceFile()
|
||||
} else {
|
||||
ValidateWorkspaceFile(projectConfigFilePath)
|
||||
}
|
||||
|
||||
RequireLogin()
|
||||
if projectConfigFilePath == "" {
|
||||
RequireLocalWorkspaceFile()
|
||||
} else {
|
||||
ValidateWorkspaceFile(projectConfigFilePath)
|
||||
}
|
||||
|
||||
RequireLogin()
|
||||
|
||||
log.Debug().Msg("GetAllEnvironmentVariables: Trying to fetch secrets using logged in details")
|
||||
|
||||
loggedInUserDetails, err := GetCurrentLoggedInUserDetails()
|
||||
isConnected := ValidateInfisicalAPIConnection()
|
||||
|
||||
if isConnected {
|
||||
log.Debug().Msg("GetAllEnvironmentVariables: Connected to Infisical instance, checking logged in creds")
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if loggedInUserDetails.LoginExpired {
|
||||
if isConnected && loggedInUserDetails.LoginExpired {
|
||||
PrintErrorMessageAndExit("Your login session has expired, please run [infisical login] and try again")
|
||||
}
|
||||
|
||||
@ -364,12 +365,12 @@ func GetAllEnvironmentVariables(params models.GetAllSecretsParameters, projectCo
|
||||
|
||||
backupSecretsEncryptionKey := []byte(loggedInUserDetails.UserCredentials.PrivateKey)[0:32]
|
||||
if errorToReturn == nil {
|
||||
WriteBackupSecrets(infisicalDotJson.WorkspaceId, params.Environment, backupSecretsEncryptionKey, secretsToReturn)
|
||||
WriteBackupSecrets(infisicalDotJson.WorkspaceId, params.Environment, params.SecretsPath, backupSecretsEncryptionKey, secretsToReturn)
|
||||
}
|
||||
|
||||
// only attempt to serve cached secrets if no internet connection and if at least one secret cached
|
||||
if !isConnected {
|
||||
backedSecrets, err := ReadBackupSecrets(infisicalDotJson.WorkspaceId, params.Environment, backupSecretsEncryptionKey)
|
||||
backedSecrets, err := ReadBackupSecrets(infisicalDotJson.WorkspaceId, params.Environment, params.SecretsPath, backupSecretsEncryptionKey)
|
||||
if len(backedSecrets) > 0 {
|
||||
PrintWarning("Unable to fetch latest secret(s) due to connection error, serving secrets from last successful fetch. For more info, run with --debug")
|
||||
secretsToReturn = backedSecrets
|
||||
@ -634,8 +635,9 @@ func GetPlainTextSecrets(key []byte, encryptedSecrets []api.EncryptedSecretV3) (
|
||||
return plainTextSecrets, nil
|
||||
}
|
||||
|
||||
func WriteBackupSecrets(workspace string, environment string, encryptionKey []byte, secrets []models.SingleEnvironmentVariable) error {
|
||||
fileName := fmt.Sprintf("secrets_%s_%s", workspace, environment)
|
||||
func WriteBackupSecrets(workspace string, environment string, secretsPath string, encryptionKey []byte, secrets []models.SingleEnvironmentVariable) error {
|
||||
formattedPath := strings.ReplaceAll(secretsPath, "/", "-")
|
||||
fileName := fmt.Sprintf("secrets_%s_%s_%s", workspace, environment, formattedPath)
|
||||
secrets_backup_folder_name := "secrets-backup"
|
||||
|
||||
_, fullConfigFileDirPath, err := GetFullConfigFilePath()
|
||||
@ -672,8 +674,9 @@ func WriteBackupSecrets(workspace string, environment string, encryptionKey []by
|
||||
return nil
|
||||
}
|
||||
|
||||
func ReadBackupSecrets(workspace string, environment string, encryptionKey []byte) ([]models.SingleEnvironmentVariable, error) {
|
||||
fileName := fmt.Sprintf("secrets_%s_%s", workspace, environment)
|
||||
func ReadBackupSecrets(workspace string, environment string, secretsPath string, encryptionKey []byte) ([]models.SingleEnvironmentVariable, error) {
|
||||
formattedPath := strings.ReplaceAll(secretsPath, "/", "-")
|
||||
fileName := fmt.Sprintf("secrets_%s_%s_%s", workspace, environment, formattedPath)
|
||||
secrets_backup_folder_name := "secrets-backup"
|
||||
|
||||
_, fullConfigFileDirPath, err := GetFullConfigFilePath()
|
||||
|
23
cli/scripts/export_test_env.sh
Normal file
23
cli/scripts/export_test_env.sh
Normal file
@ -0,0 +1,23 @@
|
||||
#!/bin/bash
|
||||
|
||||
TEST_ENV_FILE=".test.env"
|
||||
|
||||
# Check if the .env file exists
|
||||
if [ ! -f "$TEST_ENV_FILE" ]; then
|
||||
echo "$TEST_ENV_FILE does not exist."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Export the variables
|
||||
while IFS= read -r line
|
||||
do
|
||||
# Skip empty lines and lines starting with #
|
||||
if [[ -z "$line" || "$line" =~ ^\# ]]; then
|
||||
continue
|
||||
fi
|
||||
# Read the key-value pair
|
||||
IFS='=' read -r key value <<< "$line"
|
||||
eval export $key=\$value
|
||||
done < "$TEST_ENV_FILE"
|
||||
|
||||
echo "Test environment variables set."
|
7
cli/test/.snapshots/test-TestUserAuth_SecretsGetAll
Normal file
7
cli/test/.snapshots/test-TestUserAuth_SecretsGetAll
Normal file
@ -0,0 +1,7 @@
|
||||
┌───────────────┬──────────────┬─────────────┐
|
||||
│ SECRET NAME │ SECRET VALUE │ SECRET TYPE │
|
||||
├───────────────┼──────────────┼─────────────┤
|
||||
│ TEST-SECRET-1 │ test-value-1 │ shared │
|
||||
│ TEST-SECRET-2 │ test-value-2 │ shared │
|
||||
│ TEST-SECRET-3 │ test-value-3 │ shared │
|
||||
└───────────────┴──────────────┴─────────────┘
|
@ -0,0 +1,8 @@
|
||||
Warning: Unable to fetch latest secret(s) due to connection error, serving secrets from last successful fetch. For more info, run with --debug
|
||||
┌───────────────┬──────────────┬─────────────┐
|
||||
│ SECRET NAME │ SECRET VALUE │ SECRET TYPE │
|
||||
├───────────────┼──────────────┼─────────────┤
|
||||
│ TEST-SECRET-1 │ test-value-1 │ shared │
|
||||
│ TEST-SECRET-2 │ test-value-2 │ shared │
|
||||
│ TEST-SECRET-3 │ test-value-3 │ shared │
|
||||
└───────────────┴──────────────┴─────────────┘
|
@ -8,7 +8,6 @@ import (
|
||||
|
||||
func TestUniversalAuth_ExportSecretsWithImports(t *testing.T) {
|
||||
MachineIdentityLoginCmd(t)
|
||||
SetupCli(t)
|
||||
|
||||
output, err := ExecuteCliCommand(FORMATTED_CLI_NAME, "export", "--token", creds.UAAccessToken, "--projectId", creds.ProjectID, "--env", creds.EnvSlug, "--silent")
|
||||
|
||||
@ -24,8 +23,6 @@ func TestUniversalAuth_ExportSecretsWithImports(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestServiceToken_ExportSecretsWithImports(t *testing.T) {
|
||||
SetupCli(t)
|
||||
|
||||
output, err := ExecuteCliCommand(FORMATTED_CLI_NAME, "export", "--token", creds.ServiceToken, "--projectId", creds.ProjectID, "--env", creds.EnvSlug, "--silent")
|
||||
|
||||
if err != nil {
|
||||
@ -41,8 +38,6 @@ func TestServiceToken_ExportSecretsWithImports(t *testing.T) {
|
||||
|
||||
func TestUniversalAuth_ExportSecretsWithoutImports(t *testing.T) {
|
||||
MachineIdentityLoginCmd(t)
|
||||
SetupCli(t)
|
||||
|
||||
output, err := ExecuteCliCommand(FORMATTED_CLI_NAME, "export", "--token", creds.UAAccessToken, "--projectId", creds.ProjectID, "--env", creds.EnvSlug, "--silent", "--include-imports=false")
|
||||
|
||||
if err != nil {
|
||||
@ -57,8 +52,6 @@ func TestUniversalAuth_ExportSecretsWithoutImports(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestServiceToken_ExportSecretsWithoutImports(t *testing.T) {
|
||||
SetupCli(t)
|
||||
|
||||
output, err := ExecuteCliCommand(FORMATTED_CLI_NAME, "export", "--token", creds.ServiceToken, "--projectId", creds.ProjectID, "--env", creds.EnvSlug, "--silent", "--include-imports=false")
|
||||
|
||||
if err != nil {
|
||||
|
@ -2,10 +2,10 @@ package tests
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
"os"
|
||||
"os/exec"
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
const (
|
||||
@ -23,6 +23,8 @@ type Credentials struct {
|
||||
ServiceToken string
|
||||
ProjectID string
|
||||
EnvSlug string
|
||||
UserEmail string
|
||||
UserPassword string
|
||||
}
|
||||
|
||||
var creds = Credentials{
|
||||
@ -32,18 +34,21 @@ var creds = Credentials{
|
||||
ServiceToken: os.Getenv("CLI_TESTS_SERVICE_TOKEN"),
|
||||
ProjectID: os.Getenv("CLI_TESTS_PROJECT_ID"),
|
||||
EnvSlug: os.Getenv("CLI_TESTS_ENV_SLUG"),
|
||||
UserEmail: os.Getenv("CLI_TESTS_USER_EMAIL"),
|
||||
UserPassword: os.Getenv("CLI_TESTS_USER_PASSWORD"),
|
||||
}
|
||||
|
||||
func ExecuteCliCommand(command string, args ...string) (string, error) {
|
||||
cmd := exec.Command(command, args...)
|
||||
output, err := cmd.CombinedOutput()
|
||||
if err != nil {
|
||||
fmt.Println(fmt.Sprint(err) + ": " + string(output))
|
||||
return strings.TrimSpace(string(output)), err
|
||||
}
|
||||
return strings.TrimSpace(string(output)), nil
|
||||
}
|
||||
|
||||
func SetupCli(t *testing.T) {
|
||||
func SetupCli() {
|
||||
|
||||
if creds.ClientID == "" || creds.ClientSecret == "" || creds.ServiceToken == "" || creds.ProjectID == "" || creds.EnvSlug == "" {
|
||||
panic("Missing required environment variables")
|
||||
@ -57,7 +62,7 @@ func SetupCli(t *testing.T) {
|
||||
|
||||
if !alreadyBuilt {
|
||||
if err := exec.Command("go", "build", "../.").Run(); err != nil {
|
||||
t.Fatal(err)
|
||||
log.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1,14 +1,124 @@
|
||||
package tests
|
||||
|
||||
import (
|
||||
"log"
|
||||
"os/exec"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/creack/pty"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func MachineIdentityLoginCmd(t *testing.T) {
|
||||
SetupCli(t)
|
||||
func UserInitCmd() {
|
||||
c := exec.Command(FORMATTED_CLI_NAME, "init")
|
||||
ptmx, err := pty.Start(c)
|
||||
if err != nil {
|
||||
log.Fatalf("error running CLI command: %v", err)
|
||||
}
|
||||
defer func() { _ = ptmx.Close() }()
|
||||
|
||||
stepChan := make(chan int, 10)
|
||||
|
||||
go func() {
|
||||
buf := make([]byte, 1024)
|
||||
step := -1
|
||||
for {
|
||||
n, err := ptmx.Read(buf)
|
||||
if n > 0 {
|
||||
terminalOut := string(buf)
|
||||
if strings.Contains(terminalOut, "Which Infisical organization would you like to select a project from?") && step < 0 {
|
||||
step += 1
|
||||
stepChan <- step
|
||||
} else if strings.Contains(terminalOut, "Which of your Infisical projects would you like to connect this project to?") && step < 1 {
|
||||
step += 1;
|
||||
stepChan <- step
|
||||
}
|
||||
}
|
||||
if err != nil {
|
||||
close(stepChan)
|
||||
return
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
for i := range stepChan {
|
||||
switch i {
|
||||
case 0:
|
||||
ptmx.Write([]byte("\n"))
|
||||
case 1:
|
||||
ptmx.Write([]byte("\n"))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func UserLoginCmd() {
|
||||
// set vault to file because CI has no keyring
|
||||
vaultCmd := exec.Command(FORMATTED_CLI_NAME, "vault", "set", "file")
|
||||
_, err := vaultCmd.Output()
|
||||
if err != nil {
|
||||
log.Fatalf("error setting vault: %v", err)
|
||||
}
|
||||
|
||||
// Start programmatic interaction with CLI
|
||||
c := exec.Command(FORMATTED_CLI_NAME, "login", "--interactive")
|
||||
ptmx, err := pty.Start(c)
|
||||
if err != nil {
|
||||
log.Fatalf("error running CLI command: %v", err)
|
||||
}
|
||||
defer func() { _ = ptmx.Close() }()
|
||||
|
||||
stepChan := make(chan int, 10)
|
||||
|
||||
go func() {
|
||||
buf := make([]byte, 1024)
|
||||
step := -1
|
||||
for {
|
||||
n, err := ptmx.Read(buf)
|
||||
if n > 0 {
|
||||
terminalOut := string(buf)
|
||||
if strings.Contains(terminalOut, "Infisical Cloud") && step < 0 {
|
||||
step += 1;
|
||||
stepChan <- step
|
||||
} else if strings.Contains(terminalOut, "Email") && step < 1 {
|
||||
step += 1;
|
||||
stepChan <- step
|
||||
} else if strings.Contains(terminalOut, "Password") && step < 2 {
|
||||
step += 1;
|
||||
stepChan <- step
|
||||
} else if strings.Contains(terminalOut, "Infisical organization") && step < 3 {
|
||||
step += 1;
|
||||
stepChan <- step
|
||||
} else if strings.Contains(terminalOut, "Enter passphrase") && step < 4 {
|
||||
step += 1;
|
||||
stepChan <- step
|
||||
}
|
||||
}
|
||||
if err != nil {
|
||||
close(stepChan)
|
||||
return
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
for i := range stepChan {
|
||||
switch i {
|
||||
case 0:
|
||||
ptmx.Write([]byte("\n"))
|
||||
case 1:
|
||||
ptmx.Write([]byte(creds.UserEmail))
|
||||
ptmx.Write([]byte("\n"))
|
||||
case 2:
|
||||
ptmx.Write([]byte(creds.UserPassword))
|
||||
ptmx.Write([]byte("\n"))
|
||||
case 3:
|
||||
ptmx.Write([]byte("\n"))
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func MachineIdentityLoginCmd(t *testing.T) {
|
||||
if creds.UAAccessToken != "" {
|
||||
return
|
||||
}
|
||||
|
23
cli/test/main_test.go
Normal file
23
cli/test/main_test.go
Normal file
@ -0,0 +1,23 @@
|
||||
package tests
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestMain(m *testing.M) {
|
||||
// Setup
|
||||
fmt.Println("Setting up CLI...")
|
||||
SetupCli()
|
||||
fmt.Println("Performing user login...")
|
||||
UserLoginCmd()
|
||||
fmt.Println("Performing infisical init...")
|
||||
UserInitCmd()
|
||||
|
||||
// Run the tests
|
||||
code := m.Run()
|
||||
|
||||
// Exit
|
||||
os.Exit(code)
|
||||
}
|
@ -8,8 +8,6 @@ import (
|
||||
)
|
||||
|
||||
func TestServiceToken_RunCmdRecursiveAndImports(t *testing.T) {
|
||||
SetupCli(t)
|
||||
|
||||
output, err := ExecuteCliCommand(FORMATTED_CLI_NAME, "run", "--token", creds.ServiceToken, "--projectId", creds.ProjectID, "--env", creds.EnvSlug, "--recursive", "--silent", "--", "echo", "hello world")
|
||||
|
||||
if err != nil {
|
||||
@ -25,8 +23,6 @@ func TestServiceToken_RunCmdRecursiveAndImports(t *testing.T) {
|
||||
}
|
||||
}
|
||||
func TestServiceToken_RunCmdWithImports(t *testing.T) {
|
||||
SetupCli(t)
|
||||
|
||||
output, err := ExecuteCliCommand(FORMATTED_CLI_NAME, "run", "--token", creds.ServiceToken, "--projectId", creds.ProjectID, "--env", creds.EnvSlug, "--silent", "--", "echo", "hello world")
|
||||
|
||||
if err != nil {
|
||||
@ -44,8 +40,6 @@ func TestServiceToken_RunCmdWithImports(t *testing.T) {
|
||||
|
||||
func TestUniversalAuth_RunCmdRecursiveAndImports(t *testing.T) {
|
||||
MachineIdentityLoginCmd(t)
|
||||
SetupCli(t)
|
||||
|
||||
output, err := ExecuteCliCommand(FORMATTED_CLI_NAME, "run", "--token", creds.UAAccessToken, "--projectId", creds.ProjectID, "--env", creds.EnvSlug, "--recursive", "--silent", "--", "echo", "hello world")
|
||||
|
||||
if err != nil {
|
||||
@ -63,8 +57,6 @@ func TestUniversalAuth_RunCmdRecursiveAndImports(t *testing.T) {
|
||||
|
||||
func TestUniversalAuth_RunCmdWithImports(t *testing.T) {
|
||||
MachineIdentityLoginCmd(t)
|
||||
SetupCli(t)
|
||||
|
||||
output, err := ExecuteCliCommand(FORMATTED_CLI_NAME, "run", "--token", creds.UAAccessToken, "--projectId", creds.ProjectID, "--env", creds.EnvSlug, "--silent", "--", "echo", "hello world")
|
||||
|
||||
if err != nil {
|
||||
@ -83,8 +75,6 @@ func TestUniversalAuth_RunCmdWithImports(t *testing.T) {
|
||||
|
||||
func TestUniversalAuth_RunCmdWithoutImports(t *testing.T) {
|
||||
MachineIdentityLoginCmd(t)
|
||||
SetupCli(t)
|
||||
|
||||
output, err := ExecuteCliCommand(FORMATTED_CLI_NAME, "run", "--token", creds.UAAccessToken, "--projectId", creds.ProjectID, "--env", creds.EnvSlug, "--silent", "--include-imports=false", "--", "echo", "hello world")
|
||||
|
||||
if err != nil {
|
||||
@ -101,8 +91,6 @@ func TestUniversalAuth_RunCmdWithoutImports(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestServiceToken_RunCmdWithoutImports(t *testing.T) {
|
||||
SetupCli(t)
|
||||
|
||||
output, err := ExecuteCliCommand(FORMATTED_CLI_NAME, "run", "--token", creds.ServiceToken, "--projectId", creds.ProjectID, "--env", creds.EnvSlug, "--silent", "--include-imports=false", "--", "echo", "hello world")
|
||||
|
||||
if err != nil {
|
||||
|
@ -7,8 +7,6 @@ import (
|
||||
)
|
||||
|
||||
func TestServiceToken_GetSecretsByNameRecursive(t *testing.T) {
|
||||
SetupCli(t)
|
||||
|
||||
output, err := ExecuteCliCommand(FORMATTED_CLI_NAME, "secrets", "get", "TEST-SECRET-1", "TEST-SECRET-2", "FOLDER-SECRET-1", "--token", creds.ServiceToken, "--projectId", creds.ProjectID, "--env", creds.EnvSlug, "--recursive", "--silent")
|
||||
|
||||
if err != nil {
|
||||
@ -23,8 +21,6 @@ func TestServiceToken_GetSecretsByNameRecursive(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestServiceToken_GetSecretsByNameWithNotFoundSecret(t *testing.T) {
|
||||
SetupCli(t)
|
||||
|
||||
output, err := ExecuteCliCommand(FORMATTED_CLI_NAME, "secrets", "get", "TEST-SECRET-1", "TEST-SECRET-2", "FOLDER-SECRET-1", "DOES-NOT-EXIST", "--token", creds.ServiceToken, "--projectId", creds.ProjectID, "--env", creds.EnvSlug, "--recursive", "--silent")
|
||||
|
||||
if err != nil {
|
||||
@ -39,8 +35,6 @@ func TestServiceToken_GetSecretsByNameWithNotFoundSecret(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestServiceToken_GetSecretsByNameWithImports(t *testing.T) {
|
||||
SetupCli(t)
|
||||
|
||||
output, err := ExecuteCliCommand(FORMATTED_CLI_NAME, "secrets", "get", "TEST-SECRET-1", "STAGING-SECRET-2", "FOLDER-SECRET-1", "--token", creds.ServiceToken, "--projectId", creds.ProjectID, "--env", creds.EnvSlug, "--recursive", "--silent")
|
||||
|
||||
if err != nil {
|
||||
@ -56,8 +50,6 @@ func TestServiceToken_GetSecretsByNameWithImports(t *testing.T) {
|
||||
|
||||
func TestUniversalAuth_GetSecretsByNameRecursive(t *testing.T) {
|
||||
MachineIdentityLoginCmd(t)
|
||||
SetupCli(t)
|
||||
|
||||
output, err := ExecuteCliCommand(FORMATTED_CLI_NAME, "secrets", "get", "TEST-SECRET-1", "TEST-SECRET-2", "FOLDER-SECRET-1", "--token", creds.UAAccessToken, "--projectId", creds.ProjectID, "--env", creds.EnvSlug, "--recursive", "--silent")
|
||||
|
||||
if err != nil {
|
||||
@ -73,8 +65,6 @@ func TestUniversalAuth_GetSecretsByNameRecursive(t *testing.T) {
|
||||
|
||||
func TestUniversalAuth_GetSecretsByNameWithNotFoundSecret(t *testing.T) {
|
||||
MachineIdentityLoginCmd(t)
|
||||
SetupCli(t)
|
||||
|
||||
output, err := ExecuteCliCommand(FORMATTED_CLI_NAME, "secrets", "get", "TEST-SECRET-1", "TEST-SECRET-2", "FOLDER-SECRET-1", "DOES-NOT-EXIST", "--token", creds.UAAccessToken, "--projectId", creds.ProjectID, "--env", creds.EnvSlug, "--recursive", "--silent")
|
||||
|
||||
if err != nil {
|
||||
@ -90,8 +80,6 @@ func TestUniversalAuth_GetSecretsByNameWithNotFoundSecret(t *testing.T) {
|
||||
|
||||
func TestUniversalAuth_GetSecretsByNameWithImports(t *testing.T) {
|
||||
MachineIdentityLoginCmd(t)
|
||||
SetupCli(t)
|
||||
|
||||
output, err := ExecuteCliCommand(FORMATTED_CLI_NAME, "secrets", "get", "TEST-SECRET-1", "STAGING-SECRET-2", "FOLDER-SECRET-1", "--token", creds.UAAccessToken, "--projectId", creds.ProjectID, "--env", creds.EnvSlug, "--recursive", "--silent")
|
||||
|
||||
if err != nil {
|
||||
|
@ -3,12 +3,12 @@ package tests
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/Infisical/infisical-merge/packages/util"
|
||||
"github.com/bradleyjkemp/cupaloy/v2"
|
||||
)
|
||||
|
||||
func TestServiceToken_SecretsGetWithImportsAndRecursiveCmd(t *testing.T) {
|
||||
SetupCli(t)
|
||||
|
||||
func TestServiceToken_SecretsGetWithImportsAndRecursiveCmd(t *testing.T) {
|
||||
output, err := ExecuteCliCommand(FORMATTED_CLI_NAME, "secrets", "--token", creds.ServiceToken, "--projectId", creds.ProjectID, "--env", creds.EnvSlug, "--recursive", "--silent")
|
||||
|
||||
if err != nil {
|
||||
@ -23,8 +23,6 @@ func TestServiceToken_SecretsGetWithImportsAndRecursiveCmd(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestServiceToken_SecretsGetWithoutImportsAndWithoutRecursiveCmd(t *testing.T) {
|
||||
SetupCli(t)
|
||||
|
||||
output, err := ExecuteCliCommand(FORMATTED_CLI_NAME, "secrets", "--token", creds.ServiceToken, "--projectId", creds.ProjectID, "--env", creds.EnvSlug, "--include-imports=false", "--silent")
|
||||
|
||||
if err != nil {
|
||||
@ -39,7 +37,6 @@ func TestServiceToken_SecretsGetWithoutImportsAndWithoutRecursiveCmd(t *testing.
|
||||
}
|
||||
|
||||
func TestUniversalAuth_SecretsGetWithImportsAndRecursiveCmd(t *testing.T) {
|
||||
SetupCli(t)
|
||||
MachineIdentityLoginCmd(t)
|
||||
|
||||
output, err := ExecuteCliCommand(FORMATTED_CLI_NAME, "secrets", "--token", creds.UAAccessToken, "--projectId", creds.ProjectID, "--env", creds.EnvSlug, "--recursive", "--silent")
|
||||
@ -56,7 +53,6 @@ func TestUniversalAuth_SecretsGetWithImportsAndRecursiveCmd(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestUniversalAuth_SecretsGetWithoutImportsAndWithoutRecursiveCmd(t *testing.T) {
|
||||
SetupCli(t)
|
||||
MachineIdentityLoginCmd(t)
|
||||
|
||||
output, err := ExecuteCliCommand(FORMATTED_CLI_NAME, "secrets", "--token", creds.UAAccessToken, "--projectId", creds.ProjectID, "--env", creds.EnvSlug, "--include-imports=false", "--silent")
|
||||
@ -73,7 +69,6 @@ func TestUniversalAuth_SecretsGetWithoutImportsAndWithoutRecursiveCmd(t *testing
|
||||
}
|
||||
|
||||
func TestUniversalAuth_SecretsGetWrongEnvironment(t *testing.T) {
|
||||
SetupCli(t)
|
||||
MachineIdentityLoginCmd(t)
|
||||
|
||||
output, _ := ExecuteCliCommand(FORMATTED_CLI_NAME, "secrets", "--token", creds.UAAccessToken, "--projectId", creds.ProjectID, "--env", "invalid-env", "--recursive", "--silent")
|
||||
@ -85,3 +80,45 @@ func TestUniversalAuth_SecretsGetWrongEnvironment(t *testing.T) {
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func TestUserAuth_SecretsGetAll(t *testing.T) {
|
||||
output, err := ExecuteCliCommand(FORMATTED_CLI_NAME, "secrets", "--projectId", creds.ProjectID, "--env", creds.EnvSlug, "--include-imports=false", "--silent")
|
||||
if err != nil {
|
||||
t.Fatalf("error running CLI command: %v", err)
|
||||
}
|
||||
|
||||
// Use cupaloy to snapshot test the output
|
||||
err = cupaloy.Snapshot(output)
|
||||
if err != nil {
|
||||
t.Fatalf("snapshot failed: %v", err)
|
||||
}
|
||||
|
||||
// explicitly called here because it should happen directly after successful secretsGetAll
|
||||
testUserAuth_SecretsGetAllWithoutConnection(t)
|
||||
}
|
||||
|
||||
func testUserAuth_SecretsGetAllWithoutConnection(t *testing.T) {
|
||||
originalConfigFile, err := util.GetConfigFile()
|
||||
if err != nil {
|
||||
t.Fatalf("error getting config file")
|
||||
}
|
||||
newConfigFile := originalConfigFile
|
||||
|
||||
// set it to a URL that will always be unreachable
|
||||
newConfigFile.LoggedInUserDomain = "http://localhost:4999"
|
||||
util.WriteConfigFile(&newConfigFile)
|
||||
|
||||
// restore config file
|
||||
defer util.WriteConfigFile(&originalConfigFile)
|
||||
|
||||
output, err := ExecuteCliCommand(FORMATTED_CLI_NAME, "secrets", "--projectId", creds.ProjectID, "--env", creds.EnvSlug, "--include-imports=false", "--silent")
|
||||
if err != nil {
|
||||
t.Fatalf("error running CLI command: %v", err)
|
||||
}
|
||||
|
||||
// Use cupaloy to snapshot test the output
|
||||
err = cupaloy.Snapshot(output)
|
||||
if err != nil {
|
||||
t.Fatalf("snapshot failed: %v", err)
|
||||
}
|
||||
}
|
@ -58,46 +58,108 @@ Once you apply the manifest, the operator will be installed in `infisical-operat
|
||||
Once you have installed the operator to your cluster, you'll need to create a `InfisicalSecret` custom resource definition (CRD).
|
||||
|
||||
```yaml example-infisical-secret-crd.yaml
|
||||
|
||||
apiVersion: secrets.infisical.com/v1alpha1
|
||||
kind: InfisicalSecret
|
||||
metadata:
|
||||
name: infisicalsecret-sample
|
||||
labels:
|
||||
label-to-be-passed-to-managed-secret: sample-value
|
||||
annotations:
|
||||
example.com/annotation-to-be-passed-to-managed-secret: "sample-value"
|
||||
name: infisicalsecret-sample
|
||||
labels:
|
||||
label-to-be-passed-to-managed-secret: sample-value
|
||||
annotations:
|
||||
example.com/annotation-to-be-passed-to-managed-secret: "sample-value"
|
||||
spec:
|
||||
hostAPI: https://app.infisical.com/api
|
||||
resyncInterval: 10
|
||||
authentication:
|
||||
# Make sure to only have 1 authentication method defined, serviceToken/universalAuth.
|
||||
# If you have multiple authentication methods defined, it may cause issues.
|
||||
universalAuth:
|
||||
secretsScope:
|
||||
projectSlug: <project-slug>
|
||||
envSlug: <env-slug> # "dev", "staging", "prod", etc..
|
||||
secretsPath: "<secrets-path>" # Root is "/"
|
||||
recursive: true # Fetch all secrets from the specified path and all sub-directories. Default is false.
|
||||
|
||||
credentialsRef:
|
||||
secretName: universal-auth-credentials
|
||||
secretNamespace: default
|
||||
hostAPI: https://app.infisical.com/api
|
||||
resyncInterval: 10
|
||||
authentication:
|
||||
# Make sure to only have 1 authentication method defined, serviceToken/universalAuth.
|
||||
# If you have multiple authentication methods defined, it may cause issues.
|
||||
|
||||
# (Deprecated) Service Token Auth
|
||||
serviceToken:
|
||||
serviceTokenSecretReference:
|
||||
secretName: service-token
|
||||
secretNamespace: default
|
||||
secretsScope:
|
||||
envSlug: <env-slug>
|
||||
secretsPath: <secrets-path>
|
||||
recursive: true
|
||||
|
||||
# Universal Auth
|
||||
universalAuth:
|
||||
secretsScope:
|
||||
projectSlug: new-ob-em
|
||||
envSlug: dev # "dev", "staging", "prod", etc..
|
||||
secretsPath: "/" # Root is "/"
|
||||
recursive: true # Wether or not to use recursive mode (Fetches all secrets in an environment from a given secret path, and all folders inside the path) / defaults to false
|
||||
credentialsRef:
|
||||
secretName: universal-auth-credentials
|
||||
secretNamespace: default
|
||||
|
||||
# Native Kubernetes Auth
|
||||
kubernetesAuth:
|
||||
identityId: <machine-identity-id>
|
||||
serviceAccountRef:
|
||||
name: <service-account-name>
|
||||
namespace: <service-account-namespace>
|
||||
|
||||
# secretsScope is identical to the secrets scope in the universalAuth field in this sample.
|
||||
secretsScope:
|
||||
projectSlug: your-project-slug
|
||||
envSlug: prod
|
||||
secretsPath: "/path"
|
||||
recursive: true
|
||||
|
||||
# AWS IAM Auth
|
||||
awsIamAuth:
|
||||
identityId: <your-machine-identity-id>
|
||||
|
||||
# secretsScope is identical to the secrets scope in the universalAuth field in this sample.
|
||||
secretsScope:
|
||||
projectSlug: your-project-slug
|
||||
envSlug: prod
|
||||
secretsPath: "/path"
|
||||
recursive: true
|
||||
|
||||
# Azure Auth
|
||||
azureAuth:
|
||||
identityId: <your-machine-identity-id>
|
||||
|
||||
# secretsScope is identical to the secrets scope in the universalAuth field in this sample.
|
||||
secretsScope:
|
||||
projectSlug: your-project-slug
|
||||
envSlug: prod
|
||||
secretsPath: "/path"
|
||||
recursive: true
|
||||
|
||||
# GCP ID Token Auth
|
||||
gcpIdTokenAuth:
|
||||
identityId: <your-machine-identity-id>
|
||||
|
||||
# secretsScope is identical to the secrets scope in the universalAuth field in this sample.
|
||||
secretsScope:
|
||||
projectSlug: your-project-slug
|
||||
envSlug: prod
|
||||
secretsPath: "/path"
|
||||
recursive: true
|
||||
|
||||
# GCP IAM Auth
|
||||
gcpIamAuth:
|
||||
identityId: <your-machine-identity-id>
|
||||
|
||||
# secretsScope is identical to the secrets scope in the universalAuth field in this sample.
|
||||
secretsScope:
|
||||
projectSlug: your-project-slug
|
||||
envSlug: prod
|
||||
secretsPath: "/path"
|
||||
recursive: true
|
||||
|
||||
managedSecretReference:
|
||||
secretName: managed-secret
|
||||
secretNamespace: default
|
||||
creationPolicy: "Orphan" ## Owner | Orphan
|
||||
# secretType: kubernetes.io/dockerconfigjson
|
||||
|
||||
# Service tokens are deprecated and will be removed in the near future. Please use Machine Identities for authenticating with Infisical.
|
||||
serviceToken:
|
||||
serviceTokenSecretReference:
|
||||
secretName: service-token
|
||||
secretNamespace: default
|
||||
secretsScope:
|
||||
envSlug: <env-slug>
|
||||
secretsPath: <secrets-path> # Root is "/"
|
||||
recursive: true # Fetch all secrets from the specified path and all sub-directories. Default is false.
|
||||
|
||||
managedSecretReference:
|
||||
secretName: managed-secret
|
||||
secretNamespace: default
|
||||
creationPolicy: "Orphan" ## Owner | Orphan (default)
|
||||
# secretType: kubernetes.io/dockerconfigjson
|
||||
```
|
||||
|
||||
### InfisicalSecret CRD properties
|
||||
@ -156,7 +218,7 @@ When `hostAPI` is not defined the operator fetches secrets from Infisical Cloud.
|
||||
|
||||
</Steps>
|
||||
|
||||
{" "}
|
||||
|
||||
|
||||
<Info>
|
||||
Make sure to also populate the `secretsScope` field with the project slug
|
||||
@ -187,6 +249,233 @@ spec:
|
||||
|
||||
</Accordion>
|
||||
|
||||
<Accordion title="authentication.kubernetesAuth">
|
||||
The Kubernetes machine identity authentication method is used to authenticate with Infisical. The identity ID is stored in a field in the InfisicalSecret resource. This authentication method can only be used within a Kubernetes environment.
|
||||
|
||||
<Steps>
|
||||
<Step title="Create a machine identity">
|
||||
You need to create a machine identity, and give it access to the project(s) you want to interact with. You can [read more about Kubernetes machine identities here](/documentation/platform/identities/kubernetes-auth).
|
||||
</Step>
|
||||
<Step title="Add your identity ID to your InfisicalSecret resource">
|
||||
Once you have created your machine identity and added it to your project(s), you will need to add the identity ID to your InfisicalSecret resource. In the `authentication.kubernetesAuth.identityId` field, add the identity ID of the machine identity you created. See the example below for more details.
|
||||
</Step>
|
||||
<Step title="Add your Kubernetes service account token to the InfisicalSecret resource">
|
||||
When you configured your Kubernetes machine identity, you would have created a service account token if you followed the [Kubernetes machine identity guide](/documentation/platform/identities/kubernetes-auth). If you did not create a service account token, please follow the guide to do so.
|
||||
|
||||
You will need to enter the name of the service account and the namespace where the service account lives. The example below shows how to add the service account token to the InfisicalSecret resource.
|
||||
</Step>
|
||||
|
||||
</Steps>
|
||||
|
||||
<Info>
|
||||
Make sure to also populate the `secretsScope` field with the project slug
|
||||
_`projectSlug`_, environment slug _`envSlug`_, and secrets path
|
||||
_`secretsPath`_ that you want to fetch secrets from. Please see the example
|
||||
below.
|
||||
</Info>
|
||||
|
||||
## Example
|
||||
|
||||
```yaml example-kubernetes-auth.yaml
|
||||
apiVersion: secrets.infisical.com/v1alpha1
|
||||
kind: InfisicalSecret
|
||||
metadata:
|
||||
name: infisicalsecret-sample-crd
|
||||
spec:
|
||||
authentication:
|
||||
kubernetesAuth:
|
||||
identityId: <machine-identity-id>
|
||||
serviceAccountRef:
|
||||
name: <service-account-name>
|
||||
namespace: <service-account-namespace>
|
||||
|
||||
# secretsScope is identical to the secrets scope in the universalAuth field in this sample.
|
||||
secretsScope:
|
||||
projectSlug: your-project-slug
|
||||
envSlug: prod
|
||||
secretsPath: "/path"
|
||||
recursive: true
|
||||
...
|
||||
```
|
||||
|
||||
</Accordion>
|
||||
|
||||
<Accordion title="authentication.awsIamAuth">
|
||||
The AWS IAM machine identity authentication method is used to authenticate with Infisical. The identity ID is stored in a field in the InfisicalSecret resource. This authentication method can only be used within an AWS environment like an EC2 or a Lambda function.
|
||||
|
||||
<Steps>
|
||||
<Step title="Create a machine identity">
|
||||
You need to create a machine identity, and give it access to the project(s) you want to interact with. You can [read more about AWS machine identities here](/documentation/platform/identities/aws-auth).
|
||||
</Step>
|
||||
<Step title="Add your identity ID to your InfisicalSecret resource">
|
||||
Once you have created your machine identity and added it to your project(s), you will need to add the identity ID to your InfisicalSecret resource. In the `authentication.awsIamAuth.identityId` field, add the identity ID of the machine identity you created. See the example below for more details.
|
||||
</Step>
|
||||
|
||||
</Steps>
|
||||
|
||||
<Info>
|
||||
Make sure to also populate the `secretsScope` field with the project slug
|
||||
_`projectSlug`_, environment slug _`envSlug`_, and secrets path
|
||||
_`secretsPath`_ that you want to fetch secrets from. Please see the example
|
||||
below.
|
||||
</Info>
|
||||
|
||||
## Example
|
||||
|
||||
```yaml example-aws-iam-auth.yaml
|
||||
apiVersion: secrets.infisical.com/v1alpha1
|
||||
kind: InfisicalSecret
|
||||
metadata:
|
||||
name: infisicalsecret-sample-crd
|
||||
spec:
|
||||
authentication:
|
||||
awsIamAuth:
|
||||
identityId: <your-machine-identity-id>
|
||||
|
||||
# secretsScope is identical to the secrets scope in the universalAuth field in this sample.
|
||||
secretsScope:
|
||||
projectSlug: your-project-slug
|
||||
envSlug: prod
|
||||
secretsPath: "/path"
|
||||
recursive: true
|
||||
...
|
||||
```
|
||||
|
||||
</Accordion>
|
||||
|
||||
<Accordion title="authentication.azureAuth">
|
||||
The Azure machine identity authentication method is used to authenticate with Infisical. The identity ID is stored in a field in the InfisicalSecret resource. This authentication method can only be used within an Azure environment.
|
||||
|
||||
<Steps>
|
||||
<Step title="Create a machine identity">
|
||||
You need to create a machine identity, and give it access to the project(s) you want to interact with. You can [read more about Azure machine identities here](/documentation/platform/identities/azure-auth).
|
||||
</Step>
|
||||
<Step title="Add your identity ID to your InfisicalSecret resource">
|
||||
Once you have created your machine identity and added it to your project(s), you will need to add the identity ID to your InfisicalSecret resource. In the `authentication.azureAuth.identityId` field, add the identity ID of the machine identity you created. See the example below for more details.
|
||||
</Step>
|
||||
|
||||
</Steps>
|
||||
|
||||
<Info>
|
||||
Make sure to also populate the `secretsScope` field with the project slug
|
||||
_`projectSlug`_, environment slug _`envSlug`_, and secrets path
|
||||
_`secretsPath`_ that you want to fetch secrets from. Please see the example
|
||||
below.
|
||||
</Info>
|
||||
|
||||
## Example
|
||||
|
||||
```yaml example-azure-auth.yaml
|
||||
apiVersion: secrets.infisical.com/v1alpha1
|
||||
kind: InfisicalSecret
|
||||
metadata:
|
||||
name: infisicalsecret-sample-crd
|
||||
spec:
|
||||
authentication:
|
||||
azureAuth:
|
||||
identityId: <your-machine-identity-id>
|
||||
|
||||
# secretsScope is identical to the secrets scope in the universalAuth field in this sample.
|
||||
secretsScope:
|
||||
projectSlug: your-project-slug
|
||||
envSlug: prod
|
||||
secretsPath: "/path"
|
||||
recursive: true
|
||||
...
|
||||
```
|
||||
|
||||
</Accordion>
|
||||
|
||||
<Accordion title="authentication.gcpIdTokenAuth">
|
||||
The GCP ID Token machine identity authentication method is used to authenticate with Infisical. The identity ID is stored in a field in the InfisicalSecret resource. This authentication method can only be used within GCP environments.
|
||||
|
||||
<Steps>
|
||||
<Step title="Create a machine identity">
|
||||
You need to create a machine identity, and give it access to the project(s) you want to interact with. You can [read more about GCP machine identities here](/documentation/platform/identities/gcp-auth).
|
||||
</Step>
|
||||
<Step title="Add your identity ID to your InfisicalSecret resource">
|
||||
Once you have created your machine identity and added it to your project(s), you will need to add the identity ID to your InfisicalSecret resource. In the `authentication.gcpIdTokenAuth.identityId` field, add the identity ID of the machine identity you created. See the example below for more details.
|
||||
</Step>
|
||||
|
||||
</Steps>
|
||||
|
||||
<Info>
|
||||
Make sure to also populate the `secretsScope` field with the project slug
|
||||
_`projectSlug`_, environment slug _`envSlug`_, and secrets path
|
||||
_`secretsPath`_ that you want to fetch secrets from. Please see the example
|
||||
below.
|
||||
</Info>
|
||||
|
||||
## Example
|
||||
|
||||
```yaml example-gcp-id-token-auth.yaml
|
||||
apiVersion: secrets.infisical.com/v1alpha1
|
||||
kind: InfisicalSecret
|
||||
metadata:
|
||||
name: infisicalsecret-sample-crd
|
||||
spec:
|
||||
authentication:
|
||||
gcpIdTokenAuth:
|
||||
identityId: <your-machine-identity-id>
|
||||
|
||||
# secretsScope is identical to the secrets scope in the universalAuth field in this sample.
|
||||
secretsScope:
|
||||
projectSlug: your-project-slug
|
||||
envSlug: prod
|
||||
secretsPath: "/path"
|
||||
recursive: true
|
||||
...
|
||||
```
|
||||
|
||||
</Accordion>
|
||||
|
||||
|
||||
|
||||
<Accordion title="authentication.gcpIamAuth">
|
||||
The GCP IAM machine identity authentication method is used to authenticate with Infisical. The identity ID is stored in a field in the InfisicalSecret resource. This authentication method can only be used both within and outside GCP environments.
|
||||
|
||||
<Steps>
|
||||
<Step title="Create a machine identity">
|
||||
You need to create a machine identity, and give it access to the project(s) you want to interact with. You can [read more about GCP machine identities here](/documentation/platform/identities/gcp-auth).
|
||||
</Step>
|
||||
<Step title="Add your identity ID and service account token path to your InfisicalSecret resource">
|
||||
Once you have created your machine identity and added it to your project(s), you will need to add the identity ID to your InfisicalSecret resource. In the `authentication.gcpIamAuth.identityId` field, add the identity ID of the machine identity you created.
|
||||
You'll also need to add the service account key file path to your InfisicalSecret resource. In the `authentication.gcpIamAuth.serviceAccountKeyFilePath` field, add the path to your service account key file path. Please see the example below for more details.
|
||||
</Step>
|
||||
|
||||
</Steps>
|
||||
|
||||
<Info>
|
||||
Make sure to also populate the `secretsScope` field with the project slug
|
||||
_`projectSlug`_, environment slug _`envSlug`_, and secrets path
|
||||
_`secretsPath`_ that you want to fetch secrets from. Please see the example
|
||||
below.
|
||||
</Info>
|
||||
|
||||
## Example
|
||||
|
||||
```yaml example-gcp-id-token-auth.yaml
|
||||
apiVersion: secrets.infisical.com/v1alpha1
|
||||
kind: InfisicalSecret
|
||||
metadata:
|
||||
name: infisicalsecret-sample-crd
|
||||
spec:
|
||||
authentication:
|
||||
gcpIamAuth:
|
||||
identityId: <your-machine-identity-id>
|
||||
serviceAccountKeyFilePath: "/path/to-service-account-key-file-path.json"
|
||||
|
||||
# secretsScope is identical to the secrets scope in the universalAuth field in this sample.
|
||||
secretsScope:
|
||||
projectSlug: your-project-slug
|
||||
envSlug: prod
|
||||
secretsPath: "/path"
|
||||
recursive: true
|
||||
...
|
||||
```
|
||||
|
||||
</Accordion>
|
||||
|
||||
<Accordion title="authentication.serviceToken">
|
||||
<Warning>
|
||||
Service tokens are being deprecated in favor of [machine identities](/documentation/platform/identities/machine-identities).
|
||||
|
@ -384,6 +384,7 @@
|
||||
"pages": [
|
||||
"sdks/languages/node",
|
||||
"sdks/languages/python",
|
||||
"sdks/languages/go",
|
||||
"sdks/languages/java",
|
||||
"sdks/languages/csharp"
|
||||
]
|
||||
|
438
docs/sdks/languages/go.mdx
Normal file
438
docs/sdks/languages/go.mdx
Normal file
@ -0,0 +1,438 @@
|
||||
---
|
||||
title: "Infisical Go SDK"
|
||||
sidebarTitle: "Go"
|
||||
icon: "golang"
|
||||
---
|
||||
|
||||
|
||||
|
||||
If you're working with Go Lang, the official [Infisical Go SDK](https://github.com/infisical/go-sdk) package is the easiest way to fetch and work with secrets for your application.
|
||||
|
||||
- [Package](https://pkg.go.dev/github.com/infisical/go-sdk)
|
||||
- [Github Repository](https://github.com/infisical/go-sdk)
|
||||
|
||||
## Basic Usage
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
infisical "github.com/infisical/go-sdk"
|
||||
)
|
||||
|
||||
func main() {
|
||||
|
||||
client, err := infisical.NewInfisicalClient(infisical.Config{
|
||||
SiteUrl: "https://app.infisical.com", // Optional, default is https://app.infisical.com
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
fmt.Printf("Error: %v", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
_, err = client.Auth().UniversalAuthLogin("YOUR_CLIENT_ID", "YOUR_CLIENT_SECRET")
|
||||
|
||||
if err != nil {
|
||||
fmt.Printf("Authentication failed: %v", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
apiKeySecret, err := client.Secrets().Retrieve(infisical.RetrieveSecretOptions{
|
||||
SecretKey: "API_KEY",
|
||||
Environment: "dev",
|
||||
ProjectID: "YOUR_PROJECT_ID",
|
||||
SecretPath: "/",
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
fmt.Printf("Error: %v", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
fmt.Printf("API Key Secret: %v", apiKeySecret)
|
||||
|
||||
}
|
||||
```
|
||||
|
||||
This example demonstrates how to use the Infisical Go SDK in a simple Go application. The application retrieves a secret named `API_KEY` from the `dev` environment of the `YOUR_PROJECT_ID` project.
|
||||
|
||||
<Warning>
|
||||
We do not recommend hardcoding your [Machine Identity Tokens](/platform/identities/overview). Setting it as an environment variable would be best.
|
||||
</Warning>
|
||||
|
||||
# Installation
|
||||
|
||||
```console
|
||||
$ go get github.com/infisical/go-sdk
|
||||
```
|
||||
# Configuration
|
||||
|
||||
Import the SDK and create a client instance.
|
||||
|
||||
```go
|
||||
client, err := infisical.NewInfisicalClient(infisical.Config{
|
||||
SiteUrl: "https://app.infisical.com", // Optional, default is https://api.infisical.com
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
fmt.Printf("Error: %v", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
```
|
||||
|
||||
### ClientSettings methods
|
||||
|
||||
<ParamField query="options" type="object">
|
||||
<Expandable title="properties">
|
||||
<ParamField query="SiteUrl" type="string" optional>
|
||||
The URL of the Infisical API. Default is `https://api.infisical.com`.
|
||||
</ParamField>
|
||||
|
||||
<ParamField query="UserAgent" type="string" required>
|
||||
Optionally set the user agent that will be used for HTTP requests. _(Not recommended)_
|
||||
</ParamField>
|
||||
</Expandable>
|
||||
|
||||
</ParamField>
|
||||
|
||||
### Authentication
|
||||
|
||||
The SDK supports a variety of authentication methods. The most common authentication method is Universal Auth, which uses a client ID and client secret to authenticate.
|
||||
|
||||
#### Universal Auth
|
||||
|
||||
**Using environment variables**
|
||||
|
||||
Call `.Auth().UniversalAuthLogin()` with empty arguments to use the following environment variables:
|
||||
|
||||
- `INFISICAL_UNIVERSAL_AUTH_CLIENT_ID` - Your machine identity client ID.
|
||||
- `INFISICAL_UNIVERSAL_AUTH_CLIENT_SECRET` - Your machine identity client secret.
|
||||
|
||||
**Using the SDK directly**
|
||||
```go
|
||||
_, err := client.Auth().UniversalAuthLogin("CLIENT_ID", "CLIENT_SECRET")
|
||||
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
os.Exit(1)
|
||||
}
|
||||
```
|
||||
|
||||
#### GCP ID Token Auth
|
||||
<Info>
|
||||
Please note that this authentication method will only work if you're running your application on Google Cloud Platform.
|
||||
Please [read more](/documentation/platform/identities/gcp-auth) about this authentication method.
|
||||
</Info>
|
||||
|
||||
**Using environment variables**
|
||||
|
||||
Call `.Auth().GcpIdTokenAuthLogin()` with empty arguments to use the following environment variables:
|
||||
|
||||
- `INFISICAL_GCP_AUTH_IDENTITY_ID` - Your Infisical Machine Identity ID.
|
||||
|
||||
**Using the SDK directly**
|
||||
```go
|
||||
_, err := client.Auth().GcpIdTokenAuthLogin("YOUR_MACHINE_IDENTITY_ID")
|
||||
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
os.Exit(1)
|
||||
}
|
||||
```
|
||||
|
||||
#### GCP IAM Auth
|
||||
|
||||
**Using environment variables**
|
||||
|
||||
Call `.Auth().GcpIamAuthLogin()` with empty arguments to use the following environment variables:
|
||||
|
||||
- `INFISICAL_GCP_IAM_AUTH_IDENTITY_ID` - Your Infisical Machine Identity ID.
|
||||
- `INFISICAL_GCP_IAM_SERVICE_ACCOUNT_KEY_FILE_PATH` - The path to your GCP service account key file.
|
||||
|
||||
**Using the SDK directly**
|
||||
```go
|
||||
_, err = client.Auth().GcpIamAuthLogin("MACHINE_IDENTITY_ID", "SERVICE_ACCOUNT_KEY_FILE_PATH")
|
||||
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
os.Exit(1)
|
||||
}
|
||||
```
|
||||
|
||||
#### AWS IAM Auth
|
||||
<Info>
|
||||
Please note that this authentication method will only work if you're running your application on AWS.
|
||||
Please [read more](/documentation/platform/identities/aws-auth) about this authentication method.
|
||||
</Info>
|
||||
|
||||
**Using environment variables**
|
||||
|
||||
Call `.Auth().AwsIamAuthLogin()` with empty arguments to use the following environment variables:
|
||||
|
||||
- `INFISICAL_AWS_IAM_AUTH_IDENTITY_ID` - Your Infisical Machine Identity ID.
|
||||
|
||||
**Using the SDK directly**
|
||||
```go
|
||||
_, err = client.Auth().AwsIamAuthLogin("MACHINE_IDENTITY_ID")
|
||||
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
os.Exit(1)
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
#### Azure Auth
|
||||
<Info>
|
||||
Please note that this authentication method will only work if you're running your application on Azure.
|
||||
Please [read more](/documentation/platform/identities/azure-auth) about this authentication method.
|
||||
</Info>
|
||||
|
||||
**Using environment variables**
|
||||
|
||||
Call `.Auth().AzureAuthLogin()` with empty arguments to use the following environment variables:
|
||||
|
||||
- `INFISICAL_AZURE_AUTH_IDENTITY_ID` - Your Infisical Machine Identity ID.
|
||||
|
||||
**Using the SDK directly**
|
||||
```go
|
||||
_, err = client.Auth().AzureAuthLogin("MACHINE_IDENTITY_ID")
|
||||
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
os.Exit(1)
|
||||
}
|
||||
```
|
||||
|
||||
#### Kubernetes Auth
|
||||
<Info>
|
||||
Please note that this authentication method will only work if you're running your application on Kubernetes.
|
||||
Please [read more](/documentation/platform/identities/kubernetes-auth) about this authentication method.
|
||||
</Info>
|
||||
|
||||
**Using environment variables**
|
||||
|
||||
Call `.Auth().KubernetesAuthLogin()` with empty arguments to use the following environment variables:
|
||||
|
||||
- `INFISICAL_KUBERNETES_IDENTITY_ID` - Your Infisical Machine Identity ID.
|
||||
- `INFISICAL_KUBERNETES_SERVICE_ACCOUNT_TOKEN_PATH_ENV_NAME` - The environment variable name that contains the path to the service account token. This is optional and will default to `/var/run/secrets/kubernetes.io/serviceaccount/token`.
|
||||
|
||||
**Using the SDK directly**
|
||||
```go
|
||||
// Service account token path will default to /var/run/secrets/kubernetes.io/serviceaccount/token if empty value is passed
|
||||
_, err = client.Auth().KubernetesAuthLogin("MACHINE_IDENTITY_ID", "SERVICE_ACCOUNT_TOKEN_PATH")
|
||||
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
os.Exit(1)
|
||||
}
|
||||
```
|
||||
|
||||
## Working with Secrets
|
||||
|
||||
### client.Secrets().List(options)
|
||||
|
||||
```go
|
||||
secrets, err := client.Secrets().List(infisical.ListSecretsOptions{
|
||||
ProjectID: "PROJECT_ID",
|
||||
Environment: "dev",
|
||||
SecretPath: "/foo/bar",
|
||||
AttachToProcessEnv: false,
|
||||
})
|
||||
```
|
||||
|
||||
Retrieve all secrets within the Infisical project and environment that client is connected to
|
||||
|
||||
#### Parameters
|
||||
|
||||
<ParamField query="Parameters" type="object">
|
||||
<Expandable title="properties">
|
||||
<ParamField query="Environment" type="string" required>
|
||||
The slug name (dev, prod, etc) of the environment from where secrets should be fetched from.
|
||||
</ParamField>
|
||||
|
||||
<ParamField query="ProjectID" type="string">
|
||||
The project ID where the secret lives in.
|
||||
</ParamField>
|
||||
|
||||
<ParamField query="SecretPath" type="string" optional>
|
||||
The path from where secrets should be fetched from.
|
||||
</ParamField>
|
||||
|
||||
<ParamField query="AttachToProcessEnv" type="boolean" default="false" optional>
|
||||
Whether or not to set the fetched secrets to the process environment. If true, you can access the secrets like so `System.getenv("SECRET_NAME")`.
|
||||
</ParamField>
|
||||
|
||||
<ParamField query="IncludeImports" type="boolean" default="false" optional>
|
||||
Whether or not to include imported secrets from the current path. Read about [secret import](/documentation/platform/secret-reference)
|
||||
</ParamField>
|
||||
|
||||
<ParamField query="Recursive" type="boolean" default="false" optional>
|
||||
Whether or not to fetch secrets recursively from the specified path. Please note that there's a 20-depth limit for recursive fetching.
|
||||
</ParamField>
|
||||
|
||||
<ParamField query="ExpandSecretReferences" type="boolean" default="true" optional>
|
||||
Whether or not to expand secret references in the fetched secrets. Read about [secret reference](/documentation/platform/secret-reference)
|
||||
</ParamField>
|
||||
</Expandable>
|
||||
|
||||
</ParamField>
|
||||
|
||||
### client.Secrets().Retrieve(options)
|
||||
|
||||
```go
|
||||
secret, err := client.Secrets().Retrieve(infisical.RetrieveSecretOptions{
|
||||
SecretKey: "API_KEY",
|
||||
ProjectID: "PROJECT_ID",
|
||||
Environment: "dev",
|
||||
})
|
||||
```
|
||||
|
||||
Retrieve a secret from Infisical.
|
||||
|
||||
By default, `Secrets().Retrieve()` fetches and returns a shared secret.
|
||||
|
||||
#### Parameters
|
||||
|
||||
<ParamField query="Parameters" type="object" optional>
|
||||
<Expandable title="properties">
|
||||
<ParamField query="SecretKey" type="string" required>
|
||||
The key of the secret to retrieve.
|
||||
</ParamField>
|
||||
<ParamField query="ProjectID" type="string" required>
|
||||
The project ID where the secret lives in.
|
||||
</ParamField>
|
||||
<ParamField query="Environment" type="string" required>
|
||||
The slug name (dev, prod, etc) of the environment from where secrets should be fetched from.
|
||||
</ParamField>
|
||||
<ParamField query="SecretPath" type="string" optional>
|
||||
The path from where secret should be fetched from.
|
||||
</ParamField>
|
||||
<ParamField query="Type" type="string" optional>
|
||||
The type of the secret. Valid options are "shared" or "personal". If not specified, the default value is "shared".
|
||||
</ParamField>
|
||||
</Expandable>
|
||||
</ParamField>
|
||||
|
||||
### client.Secrets().Create(options)
|
||||
|
||||
```go
|
||||
secret, err := client.Secrets().Create(infisical.CreateSecretOptions{
|
||||
ProjectID: "PROJECT_ID",
|
||||
Environment: "dev",
|
||||
|
||||
SecretKey: "NEW_SECRET_KEY",
|
||||
SecretValue: "NEW_SECRET_VALUE",
|
||||
SecretComment: "This is a new secret",
|
||||
})
|
||||
```
|
||||
|
||||
Create a new secret in Infisical.
|
||||
|
||||
#### Parameters
|
||||
|
||||
<ParamField query="Parameters" type="object" optional>
|
||||
<Expandable title="properties">
|
||||
<ParamField query="SecretKey" type="string" required>
|
||||
The key of the secret to create.
|
||||
</ParamField>
|
||||
<ParamField query="SecretValue" type="string" required>
|
||||
The value of the secret.
|
||||
</ParamField>
|
||||
<ParamField query="SecretComment" type="string" optional>
|
||||
A comment for the secret.
|
||||
</ParamField>
|
||||
<ParamField query="ProjectID" type="string" required>
|
||||
The project ID where the secret lives in.
|
||||
</ParamField>
|
||||
<ParamField query="Environment" type="string" required>
|
||||
The slug name (dev, prod, etc) of the environment from where secrets should be fetched from.
|
||||
</ParamField>
|
||||
<ParamField query="SecretPath" type="string" optional>
|
||||
The path from where secret should be created.
|
||||
</ParamField>
|
||||
<ParamField query="Type" type="string" optional>
|
||||
The type of the secret. Valid options are "shared" or "personal". If not specified, the default value is "shared".
|
||||
</ParamField>
|
||||
</Expandable>
|
||||
</ParamField>
|
||||
|
||||
### client.Secrets().Update(options)
|
||||
|
||||
```go
|
||||
secret, err := client.Secrets().Update(infisical.UpdateSecretOptions{
|
||||
ProjectID: "PROJECT_ID",
|
||||
Environment: "dev",
|
||||
SecretKey: "NEW_SECRET_KEY",
|
||||
NewSecretValue: "NEW_SECRET_VALUE",
|
||||
NewSkipMultilineEncoding: false,
|
||||
})
|
||||
```
|
||||
|
||||
Update an existing secret in Infisical.
|
||||
|
||||
#### Parameters
|
||||
|
||||
<ParamField query="Parameters" type="object" optional>
|
||||
<Expandable title="properties">
|
||||
<ParamField query="SecretKey" type="string" required>
|
||||
The key of the secret to update.
|
||||
</ParamField>
|
||||
<ParamField query="NewSecretValue" type="string" required>
|
||||
The new value of the secret.
|
||||
</ParamField>
|
||||
<ParamField query="NewSkipMultilineEncoding" type="boolean" default="false" optional>
|
||||
Whether or not to skip multiline encoding for the new secret value.
|
||||
</ParamField>
|
||||
<ParamField query="ProjectID" type="string" required>
|
||||
The project ID where the secret lives in.
|
||||
</ParamField>
|
||||
<ParamField query="Environment" type="string" required>
|
||||
The slug name (dev, prod, etc) of the environment from where secrets should be fetched from.
|
||||
</ParamField>
|
||||
<ParamField query="SecretPath" type="string" optional>
|
||||
The path from where secret should be updated.
|
||||
</ParamField>
|
||||
<ParamField query="Type" type="string" optional>
|
||||
The type of the secret. Valid options are "shared" or "personal". If not specified, the default value is "shared".
|
||||
</ParamField>
|
||||
</Expandable>
|
||||
</ParamField>
|
||||
|
||||
### client.Secrets().Delete(options)
|
||||
|
||||
```go
|
||||
secret, err := client.Secrets().Delete(infisical.DeleteSecretOptions{
|
||||
ProjectID: "PROJECT_ID",
|
||||
Environment: "dev",
|
||||
SecretKey: "SECRET_KEY",
|
||||
})
|
||||
```
|
||||
|
||||
Delete a secret in Infisical.
|
||||
|
||||
#### Parameters
|
||||
|
||||
<ParamField query="Parameters" type="object" optional>
|
||||
<Expandable title="properties">
|
||||
<ParamField query="SecretKey" type="string">
|
||||
The key of the secret to update.
|
||||
</ParamField>
|
||||
<ParamField query="ProjectID" type="string" required>
|
||||
The project ID where the secret lives in.
|
||||
</ParamField>
|
||||
<ParamField query="Environment" type="string" required>
|
||||
The slug name (dev, prod, etc) of the environment from where secrets should be fetched from.
|
||||
</ParamField>
|
||||
<ParamField query="SecretPath" type="string" optional>
|
||||
The path from where secret should be deleted.
|
||||
</ParamField>
|
||||
<ParamField query="Type" type="string" optional>
|
||||
The type of the secret. Valid options are "shared" or "personal". If not specified, the default value is "shared".
|
||||
</ParamField>
|
||||
</Expandable>
|
||||
</ParamField>
|
@ -48,44 +48,44 @@ The platform utilizes Postgres to persist all of its data and Redis for caching
|
||||
Without email configuration, Infisical's core functions like sign-up/login and secret operations work, but this disables multi-factor authentication, email invites for projects, alerts for suspicious logins, and all other email-dependent features.
|
||||
|
||||
<Accordion title="Generic Configuration">
|
||||
<ParamField query="SMTP_HOST" type="string" default="none" optional>
|
||||
Hostname to connect to for establishing SMTP connections
|
||||
</ParamField>
|
||||
|
||||
{" "}
|
||||
|
||||
<ParamField query="SMTP_USERNAME" type="string" default="none" optional>
|
||||
Credential to connect to host (e.g. team@infisical.com)
|
||||
<ParamField query="SMTP_HOST" type="string" default="none" optional>
|
||||
Hostname to connect to for establishing SMTP connections
|
||||
</ParamField>
|
||||
|
||||
{" "}
|
||||
|
||||
<ParamField query="SMTP_PASSWORD" type="string" default="none" optional>
|
||||
Credential to connect to host
|
||||
</ParamField>
|
||||
|
||||
{" "}
|
||||
|
||||
<ParamField query="SMTP_PORT" type="string" default="587" optional>
|
||||
Port to connect to for establishing SMTP connections
|
||||
</ParamField>
|
||||
|
||||
{" "}
|
||||
|
||||
<ParamField query="SMTP_SECURE" type="string" default="none" optional>
|
||||
If true, use TLS when connecting to host. If false, TLS will be used if
|
||||
STARTTLS is supported
|
||||
<ParamField query="SMTP_USERNAME" type="string" default="none" optional>
|
||||
Credential to connect to host (e.g. team@infisical.com)
|
||||
</ParamField>
|
||||
|
||||
{" "}
|
||||
<ParamField query="SMTP_PASSWORD" type="string" default="none" optional>
|
||||
Credential to connect to host
|
||||
</ParamField>
|
||||
|
||||
<ParamField query="SMTP_FROM_ADDRESS" type="string" default="none" optional>
|
||||
Email address to be used for sending emails
|
||||
</ParamField>
|
||||
|
||||
<ParamField query="SMTP_FROM_NAME" type="string" default="none" optional>
|
||||
Name label to be used in From field (e.g. Team)
|
||||
</ParamField>
|
||||
<ParamField query="SMTP_FROM_NAME" type="string" default="none" optional>
|
||||
Name label to be used in From field (e.g. Team)
|
||||
</ParamField>
|
||||
|
||||
<ParamField query="SMTP_IGNORE_TLS" type="bool" default="false" optional>
|
||||
If this is `true` and `SMTP_PORT` is not 465 then TLS is not used even if the
|
||||
server supports STARTTLS extension.
|
||||
</ParamField>
|
||||
|
||||
<ParamField query="SMTP_REQUIRE_TLS" type="bool" default="true" optional>
|
||||
If this is `true` and `SMTP_PORT` is not 465 then Infisical tries to use
|
||||
STARTTLS even if the server does not advertise support for it. If the
|
||||
connection can not be encrypted then message is not sent.
|
||||
</ParamField>
|
||||
|
||||
<ParamField query="SMTP_TLS_REJECT_UNAUTHORIZED" type="bool" default="true" optional>
|
||||
If this is `true`, Infisical will validate the server's SSL/TLS certificate and reject the connection if the certificate is invalid or not trusted. If set to `false`, the client will accept the server's certificate regardless of its validity, which can be useful in development or testing environments but is not recommended for production use.
|
||||
</ParamField>
|
||||
</Accordion>
|
||||
|
||||
<Accordion title="Twilio SendGrid">
|
||||
@ -105,7 +105,6 @@ SMTP_HOST=smtp.sendgrid.net
|
||||
SMTP_USERNAME=apikey
|
||||
SMTP_PASSWORD=SG.rqFsfjxYPiqE1lqZTgD_lz7x8IVLx # your SendGrid API Key from step above
|
||||
SMTP_PORT=587
|
||||
SMTP_SECURE=true
|
||||
SMTP_FROM_ADDRESS=hey@example.com # your email address being used to send out emails
|
||||
SMTP_FROM_NAME=Infisical
|
||||
```
|
||||
@ -128,7 +127,6 @@ SMTP_HOST=smtp.mailgun.org # obtained from credentials page
|
||||
SMTP_USERNAME=postmaster@example.mailgun.org # obtained from credentials page
|
||||
SMTP_PASSWORD=password # obtained from credentials page
|
||||
SMTP_PORT=587
|
||||
SMTP_SECURE=true
|
||||
SMTP_FROM_ADDRESS=hey@example.com # your email address being used to send out emails
|
||||
SMTP_FROM_NAME=Infisical
|
||||
```
|
||||
@ -159,7 +157,6 @@ SMTP_FROM_NAME=Infisical
|
||||
SMTP_USERNAME=xxx # your SMTP username
|
||||
SMTP_PASSWORD=xxx # your SMTP password
|
||||
SMTP_PORT=465
|
||||
SMTP_SECURE=true
|
||||
SMTP_FROM_ADDRESS=hey@example.com # your email address being used to send out emails
|
||||
SMTP_FROM_NAME=Infisical
|
||||
```
|
||||
@ -187,7 +184,6 @@ SMTP_HOST=smtp.socketlabs.com
|
||||
SMTP_USERNAME=username # obtained from your credentials
|
||||
SMTP_PASSWORD=password # obtained from your credentials
|
||||
SMTP_PORT=587
|
||||
SMTP_SECURE=true
|
||||
SMTP_FROM_ADDRESS=hey@example.com # your email address being used to send out emails
|
||||
SMTP_FROM_NAME=Infisical
|
||||
```
|
||||
@ -229,7 +225,6 @@ SMTP_HOST=smtp.resend.com
|
||||
SMTP_USERNAME=resend
|
||||
SMTP_PASSWORD=YOUR_API_KEY
|
||||
SMTP_PORT=587
|
||||
SMTP_SECURE=true
|
||||
SMTP_FROM_ADDRESS=hey@example.com # your email address being used to send out emails
|
||||
SMTP_FROM_NAME=Infisical
|
||||
```
|
||||
@ -253,7 +248,6 @@ SMTP_HOST=smtp.gmail.com
|
||||
SMTP_USERNAME=hey@gmail.com # your email
|
||||
SMTP_PASSWORD=password # your password
|
||||
SMTP_PORT=587
|
||||
SMTP_SECURE=true
|
||||
SMTP_FROM_ADDRESS=hey@gmail.com
|
||||
SMTP_FROM_NAME=Infisical
|
||||
```
|
||||
@ -277,7 +271,6 @@ SMTP_HOST=smtp.office365.com
|
||||
SMTP_USERNAME=username@yourdomain.com # your username
|
||||
SMTP_PASSWORD=password # your password
|
||||
SMTP_PORT=587
|
||||
SMTP_SECURE=true
|
||||
SMTP_FROM_ADDRESS=username@yourdomain.com
|
||||
SMTP_FROM_NAME=Infisical
|
||||
```
|
||||
@ -294,7 +287,6 @@ SMTP_HOST=smtp.zoho.com
|
||||
SMTP_USERNAME=username # your email
|
||||
SMTP_PASSWORD=password # your password
|
||||
SMTP_PORT=587
|
||||
SMTP_SECURE=true
|
||||
SMTP_FROM_ADDRESS=hey@example.com # your personal Zoho email or domain-based email linked to Zoho Mail
|
||||
SMTP_FROM_NAME=Infisical
|
||||
```
|
||||
@ -320,7 +312,8 @@ To login into Infisical with OAuth providers such as Google, configure the assoc
|
||||
|
||||
<ParamField query="DEFAULT_SAML_ORG_SLUG" type="string">
|
||||
|
||||
When set, all visits to the Infisical login page will automatically redirect users of your Infisical instance to the SAML identity provider associated with the specified organization slug.
|
||||
When set, all visits to the Infisical login page will automatically redirect users of your Infisical instance to the SAML identity provider associated with the specified organization slug.
|
||||
|
||||
</ParamField>
|
||||
|
||||
<Accordion title="Google">
|
||||
|
@ -78,7 +78,8 @@ export const SecretPathInput = ({
|
||||
const validPaths = inputValue.split("/");
|
||||
validPaths.pop();
|
||||
|
||||
const newValue = `${validPaths.join("/")}/${suggestions[selectedIndex]}/`;
|
||||
// removed trailing slash
|
||||
const newValue = `${validPaths.join("/")}/${suggestions[selectedIndex]}`;
|
||||
onChange?.(newValue);
|
||||
setInputValue(newValue);
|
||||
setSecretPath(newValue);
|
||||
|
@ -93,27 +93,29 @@ const initProjectHelper = async ({ projectName }: { projectName: string }) => {
|
||||
});
|
||||
|
||||
try {
|
||||
secrets?.forEach((secret) => {
|
||||
createSecret({
|
||||
workspaceId: project.id,
|
||||
environment: secret.environment,
|
||||
type: secret.type,
|
||||
secretKey: secret.secretName,
|
||||
secretKeyCiphertext: secret.secretKeyCiphertext,
|
||||
secretKeyIV: secret.secretKeyIV,
|
||||
secretKeyTag: secret.secretKeyTag,
|
||||
secretValueCiphertext: secret.secretValueCiphertext,
|
||||
secretValueIV: secret.secretValueIV,
|
||||
secretValueTag: secret.secretValueTag,
|
||||
secretCommentCiphertext: secret.secretCommentCiphertext,
|
||||
secretCommentIV: secret.secretCommentIV,
|
||||
secretCommentTag: secret.secretCommentTag,
|
||||
secretPath: "/",
|
||||
metadata: {
|
||||
source: "signup"
|
||||
}
|
||||
});
|
||||
});
|
||||
await Promise.allSettled(
|
||||
(secrets || []).map((secret) =>
|
||||
createSecret({
|
||||
workspaceId: project.id,
|
||||
environment: secret.environment,
|
||||
type: secret.type,
|
||||
secretKey: secret.secretName,
|
||||
secretKeyCiphertext: secret.secretKeyCiphertext,
|
||||
secretKeyIV: secret.secretKeyIV,
|
||||
secretKeyTag: secret.secretKeyTag,
|
||||
secretValueCiphertext: secret.secretValueCiphertext,
|
||||
secretValueIV: secret.secretValueIV,
|
||||
secretValueTag: secret.secretValueTag,
|
||||
secretCommentCiphertext: secret.secretCommentCiphertext,
|
||||
secretCommentIV: secret.secretCommentIV,
|
||||
secretCommentTag: secret.secretCommentTag,
|
||||
secretPath: "/",
|
||||
metadata: {
|
||||
source: "signup"
|
||||
}
|
||||
})
|
||||
)
|
||||
);
|
||||
} catch (err) {
|
||||
console.error("Failed to upload secrets", err);
|
||||
}
|
||||
|
@ -73,6 +73,8 @@ export const useCreateIntegration = () => {
|
||||
}[];
|
||||
kmsKeyId?: string;
|
||||
shouldDisableDelete?: boolean;
|
||||
shouldMaskSecrets?: boolean;
|
||||
shouldProtectSecrets?: boolean;
|
||||
shouldEnableDelete?: boolean;
|
||||
};
|
||||
}) => {
|
||||
|
@ -264,13 +264,12 @@ export const useGetImportedSecretsAllEnvs = ({
|
||||
});
|
||||
|
||||
const isImportedSecretPresentInEnv = useCallback(
|
||||
(secPath: string, envSlug: string, secretName: string) => {
|
||||
(envSlug: string, secretName: string) => {
|
||||
const selectedEnvIndex = environments.indexOf(envSlug);
|
||||
|
||||
if (selectedEnvIndex !== -1) {
|
||||
const isPresent = secretImports?.[selectedEnvIndex]?.data?.find(
|
||||
({ secretPath, secrets }) =>
|
||||
secretPath === secPath && secrets.some((s) => s.key === secretName)
|
||||
const isPresent = secretImports?.[selectedEnvIndex]?.data?.find(({ secrets }) =>
|
||||
secrets.some((s) => s.key === secretName)
|
||||
);
|
||||
|
||||
return Boolean(isPresent);
|
||||
|
@ -20,6 +20,7 @@ import {
|
||||
TUpdateWorkspaceIdentityRoleDTO,
|
||||
TUpdateWorkspaceUserRoleDTO,
|
||||
UpdateEnvironmentDTO,
|
||||
UpdatePitVersionLimitDTO,
|
||||
Workspace
|
||||
} from "./types";
|
||||
|
||||
@ -249,6 +250,21 @@ export const useToggleAutoCapitalization = () => {
|
||||
});
|
||||
};
|
||||
|
||||
export const useUpdateWorkspaceVersionLimit = () => {
|
||||
const queryClient = useQueryClient();
|
||||
|
||||
return useMutation<{}, {}, UpdatePitVersionLimitDTO>({
|
||||
mutationFn: ({ projectSlug, pitVersionLimit }) => {
|
||||
return apiRequest.put(`/api/v1/workspace/${projectSlug}/version-limit`, {
|
||||
pitVersionLimit
|
||||
});
|
||||
},
|
||||
onSuccess: () => {
|
||||
queryClient.invalidateQueries(workspaceKeys.getAllUserWorkspace);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
export const useDeleteWorkspace = () => {
|
||||
const queryClient = useQueryClient();
|
||||
|
||||
|
@ -14,8 +14,10 @@ export type Workspace = {
|
||||
orgId: string;
|
||||
version: ProjectVersion;
|
||||
upgradeStatus: string | null;
|
||||
updatedAt: string;
|
||||
autoCapitalization: boolean;
|
||||
environments: WorkspaceEnv[];
|
||||
pitVersionLimit: number;
|
||||
slug: string;
|
||||
};
|
||||
|
||||
@ -48,6 +50,7 @@ export type CreateWorkspaceDTO = {
|
||||
};
|
||||
|
||||
export type RenameWorkspaceDTO = { workspaceID: string; newWorkspaceName: string };
|
||||
export type UpdatePitVersionLimitDTO = { projectSlug: string; pitVersionLimit: number };
|
||||
export type ToggleAutoCapitalizationDTO = { workspaceID: string; state: boolean };
|
||||
|
||||
export type DeleteWorkspaceDTO = { workspaceID: string };
|
||||
@ -128,4 +131,4 @@ export type TUpdateWorkspaceGroupRoleDTO = {
|
||||
temporaryAccessStartTime: string;
|
||||
}
|
||||
)[];
|
||||
};
|
||||
};
|
||||
|
46
frontend/src/lib/fn/date.ts
Normal file
46
frontend/src/lib/fn/date.ts
Normal file
@ -0,0 +1,46 @@
|
||||
export const timeAgo = (inputDate: Date, currentDate: Date): string => {
|
||||
const now = new Date(currentDate).getTime();
|
||||
const date = new Date(inputDate).getTime();
|
||||
const elapsedMilliseconds = now - date;
|
||||
const elapsedSeconds = Math.abs(Math.floor(elapsedMilliseconds / 1000));
|
||||
const elapsedMinutes = Math.abs(Math.floor(elapsedSeconds / 60));
|
||||
const elapsedHours = Math.abs(Math.floor(elapsedMinutes / 60));
|
||||
const elapsedDays = Math.abs(Math.floor(elapsedHours / 24));
|
||||
const elapsedWeeks = Math.abs(Math.floor(elapsedDays / 7));
|
||||
const elapsedMonths = Math.abs(Math.floor(elapsedDays / 30));
|
||||
const elapsedYears = Math.abs(Math.floor(elapsedDays / 365));
|
||||
|
||||
if (elapsedYears > 0) {
|
||||
return `${elapsedYears} year${elapsedYears === 1 ? "" : "s"} ${
|
||||
elapsedMilliseconds >= 0 ? "ago" : "from now"
|
||||
}`;
|
||||
}
|
||||
if (elapsedMonths > 0) {
|
||||
return `${elapsedMonths} month${elapsedMonths === 1 ? "" : "s"} ${
|
||||
elapsedMilliseconds >= 0 ? "ago" : "from now"
|
||||
}`;
|
||||
}
|
||||
if (elapsedWeeks > 0) {
|
||||
return `${elapsedWeeks} week${elapsedWeeks === 1 ? "" : "s"} ${
|
||||
elapsedMilliseconds >= 0 ? "ago" : "from now"
|
||||
}`;
|
||||
}
|
||||
if (elapsedDays > 0) {
|
||||
return `${elapsedDays} day${elapsedDays === 1 ? "" : "s"} ${
|
||||
elapsedMilliseconds >= 0 ? "ago" : "from now"
|
||||
}`;
|
||||
}
|
||||
if (elapsedHours > 0) {
|
||||
return `${elapsedHours} hour${elapsedHours === 1 ? "" : "s"} ${
|
||||
elapsedMilliseconds >= 0 ? "ago" : "from now"
|
||||
}`;
|
||||
}
|
||||
if (elapsedMinutes > 0) {
|
||||
return `${elapsedMinutes} minute${elapsedMinutes === 1 ? "" : "s"} ${
|
||||
elapsedMilliseconds >= 0 ? "ago" : "from now"
|
||||
}`;
|
||||
}
|
||||
return `${elapsedSeconds} second${elapsedSeconds === 1 ? "" : "s"} ${
|
||||
elapsedMilliseconds >= 0 ? "ago" : "from now"
|
||||
}`;
|
||||
};
|
@ -25,6 +25,7 @@ import {
|
||||
ModalContent,
|
||||
Select,
|
||||
SelectItem,
|
||||
Switch,
|
||||
Tab,
|
||||
TabList,
|
||||
TabPanel,
|
||||
@ -58,7 +59,9 @@ const schema = yup.object({
|
||||
targetAppId: yup.string().required("GitLab project is required"),
|
||||
targetEnvironment: yup.string(),
|
||||
secretPrefix: yup.string(),
|
||||
secretSuffix: yup.string()
|
||||
secretSuffix: yup.string(),
|
||||
shouldMaskSecrets: yup.boolean(),
|
||||
shouldProtectSecrets: yup.boolean()
|
||||
});
|
||||
|
||||
type FormData = yup.InferType<typeof schema>;
|
||||
@ -138,7 +141,9 @@ export default function GitLabCreateIntegrationPage() {
|
||||
targetAppId,
|
||||
targetEnvironment,
|
||||
secretPrefix,
|
||||
secretSuffix
|
||||
secretSuffix,
|
||||
shouldMaskSecrets,
|
||||
shouldProtectSecrets
|
||||
}: FormData) => {
|
||||
try {
|
||||
setIsLoading(true);
|
||||
@ -156,7 +161,9 @@ export default function GitLabCreateIntegrationPage() {
|
||||
secretPath,
|
||||
metadata: {
|
||||
secretPrefix,
|
||||
secretSuffix
|
||||
secretSuffix,
|
||||
shouldMaskSecrets,
|
||||
shouldProtectSecrets
|
||||
}
|
||||
});
|
||||
|
||||
@ -390,6 +397,36 @@ export default function GitLabCreateIntegrationPage() {
|
||||
exit={{ opacity: 0, translateX: 30 }}
|
||||
className="pb-[14.25rem]"
|
||||
>
|
||||
<div className="ml-1">
|
||||
<Controller
|
||||
control={control}
|
||||
name="shouldMaskSecrets"
|
||||
render={({ field: { onChange, value } }) => (
|
||||
<Switch
|
||||
id="should-mask-secrets"
|
||||
onCheckedChange={(isChecked) => onChange(isChecked)}
|
||||
isChecked={value}
|
||||
>
|
||||
<div className="max-w-md">Mark Infisical secrets in Gitlab as 'Masked' secrets</div>
|
||||
</Switch>
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
<div className="ml-1 mt-4 mb-5">
|
||||
<Controller
|
||||
control={control}
|
||||
name="shouldProtectSecrets"
|
||||
render={({ field: { onChange, value } }) => (
|
||||
<Switch
|
||||
id="should-protect-secrets"
|
||||
onCheckedChange={(isChecked) => onChange(isChecked)}
|
||||
isChecked={value}
|
||||
>
|
||||
Mark Infisical secrets in Gitlab as 'Protected' secrets
|
||||
</Switch>
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
<Controller
|
||||
control={control}
|
||||
name="secretPrefix"
|
||||
|
@ -12,11 +12,14 @@ import { faFolderOpen } from "@fortawesome/free-regular-svg-icons";
|
||||
import {
|
||||
faArrowRight,
|
||||
faArrowUpRightFromSquare,
|
||||
faBorderAll,
|
||||
faCheck,
|
||||
faCheckCircle,
|
||||
faClipboard,
|
||||
faExclamationCircle,
|
||||
faFileShield,
|
||||
faHandPeace,
|
||||
faList,
|
||||
faMagnifyingGlass,
|
||||
faNetworkWired,
|
||||
faPlug,
|
||||
@ -35,6 +38,7 @@ import {
|
||||
Button,
|
||||
Checkbox,
|
||||
FormControl,
|
||||
IconButton,
|
||||
Input,
|
||||
Modal,
|
||||
ModalContent,
|
||||
@ -86,6 +90,11 @@ type ItemProps = {
|
||||
link?: string;
|
||||
};
|
||||
|
||||
enum ProjectsViewMode {
|
||||
GRID = "grid",
|
||||
LIST = "list"
|
||||
}
|
||||
|
||||
function copyToClipboard(id: string, setState: (value: boolean) => void) {
|
||||
// Get the text field
|
||||
const copyText = document.getElementById(id) as HTMLInputElement;
|
||||
@ -309,8 +318,9 @@ const LearningItem = ({
|
||||
href={link}
|
||||
>
|
||||
<div
|
||||
className={`${complete ? "bg-gradient-to-r from-primary-500/70 p-[0.07rem]" : ""
|
||||
} mb-3 rounded-md`}
|
||||
className={`${
|
||||
complete ? "bg-gradient-to-r from-primary-500/70 p-[0.07rem]" : ""
|
||||
} mb-3 rounded-md`}
|
||||
>
|
||||
<div
|
||||
onKeyDown={() => null}
|
||||
@ -321,10 +331,11 @@ const LearningItem = ({
|
||||
await registerUserAction.mutateAsync(userAction);
|
||||
}
|
||||
}}
|
||||
className={`group relative flex h-[5.5rem] w-full items-center justify-between overflow-hidden rounded-md border ${complete
|
||||
className={`group relative flex h-[5.5rem] w-full items-center justify-between overflow-hidden rounded-md border ${
|
||||
complete
|
||||
? "cursor-default border-mineshaft-900 bg-gradient-to-r from-[#0e1f01] to-mineshaft-700"
|
||||
: "cursor-pointer border-mineshaft-600 bg-mineshaft-800 shadow-xl hover:bg-mineshaft-700"
|
||||
} text-mineshaft-100 duration-200`}
|
||||
} text-mineshaft-100 duration-200`}
|
||||
>
|
||||
<div className="mr-4 flex flex-row items-center">
|
||||
<FontAwesomeIcon icon={icon} className="mx-2 w-16 text-4xl" />
|
||||
@ -402,8 +413,9 @@ const LearningItemSquare = ({
|
||||
href={link}
|
||||
>
|
||||
<div
|
||||
className={`${complete ? "bg-gradient-to-r from-primary-500/70 p-[0.07rem]" : ""
|
||||
} w-full rounded-md`}
|
||||
className={`${
|
||||
complete ? "bg-gradient-to-r from-primary-500/70 p-[0.07rem]" : ""
|
||||
} w-full rounded-md`}
|
||||
>
|
||||
<div
|
||||
onKeyDown={() => null}
|
||||
@ -414,10 +426,11 @@ const LearningItemSquare = ({
|
||||
await registerUserAction.mutateAsync(userAction);
|
||||
}
|
||||
}}
|
||||
className={`group relative flex w-full items-center justify-between overflow-hidden rounded-md border ${complete
|
||||
className={`group relative flex w-full items-center justify-between overflow-hidden rounded-md border ${
|
||||
complete
|
||||
? "cursor-default border-mineshaft-900 bg-gradient-to-r from-[#0e1f01] to-mineshaft-700"
|
||||
: "cursor-pointer border-mineshaft-600 bg-mineshaft-800 shadow-xl hover:bg-mineshaft-700"
|
||||
} text-mineshaft-100 duration-200`}
|
||||
} text-mineshaft-100 duration-200`}
|
||||
>
|
||||
<div className="flex w-full flex-col items-center px-6 py-4">
|
||||
<div className="flex w-full flex-row items-start justify-between">
|
||||
@ -431,8 +444,9 @@ const LearningItemSquare = ({
|
||||
</div>
|
||||
)}
|
||||
<div
|
||||
className={`text-right text-sm font-normal text-mineshaft-300 ${complete ? "font-semibold text-primary" : ""
|
||||
}`}
|
||||
className={`text-right text-sm font-normal text-mineshaft-300 ${
|
||||
complete ? "font-semibold text-primary" : ""
|
||||
}`}
|
||||
>
|
||||
{complete ? "Complete!" : `About ${time}`}
|
||||
</div>
|
||||
@ -461,7 +475,6 @@ const formSchema = yup.object({
|
||||
type TAddProjectFormData = yup.InferType<typeof formSchema>;
|
||||
|
||||
// #TODO: Update all the workspaceIds
|
||||
|
||||
const OrganizationPage = withPermission(
|
||||
() => {
|
||||
const { t } = useTranslation();
|
||||
@ -496,6 +509,9 @@ const OrganizationPage = withPermission(
|
||||
const createWs = useCreateWorkspace();
|
||||
const { user } = useUser();
|
||||
const { data: serverDetails } = useFetchServerStatus();
|
||||
const [projectsViewMode, setProjectsViewMode] = useState<ProjectsViewMode>(
|
||||
(localStorage.getItem("projectsViewMode") as ProjectsViewMode) || ProjectsViewMode.GRID
|
||||
);
|
||||
|
||||
const onCreateProject = async ({ name, addMembers }: TAddProjectFormData) => {
|
||||
// type check
|
||||
@ -550,6 +566,95 @@ const OrganizationPage = withPermission(
|
||||
}, []);
|
||||
|
||||
const isWorkspaceEmpty = !isWorkspaceLoading && orgWorkspaces?.length === 0;
|
||||
const filteredWorkspaces = orgWorkspaces.filter((ws) =>
|
||||
ws?.name?.toLowerCase().includes(searchFilter.toLowerCase())
|
||||
);
|
||||
|
||||
const projectsGridView = (
|
||||
<div className="mt-4 grid w-full grid-cols-1 gap-4 lg:grid-cols-2 xl:grid-cols-3 2xl:grid-cols-4">
|
||||
{isWorkspaceLoading &&
|
||||
Array.apply(0, Array(3)).map((_x, i) => (
|
||||
<div
|
||||
key={`workspace-cards-loading-${i + 1}`}
|
||||
className="min-w-72 flex h-40 flex-col justify-between rounded-md border border-mineshaft-600 bg-mineshaft-800 p-4"
|
||||
>
|
||||
<div className="mt-0 text-lg text-mineshaft-100">
|
||||
<Skeleton className="w-3/4 bg-mineshaft-600" />
|
||||
</div>
|
||||
<div className="mt-0 pb-6 text-sm text-mineshaft-300">
|
||||
<Skeleton className="w-1/2 bg-mineshaft-600" />
|
||||
</div>
|
||||
<div className="flex justify-end">
|
||||
<Skeleton className="w-1/2 bg-mineshaft-600" />
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
{filteredWorkspaces.map((workspace) => (
|
||||
// eslint-disable-next-line jsx-a11y/no-static-element-interactions, jsx-a11y/click-events-have-key-events
|
||||
<div
|
||||
onClick={() => {
|
||||
router.push(`/project/${workspace.id}/secrets/overview`);
|
||||
localStorage.setItem("projectData.id", workspace.id);
|
||||
}}
|
||||
key={workspace.id}
|
||||
className="min-w-72 group flex h-40 cursor-pointer flex-col justify-between rounded-md border border-mineshaft-600 bg-mineshaft-800 p-4"
|
||||
>
|
||||
<div className="mt-0 truncate text-lg text-mineshaft-100">{workspace.name}</div>
|
||||
<div className="mt-0 pb-6 text-sm text-mineshaft-300">
|
||||
{workspace.environments?.length || 0} environments
|
||||
</div>
|
||||
<button type="button">
|
||||
<div className="group ml-auto w-max cursor-pointer rounded-full border border-mineshaft-600 bg-mineshaft-900 py-2 px-4 text-sm text-mineshaft-300 transition-all group-hover:border-primary-500/80 group-hover:bg-primary-800/20 group-hover:text-mineshaft-200">
|
||||
Explore{" "}
|
||||
<FontAwesomeIcon
|
||||
icon={faArrowRight}
|
||||
className="pl-1.5 pr-0.5 duration-200 group-hover:pl-2 group-hover:pr-0"
|
||||
/>
|
||||
</div>
|
||||
</button>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
|
||||
const projectsListView = (
|
||||
<div className="mt-4 w-full rounded-md">
|
||||
{isWorkspaceLoading &&
|
||||
Array.apply(0, Array(3)).map((_x, i) => (
|
||||
<div
|
||||
key={`workspace-cards-loading-${i + 1}`}
|
||||
className={`min-w-72 group flex h-12 cursor-pointer flex-row items-center justify-between border border-mineshaft-600 bg-mineshaft-800 px-6 hover:bg-mineshaft-700 ${
|
||||
i === 0 && "rounded-t-md"
|
||||
} ${i === 2 && "rounded-b-md border-b"}`}
|
||||
>
|
||||
<Skeleton className="w-full bg-mineshaft-600" />
|
||||
</div>
|
||||
))}
|
||||
{filteredWorkspaces.map((workspace, ind) => (
|
||||
// eslint-disable-next-line jsx-a11y/no-static-element-interactions, jsx-a11y/click-events-have-key-events
|
||||
<div
|
||||
onClick={() => {
|
||||
router.push(`/project/${workspace.id}/secrets/overview`);
|
||||
localStorage.setItem("projectData.id", workspace.id);
|
||||
}}
|
||||
key={workspace.id}
|
||||
className={`min-w-72 group grid h-14 cursor-pointer grid-cols-6 border-t border-l border-r border-mineshaft-600 bg-mineshaft-800 px-6 hover:bg-mineshaft-700 ${
|
||||
ind === 0 && "rounded-t-md"
|
||||
} ${ind === filteredWorkspaces.length - 1 && "rounded-b-md border-b"}`}
|
||||
>
|
||||
<div className="flex items-center sm:col-span-3 lg:col-span-4">
|
||||
<FontAwesomeIcon icon={faFileShield} className="text-sm text-primary/70" />
|
||||
<div className="ml-5 truncate text-sm text-mineshaft-100">{workspace.name}</div>
|
||||
</div>
|
||||
<div className="flex items-center justify-end sm:col-span-3 lg:col-span-2">
|
||||
<div className="text-center text-sm text-mineshaft-300">
|
||||
{workspace.environments?.length || 0} environments
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
|
||||
return (
|
||||
<div className="mx-auto flex max-w-7xl flex-col justify-start bg-bunker-800 md:h-screen">
|
||||
@ -580,7 +685,9 @@ const OrganizationPage = withPermission(
|
||||
</div>
|
||||
)}
|
||||
<div className="mb-4 flex flex-col items-start justify-start px-6 py-6 pb-0 text-3xl">
|
||||
<p className="mr-4 font-semibold text-white">Projects</p>
|
||||
<div className="flex w-full justify-between">
|
||||
<p className="mr-4 font-semibold text-white">Projects</p>
|
||||
</div>
|
||||
<div className="mt-6 flex w-full flex-row">
|
||||
<Input
|
||||
className="h-[2.3rem] bg-mineshaft-800 text-sm placeholder-mineshaft-50 duration-200 focus:bg-mineshaft-700/80"
|
||||
@ -589,6 +696,36 @@ const OrganizationPage = withPermission(
|
||||
onChange={(e) => setSearchFilter(e.target.value)}
|
||||
leftIcon={<FontAwesomeIcon icon={faMagnifyingGlass} />}
|
||||
/>
|
||||
<div className="ml-2 flex rounded-md border border-mineshaft-600 bg-mineshaft-800 p-1">
|
||||
<IconButton
|
||||
variant="outline_bg"
|
||||
onClick={() => {
|
||||
localStorage.setItem("projectsViewMode", ProjectsViewMode.GRID);
|
||||
setProjectsViewMode(ProjectsViewMode.GRID);
|
||||
}}
|
||||
ariaLabel="grid"
|
||||
size="xs"
|
||||
className={`${
|
||||
projectsViewMode === ProjectsViewMode.GRID ? "bg-mineshaft-500" : "bg-transparent"
|
||||
} min-w-[2.4rem] border-none hover:bg-mineshaft-600`}
|
||||
>
|
||||
<FontAwesomeIcon icon={faBorderAll} />
|
||||
</IconButton>
|
||||
<IconButton
|
||||
variant="outline_bg"
|
||||
onClick={() => {
|
||||
localStorage.setItem("projectsViewMode", ProjectsViewMode.LIST);
|
||||
setProjectsViewMode(ProjectsViewMode.LIST);
|
||||
}}
|
||||
ariaLabel="list"
|
||||
size="xs"
|
||||
className={`${
|
||||
projectsViewMode === ProjectsViewMode.LIST ? "bg-mineshaft-500" : "bg-transparent"
|
||||
} min-w-[2.4rem] border-none hover:bg-mineshaft-600`}
|
||||
>
|
||||
<FontAwesomeIcon icon={faList} />
|
||||
</IconButton>
|
||||
</div>
|
||||
<OrgPermissionCan I={OrgPermissionActions.Create} an={OrgPermissionSubjects.Workspace}>
|
||||
{(isAllowed) => (
|
||||
<Button
|
||||
@ -609,52 +746,7 @@ const OrganizationPage = withPermission(
|
||||
)}
|
||||
</OrgPermissionCan>
|
||||
</div>
|
||||
<div className="mt-4 grid w-full grid-cols-1 gap-4 lg:grid-cols-2 xl:grid-cols-3 2xl:grid-cols-4">
|
||||
{isWorkspaceLoading &&
|
||||
Array.apply(0, Array(3)).map((_x, i) => (
|
||||
<div
|
||||
key={`workspace-cards-loading-${i + 1}`}
|
||||
className="min-w-72 flex h-40 flex-col justify-between rounded-md border border-mineshaft-600 bg-mineshaft-800 p-4"
|
||||
>
|
||||
<div className="mt-0 text-lg text-mineshaft-100">
|
||||
<Skeleton className="w-3/4 bg-mineshaft-600" />
|
||||
</div>
|
||||
<div className="mt-0 pb-6 text-sm text-mineshaft-300">
|
||||
<Skeleton className="w-1/2 bg-mineshaft-600" />
|
||||
</div>
|
||||
<div className="flex justify-end">
|
||||
<Skeleton className="w-1/2 bg-mineshaft-600" />
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
{orgWorkspaces
|
||||
.filter((ws) => ws?.name?.toLowerCase().includes(searchFilter.toLowerCase()))
|
||||
.map((workspace) => (
|
||||
// eslint-disable-next-line jsx-a11y/no-static-element-interactions, jsx-a11y/click-events-have-key-events
|
||||
<div
|
||||
onClick={() => {
|
||||
router.push(`/project/${workspace.id}/secrets/overview`);
|
||||
localStorage.setItem("projectData.id", workspace.id);
|
||||
}}
|
||||
key={workspace.id}
|
||||
className="min-w-72 group flex h-40 cursor-pointer flex-col justify-between rounded-md border border-mineshaft-600 bg-mineshaft-800 p-4"
|
||||
>
|
||||
<div className="mt-0 truncate text-lg text-mineshaft-100">{workspace.name}</div>
|
||||
<div className="mt-0 pb-6 text-sm text-mineshaft-300">
|
||||
{workspace.environments?.length || 0} environments
|
||||
</div>
|
||||
<button type="button">
|
||||
<div className="group ml-auto w-max cursor-pointer rounded-full border border-mineshaft-600 bg-mineshaft-900 py-2 px-4 text-sm text-mineshaft-300 transition-all group-hover:border-primary-500/80 group-hover:bg-primary-800/20 group-hover:text-mineshaft-200">
|
||||
Explore{" "}
|
||||
<FontAwesomeIcon
|
||||
icon={faArrowRight}
|
||||
className="pl-1.5 pr-0.5 duration-200 group-hover:pl-2 group-hover:pr-0"
|
||||
/>
|
||||
</div>
|
||||
</button>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
{projectsViewMode === ProjectsViewMode.LIST ? projectsListView : projectsGridView}
|
||||
{isWorkspaceEmpty && (
|
||||
<div className="w-full rounded-md border border-mineshaft-700 bg-mineshaft-800 px-4 py-6 text-base text-mineshaft-300">
|
||||
<FontAwesomeIcon
|
||||
@ -709,94 +801,95 @@ const OrganizationPage = withPermission(
|
||||
new Date().getTime() - new Date(user?.createdAt).getTime() <
|
||||
30 * 24 * 60 * 60 * 1000
|
||||
) && (
|
||||
<div className="mb-4 flex flex-col items-start justify-start px-6 pb-0 text-3xl">
|
||||
<p className="mr-4 mb-4 font-semibold text-white">Onboarding Guide</p>
|
||||
<div className="mb-3 grid w-full grid-cols-1 gap-3 lg:grid-cols-2 xl:grid-cols-3 2xl:grid-cols-4">
|
||||
<LearningItemSquare
|
||||
text="Watch Infisical demo"
|
||||
subText="Set up Infisical in 3 min."
|
||||
complete={hasUserClickedIntro}
|
||||
icon={faHandPeace}
|
||||
time="3 min"
|
||||
userAction="intro_cta_clicked"
|
||||
link="https://www.youtube.com/watch?v=PK23097-25I"
|
||||
/>
|
||||
{orgWorkspaces.length !== 0 && (
|
||||
<>
|
||||
<LearningItemSquare
|
||||
text="Add your secrets"
|
||||
subText="Drop a .env file or type your secrets."
|
||||
complete={hasUserPushedSecrets}
|
||||
icon={faPlus}
|
||||
time="1 min"
|
||||
userAction="first_time_secrets_pushed"
|
||||
link={`/project/${orgWorkspaces[0]?.id}/secrets/overview`}
|
||||
/>
|
||||
<LearningItemSquare
|
||||
text="Invite your teammates"
|
||||
subText="Infisical is better used as a team."
|
||||
complete={usersInOrg}
|
||||
icon={faUserPlus}
|
||||
time="2 min"
|
||||
link={`/org/${router.query.id}/members?action=invite`}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
<div className="block xl:hidden 2xl:block">
|
||||
<LearningItemSquare
|
||||
text="Join Infisical Slack"
|
||||
subText="Have any questions? Ask us!"
|
||||
complete={hasUserClickedSlack}
|
||||
icon={faSlack}
|
||||
time="1 min"
|
||||
userAction="slack_cta_clicked"
|
||||
link="https://infisical.com/slack"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div className="mb-4 flex flex-col items-start justify-start px-6 pb-0 text-3xl">
|
||||
<p className="mr-4 mb-4 font-semibold text-white">Onboarding Guide</p>
|
||||
<div className="mb-3 grid w-full grid-cols-1 gap-3 lg:grid-cols-2 xl:grid-cols-3 2xl:grid-cols-4">
|
||||
<LearningItemSquare
|
||||
text="Watch Infisical demo"
|
||||
subText="Set up Infisical in 3 min."
|
||||
complete={hasUserClickedIntro}
|
||||
icon={faHandPeace}
|
||||
time="3 min"
|
||||
userAction="intro_cta_clicked"
|
||||
link="https://www.youtube.com/watch?v=PK23097-25I"
|
||||
/>
|
||||
{orgWorkspaces.length !== 0 && (
|
||||
<div className="group relative mb-3 flex h-full w-full cursor-default flex-col items-center justify-between overflow-hidden rounded-md border border-mineshaft-600 bg-mineshaft-800 pl-2 pr-2 pt-4 pb-2 text-mineshaft-100 shadow-xl duration-200">
|
||||
<div className="mb-4 flex w-full flex-row items-center pr-4">
|
||||
<div className="mr-4 flex w-full flex-row items-center">
|
||||
<FontAwesomeIcon icon={faNetworkWired} className="mx-2 w-16 text-4xl" />
|
||||
{false && (
|
||||
<div className="absolute left-12 top-10 flex h-7 w-7 items-center justify-center rounded-full bg-bunker-500 p-2 group-hover:bg-mineshaft-700">
|
||||
<FontAwesomeIcon
|
||||
icon={faCheckCircle}
|
||||
className="h-5 w-5 text-4xl text-green"
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
<div className="flex flex-col items-start pl-0.5">
|
||||
<div className="mt-0.5 text-xl font-semibold">Inject secrets locally</div>
|
||||
<div className="text-sm font-normal">
|
||||
Replace .env files with a more secure and efficient alternative.
|
||||
</div>
|
||||
<>
|
||||
<LearningItemSquare
|
||||
text="Add your secrets"
|
||||
subText="Drop a .env file or type your secrets."
|
||||
complete={hasUserPushedSecrets}
|
||||
icon={faPlus}
|
||||
time="1 min"
|
||||
userAction="first_time_secrets_pushed"
|
||||
link={`/project/${orgWorkspaces[0]?.id}/secrets/overview`}
|
||||
/>
|
||||
<LearningItemSquare
|
||||
text="Invite your teammates"
|
||||
subText="Infisical is better used as a team."
|
||||
complete={usersInOrg}
|
||||
icon={faUserPlus}
|
||||
time="2 min"
|
||||
link={`/org/${router.query.id}/members?action=invite`}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
<div className="block xl:hidden 2xl:block">
|
||||
<LearningItemSquare
|
||||
text="Join Infisical Slack"
|
||||
subText="Have any questions? Ask us!"
|
||||
complete={hasUserClickedSlack}
|
||||
icon={faSlack}
|
||||
time="1 min"
|
||||
userAction="slack_cta_clicked"
|
||||
link="https://infisical.com/slack"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
{orgWorkspaces.length !== 0 && (
|
||||
<div className="group relative mb-3 flex h-full w-full cursor-default flex-col items-center justify-between overflow-hidden rounded-md border border-mineshaft-600 bg-mineshaft-800 pl-2 pr-2 pt-4 pb-2 text-mineshaft-100 shadow-xl duration-200">
|
||||
<div className="mb-4 flex w-full flex-row items-center pr-4">
|
||||
<div className="mr-4 flex w-full flex-row items-center">
|
||||
<FontAwesomeIcon icon={faNetworkWired} className="mx-2 w-16 text-4xl" />
|
||||
{false && (
|
||||
<div className="absolute left-12 top-10 flex h-7 w-7 items-center justify-center rounded-full bg-bunker-500 p-2 group-hover:bg-mineshaft-700">
|
||||
<FontAwesomeIcon
|
||||
icon={faCheckCircle}
|
||||
className="h-5 w-5 text-4xl text-green"
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
<div className="flex flex-col items-start pl-0.5">
|
||||
<div className="mt-0.5 text-xl font-semibold">Inject secrets locally</div>
|
||||
<div className="text-sm font-normal">
|
||||
Replace .env files with a more secure and efficient alternative.
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
className={`w-28 pr-4 text-right text-sm font-semibold ${false && "text-green"
|
||||
}`}
|
||||
>
|
||||
About 2 min
|
||||
</div>
|
||||
</div>
|
||||
<TabsObject />
|
||||
{false && <div className="absolute bottom-0 left-0 h-1 w-full bg-green" />}
|
||||
<div
|
||||
className={`w-28 pr-4 text-right text-sm font-semibold ${
|
||||
false && "text-green"
|
||||
}`}
|
||||
>
|
||||
About 2 min
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
{orgWorkspaces.length !== 0 && (
|
||||
<LearningItem
|
||||
text="Integrate Infisical with your infrastructure"
|
||||
subText="Connect Infisical to various 3rd party services and platforms."
|
||||
complete={false}
|
||||
icon={faPlug}
|
||||
time="15 min"
|
||||
link="https://infisical.com/docs/integrations/overview"
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
<TabsObject />
|
||||
{false && <div className="absolute bottom-0 left-0 h-1 w-full bg-green" />}
|
||||
</div>
|
||||
)}
|
||||
{orgWorkspaces.length !== 0 && (
|
||||
<LearningItem
|
||||
text="Integrate Infisical with your infrastructure"
|
||||
subText="Connect Infisical to various 3rd party services and platforms."
|
||||
complete={false}
|
||||
icon={faPlug}
|
||||
time="15 min"
|
||||
link="https://infisical.com/docs/integrations/overview"
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
<Modal
|
||||
isOpen={popUp.addNewWs.isOpen}
|
||||
onOpenChange={(isModalOpen) => {
|
||||
|
@ -70,6 +70,7 @@ export const MultiEnvProjectPermission = ({
|
||||
}, [allRule]);
|
||||
|
||||
const handlePermissionChange = (val: Permission) => {
|
||||
if(!val) return
|
||||
switch (val) {
|
||||
case Permission.NoAccess: {
|
||||
const permissions = getValue("permissions");
|
||||
@ -106,7 +107,7 @@ export const MultiEnvProjectPermission = ({
|
||||
className={twMerge(
|
||||
"rounded-md bg-mineshaft-800 px-10 py-6",
|
||||
(selectedPermissionCategory !== Permission.NoAccess || isCustom) &&
|
||||
"border-l-2 border-primary-600"
|
||||
"border-l-2 border-primary-600"
|
||||
)}
|
||||
>
|
||||
<div className="flex items-center space-x-4">
|
||||
|
@ -53,6 +53,7 @@ export const SecretRollbackPermission = ({ isNonEditable, setValue, control }: P
|
||||
}, [selectedPermissionCategory]);
|
||||
|
||||
const handlePermissionChange = (val: Permission) => {
|
||||
if(!val) return;
|
||||
if (val === Permission.Custom) setIsCustom.on();
|
||||
else setIsCustom.off();
|
||||
|
||||
|
@ -98,6 +98,7 @@ export const SingleProjectPermission = ({
|
||||
}, [selectedPermissionCategory]);
|
||||
|
||||
const handlePermissionChange = (val: Permission) => {
|
||||
if(!val) return;
|
||||
if (val === Permission.Custom) setIsCustom.on();
|
||||
else setIsCustom.off();
|
||||
|
||||
|
@ -52,6 +52,7 @@ export const WsProjectPermission = ({ isNonEditable, setValue, control }: Props)
|
||||
}, [selectedPermissionCategory]);
|
||||
|
||||
const handlePermissionChange = (val: Permission) => {
|
||||
if(!val) return;
|
||||
if (val === Permission.Custom) setIsCustom.on();
|
||||
else setIsCustom.off();
|
||||
|
||||
|
@ -45,7 +45,6 @@ export const computeImportedSecretRows = (
|
||||
if (importedSecIndex === -1) return [];
|
||||
|
||||
const importedSec = importSecrets[importedSecIndex];
|
||||
|
||||
const overridenSec: Record<string, { env: string; secretPath: string }> = {};
|
||||
|
||||
for (let i = importedSecIndex + 1; i < importSecrets.length; i += 1) {
|
||||
@ -61,11 +60,28 @@ export const computeImportedSecretRows = (
|
||||
overridenSec[el.key] = { env: SECRET_IN_DASHBOARD, secretPath: "" };
|
||||
});
|
||||
|
||||
return importedSec.secrets.map(({ key, value }) => ({
|
||||
key,
|
||||
value,
|
||||
overriden: overridenSec?.[key]
|
||||
}));
|
||||
const importedEntry: Record<string, boolean> = {};
|
||||
const importedSecretEntries: {
|
||||
key: string;
|
||||
value: string;
|
||||
overriden: {
|
||||
env: string;
|
||||
secretPath: string;
|
||||
};
|
||||
}[] = [];
|
||||
|
||||
importedSec.secrets.forEach(({ key, value }) => {
|
||||
if (!importedEntry[key]) {
|
||||
importedSecretEntries.push({
|
||||
key,
|
||||
value,
|
||||
overriden: overridenSec?.[key]
|
||||
});
|
||||
importedEntry[key] = true;
|
||||
}
|
||||
});
|
||||
|
||||
return importedSecretEntries;
|
||||
};
|
||||
|
||||
type Props = {
|
||||
@ -159,8 +175,9 @@ export const SecretImportListView = ({
|
||||
importEnv.slug === environment &&
|
||||
isReserved &&
|
||||
importPath ===
|
||||
`${secretPath === "/" ? "" : secretPath}/${ReservedFolders.SecretReplication
|
||||
}${replicationImportId}`
|
||||
`${secretPath === "/" ? "" : secretPath}/${
|
||||
ReservedFolders.SecretReplication
|
||||
}${replicationImportId}`
|
||||
);
|
||||
if (reservedImport) {
|
||||
setReplicationSecrets((state) => ({
|
||||
@ -206,8 +223,9 @@ export const SecretImportListView = ({
|
||||
isOpen={popUp.deleteSecretImport.isOpen}
|
||||
deleteKey="unlink"
|
||||
title="Do you want to remove this secret import?"
|
||||
subTitle={`This will unlink secrets from environment ${(popUp.deleteSecretImport?.data as TSecretImport)?.importEnv
|
||||
} of path ${(popUp.deleteSecretImport?.data as TSecretImport)?.importPath}?`}
|
||||
subTitle={`This will unlink secrets from environment ${
|
||||
(popUp.deleteSecretImport?.data as TSecretImport)?.importEnv
|
||||
} of path ${(popUp.deleteSecretImport?.data as TSecretImport)?.importPath}?`}
|
||||
onChange={(isOpen) => handlePopUpToggle("deleteSecretImport", isOpen)}
|
||||
onDeleteApproved={handleSecretImportDelete}
|
||||
/>
|
||||
|
@ -29,7 +29,7 @@ type Props = {
|
||||
onSecretCreate: (env: string, key: string, value: string) => Promise<void>;
|
||||
onSecretUpdate: (env: string, key: string, value: string, secretId?: string) => Promise<void>;
|
||||
onSecretDelete: (env: string, key: string, secretId?: string) => Promise<void>;
|
||||
isImportedSecretPresentInEnv: (name: string, env: string, secretName: string) => boolean;
|
||||
isImportedSecretPresentInEnv: (env: string, secretName: string) => boolean;
|
||||
};
|
||||
|
||||
export const SecretOverviewTableRow = ({
|
||||
@ -53,9 +53,8 @@ export const SecretOverviewTableRow = ({
|
||||
<>
|
||||
<Tr isHoverable isSelectable onClick={() => setIsFormExpanded.toggle()} className="group">
|
||||
<Td
|
||||
className={`sticky left-0 z-10 bg-mineshaft-800 bg-clip-padding py-0 px-0 group-hover:bg-mineshaft-700 ${
|
||||
isFormExpanded && "border-t-2 border-mineshaft-500"
|
||||
}`}
|
||||
className={`sticky left-0 z-10 bg-mineshaft-800 bg-clip-padding py-0 px-0 group-hover:bg-mineshaft-700 ${isFormExpanded && "border-t-2 border-mineshaft-500"
|
||||
}`}
|
||||
>
|
||||
<div className="h-full w-full border-r border-mineshaft-600 py-2.5 px-5">
|
||||
<div className="flex items-center space-x-5">
|
||||
@ -83,7 +82,7 @@ export const SecretOverviewTableRow = ({
|
||||
{environments.map(({ slug }, i) => {
|
||||
const secret = getSecretByKey(slug, secretKey);
|
||||
|
||||
const isSecretImported = isImportedSecretPresentInEnv(secretPath, slug, secretKey);
|
||||
const isSecretImported = isImportedSecretPresentInEnv(slug, secretKey);
|
||||
|
||||
const isSecretPresent = Boolean(secret);
|
||||
const isSecretEmpty = secret?.value === "";
|
||||
@ -108,8 +107,8 @@ export const SecretOverviewTableRow = ({
|
||||
isSecretPresent
|
||||
? "Present secret"
|
||||
: isSecretImported
|
||||
? "Imported secret"
|
||||
: "Missing secret"
|
||||
? "Imported secret"
|
||||
: "Missing secret"
|
||||
}
|
||||
>
|
||||
<FontAwesomeIcon
|
||||
@ -133,9 +132,8 @@ export const SecretOverviewTableRow = ({
|
||||
<Tr>
|
||||
<Td
|
||||
colSpan={totalCols}
|
||||
className={`bg-bunker-600 px-0 py-0 ${
|
||||
isFormExpanded && "border-b-2 border-mineshaft-500"
|
||||
}`}
|
||||
className={`bg-bunker-600 px-0 py-0 ${isFormExpanded && "border-b-2 border-mineshaft-500"
|
||||
}`}
|
||||
>
|
||||
<div
|
||||
className="ml-2 p-2"
|
||||
@ -180,11 +178,7 @@ export const SecretOverviewTableRow = ({
|
||||
const secret = getSecretByKey(slug, secretKey);
|
||||
const isCreatable = !secret;
|
||||
|
||||
const isImportedSecret = isImportedSecretPresentInEnv(
|
||||
secretPath,
|
||||
slug,
|
||||
secretKey
|
||||
);
|
||||
const isImportedSecret = isImportedSecretPresentInEnv(slug, secretKey);
|
||||
|
||||
return (
|
||||
<tr
|
||||
|
@ -0,0 +1,92 @@
|
||||
import { Controller, useForm } from "react-hook-form";
|
||||
import { zodResolver } from "@hookform/resolvers/zod";
|
||||
import { z } from "zod";
|
||||
|
||||
import { createNotification } from "@app/components/notifications";
|
||||
import { Button, FormControl, Input } from "@app/components/v2";
|
||||
import { useProjectPermission, useWorkspace } from "@app/context";
|
||||
import { ProjectMembershipRole } from "@app/hooks/api/roles/types";
|
||||
import { useUpdateWorkspaceVersionLimit } from "@app/hooks/api/workspace/queries";
|
||||
|
||||
const formSchema = z.object({
|
||||
pitVersionLimit: z.coerce.number().min(1).max(100)
|
||||
});
|
||||
|
||||
type TForm = z.infer<typeof formSchema>;
|
||||
|
||||
export const PointInTimeVersionLimitSection = () => {
|
||||
const { mutateAsync: updatePitVersion } = useUpdateWorkspaceVersionLimit();
|
||||
|
||||
const { currentWorkspace } = useWorkspace();
|
||||
const { membership } = useProjectPermission();
|
||||
|
||||
const {
|
||||
control,
|
||||
formState: { isSubmitting, isDirty },
|
||||
handleSubmit
|
||||
} = useForm<TForm>({
|
||||
resolver: zodResolver(formSchema),
|
||||
values: {
|
||||
pitVersionLimit: currentWorkspace?.pitVersionLimit || 10
|
||||
}
|
||||
});
|
||||
|
||||
if (!currentWorkspace) return null;
|
||||
|
||||
const handleVersionLimitSubmit = async ({ pitVersionLimit }: TForm) => {
|
||||
try {
|
||||
await updatePitVersion({
|
||||
pitVersionLimit,
|
||||
projectSlug: currentWorkspace.slug
|
||||
});
|
||||
|
||||
createNotification({
|
||||
text: "Successfully updated version limit",
|
||||
type: "success"
|
||||
});
|
||||
} catch (err) {
|
||||
createNotification({
|
||||
text: "Failed updating project's version limit",
|
||||
type: "error"
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
const isAdmin = membership.roles.includes(ProjectMembershipRole.Admin);
|
||||
return (
|
||||
<div className="mb-6 rounded-lg border border-mineshaft-600 bg-mineshaft-900 p-4">
|
||||
<div className="flex w-full items-center justify-between">
|
||||
<p className="text-xl font-semibold">Version Retention</p>
|
||||
</div>
|
||||
<p className="mb-4 mt-2 max-w-2xl text-sm text-gray-400">
|
||||
This defines the maximum number of recent secret versions to keep per folder. Excess versions will be removed at midnight (UTC) each day.
|
||||
</p>
|
||||
<form onSubmit={handleSubmit(handleVersionLimitSubmit)} autoComplete="off">
|
||||
<div className="max-w-xs">
|
||||
<Controller
|
||||
control={control}
|
||||
defaultValue={0}
|
||||
name="pitVersionLimit"
|
||||
render={({ field, fieldState: { error } }) => (
|
||||
<FormControl
|
||||
isError={Boolean(error)}
|
||||
errorText={error?.message}
|
||||
label="Recent versions to keep"
|
||||
>
|
||||
<Input {...field} type="number" min={1} step={1} isDisabled={!isAdmin} />
|
||||
</FormControl>
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
<Button
|
||||
colorSchema="secondary"
|
||||
type="submit"
|
||||
isLoading={isSubmitting}
|
||||
disabled={!isAdmin || !isDirty}
|
||||
>
|
||||
Save
|
||||
</Button>
|
||||
</form>
|
||||
</div>
|
||||
);
|
||||
};
|
@ -0,0 +1 @@
|
||||
export { PointInTimeVersionLimitSection } from "./PointInTimeVersionLimitSection";
|
@ -3,6 +3,7 @@ import { BackfillSecretReferenceSecretion } from "../BackfillSecretReferenceSect
|
||||
import { DeleteProjectSection } from "../DeleteProjectSection";
|
||||
import { E2EESection } from "../E2EESection";
|
||||
import { EnvironmentSection } from "../EnvironmentSection";
|
||||
import { PointInTimeVersionLimitSection } from "../PointInTimeVersionLimitSection";
|
||||
import { ProjectNameChangeSection } from "../ProjectNameChangeSection";
|
||||
import { SecretTagsSection } from "../SecretTagsSection";
|
||||
|
||||
@ -14,6 +15,7 @@ export const ProjectGeneralTab = () => {
|
||||
<SecretTagsSection />
|
||||
<AutoCapitalizationSection />
|
||||
<E2EESection />
|
||||
<PointInTimeVersionLimitSection />
|
||||
<BackfillSecretReferenceSecretion />
|
||||
<DeleteProjectSection />
|
||||
</div>
|
||||
|
@ -13,9 +13,9 @@ type: application
|
||||
# This is the chart version. This version number should be incremented each time you make changes
|
||||
# to the chart and its templates, including the app version.
|
||||
# Versions are expected to follow Semantic Versioning (https://semver.org/)
|
||||
version: v0.5.2
|
||||
version: v0.6.0
|
||||
# This is the version number of the application being deployed. This version number should be
|
||||
# incremented each time you make changes to the application. Versions are not expected to
|
||||
# follow Semantic Versioning. They should reflect the version the application is using.
|
||||
# It is recommended to use it with quotes.
|
||||
appVersion: "v0.5.2"
|
||||
appVersion: "v0.6.0"
|
||||
|
@ -37,6 +37,135 @@ spec:
|
||||
properties:
|
||||
authentication:
|
||||
properties:
|
||||
awsIamAuth:
|
||||
properties:
|
||||
identityId:
|
||||
type: string
|
||||
secretsScope:
|
||||
properties:
|
||||
envSlug:
|
||||
type: string
|
||||
projectSlug:
|
||||
type: string
|
||||
recursive:
|
||||
type: boolean
|
||||
secretsPath:
|
||||
type: string
|
||||
required:
|
||||
- envSlug
|
||||
- projectSlug
|
||||
- secretsPath
|
||||
type: object
|
||||
required:
|
||||
- identityId
|
||||
- secretsScope
|
||||
type: object
|
||||
azureAuth:
|
||||
properties:
|
||||
identityId:
|
||||
type: string
|
||||
secretsScope:
|
||||
properties:
|
||||
envSlug:
|
||||
type: string
|
||||
projectSlug:
|
||||
type: string
|
||||
recursive:
|
||||
type: boolean
|
||||
secretsPath:
|
||||
type: string
|
||||
required:
|
||||
- envSlug
|
||||
- projectSlug
|
||||
- secretsPath
|
||||
type: object
|
||||
required:
|
||||
- identityId
|
||||
- secretsScope
|
||||
type: object
|
||||
gcpIamAuth:
|
||||
properties:
|
||||
identityId:
|
||||
type: string
|
||||
secretsScope:
|
||||
properties:
|
||||
envSlug:
|
||||
type: string
|
||||
projectSlug:
|
||||
type: string
|
||||
recursive:
|
||||
type: boolean
|
||||
secretsPath:
|
||||
type: string
|
||||
required:
|
||||
- envSlug
|
||||
- projectSlug
|
||||
- secretsPath
|
||||
type: object
|
||||
serviceAccountKeyFilePath:
|
||||
type: string
|
||||
required:
|
||||
- identityId
|
||||
- secretsScope
|
||||
- serviceAccountKeyFilePath
|
||||
type: object
|
||||
gcpIdTokenAuth:
|
||||
properties:
|
||||
identityId:
|
||||
type: string
|
||||
secretsScope:
|
||||
properties:
|
||||
envSlug:
|
||||
type: string
|
||||
projectSlug:
|
||||
type: string
|
||||
recursive:
|
||||
type: boolean
|
||||
secretsPath:
|
||||
type: string
|
||||
required:
|
||||
- envSlug
|
||||
- projectSlug
|
||||
- secretsPath
|
||||
type: object
|
||||
required:
|
||||
- identityId
|
||||
- secretsScope
|
||||
type: object
|
||||
kubernetesAuth:
|
||||
properties:
|
||||
identityId:
|
||||
type: string
|
||||
secretsScope:
|
||||
properties:
|
||||
envSlug:
|
||||
type: string
|
||||
projectSlug:
|
||||
type: string
|
||||
recursive:
|
||||
type: boolean
|
||||
secretsPath:
|
||||
type: string
|
||||
required:
|
||||
- envSlug
|
||||
- projectSlug
|
||||
- secretsPath
|
||||
type: object
|
||||
serviceAccountRef:
|
||||
properties:
|
||||
name:
|
||||
type: string
|
||||
namespace:
|
||||
type: string
|
||||
required:
|
||||
- name
|
||||
- namespace
|
||||
type: object
|
||||
required:
|
||||
- identityId
|
||||
- secretsScope
|
||||
- serviceAccountRef
|
||||
type: object
|
||||
serviceAccount:
|
||||
properties:
|
||||
environmentName:
|
||||
|
@ -32,7 +32,7 @@ controllerManager:
|
||||
- ALL
|
||||
image:
|
||||
repository: infisical/kubernetes-operator
|
||||
tag: v0.5.2 # fixed to prevent accidental upgrade
|
||||
tag: v0.6.0
|
||||
resources:
|
||||
limits:
|
||||
cpu: 500m
|
||||
|
@ -11,6 +11,16 @@ type Authentication struct {
|
||||
ServiceToken ServiceTokenDetails `json:"serviceToken"`
|
||||
// +kubebuilder:validation:Optional
|
||||
UniversalAuth UniversalAuthDetails `json:"universalAuth"`
|
||||
// +kubebuilder:validation:Optional
|
||||
KubernetesAuth KubernetesAuthDetails `json:"kubernetesAuth"`
|
||||
// +kubebuilder:validation:Optional
|
||||
AwsIamAuth AWSIamAuthDetails `json:"awsIamAuth"`
|
||||
// +kubebuilder:validation:Optional
|
||||
AzureAuth AzureAuthDetails `json:"azureAuth"`
|
||||
// +kubebuilder:validation:Optional
|
||||
GcpIdTokenAuth GCPIdTokenAuthDetails `json:"gcpIdTokenAuth"`
|
||||
// +kubebuilder:validation:Optional
|
||||
GcpIamAuth GcpIamAuthDetails `json:"gcpIamAuth"`
|
||||
}
|
||||
|
||||
type UniversalAuthDetails struct {
|
||||
@ -20,6 +30,57 @@ type UniversalAuthDetails struct {
|
||||
SecretsScope MachineIdentityScopeInWorkspace `json:"secretsScope"`
|
||||
}
|
||||
|
||||
type KubernetesAuthDetails struct {
|
||||
// +kubebuilder:validation:Required
|
||||
IdentityID string `json:"identityId"`
|
||||
// +kubebuilder:validation:Required
|
||||
ServiceAccountRef KubernetesServiceAccountRef `json:"serviceAccountRef"`
|
||||
|
||||
// +kubebuilder:validation:Required
|
||||
SecretsScope MachineIdentityScopeInWorkspace `json:"secretsScope"`
|
||||
}
|
||||
|
||||
type KubernetesServiceAccountRef struct {
|
||||
// +kubebuilder:validation:Required
|
||||
Name string `json:"name"`
|
||||
// +kubebuilder:validation:Required
|
||||
Namespace string `json:"namespace"`
|
||||
}
|
||||
|
||||
type AWSIamAuthDetails struct {
|
||||
// +kubebuilder:validation:Required
|
||||
IdentityID string `json:"identityId"`
|
||||
|
||||
// +kubebuilder:validation:Required
|
||||
SecretsScope MachineIdentityScopeInWorkspace `json:"secretsScope"`
|
||||
}
|
||||
|
||||
type AzureAuthDetails struct {
|
||||
// +kubebuilder:validation:Required
|
||||
IdentityID string `json:"identityId"`
|
||||
|
||||
// +kubebuilder:validation:Required
|
||||
SecretsScope MachineIdentityScopeInWorkspace `json:"secretsScope"`
|
||||
}
|
||||
|
||||
type GCPIdTokenAuthDetails struct {
|
||||
// +kubebuilder:validation:Required
|
||||
IdentityID string `json:"identityId"`
|
||||
|
||||
// +kubebuilder:validation:Required
|
||||
SecretsScope MachineIdentityScopeInWorkspace `json:"secretsScope"`
|
||||
}
|
||||
|
||||
type GcpIamAuthDetails struct {
|
||||
// +kubebuilder:validation:Required
|
||||
IdentityID string `json:"identityId"`
|
||||
// +kubebuilder:validation:Required
|
||||
ServiceAccountKeyFilePath string `json:"serviceAccountKeyFilePath"`
|
||||
|
||||
// +kubebuilder:validation:Required
|
||||
SecretsScope MachineIdentityScopeInWorkspace `json:"secretsScope"`
|
||||
}
|
||||
|
||||
type ServiceTokenDetails struct {
|
||||
// +kubebuilder:validation:Required
|
||||
ServiceTokenSecretReference KubeSecretReference `json:"serviceTokenSecretReference"`
|
||||
|
@ -26,12 +26,33 @@ import (
|
||||
runtime "k8s.io/apimachinery/pkg/runtime"
|
||||
)
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *AWSIamAuthDetails) DeepCopyInto(out *AWSIamAuthDetails) {
|
||||
*out = *in
|
||||
out.SecretsScope = in.SecretsScope
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new AWSIamAuthDetails.
|
||||
func (in *AWSIamAuthDetails) DeepCopy() *AWSIamAuthDetails {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(AWSIamAuthDetails)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *Authentication) DeepCopyInto(out *Authentication) {
|
||||
*out = *in
|
||||
out.ServiceAccount = in.ServiceAccount
|
||||
out.ServiceToken = in.ServiceToken
|
||||
out.UniversalAuth = in.UniversalAuth
|
||||
out.KubernetesAuth = in.KubernetesAuth
|
||||
out.AwsIamAuth = in.AwsIamAuth
|
||||
out.AzureAuth = in.AzureAuth
|
||||
out.GcpIdTokenAuth = in.GcpIdTokenAuth
|
||||
out.GcpIamAuth = in.GcpIamAuth
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Authentication.
|
||||
@ -44,6 +65,54 @@ func (in *Authentication) DeepCopy() *Authentication {
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *AzureAuthDetails) DeepCopyInto(out *AzureAuthDetails) {
|
||||
*out = *in
|
||||
out.SecretsScope = in.SecretsScope
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new AzureAuthDetails.
|
||||
func (in *AzureAuthDetails) DeepCopy() *AzureAuthDetails {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(AzureAuthDetails)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *GCPIdTokenAuthDetails) DeepCopyInto(out *GCPIdTokenAuthDetails) {
|
||||
*out = *in
|
||||
out.SecretsScope = in.SecretsScope
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new GCPIdTokenAuthDetails.
|
||||
func (in *GCPIdTokenAuthDetails) DeepCopy() *GCPIdTokenAuthDetails {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(GCPIdTokenAuthDetails)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *GcpIamAuthDetails) DeepCopyInto(out *GcpIamAuthDetails) {
|
||||
*out = *in
|
||||
out.SecretsScope = in.SecretsScope
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new GcpIamAuthDetails.
|
||||
func (in *GcpIamAuthDetails) DeepCopy() *GcpIamAuthDetails {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(GcpIamAuthDetails)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *InfisicalSecret) DeepCopyInto(out *InfisicalSecret) {
|
||||
*out = *in
|
||||
@ -158,6 +227,38 @@ func (in *KubeSecretReference) DeepCopy() *KubeSecretReference {
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *KubernetesAuthDetails) DeepCopyInto(out *KubernetesAuthDetails) {
|
||||
*out = *in
|
||||
out.ServiceAccountRef = in.ServiceAccountRef
|
||||
out.SecretsScope = in.SecretsScope
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new KubernetesAuthDetails.
|
||||
func (in *KubernetesAuthDetails) DeepCopy() *KubernetesAuthDetails {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(KubernetesAuthDetails)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *KubernetesServiceAccountRef) DeepCopyInto(out *KubernetesServiceAccountRef) {
|
||||
*out = *in
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new KubernetesServiceAccountRef.
|
||||
func (in *KubernetesServiceAccountRef) DeepCopy() *KubernetesServiceAccountRef {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(KubernetesServiceAccountRef)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *MachineIdentityScopeInWorkspace) DeepCopyInto(out *MachineIdentityScopeInWorkspace) {
|
||||
*out = *in
|
||||
|
@ -37,6 +37,135 @@ spec:
|
||||
properties:
|
||||
authentication:
|
||||
properties:
|
||||
awsIamAuth:
|
||||
properties:
|
||||
identityId:
|
||||
type: string
|
||||
secretsScope:
|
||||
properties:
|
||||
envSlug:
|
||||
type: string
|
||||
projectSlug:
|
||||
type: string
|
||||
recursive:
|
||||
type: boolean
|
||||
secretsPath:
|
||||
type: string
|
||||
required:
|
||||
- envSlug
|
||||
- projectSlug
|
||||
- secretsPath
|
||||
type: object
|
||||
required:
|
||||
- identityId
|
||||
- secretsScope
|
||||
type: object
|
||||
azureAuth:
|
||||
properties:
|
||||
identityId:
|
||||
type: string
|
||||
secretsScope:
|
||||
properties:
|
||||
envSlug:
|
||||
type: string
|
||||
projectSlug:
|
||||
type: string
|
||||
recursive:
|
||||
type: boolean
|
||||
secretsPath:
|
||||
type: string
|
||||
required:
|
||||
- envSlug
|
||||
- projectSlug
|
||||
- secretsPath
|
||||
type: object
|
||||
required:
|
||||
- identityId
|
||||
- secretsScope
|
||||
type: object
|
||||
gcpIamAuth:
|
||||
properties:
|
||||
identityId:
|
||||
type: string
|
||||
secretsScope:
|
||||
properties:
|
||||
envSlug:
|
||||
type: string
|
||||
projectSlug:
|
||||
type: string
|
||||
recursive:
|
||||
type: boolean
|
||||
secretsPath:
|
||||
type: string
|
||||
required:
|
||||
- envSlug
|
||||
- projectSlug
|
||||
- secretsPath
|
||||
type: object
|
||||
serviceAccountKeyFilePath:
|
||||
type: string
|
||||
required:
|
||||
- identityId
|
||||
- secretsScope
|
||||
- serviceAccountKeyFilePath
|
||||
type: object
|
||||
gcpIdTokenAuth:
|
||||
properties:
|
||||
identityId:
|
||||
type: string
|
||||
secretsScope:
|
||||
properties:
|
||||
envSlug:
|
||||
type: string
|
||||
projectSlug:
|
||||
type: string
|
||||
recursive:
|
||||
type: boolean
|
||||
secretsPath:
|
||||
type: string
|
||||
required:
|
||||
- envSlug
|
||||
- projectSlug
|
||||
- secretsPath
|
||||
type: object
|
||||
required:
|
||||
- identityId
|
||||
- secretsScope
|
||||
type: object
|
||||
kubernetesAuth:
|
||||
properties:
|
||||
identityId:
|
||||
type: string
|
||||
secretsScope:
|
||||
properties:
|
||||
envSlug:
|
||||
type: string
|
||||
projectSlug:
|
||||
type: string
|
||||
recursive:
|
||||
type: boolean
|
||||
secretsPath:
|
||||
type: string
|
||||
required:
|
||||
- envSlug
|
||||
- projectSlug
|
||||
- secretsPath
|
||||
type: object
|
||||
serviceAccountRef:
|
||||
properties:
|
||||
name:
|
||||
type: string
|
||||
namespace:
|
||||
type: string
|
||||
required:
|
||||
- name
|
||||
- namespace
|
||||
type: object
|
||||
required:
|
||||
- identityId
|
||||
- secretsScope
|
||||
- serviceAccountRef
|
||||
type: object
|
||||
serviceAccount:
|
||||
properties:
|
||||
environmentName:
|
||||
|
@ -0,0 +1,13 @@
|
||||
apiVersion: rbac.authorization.k8s.io/v1
|
||||
kind: ClusterRoleBinding
|
||||
metadata:
|
||||
name: role-tokenreview-binding
|
||||
namespace: default
|
||||
roleRef:
|
||||
apiGroup: rbac.authorization.k8s.io
|
||||
kind: ClusterRole
|
||||
name: system:auth-delegator
|
||||
subjects:
|
||||
- kind: ServiceAccount
|
||||
name: infisical-auth
|
||||
namespace: default
|
@ -0,0 +1,5 @@
|
||||
apiVersion: v1
|
||||
kind: ServiceAccount
|
||||
metadata:
|
||||
name: infisical-auth
|
||||
namespace: default
|
32
k8-operator/config/samples/k8s-auth/sample.yaml
Normal file
32
k8-operator/config/samples/k8s-auth/sample.yaml
Normal file
@ -0,0 +1,32 @@
|
||||
apiVersion: secrets.infisical.com/v1alpha1
|
||||
kind: InfisicalSecret
|
||||
metadata:
|
||||
name: infisicalsecret-sample
|
||||
labels:
|
||||
label-to-be-passed-to-managed-secret: sample-value
|
||||
annotations:
|
||||
example.com/annotation-to-be-passed-to-managed-secret: "sample-value"
|
||||
spec:
|
||||
hostAPI: https://app.infisical.com/api
|
||||
resyncInterval: 10
|
||||
authentication:
|
||||
# Native Kubernetes Auth
|
||||
kubernetesAuth:
|
||||
identityId: <>
|
||||
serviceAccountRef:
|
||||
name: infisical-auth
|
||||
namespace: default
|
||||
|
||||
# secretsScope is identical to the secrets scope in the universalAuth field in this sample.
|
||||
secretsScope:
|
||||
projectSlug: dsf-gpb-t
|
||||
envSlug: dev
|
||||
secretsPath: "/"
|
||||
recursive: true
|
||||
|
||||
|
||||
managedSecretReference:
|
||||
secretName: managed-secret-k8s
|
||||
secretNamespace: default
|
||||
creationPolicy: "Orphan" ## Owner | Orphan
|
||||
# secretType: kubernetes.io/dockerconfigjson
|
@ -0,0 +1,7 @@
|
||||
apiVersion: v1
|
||||
kind: Secret
|
||||
type: kubernetes.io/service-account-token
|
||||
metadata:
|
||||
name: infisical-auth-token
|
||||
annotations:
|
||||
kubernetes.io/service-account.name: "infisical-auth"
|
@ -12,26 +12,85 @@ spec:
|
||||
authentication:
|
||||
# Make sure to only have 1 authentication method defined, serviceToken/universalAuth.
|
||||
# If you have multiple authentication methods defined, it may cause issues.
|
||||
|
||||
# (Deprecated) Service Token Auth
|
||||
serviceToken:
|
||||
serviceTokenSecretReference:
|
||||
secretName: service-token
|
||||
secretNamespace: default
|
||||
secretsScope:
|
||||
envSlug: <env-slug>
|
||||
secretsPath: <secrets-path> # Root is "/"
|
||||
recursive: true # Wether or not to use recursive mode (Fetches all secrets in an environment from a given secret path, and all folders inside the path) / defaults to false
|
||||
secretsPath: <secrets-path>
|
||||
recursive: true
|
||||
|
||||
# Universal Auth
|
||||
universalAuth:
|
||||
secretsScope:
|
||||
projectSlug: <project-slug>
|
||||
envSlug: <env-slug> # "dev", "staging", "prod", etc..
|
||||
secretsPath: "<secrets-path>" # Root is "/"
|
||||
projectSlug: new-ob-em
|
||||
envSlug: dev # "dev", "staging", "prod", etc..
|
||||
secretsPath: "/" # Root is "/"
|
||||
recursive: true # Wether or not to use recursive mode (Fetches all secrets in an environment from a given secret path, and all folders inside the path) / defaults to false
|
||||
|
||||
credentialsRef:
|
||||
secretName: universal-auth-credentials
|
||||
secretNamespace: default
|
||||
|
||||
# Native Kubernetes Auth
|
||||
kubernetesAuth:
|
||||
identityId: <machine-identity-id>
|
||||
serviceAccountTokenPath: "/path/to/your/service-account/token" # Optional, defaults to /var/run/secrets/kubernetes.io/serviceaccount/token
|
||||
|
||||
# secretsScope is identical to the secrets scope in the universalAuth field in this sample.
|
||||
secretsScope:
|
||||
projectSlug: your-project-slug
|
||||
envSlug: prod
|
||||
secretsPath: "/path"
|
||||
recursive: true
|
||||
|
||||
# AWS IAM Auth
|
||||
awsIamAuth:
|
||||
identityId: <your-machine-identity-id>
|
||||
|
||||
# secretsScope is identical to the secrets scope in the universalAuth field in this sample.
|
||||
secretsScope:
|
||||
projectSlug: your-project-slug
|
||||
envSlug: prod
|
||||
secretsPath: "/path"
|
||||
recursive: true
|
||||
|
||||
# Azure Auth
|
||||
azureAuth:
|
||||
identityId: <your-machine-identity-id>
|
||||
|
||||
# secretsScope is identical to the secrets scope in the universalAuth field in this sample.
|
||||
secretsScope:
|
||||
projectSlug: your-project-slug
|
||||
envSlug: prod
|
||||
secretsPath: "/path"
|
||||
recursive: true
|
||||
|
||||
# GCP ID Token Auth
|
||||
gcpIdTokenAuth:
|
||||
identityId: <your-machine-identity-id>
|
||||
|
||||
# secretsScope is identical to the secrets scope in the universalAuth field in this sample.
|
||||
secretsScope:
|
||||
projectSlug: your-project-slug
|
||||
envSlug: prod
|
||||
secretsPath: "/path"
|
||||
recursive: true
|
||||
|
||||
# GCP IAM Auth
|
||||
gcpIamAuth:
|
||||
identityId: <your-machine-identity-id>
|
||||
serviceAccountKeyFilePath: "/path/to-service-account-key-file-path.json"
|
||||
|
||||
# secretsScope is identical to the secrets scope in the universalAuth field in this sample.
|
||||
secretsScope:
|
||||
projectSlug: your-project-slug
|
||||
envSlug: prod
|
||||
secretsPath: "/path"
|
||||
recursive: true
|
||||
|
||||
managedSecretReference:
|
||||
secretName: managed-secret
|
||||
secretNamespace: default
|
||||
|
@ -40,7 +40,7 @@ func (r *InfisicalSecretReconciler) SetReadyToSyncSecretsConditions(ctx context.
|
||||
return r.Client.Status().Update(ctx, infisicalSecret)
|
||||
}
|
||||
|
||||
func (r *InfisicalSecretReconciler) SetInfisicalTokenLoadCondition(ctx context.Context, infisicalSecret *v1alpha1.InfisicalSecret, errorToConditionOn error) {
|
||||
func (r *InfisicalSecretReconciler) SetInfisicalTokenLoadCondition(ctx context.Context, infisicalSecret *v1alpha1.InfisicalSecret, authStrategy AuthStrategyType, errorToConditionOn error) {
|
||||
if infisicalSecret.Status.Conditions == nil {
|
||||
infisicalSecret.Status.Conditions = []metav1.Condition{}
|
||||
}
|
||||
@ -50,7 +50,7 @@ func (r *InfisicalSecretReconciler) SetInfisicalTokenLoadCondition(ctx context.C
|
||||
Type: "secrets.infisical.com/LoadedInfisicalToken",
|
||||
Status: metav1.ConditionTrue,
|
||||
Reason: "OK",
|
||||
Message: "Infisical controller has located the Infisical token in provided Kubernetes secret",
|
||||
Message: fmt.Sprintf("Infisical controller has loaded the Infisical token in provided Kubernetes secret, using %v authentication strategy", authStrategy),
|
||||
})
|
||||
} else {
|
||||
meta.SetStatusCondition(&infisicalSecret.Status.Conditions, metav1.Condition{
|
||||
|
150
k8-operator/controllers/infisicalsecret_auth.go
Normal file
150
k8-operator/controllers/infisicalsecret_auth.go
Normal file
@ -0,0 +1,150 @@
|
||||
package controllers
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
|
||||
"github.com/Infisical/infisical/k8-operator/api/v1alpha1"
|
||||
"github.com/Infisical/infisical/k8-operator/packages/util"
|
||||
infisicalSdk "github.com/infisical/go-sdk"
|
||||
)
|
||||
|
||||
type AuthStrategyType string
|
||||
|
||||
var AuthStrategy = struct {
|
||||
SERVICE_TOKEN AuthStrategyType
|
||||
SERVICE_ACCOUNT AuthStrategyType
|
||||
UNIVERSAL_MACHINE_IDENTITY AuthStrategyType
|
||||
KUBERNETES_MACHINE_IDENTITY AuthStrategyType
|
||||
AWS_IAM_MACHINE_IDENTITY AuthStrategyType
|
||||
AZURE_MACHINE_IDENTITY AuthStrategyType
|
||||
GCP_ID_TOKEN_MACHINE_IDENTITY AuthStrategyType
|
||||
GCP_IAM_MACHINE_IDENTITY AuthStrategyType
|
||||
}{
|
||||
SERVICE_TOKEN: "SERVICE_TOKEN",
|
||||
SERVICE_ACCOUNT: "SERVICE_ACCOUNT",
|
||||
UNIVERSAL_MACHINE_IDENTITY: "UNIVERSAL_MACHINE_IDENTITY",
|
||||
KUBERNETES_MACHINE_IDENTITY: "KUBERNETES_AUTH_MACHINE_IDENTITY",
|
||||
AWS_IAM_MACHINE_IDENTITY: "AWS_IAM_MACHINE_IDENTITY",
|
||||
AZURE_MACHINE_IDENTITY: "AZURE_MACHINE_IDENTITY",
|
||||
GCP_ID_TOKEN_MACHINE_IDENTITY: "GCP_ID_TOKEN_MACHINE_IDENTITY",
|
||||
GCP_IAM_MACHINE_IDENTITY: "GCP_IAM_MACHINE_IDENTITY",
|
||||
}
|
||||
|
||||
type AuthenticationDetails struct {
|
||||
authStrategy AuthStrategyType
|
||||
machineIdentityScope v1alpha1.MachineIdentityScopeInWorkspace // This will only be set if a machine identity auth method is used (e.g. UniversalAuth or KubernetesAuth, etc.)
|
||||
isMachineIdentityAuth bool
|
||||
}
|
||||
|
||||
var ErrAuthNotApplicable = errors.New("authentication not applicable")
|
||||
|
||||
func (r *InfisicalSecretReconciler) handleUniversalAuth(ctx context.Context, infisicalSecret v1alpha1.InfisicalSecret, infisicalClient infisicalSdk.InfisicalClientInterface) (AuthenticationDetails, error) {
|
||||
|
||||
// Machine Identities:
|
||||
universalAuthKubeSecret, err := r.GetInfisicalUniversalAuthFromKubeSecret(ctx, infisicalSecret)
|
||||
universalAuthSpec := infisicalSecret.Spec.Authentication.UniversalAuth
|
||||
|
||||
if err != nil {
|
||||
return AuthenticationDetails{}, fmt.Errorf("ReconcileInfisicalSecret: unable to get machine identity creds from kube secret [err=%s]", err)
|
||||
}
|
||||
|
||||
if universalAuthKubeSecret.ClientId == "" && universalAuthKubeSecret.ClientSecret == "" {
|
||||
return AuthenticationDetails{}, ErrAuthNotApplicable
|
||||
}
|
||||
|
||||
_, err = infisicalClient.Auth().UniversalAuthLogin(universalAuthKubeSecret.ClientId, universalAuthKubeSecret.ClientSecret)
|
||||
if err != nil {
|
||||
return AuthenticationDetails{}, fmt.Errorf("unable to login with machine identity credentials [err=%s]", err)
|
||||
}
|
||||
|
||||
fmt.Println("Successfully authenticated with machine identity credentials")
|
||||
|
||||
return AuthenticationDetails{authStrategy: AuthStrategy.UNIVERSAL_MACHINE_IDENTITY, machineIdentityScope: universalAuthSpec.SecretsScope, isMachineIdentityAuth: true}, nil
|
||||
|
||||
}
|
||||
|
||||
func (r *InfisicalSecretReconciler) handleKubernetesAuth(ctx context.Context, infisicalSecret v1alpha1.InfisicalSecret, infisicalClient infisicalSdk.InfisicalClientInterface) (AuthenticationDetails, error) {
|
||||
kubernetesAuthSpec := infisicalSecret.Spec.Authentication.KubernetesAuth
|
||||
|
||||
if kubernetesAuthSpec.IdentityID == "" {
|
||||
return AuthenticationDetails{}, ErrAuthNotApplicable
|
||||
}
|
||||
|
||||
serviceAccountToken, err := util.GetServiceAccountToken(r.Client, kubernetesAuthSpec.ServiceAccountRef.Namespace, kubernetesAuthSpec.ServiceAccountRef.Name)
|
||||
if err != nil {
|
||||
return AuthenticationDetails{}, fmt.Errorf("unable to get service account token [err=%s]", err)
|
||||
}
|
||||
|
||||
_, err = infisicalClient.Auth().KubernetesRawServiceAccountTokenLogin(kubernetesAuthSpec.IdentityID, serviceAccountToken)
|
||||
if err != nil {
|
||||
return AuthenticationDetails{}, fmt.Errorf("unable to login with Kubernetes native auth [err=%s]", err)
|
||||
}
|
||||
|
||||
return AuthenticationDetails{authStrategy: AuthStrategy.KUBERNETES_MACHINE_IDENTITY, machineIdentityScope: kubernetesAuthSpec.SecretsScope, isMachineIdentityAuth: true}, nil
|
||||
|
||||
}
|
||||
|
||||
func (r *InfisicalSecretReconciler) handleAwsIamAuth(ctx context.Context, infisicalSecret v1alpha1.InfisicalSecret, infisicalClient infisicalSdk.InfisicalClientInterface) (AuthenticationDetails, error) {
|
||||
awsIamAuthSpec := infisicalSecret.Spec.Authentication.AwsIamAuth
|
||||
|
||||
if awsIamAuthSpec.IdentityID == "" {
|
||||
return AuthenticationDetails{}, ErrAuthNotApplicable
|
||||
}
|
||||
|
||||
_, err := infisicalClient.Auth().AwsIamAuthLogin(awsIamAuthSpec.IdentityID)
|
||||
if err != nil {
|
||||
return AuthenticationDetails{}, fmt.Errorf("unable to login with AWS IAM auth [err=%s]", err)
|
||||
}
|
||||
|
||||
return AuthenticationDetails{authStrategy: AuthStrategy.AWS_IAM_MACHINE_IDENTITY, machineIdentityScope: awsIamAuthSpec.SecretsScope, isMachineIdentityAuth: true}, nil
|
||||
|
||||
}
|
||||
|
||||
func (r *InfisicalSecretReconciler) handleAzureAuth(ctx context.Context, infisicalSecret v1alpha1.InfisicalSecret, infisicalClient infisicalSdk.InfisicalClientInterface) (AuthenticationDetails, error) {
|
||||
azureAuthSpec := infisicalSecret.Spec.Authentication.AzureAuth
|
||||
|
||||
if azureAuthSpec.IdentityID == "" {
|
||||
return AuthenticationDetails{}, ErrAuthNotApplicable
|
||||
}
|
||||
|
||||
_, err := infisicalClient.Auth().AzureAuthLogin(azureAuthSpec.IdentityID)
|
||||
if err != nil {
|
||||
return AuthenticationDetails{}, fmt.Errorf("unable to login with Azure auth [err=%s]", err)
|
||||
}
|
||||
|
||||
return AuthenticationDetails{authStrategy: AuthStrategy.AZURE_MACHINE_IDENTITY, machineIdentityScope: azureAuthSpec.SecretsScope, isMachineIdentityAuth: true}, nil
|
||||
|
||||
}
|
||||
|
||||
func (r *InfisicalSecretReconciler) handleGcpIdTokenAuth(ctx context.Context, infisicalSecret v1alpha1.InfisicalSecret, infisicalClient infisicalSdk.InfisicalClientInterface) (AuthenticationDetails, error) {
|
||||
gcpIdTokenSpec := infisicalSecret.Spec.Authentication.GcpIdTokenAuth
|
||||
|
||||
if gcpIdTokenSpec.IdentityID == "" {
|
||||
return AuthenticationDetails{}, ErrAuthNotApplicable
|
||||
}
|
||||
|
||||
_, err := infisicalClient.Auth().GcpIdTokenAuthLogin(gcpIdTokenSpec.IdentityID)
|
||||
if err != nil {
|
||||
return AuthenticationDetails{}, fmt.Errorf("unable to login with GCP Id Token auth [err=%s]", err)
|
||||
}
|
||||
|
||||
return AuthenticationDetails{authStrategy: AuthStrategy.GCP_ID_TOKEN_MACHINE_IDENTITY, machineIdentityScope: gcpIdTokenSpec.SecretsScope, isMachineIdentityAuth: true}, nil
|
||||
|
||||
}
|
||||
|
||||
func (r *InfisicalSecretReconciler) handleGcpIamAuth(ctx context.Context, infisicalSecret v1alpha1.InfisicalSecret, infisicalClient infisicalSdk.InfisicalClientInterface) (AuthenticationDetails, error) {
|
||||
gcpIamSpec := infisicalSecret.Spec.Authentication.GcpIamAuth
|
||||
|
||||
if gcpIamSpec.IdentityID == "" && gcpIamSpec.ServiceAccountKeyFilePath == "" {
|
||||
return AuthenticationDetails{}, ErrAuthNotApplicable
|
||||
}
|
||||
|
||||
_, err := infisicalClient.Auth().GcpIamAuthLogin(gcpIamSpec.IdentityID, gcpIamSpec.ServiceAccountKeyFilePath)
|
||||
if err != nil {
|
||||
return AuthenticationDetails{}, fmt.Errorf("unable to login with GCP IAM auth [err=%s]", err)
|
||||
}
|
||||
|
||||
return AuthenticationDetails{authStrategy: AuthStrategy.GCP_IAM_MACHINE_IDENTITY, machineIdentityScope: gcpIamSpec.SecretsScope, isMachineIdentityAuth: true}, nil
|
||||
}
|
@ -9,10 +9,11 @@ import (
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
ctrl "sigs.k8s.io/controller-runtime"
|
||||
"sigs.k8s.io/controller-runtime/pkg/client"
|
||||
controllerUtil "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil"
|
||||
|
||||
"github.com/Infisical/infisical/k8-operator/api/v1alpha1"
|
||||
secretsv1alpha1 "github.com/Infisical/infisical/k8-operator/api/v1alpha1"
|
||||
"github.com/Infisical/infisical/k8-operator/packages/api"
|
||||
infisicalSdk "github.com/infisical/go-sdk"
|
||||
)
|
||||
|
||||
// InfisicalSecretReconciler reconciles a InfisicalSecret object
|
||||
@ -32,19 +33,54 @@ type InfisicalSecretReconciler struct {
|
||||
// move the current state of the cluster closer to the desired state.
|
||||
// For more details, check Reconcile and its Result here:
|
||||
// - https://pkg.go.dev/sigs.k8s.io/controller-runtime@v0.13.1/pkg/reconcile
|
||||
|
||||
type ResourceVariables struct {
|
||||
infisicalClient infisicalSdk.InfisicalClientInterface
|
||||
authDetails AuthenticationDetails
|
||||
}
|
||||
|
||||
// Maps the infisicalSecretCR.UID to a infisicalSdk.InfisicalClientInterface and AuthenticationDetails.
|
||||
var resourceVariablesMap = make(map[string]ResourceVariables)
|
||||
|
||||
const FINALIZER_NAME = "secrets.finalizers.infisical.com"
|
||||
|
||||
func (r *InfisicalSecretReconciler) addFinalizer(ctx context.Context, infisicalSecret *secretsv1alpha1.InfisicalSecret) error {
|
||||
if !controllerUtil.ContainsFinalizer(infisicalSecret, FINALIZER_NAME) {
|
||||
controllerUtil.AddFinalizer(infisicalSecret, FINALIZER_NAME)
|
||||
if err := r.Update(ctx, infisicalSecret); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *InfisicalSecretReconciler) handleFinalizer(ctx context.Context, infisicalSecret *secretsv1alpha1.InfisicalSecret) error {
|
||||
if controllerUtil.ContainsFinalizer(infisicalSecret, FINALIZER_NAME) {
|
||||
// Cleanup deployment variables
|
||||
delete(resourceVariablesMap, string(infisicalSecret.UID))
|
||||
|
||||
// Remove the finalizer and update the resource
|
||||
controllerUtil.RemoveFinalizer(infisicalSecret, FINALIZER_NAME)
|
||||
if err := r.Update(ctx, infisicalSecret); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *InfisicalSecretReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) {
|
||||
var infisicalSecretCR v1alpha1.InfisicalSecret
|
||||
var infisicalSecretCR secretsv1alpha1.InfisicalSecret
|
||||
requeueTime := time.Minute // seconds
|
||||
|
||||
err := r.Get(ctx, req.NamespacedName, &infisicalSecretCR)
|
||||
if err != nil {
|
||||
if errors.IsNotFound(err) {
|
||||
fmt.Printf("Infisical Secret CRD not found [err=%v]", err)
|
||||
fmt.Printf("\nInfisical Secret CRD not found [err=%v]", err)
|
||||
return ctrl.Result{
|
||||
Requeue: false,
|
||||
}, nil
|
||||
} else {
|
||||
fmt.Printf("Unable to fetch Infisical Secret CRD from cluster because [err=%v]", err)
|
||||
fmt.Printf("\nUnable to fetch Infisical Secret CRD from cluster because [err=%v]", err)
|
||||
return ctrl.Result{
|
||||
RequeueAfter: requeueTime,
|
||||
}, nil
|
||||
@ -58,8 +94,20 @@ func (r *InfisicalSecretReconciler) Reconcile(ctx context.Context, req ctrl.Requ
|
||||
fmt.Printf("\nRe-sync interval set. Interval: %v\n", requeueTime)
|
||||
}
|
||||
|
||||
// Add the finalizer if it does not exist, and only add it if the resource is not marked for deletion
|
||||
if infisicalSecretCR.GetDeletionTimestamp() == nil || infisicalSecretCR.GetDeletionTimestamp().IsZero() {
|
||||
if err := r.addFinalizer(ctx, &infisicalSecretCR); err != nil {
|
||||
return ctrl.Result{}, err
|
||||
}
|
||||
}
|
||||
|
||||
// Check if the resource is already marked for deletion
|
||||
if infisicalSecretCR.GetDeletionTimestamp() != nil {
|
||||
// Handle the finalizer logic
|
||||
if err := r.handleFinalizer(ctx, &infisicalSecretCR); err != nil {
|
||||
return ctrl.Result{}, err
|
||||
}
|
||||
|
||||
return ctrl.Result{
|
||||
Requeue: false,
|
||||
}, nil
|
||||
|
@ -2,16 +2,21 @@ package controllers
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/Infisical/infisical/k8-operator/api/v1alpha1"
|
||||
"github.com/Infisical/infisical/k8-operator/packages/api"
|
||||
"github.com/Infisical/infisical/k8-operator/packages/model"
|
||||
"github.com/Infisical/infisical/k8-operator/packages/util"
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
"k8s.io/apimachinery/pkg/api/errors"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
|
||||
"k8s.io/apimachinery/pkg/types"
|
||||
|
||||
infisicalSdk "github.com/infisical/go-sdk"
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
k8Errors "k8s.io/apimachinery/pkg/api/errors"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
ctrl "sigs.k8s.io/controller-runtime"
|
||||
)
|
||||
|
||||
@ -28,20 +33,51 @@ const OPERATOR_SETTINGS_CONFIGMAP_NAME = "infisical-config"
|
||||
const OPERATOR_SETTINGS_CONFIGMAP_NAMESPACE = "infisical-operator-system"
|
||||
const INFISICAL_DOMAIN = "https://app.infisical.com/api"
|
||||
|
||||
type AuthStrategyType string
|
||||
func (r *InfisicalSecretReconciler) HandleAuthentication(ctx context.Context, infisicalSecret v1alpha1.InfisicalSecret, infisicalClient infisicalSdk.InfisicalClientInterface) (AuthenticationDetails, error) {
|
||||
|
||||
// ? Legacy support, service token auth
|
||||
infisicalToken, err := r.GetInfisicalTokenFromKubeSecret(ctx, infisicalSecret)
|
||||
if err != nil {
|
||||
return AuthenticationDetails{}, fmt.Errorf("ReconcileInfisicalSecret: unable to get service token from kube secret [err=%s]", err)
|
||||
}
|
||||
|
||||
// ? Legacy support, service account auth
|
||||
serviceAccountCreds, err := r.GetInfisicalServiceAccountCredentialsFromKubeSecret(ctx, infisicalSecret)
|
||||
if err != nil {
|
||||
return AuthenticationDetails{}, fmt.Errorf("ReconcileInfisicalSecret: unable to get service account creds from kube secret [err=%s]", err)
|
||||
}
|
||||
|
||||
if serviceAccountCreds.AccessKey != "" || serviceAccountCreds.PrivateKey != "" || serviceAccountCreds.PublicKey != "" {
|
||||
return AuthenticationDetails{authStrategy: AuthStrategy.SERVICE_ACCOUNT}, nil
|
||||
} else if infisicalToken != "" {
|
||||
return AuthenticationDetails{authStrategy: AuthStrategy.SERVICE_TOKEN}, nil
|
||||
}
|
||||
|
||||
authStrategies := map[AuthStrategyType]func(ctx context.Context, infisicalSecret v1alpha1.InfisicalSecret, infisicalClient infisicalSdk.InfisicalClientInterface) (AuthenticationDetails, error){
|
||||
AuthStrategy.UNIVERSAL_MACHINE_IDENTITY: r.handleUniversalAuth,
|
||||
AuthStrategy.KUBERNETES_MACHINE_IDENTITY: r.handleKubernetesAuth,
|
||||
AuthStrategy.AWS_IAM_MACHINE_IDENTITY: r.handleAwsIamAuth,
|
||||
AuthStrategy.AZURE_MACHINE_IDENTITY: r.handleAzureAuth,
|
||||
AuthStrategy.GCP_ID_TOKEN_MACHINE_IDENTITY: r.handleGcpIdTokenAuth,
|
||||
AuthStrategy.GCP_IAM_MACHINE_IDENTITY: r.handleGcpIamAuth,
|
||||
}
|
||||
|
||||
for authStrategy, authHandler := range authStrategies {
|
||||
authDetails, err := authHandler(ctx, infisicalSecret, infisicalClient)
|
||||
|
||||
if err == nil {
|
||||
return authDetails, nil
|
||||
}
|
||||
|
||||
if err != nil && !errors.Is(err, ErrAuthNotApplicable) {
|
||||
return AuthenticationDetails{}, fmt.Errorf("authentication failed for strategy [%s] [err=%w]", authStrategy, err)
|
||||
}
|
||||
}
|
||||
|
||||
return AuthenticationDetails{}, fmt.Errorf("no authentication method provided")
|
||||
|
||||
var AuthStrategy = struct {
|
||||
SERVICE_TOKEN AuthStrategyType
|
||||
SERVICE_ACCOUNT AuthStrategyType
|
||||
UNIVERSAL_MACHINE_IDENTITY AuthStrategyType
|
||||
}{
|
||||
SERVICE_TOKEN: "SERVICE_TOKEN",
|
||||
SERVICE_ACCOUNT: "SERVICE_ACCOUNT",
|
||||
UNIVERSAL_MACHINE_IDENTITY: "UNIVERSAL_MACHINE_IDENTITY",
|
||||
}
|
||||
|
||||
var machineIdentityTokenInstance *util.MachineIdentityToken
|
||||
|
||||
func (r *InfisicalSecretReconciler) GetInfisicalConfigMap(ctx context.Context) (configMap map[string]string, errToReturn error) {
|
||||
// default key values
|
||||
defaultConfigMapData := make(map[string]string)
|
||||
@ -54,7 +90,7 @@ func (r *InfisicalSecretReconciler) GetInfisicalConfigMap(ctx context.Context) (
|
||||
}, kubeConfigMap)
|
||||
|
||||
if err != nil {
|
||||
if errors.IsNotFound(err) {
|
||||
if k8Errors.IsNotFound(err) {
|
||||
kubeConfigMap = nil
|
||||
} else {
|
||||
return nil, fmt.Errorf("GetConfigMapByNamespacedName: unable to fetch config map in [namespacedName=%s] [err=%s]", OPERATOR_SETTINGS_CONFIGMAP_NAMESPACE, err)
|
||||
@ -103,7 +139,7 @@ func (r *InfisicalSecretReconciler) GetInfisicalTokenFromKubeSecret(ctx context.
|
||||
Name: secretName,
|
||||
})
|
||||
|
||||
if errors.IsNotFound(err) {
|
||||
if k8Errors.IsNotFound(err) {
|
||||
return "", nil
|
||||
}
|
||||
|
||||
@ -123,7 +159,7 @@ func (r *InfisicalSecretReconciler) GetInfisicalUniversalAuthFromKubeSecret(ctx
|
||||
Name: infisicalSecret.Spec.Authentication.UniversalAuth.CredentialsRef.SecretName,
|
||||
})
|
||||
|
||||
if errors.IsNotFound(err) {
|
||||
if k8Errors.IsNotFound(err) {
|
||||
return model.MachineIdentityDetails{}, nil
|
||||
}
|
||||
|
||||
@ -146,7 +182,7 @@ func (r *InfisicalSecretReconciler) GetInfisicalServiceAccountCredentialsFromKub
|
||||
Name: infisicalSecret.Spec.Authentication.ServiceAccount.ServiceAccountSecretReference.SecretName,
|
||||
})
|
||||
|
||||
if errors.IsNotFound(err) {
|
||||
if k8Errors.IsNotFound(err) {
|
||||
return model.ServiceAccountDetails{}, nil
|
||||
}
|
||||
|
||||
@ -243,37 +279,55 @@ func (r *InfisicalSecretReconciler) UpdateInfisicalManagedKubeSecret(ctx context
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *InfisicalSecretReconciler) ReconcileInfisicalSecret(ctx context.Context, infisicalSecret v1alpha1.InfisicalSecret) error {
|
||||
infisicalToken, err := r.GetInfisicalTokenFromKubeSecret(ctx, infisicalSecret)
|
||||
if err != nil {
|
||||
return fmt.Errorf("ReconcileInfisicalSecret: unable to get service token from kube secret [err=%s]", err)
|
||||
}
|
||||
func (r *InfisicalSecretReconciler) GetResourceVariables(infisicalSecret v1alpha1.InfisicalSecret) ResourceVariables {
|
||||
|
||||
var authStrategy AuthStrategyType
|
||||
var resourceVariables ResourceVariables
|
||||
|
||||
serviceAccountCreds, err := r.GetInfisicalServiceAccountCredentialsFromKubeSecret(ctx, infisicalSecret)
|
||||
if err != nil {
|
||||
return fmt.Errorf("ReconcileInfisicalSecret: unable to get service account creds from kube secret [err=%s]", err)
|
||||
}
|
||||
if _, ok := resourceVariablesMap[string(infisicalSecret.UID)]; !ok {
|
||||
|
||||
infisicalMachineIdentityCreds, err := r.GetInfisicalUniversalAuthFromKubeSecret(ctx, infisicalSecret)
|
||||
if err != nil {
|
||||
return fmt.Errorf("ReconcileInfisicalSecret: unable to get machine identity creds from kube secret [err=%s]", err)
|
||||
}
|
||||
client := infisicalSdk.NewInfisicalClient(infisicalSdk.Config{
|
||||
SiteUrl: infisicalSecret.Spec.HostAPI,
|
||||
UserAgent: api.USER_AGENT_NAME,
|
||||
})
|
||||
|
||||
resourceVariablesMap[string(infisicalSecret.UID)] = ResourceVariables{
|
||||
infisicalClient: client,
|
||||
authDetails: AuthenticationDetails{},
|
||||
}
|
||||
|
||||
resourceVariables = resourceVariablesMap[string(infisicalSecret.UID)]
|
||||
|
||||
if serviceAccountCreds.AccessKey != "" || serviceAccountCreds.PrivateKey != "" || serviceAccountCreds.PublicKey != "" {
|
||||
authStrategy = AuthStrategy.SERVICE_ACCOUNT
|
||||
} else if infisicalToken != "" {
|
||||
authStrategy = AuthStrategy.SERVICE_TOKEN
|
||||
} else if infisicalMachineIdentityCreds.ClientId != "" && infisicalMachineIdentityCreds.ClientSecret != "" {
|
||||
authStrategy = AuthStrategy.UNIVERSAL_MACHINE_IDENTITY
|
||||
} else {
|
||||
return fmt.Errorf("no authentication method provided. You must provide either a valid service token or a service account details to fetch secrets\n")
|
||||
resourceVariables = resourceVariablesMap[string(infisicalSecret.UID)]
|
||||
}
|
||||
|
||||
r.SetInfisicalTokenLoadCondition(ctx, &infisicalSecret, err)
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to load Infisical Token from the specified Kubernetes secret with error [%w]", err)
|
||||
return resourceVariables
|
||||
|
||||
}
|
||||
|
||||
func (r *InfisicalSecretReconciler) UpdateResourceVariables(infisicalSecret v1alpha1.InfisicalSecret, resourceVariables ResourceVariables) {
|
||||
resourceVariablesMap[string(infisicalSecret.UID)] = resourceVariables
|
||||
}
|
||||
|
||||
func (r *InfisicalSecretReconciler) ReconcileInfisicalSecret(ctx context.Context, infisicalSecret v1alpha1.InfisicalSecret) error {
|
||||
|
||||
resourceVariables := r.GetResourceVariables(infisicalSecret)
|
||||
infisicalClient := resourceVariables.infisicalClient
|
||||
authDetails := resourceVariables.authDetails
|
||||
|
||||
if authDetails.authStrategy == "" {
|
||||
fmt.Println("ReconcileInfisicalSecret: No authentication strategy found. Attempting to authenticate")
|
||||
authDetails, err := r.HandleAuthentication(ctx, infisicalSecret, infisicalClient)
|
||||
r.SetInfisicalTokenLoadCondition(ctx, &infisicalSecret, authDetails.authStrategy, err)
|
||||
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to authenticate [err=%s]", err)
|
||||
}
|
||||
|
||||
r.UpdateResourceVariables(infisicalSecret, ResourceVariables{
|
||||
infisicalClient: infisicalClient,
|
||||
authDetails: authDetails,
|
||||
})
|
||||
}
|
||||
|
||||
// Look for managed secret by name and namespace
|
||||
@ -282,7 +336,7 @@ func (r *InfisicalSecretReconciler) ReconcileInfisicalSecret(ctx context.Context
|
||||
Namespace: infisicalSecret.Spec.ManagedSecretReference.SecretNamespace,
|
||||
})
|
||||
|
||||
if err != nil && !errors.IsNotFound(err) {
|
||||
if err != nil && !k8Errors.IsNotFound(err) {
|
||||
return fmt.Errorf("something went wrong when fetching the managed Kubernetes secret [%w]", err)
|
||||
}
|
||||
|
||||
@ -292,15 +346,19 @@ func (r *InfisicalSecretReconciler) ReconcileInfisicalSecret(ctx context.Context
|
||||
secretVersionBasedOnETag = managedKubeSecret.Annotations[SECRET_VERSION_ANNOTATION]
|
||||
}
|
||||
|
||||
if authStrategy == AuthStrategy.UNIVERSAL_MACHINE_IDENTITY && machineIdentityTokenInstance == nil {
|
||||
// Create new machine identity token instance
|
||||
machineIdentityTokenInstance = util.NewMachineIdentityToken(infisicalMachineIdentityCreds.ClientId, infisicalMachineIdentityCreds.ClientSecret)
|
||||
}
|
||||
|
||||
var plainTextSecretsFromApi []model.SingleEnvironmentVariable
|
||||
var updateDetails model.RequestUpdateUpdateDetails
|
||||
|
||||
if authStrategy == AuthStrategy.SERVICE_ACCOUNT { // Service Account
|
||||
if authDetails.authStrategy == AuthStrategy.SERVICE_ACCOUNT { // Service Account // ! Legacy auth method
|
||||
serviceAccountCreds, err := r.GetInfisicalServiceAccountCredentialsFromKubeSecret(ctx, infisicalSecret)
|
||||
if err != nil {
|
||||
return fmt.Errorf("ReconcileInfisicalSecret: unable to get service account creds from kube secret [err=%s]", err)
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to load Infisical Token from the specified Kubernetes secret with error [%w]", err)
|
||||
}
|
||||
|
||||
plainTextSecretsFromApi, updateDetails, err = util.GetPlainTextSecretsViaServiceAccount(serviceAccountCreds, infisicalSecret.Spec.Authentication.ServiceAccount.ProjectId, infisicalSecret.Spec.Authentication.ServiceAccount.EnvironmentName, secretVersionBasedOnETag)
|
||||
if err != nil {
|
||||
return fmt.Errorf("\nfailed to get secrets because [err=%v]", err)
|
||||
@ -308,7 +366,12 @@ func (r *InfisicalSecretReconciler) ReconcileInfisicalSecret(ctx context.Context
|
||||
|
||||
fmt.Println("ReconcileInfisicalSecret: Fetched secrets via service account")
|
||||
|
||||
} else if authStrategy == AuthStrategy.SERVICE_TOKEN { // Service Tokens (deprecated)
|
||||
} else if authDetails.authStrategy == AuthStrategy.SERVICE_TOKEN { // Service Tokens // ! Legacy / Deprecated auth method
|
||||
infisicalToken, err := r.GetInfisicalTokenFromKubeSecret(ctx, infisicalSecret)
|
||||
if err != nil {
|
||||
return fmt.Errorf("ReconcileInfisicalSecret: unable to get service token from kube secret [err=%s]", err)
|
||||
}
|
||||
|
||||
envSlug := infisicalSecret.Spec.Authentication.ServiceToken.SecretsScope.EnvSlug
|
||||
secretsPath := infisicalSecret.Spec.Authentication.ServiceToken.SecretsScope.SecretsPath
|
||||
recursive := infisicalSecret.Spec.Authentication.ServiceToken.SecretsScope.Recursive
|
||||
@ -318,24 +381,17 @@ func (r *InfisicalSecretReconciler) ReconcileInfisicalSecret(ctx context.Context
|
||||
return fmt.Errorf("\nfailed to get secrets because [err=%v]", err)
|
||||
}
|
||||
|
||||
fmt.Println("ReconcileInfisicalSecret: Fetched secrets via service token")
|
||||
} else if authStrategy == AuthStrategy.UNIVERSAL_MACHINE_IDENTITY { // Machine Identity
|
||||
|
||||
accessToken, err := machineIdentityTokenInstance.GetToken()
|
||||
|
||||
if err != nil {
|
||||
return fmt.Errorf("%s", "Waiting for access token to become available")
|
||||
}
|
||||
scope := infisicalSecret.Spec.Authentication.UniversalAuth.SecretsScope
|
||||
plainTextSecretsFromApi, updateDetails, err = util.GetPlainTextSecretsViaUniversalAuth(accessToken, secretVersionBasedOnETag, scope)
|
||||
fmt.Println("ReconcileInfisicalSecret: Fetched secrets via [type=SERVICE_TOKEN]")
|
||||
} else if authDetails.isMachineIdentityAuth { // * Machine Identity authentication, the SDK will be authenticated at this point
|
||||
plainTextSecretsFromApi, updateDetails, err = util.GetPlainTextSecretsViaMachineIdentity(infisicalClient, secretVersionBasedOnETag, authDetails.machineIdentityScope)
|
||||
|
||||
if err != nil {
|
||||
return fmt.Errorf("\nfailed to get secrets because [err=%v]", err)
|
||||
}
|
||||
fmt.Println("ReconcileInfisicalSecret: Fetched secrets via universal auth")
|
||||
fmt.Printf("ReconcileInfisicalSecret: Fetched secrets via machine identity [type=%v]\n", authDetails.authStrategy)
|
||||
|
||||
} else {
|
||||
return fmt.Errorf("no authentication method provided. You must provide either a valid service token or a service account details to fetch secrets")
|
||||
return errors.New("no authentication method provided yet. Please configure a authentication method then try again")
|
||||
}
|
||||
|
||||
if !updateDetails.Modified {
|
||||
|
@ -1,8 +1,9 @@
|
||||
module github.com/Infisical/infisical/k8-operator
|
||||
|
||||
go 1.19
|
||||
go 1.21
|
||||
|
||||
require (
|
||||
github.com/infisical/go-sdk v0.1.9
|
||||
github.com/onsi/ginkgo/v2 v2.6.0
|
||||
github.com/onsi/gomega v1.24.1
|
||||
k8s.io/apimachinery v0.26.1
|
||||
@ -10,26 +11,62 @@ require (
|
||||
sigs.k8s.io/controller-runtime v0.14.4
|
||||
)
|
||||
|
||||
require (
|
||||
cloud.google.com/go/auth v0.5.1 // indirect
|
||||
cloud.google.com/go/auth/oauth2adapt v0.2.2 // indirect
|
||||
cloud.google.com/go/compute/metadata v0.3.0 // indirect
|
||||
cloud.google.com/go/iam v1.1.8 // indirect
|
||||
github.com/aws/aws-sdk-go-v2 v1.27.2 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/config v1.27.18 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/credentials v1.17.18 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.5 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.9 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.9 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/internal/ini v1.8.0 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.11.2 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.11.11 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/sso v1.20.11 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.24.5 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/sts v1.28.12 // indirect
|
||||
github.com/aws/smithy-go v1.20.2 // indirect
|
||||
github.com/felixge/httpsnoop v1.0.4 // indirect
|
||||
github.com/go-logr/stdr v1.2.2 // indirect
|
||||
github.com/google/s2a-go v0.1.7 // indirect
|
||||
github.com/googleapis/enterprise-certificate-proxy v0.3.2 // indirect
|
||||
github.com/googleapis/gax-go/v2 v2.12.4 // indirect
|
||||
go.opencensus.io v0.24.0 // indirect
|
||||
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.49.0 // indirect
|
||||
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.49.0 // indirect
|
||||
go.opentelemetry.io/otel v1.24.0 // indirect
|
||||
go.opentelemetry.io/otel/metric v1.24.0 // indirect
|
||||
go.opentelemetry.io/otel/trace v1.24.0 // indirect
|
||||
golang.org/x/sync v0.7.0 // indirect
|
||||
google.golang.org/api v0.183.0 // indirect
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20240521202816-d264139d666e // indirect
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20240528184218-531527333157 // indirect
|
||||
google.golang.org/grpc v1.64.0 // indirect
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/beorn7/perks v1.0.1 // indirect
|
||||
github.com/cespare/xxhash/v2 v2.1.2 // indirect
|
||||
github.com/cespare/xxhash/v2 v2.2.0 // indirect
|
||||
github.com/davecgh/go-spew v1.1.1 // indirect
|
||||
github.com/emicklei/go-restful/v3 v3.9.0 // indirect
|
||||
github.com/evanphx/json-patch/v5 v5.6.0 // indirect
|
||||
github.com/fsnotify/fsnotify v1.6.0 // indirect
|
||||
github.com/go-logr/logr v1.2.3 // indirect
|
||||
github.com/go-logr/logr v1.4.1 // indirect
|
||||
github.com/go-logr/zapr v1.2.3 // indirect
|
||||
github.com/go-openapi/jsonpointer v0.19.5 // indirect
|
||||
github.com/go-openapi/jsonreference v0.20.0 // indirect
|
||||
github.com/go-openapi/swag v0.19.14 // indirect
|
||||
github.com/go-resty/resty/v2 v2.10.0
|
||||
github.com/go-resty/resty/v2 v2.13.1
|
||||
github.com/gogo/protobuf v1.3.2 // indirect
|
||||
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect
|
||||
github.com/golang/protobuf v1.5.2 // indirect
|
||||
github.com/golang/protobuf v1.5.4 // indirect
|
||||
github.com/google/gnostic v0.5.7-v3refs // indirect
|
||||
github.com/google/go-cmp v0.5.9 // indirect
|
||||
github.com/google/go-cmp v0.6.0 // indirect
|
||||
github.com/google/gofuzz v1.1.0 // indirect
|
||||
github.com/google/uuid v1.1.2 // indirect
|
||||
github.com/google/uuid v1.6.0 // indirect
|
||||
github.com/imdario/mergo v0.3.12 // indirect
|
||||
github.com/josharian/intern v1.0.0 // indirect
|
||||
github.com/json-iterator/go v1.1.12 // indirect
|
||||
@ -47,16 +84,15 @@ require (
|
||||
go.uber.org/atomic v1.7.0 // indirect
|
||||
go.uber.org/multierr v1.6.0 // indirect
|
||||
go.uber.org/zap v1.24.0 // indirect
|
||||
golang.org/x/crypto v0.14.0
|
||||
golang.org/x/net v0.17.0 // indirect
|
||||
golang.org/x/oauth2 v0.0.0-20220223155221-ee480838109b // indirect
|
||||
golang.org/x/sys v0.13.0 // indirect
|
||||
golang.org/x/term v0.13.0 // indirect
|
||||
golang.org/x/text v0.13.0 // indirect
|
||||
golang.org/x/time v0.3.0 // indirect
|
||||
golang.org/x/crypto v0.23.0
|
||||
golang.org/x/net v0.25.0 // indirect
|
||||
golang.org/x/oauth2 v0.21.0 // indirect
|
||||
golang.org/x/sys v0.20.0 // indirect
|
||||
golang.org/x/term v0.20.0 // indirect
|
||||
golang.org/x/text v0.15.0 // indirect
|
||||
golang.org/x/time v0.5.0 // indirect
|
||||
gomodules.xyz/jsonpatch/v2 v2.2.0 // indirect
|
||||
google.golang.org/appengine v1.6.7 // indirect
|
||||
google.golang.org/protobuf v1.28.1 // indirect
|
||||
google.golang.org/protobuf v1.34.1 // indirect
|
||||
gopkg.in/inf.v0 v0.9.1 // indirect
|
||||
gopkg.in/yaml.v2 v2.4.0 // indirect
|
||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||
|
@ -13,14 +13,22 @@ cloud.google.com/go v0.56.0/go.mod h1:jr7tqZxxKOVYizybht9+26Z/gUq7tiRzu+ACVAMbKV
|
||||
cloud.google.com/go v0.57.0/go.mod h1:oXiQ6Rzq3RAkkY7N6t3TcE6jE+CIBBbA36lwQ1JyzZs=
|
||||
cloud.google.com/go v0.62.0/go.mod h1:jmCYTdRCQuc1PHIIJ/maLInMho30T/Y0M4hTdTShOYc=
|
||||
cloud.google.com/go v0.65.0/go.mod h1:O5N8zS7uWy9vkA9vayVHs65eM1ubvY4h553ofrNHObY=
|
||||
cloud.google.com/go/auth v0.5.1 h1:0QNO7VThG54LUzKiQxv8C6x1YX7lUrzlAa1nVLF8CIw=
|
||||
cloud.google.com/go/auth v0.5.1/go.mod h1:vbZT8GjzDf3AVqCcQmqeeM32U9HBFc32vVVAbwDsa6s=
|
||||
cloud.google.com/go/auth/oauth2adapt v0.2.2 h1:+TTV8aXpjeChS9M+aTtN/TjdQnzJvmzKFt//oWu7HX4=
|
||||
cloud.google.com/go/auth/oauth2adapt v0.2.2/go.mod h1:wcYjgpZI9+Yu7LyYBg4pqSiaRkfEK3GQcpb7C/uyF1Q=
|
||||
cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o=
|
||||
cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE=
|
||||
cloud.google.com/go/bigquery v1.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvftPBK2Dvzc=
|
||||
cloud.google.com/go/bigquery v1.5.0/go.mod h1:snEHRnqQbz117VIFhE8bmtwIDY80NLUZUMb4Nv6dBIg=
|
||||
cloud.google.com/go/bigquery v1.7.0/go.mod h1://okPTzCYNXSlb24MZs83e2Do+h+VXtc4gLoIoXIAPc=
|
||||
cloud.google.com/go/bigquery v1.8.0/go.mod h1:J5hqkt3O0uAFnINi6JXValWIb1v0goeZM77hZzJN/fQ=
|
||||
cloud.google.com/go/compute/metadata v0.3.0 h1:Tz+eQXMEqDIKRsmY3cHTL6FVaynIjX2QxYC4trgAKZc=
|
||||
cloud.google.com/go/compute/metadata v0.3.0/go.mod h1:zFmK7XCadkQkj6TtorcaGlCW1hT1fIilQDwofLpJ20k=
|
||||
cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE=
|
||||
cloud.google.com/go/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1b3c64qFpCk=
|
||||
cloud.google.com/go/iam v1.1.8 h1:r7umDwhj+BQyz0ScZMp4QrGXjSTI3ZINnpgU2nlB/K0=
|
||||
cloud.google.com/go/iam v1.1.8/go.mod h1:GvE6lyMmfxXauzNq8NbgJbeVQNspG+tcdL/W8QO1+zE=
|
||||
cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I=
|
||||
cloud.google.com/go/pubsub v1.1.0/go.mod h1:EwwdRX2sKPjnvnqCa270oGRyludottCI76h+R3AArQw=
|
||||
cloud.google.com/go/pubsub v1.2.0/go.mod h1:jhfEVHT8odbXTkndysNHCcx0awwzvfOlguIAii9o8iA=
|
||||
@ -38,6 +46,32 @@ github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuy
|
||||
github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
|
||||
github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
|
||||
github.com/alecthomas/units v0.0.0-20190924025748-f65c72e2690d/go.mod h1:rBZYJk541a8SKzHPHnH3zbiI+7dagKZ0cgpgrD7Fyho=
|
||||
github.com/aws/aws-sdk-go-v2 v1.27.2 h1:pLsTXqX93rimAOZG2FIYraDQstZaaGVVN4tNw65v0h8=
|
||||
github.com/aws/aws-sdk-go-v2 v1.27.2/go.mod h1:ffIFB97e2yNsv4aTSGkqtHnppsIJzw7G7BReUZ3jCXM=
|
||||
github.com/aws/aws-sdk-go-v2/config v1.27.18 h1:wFvAnwOKKe7QAyIxziwSKjmer9JBMH1vzIL6W+fYuKk=
|
||||
github.com/aws/aws-sdk-go-v2/config v1.27.18/go.mod h1:0xz6cgdX55+kmppvPm2IaKzIXOheGJhAufacPJaXZ7c=
|
||||
github.com/aws/aws-sdk-go-v2/credentials v1.17.18 h1:D/ALDWqK4JdY3OFgA2thcPO1c9aYTT5STS/CvnkqY1c=
|
||||
github.com/aws/aws-sdk-go-v2/credentials v1.17.18/go.mod h1:JuitCWq+F5QGUrmMPsk945rop6bB57jdscu+Glozdnc=
|
||||
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.5 h1:dDgptDO9dxeFkXy+tEgVkzSClHZje/6JkPW5aZyEvrQ=
|
||||
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.5/go.mod h1:gjvE2KBUgUQhcv89jqxrIxH9GaKs1JbZzWejj/DaHGA=
|
||||
github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.9 h1:cy8ahBJuhtM8GTTSyOkfy6WVPV1IE+SS5/wfXUYuulw=
|
||||
github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.9/go.mod h1:CZBXGLaJnEZI6EVNcPd7a6B5IC5cA/GkRWtu9fp3S6Y=
|
||||
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.9 h1:A4SYk07ef04+vxZToz9LWvAXl9LW0NClpPpMsi31cz0=
|
||||
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.9/go.mod h1:5jJcHuwDagxN+ErjQ3PU3ocf6Ylc/p9x+BLO/+X4iXw=
|
||||
github.com/aws/aws-sdk-go-v2/internal/ini v1.8.0 h1:hT8rVHwugYE2lEfdFE0QWVo81lF7jMrYJVDWI+f+VxU=
|
||||
github.com/aws/aws-sdk-go-v2/internal/ini v1.8.0/go.mod h1:8tu/lYfQfFe6IGnaOdrpVgEL2IrrDOf6/m9RQum4NkY=
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.11.2 h1:Ji0DY1xUsUr3I8cHps0G+XM3WWU16lP6yG8qu1GAZAs=
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.11.2/go.mod h1:5CsjAbs3NlGQyZNFACh+zztPDI7fU6eW9QsxjfnuBKg=
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.11.11 h1:o4T+fKxA3gTMcluBNZZXE9DNaMkJuUL1O3mffCUjoJo=
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.11.11/go.mod h1:84oZdJ+VjuJKs9v1UTC9NaodRZRseOXCTgku+vQJWR8=
|
||||
github.com/aws/aws-sdk-go-v2/service/sso v1.20.11 h1:gEYM2GSpr4YNWc6hCd5nod4+d4kd9vWIAWrmGuLdlMw=
|
||||
github.com/aws/aws-sdk-go-v2/service/sso v1.20.11/go.mod h1:gVvwPdPNYehHSP9Rs7q27U1EU+3Or2ZpXvzAYJNh63w=
|
||||
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.24.5 h1:iXjh3uaH3vsVcnyZX7MqCoCfcyxIrVE9iOQruRaWPrQ=
|
||||
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.24.5/go.mod h1:5ZXesEuy/QcO0WUnt+4sDkxhdXRHTu2yG0uCSH8B6os=
|
||||
github.com/aws/aws-sdk-go-v2/service/sts v1.28.12 h1:M/1u4HBpwLuMtjlxuI2y6HoVLzF5e2mfxHCg7ZVMYmk=
|
||||
github.com/aws/aws-sdk-go-v2/service/sts v1.28.12/go.mod h1:kcfd+eTdEi/40FIbLq4Hif3XMXnl5b/+t/KTfLt9xIk=
|
||||
github.com/aws/smithy-go v1.20.2 h1:tbp628ireGtzcHDDmLT/6ADHidqnwgF57XOXZe6tp4Q=
|
||||
github.com/aws/smithy-go v1.20.2/go.mod h1:krry+ya/rV9RDcV/Q16kpu6ypI4K2czasz0NC3qS14E=
|
||||
github.com/benbjohnson/clock v1.1.0 h1:Q92kusRqC1XV2MjkWETPvjJVqKetz1OzxZB7mHJLju8=
|
||||
github.com/benbjohnson/clock v1.1.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA=
|
||||
github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
|
||||
@ -46,8 +80,9 @@ github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
|
||||
github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
|
||||
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
|
||||
github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
|
||||
github.com/cespare/xxhash/v2 v2.1.2 h1:YRXhKfTDauu4ajMg1TPgFO5jnlC2HCbmLXMcTG5cbYE=
|
||||
github.com/cespare/xxhash/v2 v2.1.2/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
|
||||
github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44=
|
||||
github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
|
||||
github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=
|
||||
github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=
|
||||
github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
|
||||
@ -66,8 +101,11 @@ github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1m
|
||||
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
|
||||
github.com/evanphx/json-patch v0.5.2/go.mod h1:ZWS5hhDbVDyob71nXKNL0+PWn6ToqBHMikGIFbs31qQ=
|
||||
github.com/evanphx/json-patch v4.12.0+incompatible h1:4onqiflcdA9EOZ4RxV643DvftH5pOlLGNtQ5lPWQu84=
|
||||
github.com/evanphx/json-patch v4.12.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk=
|
||||
github.com/evanphx/json-patch/v5 v5.6.0 h1:b91NhWfaz02IuVxO9faSllyAtNXHMPkC5J8sJCLunww=
|
||||
github.com/evanphx/json-patch/v5 v5.6.0/go.mod h1:G79N1coSVB93tBe7j6PhzjmR3/2VvlbKOFpnXhI9Bw4=
|
||||
github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg=
|
||||
github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U=
|
||||
github.com/fsnotify/fsnotify v1.6.0 h1:n+5WquG0fcWoWp6xPWfHdbskMCQaFnG6PfBrh1Ky4HY=
|
||||
github.com/fsnotify/fsnotify v1.6.0/go.mod h1:sl3t1tCWJFWoRz9R8WJCbQihKKwmorjAbSClcnxKAGw=
|
||||
github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU=
|
||||
@ -83,8 +121,10 @@ github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG
|
||||
github.com/go-logfmt/logfmt v0.5.1/go.mod h1:WYhtIu8zTZfxdn5+rREduYbwxfcBr/Vr6KEVveWlfTs=
|
||||
github.com/go-logr/logr v1.2.0/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
|
||||
github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
|
||||
github.com/go-logr/logr v1.2.3 h1:2DntVwHkVopvECVRSlL5PSo9eG+cAkDCuckLubN+rq0=
|
||||
github.com/go-logr/logr v1.2.3/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
|
||||
github.com/go-logr/logr v1.4.1 h1:pKouT5E8xu9zeFC39JXRDukb6JFQPXM5p5I91188VAQ=
|
||||
github.com/go-logr/logr v1.4.1/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
|
||||
github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
|
||||
github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
|
||||
github.com/go-logr/zapr v1.2.3 h1:a9vnzlIBPQBBkeaR9IuMUfmVOrQlkoC4YfPoFkX3T7A=
|
||||
github.com/go-logr/zapr v1.2.3/go.mod h1:eIauM6P8qSvTw5o2ez6UEAfGjQKrxQTl5EoK+Qa2oG4=
|
||||
github.com/go-openapi/jsonpointer v0.19.3/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg=
|
||||
@ -95,8 +135,8 @@ github.com/go-openapi/jsonreference v0.20.0/go.mod h1:Ag74Ico3lPc+zR+qjn4XBUmXym
|
||||
github.com/go-openapi/swag v0.19.5/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk=
|
||||
github.com/go-openapi/swag v0.19.14 h1:gm3vOOXfiuw5i9p5N9xJvfjvuofpyvLA9Wr6QfK5Fng=
|
||||
github.com/go-openapi/swag v0.19.14/go.mod h1:QYRuS/SOXUCsnplDa677K7+DxSOj6IPNl/eQntq43wQ=
|
||||
github.com/go-resty/resty/v2 v2.10.0 h1:Qla4W/+TMmv0fOeeRqzEpXPLfTUnR5HZ1+lGs+CkiCo=
|
||||
github.com/go-resty/resty/v2 v2.10.0/go.mod h1:iiP/OpA0CkcL3IGt1O0+/SIItFUbkkyw5BGXiVdTu+A=
|
||||
github.com/go-resty/resty/v2 v2.13.1 h1:x+LHXBI2nMB1vqndymf26quycC4aggYJ7DECYbiz03g=
|
||||
github.com/go-resty/resty/v2 v2.13.1/go.mod h1:GznXlLxkq6Nh4sU59rPmUw3VtgpO3aS96ORAI6Q7d+0=
|
||||
github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
|
||||
github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
|
||||
github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q=
|
||||
@ -129,8 +169,9 @@ github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QD
|
||||
github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
|
||||
github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
|
||||
github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
|
||||
github.com/golang/protobuf v1.5.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw=
|
||||
github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
|
||||
github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek=
|
||||
github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps=
|
||||
github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
|
||||
github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
|
||||
github.com/google/gnostic v0.5.7-v3refs h1:FhTMOKj2VhjpouxvWJAV1TL304uMlb9zcDqkl6cEI54=
|
||||
@ -142,10 +183,11 @@ github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/
|
||||
github.com/google/go-cmp v0.4.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
|
||||
github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
||||
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
|
||||
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
||||
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
||||
github.com/google/gofuzz v1.1.0 h1:Hsa8mG0dQ46ij8Sl2AYJDUv1oA9/d6Vk+3LG99Oe02g=
|
||||
github.com/google/gofuzz v1.1.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
||||
@ -159,15 +201,24 @@ github.com/google/pprof v0.0.0-20200229191704-1ebb73c60ed3/go.mod h1:ZgVRPoUq/hf
|
||||
github.com/google/pprof v0.0.0-20200430221834-fc25d7d30c6d/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
|
||||
github.com/google/pprof v0.0.0-20200708004538-1a94d8640e99/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
|
||||
github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
|
||||
github.com/google/uuid v1.1.2 h1:EVhdT+1Kseyi1/pUmXKaFxYsDNy9RQYkMWRH68J/W7Y=
|
||||
github.com/google/s2a-go v0.1.7 h1:60BLSyTrOV4/haCDW4zb1guZItoSq8foHCXrAnjBo/o=
|
||||
github.com/google/s2a-go v0.1.7/go.mod h1:50CgR4k1jNlWBu4UfS4AcfhVe1r6pdZPygJ3R8F0Qdw=
|
||||
github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
|
||||
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/googleapis/enterprise-certificate-proxy v0.3.2 h1:Vie5ybvEvT75RniqhfFxPRy3Bf7vr3h0cechB90XaQs=
|
||||
github.com/googleapis/enterprise-certificate-proxy v0.3.2/go.mod h1:VLSiSSBs/ksPL8kq3OBOQ6WRI2QnaFynd1DCjZ62+V0=
|
||||
github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg=
|
||||
github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk=
|
||||
github.com/googleapis/gax-go/v2 v2.12.4 h1:9gWcmF85Wvq4ryPFvGFaOgPIs1AQX0d0bcbGw4Z96qg=
|
||||
github.com/googleapis/gax-go/v2 v2.12.4/go.mod h1:KYEYLorsnIGDi/rPC8b5TdlB9kbKoFubselGIoBMCwI=
|
||||
github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
|
||||
github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
|
||||
github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
|
||||
github.com/imdario/mergo v0.3.12 h1:b6R2BslTbIEToALKP7LxUvijTsNI9TAe80pLWN2g/HU=
|
||||
github.com/imdario/mergo v0.3.12/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA=
|
||||
github.com/infisical/go-sdk v0.1.9 h1:o9LUj0Tyn6OHusTEKEKQ4+PulJViAxgOrFa+SlwGJFc=
|
||||
github.com/infisical/go-sdk v0.1.9/go.mod h1:vHTDVw3k+wfStXab513TGk1n53kaKF2xgLqpw/xvtl4=
|
||||
github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI=
|
||||
github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY=
|
||||
github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y=
|
||||
@ -257,13 +308,19 @@ github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An
|
||||
github.com/stoewer/go-strcase v1.2.0/go.mod h1:IBiWB2sKIp3wVVQ3Y035++gc+knqhUQag1KpM8ahLw8=
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
|
||||
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
|
||||
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
||||
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
||||
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
|
||||
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
|
||||
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.8.0 h1:pSgiaMZlXftHpm5L7V1+rVB+AZJydKsMxsQBIJw4PKk=
|
||||
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
|
||||
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
|
||||
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
|
||||
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
||||
github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||
github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||
@ -274,10 +331,23 @@ go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8=
|
||||
go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
|
||||
go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
|
||||
go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
|
||||
go.opencensus.io v0.24.0 h1:y73uSU6J157QMP2kn2r30vwW1A2W2WFwSCGnAVxeaD0=
|
||||
go.opencensus.io v0.24.0/go.mod h1:vNK8G9p7aAivkbmorf4v+7Hgx+Zs0yY+0fOtgBfjQKo=
|
||||
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.49.0 h1:4Pp6oUg3+e/6M4C0A/3kJ2VYa++dsWVTtGgLVj5xtHg=
|
||||
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.49.0/go.mod h1:Mjt1i1INqiaoZOMGR1RIUJN+i3ChKoFRqzrRQhlkbs0=
|
||||
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.49.0 h1:jq9TW8u3so/bN+JPT166wjOI6/vQPF6Xe7nMNIltagk=
|
||||
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.49.0/go.mod h1:p8pYQP+m5XfbZm9fxtSKAbM6oIllS7s2AfxrChvc7iw=
|
||||
go.opentelemetry.io/otel v1.24.0 h1:0LAOdjNmQeSTzGBzduGe/rU4tZhMwL5rWgtp9Ku5Jfo=
|
||||
go.opentelemetry.io/otel v1.24.0/go.mod h1:W7b9Ozg4nkF5tWI5zsXkaKKDjdVjpD4oAt9Qi/MArHo=
|
||||
go.opentelemetry.io/otel/metric v1.24.0 h1:6EhoGWWK28x1fbpA4tYTOWBkPefTDQnb8WSGXlc88kI=
|
||||
go.opentelemetry.io/otel/metric v1.24.0/go.mod h1:VYhLe1rFfxuTXLgj4CBiyz+9WYBA8pNGJgDcSFRKBco=
|
||||
go.opentelemetry.io/otel/trace v1.24.0 h1:CsKnnL4dUAr/0llH9FKuc698G04IrpWV0MQA/Y1YELI=
|
||||
go.opentelemetry.io/otel/trace v1.24.0/go.mod h1:HPc3Xr/cOApsBI154IU0OI0HJexz+aw5uPdbs3UCjNU=
|
||||
go.uber.org/atomic v1.7.0 h1:ADUqmZGgLDDfbSL9ZmPxKTybcoEYHgpYfELNoN+7hsw=
|
||||
go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc=
|
||||
go.uber.org/goleak v1.1.10/go.mod h1:8a7PlsEVH3e/a/GLqe5IIrQx6GzcnRmZEufDUTk4A7A=
|
||||
go.uber.org/goleak v1.2.0 h1:xqgm/S+aQvhWFTtR0XK3Jvg7z8kGV8P4X14IzwN3Eqk=
|
||||
go.uber.org/goleak v1.2.0/go.mod h1:XJYK+MuIchqpmGmUSAzotztawfKvYLUIgg7guXrwVUo=
|
||||
go.uber.org/multierr v1.6.0 h1:y6IPFStTAIT5Ytl7/XYmHvzXQ7S3g/IeZW9hyZ5thw4=
|
||||
go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU=
|
||||
go.uber.org/zap v1.19.0/go.mod h1:xg/QME4nWcxGxrpdeYfq7UvYrLh66cuVKdrbD1XF/NI=
|
||||
@ -290,8 +360,9 @@ golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8U
|
||||
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||
golang.org/x/crypto v0.14.0 h1:wBqGXzWJW6m1XrIKlAH0Hs1JJ7+9KBwnIO8v66Q9cHc=
|
||||
golang.org/x/crypto v0.14.0/go.mod h1:MVFd36DqK4CsrnJYDkBA3VC4m2GkXAM0PvzMCn4JQf4=
|
||||
golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU=
|
||||
golang.org/x/crypto v0.23.0 h1:dIJU/v2J8Mdglj/8rJ6UUOM3Zc9zLZxVZwwxMooUSAI=
|
||||
golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8=
|
||||
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||
golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||
golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
|
||||
@ -353,6 +424,7 @@ golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81R
|
||||
golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
|
||||
golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
|
||||
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
|
||||
golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
|
||||
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
||||
golang.org/x/net v0.0.0-20210525063256-abc453219eb5/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||
golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
|
||||
@ -360,16 +432,18 @@ golang.org/x/net v0.0.0-20220225172249-27dd8689420f/go.mod h1:CfG3xpIq0wQ8r1q4Su
|
||||
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
|
||||
golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
|
||||
golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=
|
||||
golang.org/x/net v0.17.0 h1:pVaXccu2ozPjCXewfr1S7xza/zcXTity9cCdXQYSjIM=
|
||||
golang.org/x/net v0.17.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE=
|
||||
golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44=
|
||||
golang.org/x/net v0.25.0 h1:d/OCCoBEUq33pjydKrGQhw7IlUPI2Oylr+8qLx49kac=
|
||||
golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM=
|
||||
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
||||
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||
golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||
golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||
golang.org/x/oauth2 v0.0.0-20210514164344-f6687ab2804c/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
|
||||
golang.org/x/oauth2 v0.0.0-20220223155221-ee480838109b h1:clP8eMhB30EHdc0bd2Twtq6kgU7yl5ub2cQLSdrv1Dg=
|
||||
golang.org/x/oauth2 v0.0.0-20220223155221-ee480838109b/go.mod h1:DAh4E804XQdzx2j+YRIaUnCqCV2RuMz24cGBJ5QYIrc=
|
||||
golang.org/x/oauth2 v0.21.0 h1:tsimM75w1tF/uws5rbeHzIWxEqElMehnc+iW793zsZs=
|
||||
golang.org/x/oauth2 v0.21.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI=
|
||||
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
@ -382,6 +456,8 @@ golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJ
|
||||
golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.7.0 h1:YsImfSBoP9QPYL0xyKJPq0gcaJdG3rInoqxTWbfQu9M=
|
||||
golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
||||
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
@ -426,14 +502,16 @@ golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBc
|
||||
golang.org/x/sys v0.0.0-20220908164124-27713097b956/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.13.0 h1:Af8nKPmuFypiUBjVoU9V20FiaFXOcuZI21p0ycVYYGE=
|
||||
golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/sys v0.20.0 h1:Od9JTbYCk261bKm4M/mw7AklTlFYIa0bIp9BgSm1S8Y=
|
||||
golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
|
||||
golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo=
|
||||
golang.org/x/term v0.13.0 h1:bb+I9cTfFazGW51MZqBVmZy7+JEJMouUHTUSKVQLBek=
|
||||
golang.org/x/term v0.13.0/go.mod h1:LTmsnFJwVN6bCy1rVCoS+qHT1HhALEFxKncY3WNNh4U=
|
||||
golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk=
|
||||
golang.org/x/term v0.20.0 h1:VnkxpohqXaOBYJtBmEppKUG6mXpi+4O6purfc2+sMhw=
|
||||
golang.org/x/term v0.20.0/go.mod h1:8UkIAJTvZgivsXaD6/pH6U9ecQzZ45awqEOzuCvwpFY=
|
||||
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
@ -443,13 +521,14 @@ golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
||||
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
||||
golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
|
||||
golang.org/x/text v0.13.0 h1:ablQoSUd0tRdKxZewP80B+BaqeKJuVhuRxj/dkrun3k=
|
||||
golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
|
||||
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
|
||||
golang.org/x/text v0.15.0 h1:h1V/4gjBv8v9cjcR6+AR5+/cIYK5N/WAgiv4xlsEtAk=
|
||||
golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
|
||||
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/time v0.3.0 h1:rg5rLMjNzMS1RkNLzCG38eapWhnYLFYXDXj2gOlr8j4=
|
||||
golang.org/x/time v0.3.0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk=
|
||||
golang.org/x/time v0.5.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM=
|
||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
|
||||
@ -517,14 +596,14 @@ google.golang.org/api v0.24.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0M
|
||||
google.golang.org/api v0.28.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE=
|
||||
google.golang.org/api v0.29.0/go.mod h1:Lcubydp8VUV7KeIHD9z2Bys/sm/vGKnG1UHuDBSrHWM=
|
||||
google.golang.org/api v0.30.0/go.mod h1:QGmEvQ87FHZNiUVJkT14jQNYJ4ZJjdRF23ZXz5138Fc=
|
||||
google.golang.org/api v0.183.0 h1:PNMeRDwo1pJdgNcFQ9GstuLe/noWKIc89pRWRLMvLwE=
|
||||
google.golang.org/api v0.183.0/go.mod h1:q43adC5/pHoSZTx5h2mSmdF7NcyfW9JuDyIOJAgS9ZQ=
|
||||
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
|
||||
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
|
||||
google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
|
||||
google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0=
|
||||
google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
|
||||
google.golang.org/appengine v1.6.6/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
|
||||
google.golang.org/appengine v1.6.7 h1:FZR1q0exgwxzPzp/aF+VccGrSfxfPpkBqjIIEq3ru6c=
|
||||
google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
|
||||
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
|
||||
google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
|
||||
google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
|
||||
@ -555,6 +634,10 @@ google.golang.org/genproto v0.0.0-20200729003335-053ba62fc06f/go.mod h1:FWY/as6D
|
||||
google.golang.org/genproto v0.0.0-20200804131852-c06518451d9c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
|
||||
google.golang.org/genproto v0.0.0-20200825200019-8632dd797987/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
|
||||
google.golang.org/genproto v0.0.0-20201019141844-1ed22bb0c154/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20240521202816-d264139d666e h1:SkdGTrROJl2jRGT/Fxv5QUf9jtdKCQh4KQJXbXVLAi0=
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20240521202816-d264139d666e/go.mod h1:LweJcLbyVij6rCex8YunD8DYR5VDonap/jYl3ZRxcIU=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20240528184218-531527333157 h1:Zy9XzmMEflZ/MAaA7vNcoebnRAld7FsPW1EeBB7V0m8=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20240528184218-531527333157/go.mod h1:EfXuqaE1J41VCDicxHzUDm+8rk+7ZdXzHV0IhO/I6s0=
|
||||
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
|
||||
google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38=
|
||||
google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM=
|
||||
@ -567,6 +650,9 @@ google.golang.org/grpc v1.28.0/go.mod h1:rpkK4SK4GF4Ach/+MFLZUBavHOvF2JJB5uozKKa
|
||||
google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk=
|
||||
google.golang.org/grpc v1.30.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=
|
||||
google.golang.org/grpc v1.31.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=
|
||||
google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc=
|
||||
google.golang.org/grpc v1.64.0 h1:KH3VH9y/MgNQg1dE7b3XfVK0GsPSIzJwdF617gUSbvY=
|
||||
google.golang.org/grpc v1.64.0/go.mod h1:oxjF8E3FBnjp+/gVFYdWacaLDx9na1aqy9oovLpxQYg=
|
||||
google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
|
||||
google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
|
||||
google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
|
||||
@ -579,8 +665,8 @@ google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGj
|
||||
google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c=
|
||||
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
|
||||
google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
|
||||
google.golang.org/protobuf v1.28.1 h1:d0NfwRgPtno5B1Wa6L2DAG+KivqkdutMf1UhdNx175w=
|
||||
google.golang.org/protobuf v1.28.1/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
|
||||
google.golang.org/protobuf v1.34.1 h1:9ddQBjfCyZPOHPUiPxpYESBLc+T8P3E+Vo4IbKZgFWg=
|
||||
google.golang.org/protobuf v1.34.1/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos=
|
||||
gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
|
@ -3,6 +3,8 @@ package crypto
|
||||
import (
|
||||
"crypto/aes"
|
||||
"crypto/cipher"
|
||||
"fmt"
|
||||
"hash/crc32"
|
||||
|
||||
"golang.org/x/crypto/nacl/box"
|
||||
)
|
||||
@ -33,3 +35,8 @@ func DecryptAsymmetric(ciphertext []byte, nonce []byte, publicKey []byte, privat
|
||||
plainTextToReturn, _ := box.Open(nil, ciphertext, (*[24]byte)(nonce), (*[32]byte)(publicKey), (*[32]byte)(privateKey))
|
||||
return plainTextToReturn
|
||||
}
|
||||
|
||||
func ComputeEtag(data []byte) string {
|
||||
crc := crc32.ChecksumIEEE(data)
|
||||
return fmt.Sprintf(`W/"secrets-%d-%08X"`, len(data), crc)
|
||||
}
|
||||
|
34
k8-operator/packages/util/auth.go
Normal file
34
k8-operator/packages/util/auth.go
Normal file
@ -0,0 +1,34 @@
|
||||
package util
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
"sigs.k8s.io/controller-runtime/pkg/client"
|
||||
)
|
||||
|
||||
func GetServiceAccountToken(k8sClient client.Client, namespace string, serviceAccountName string) (string, error) {
|
||||
|
||||
serviceAccount := &corev1.ServiceAccount{}
|
||||
err := k8sClient.Get(context.TODO(), client.ObjectKey{Name: serviceAccountName, Namespace: namespace}, serviceAccount)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
if len(serviceAccount.Secrets) == 0 {
|
||||
return "", fmt.Errorf("no secrets found for service account %s", serviceAccountName)
|
||||
}
|
||||
|
||||
secretName := serviceAccount.Secrets[0].Name
|
||||
|
||||
secret := &corev1.Secret{}
|
||||
err = k8sClient.Get(context.TODO(), client.ObjectKey{Name: secretName, Namespace: namespace}, secret)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
token := secret.Data["token"]
|
||||
|
||||
return string(token), nil
|
||||
}
|
@ -1,170 +0,0 @@
|
||||
package util
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/Infisical/infisical/k8-operator/packages/api"
|
||||
"github.com/go-resty/resty/v2"
|
||||
)
|
||||
|
||||
type MachineIdentityToken struct {
|
||||
accessTokenTTL time.Duration
|
||||
accessTokenMaxTTL time.Duration
|
||||
accessTokenFetchedTime time.Time
|
||||
accessTokenRefreshedTime time.Time
|
||||
|
||||
mutex sync.Mutex
|
||||
|
||||
accessToken string
|
||||
clientSecret string
|
||||
clientId string
|
||||
}
|
||||
|
||||
func NewMachineIdentityToken(clientId string, clientSecret string) *MachineIdentityToken {
|
||||
|
||||
token := MachineIdentityToken{
|
||||
clientSecret: clientSecret,
|
||||
clientId: clientId,
|
||||
}
|
||||
|
||||
go token.HandleTokenLifecycle()
|
||||
|
||||
return &token
|
||||
}
|
||||
|
||||
func (t *MachineIdentityToken) HandleTokenLifecycle() error {
|
||||
|
||||
for {
|
||||
accessTokenMaxTTLExpiresInTime := t.accessTokenFetchedTime.Add(t.accessTokenMaxTTL - (5 * time.Second))
|
||||
accessTokenRefreshedTime := t.accessTokenRefreshedTime
|
||||
|
||||
if accessTokenRefreshedTime.IsZero() {
|
||||
accessTokenRefreshedTime = t.accessTokenFetchedTime
|
||||
}
|
||||
|
||||
nextAccessTokenExpiresInTime := accessTokenRefreshedTime.Add(t.accessTokenTTL - (5 * time.Second))
|
||||
|
||||
if t.accessTokenFetchedTime.IsZero() && t.accessTokenRefreshedTime.IsZero() {
|
||||
// case: init login to get access token
|
||||
fmt.Println("\nInfisical Authentication: attempting to authenticate...")
|
||||
err := t.FetchNewAccessToken()
|
||||
if err != nil {
|
||||
fmt.Printf("\nInfisical Authentication: unable to authenticate universal auth because %v. Will retry in 30 seconds", err)
|
||||
|
||||
// wait a bit before trying again
|
||||
time.Sleep((30 * time.Second))
|
||||
continue
|
||||
}
|
||||
} else if time.Now().After(accessTokenMaxTTLExpiresInTime) {
|
||||
fmt.Printf("\nInfisical Authentication: machine identity access token has reached max ttl, attempting to re authenticate...")
|
||||
err := t.FetchNewAccessToken()
|
||||
if err != nil {
|
||||
fmt.Printf("\nInfisical Authentication: unable to authenticate universal auth because %v. Will retry in 30 seconds", err)
|
||||
|
||||
// wait a bit before trying again
|
||||
time.Sleep((30 * time.Second))
|
||||
continue
|
||||
}
|
||||
} else {
|
||||
err := t.RefreshAccessToken()
|
||||
if err != nil {
|
||||
fmt.Printf("\nInfisical Authentication: unable to refresh universal auth token because %v. Will retry in 30 seconds", err)
|
||||
|
||||
// wait a bit before trying again
|
||||
time.Sleep((30 * time.Second))
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
if accessTokenRefreshedTime.IsZero() {
|
||||
accessTokenRefreshedTime = t.accessTokenFetchedTime
|
||||
} else {
|
||||
accessTokenRefreshedTime = t.accessTokenRefreshedTime
|
||||
}
|
||||
|
||||
nextAccessTokenExpiresInTime = accessTokenRefreshedTime.Add(t.accessTokenTTL - (5 * time.Second))
|
||||
accessTokenMaxTTLExpiresInTime = t.accessTokenFetchedTime.Add(t.accessTokenMaxTTL - (5 * time.Second))
|
||||
|
||||
if nextAccessTokenExpiresInTime.After(accessTokenMaxTTLExpiresInTime) {
|
||||
// case: Refreshed so close that the next refresh would occur beyond max ttl (this is because currently, token renew tries to add +access-token-ttl amount of time)
|
||||
// example: access token ttl is 11 sec and max ttl is 30 sec. So it will start with 11 seconds, then 22 seconds but the next time you call refresh it would try to extend it to 33 but max ttl only allows 30, so the token will be valid until 30 before we need to reauth
|
||||
time.Sleep(t.accessTokenTTL - nextAccessTokenExpiresInTime.Sub(accessTokenMaxTTLExpiresInTime))
|
||||
} else {
|
||||
time.Sleep(t.accessTokenTTL - (5 * time.Second))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (t *MachineIdentityToken) RefreshAccessToken() error {
|
||||
httpClient := resty.New()
|
||||
httpClient.SetRetryCount(10000).
|
||||
SetRetryMaxWaitTime(20 * time.Second).
|
||||
SetRetryWaitTime(5 * time.Second)
|
||||
|
||||
accessToken, err := t.GetToken()
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
response, err := api.CallUniversalMachineIdentityRefreshAccessToken(api.MachineIdentityUniversalAuthRefreshRequest{AccessToken: accessToken})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
accessTokenTTL := time.Duration(response.ExpiresIn * int(time.Second))
|
||||
accessTokenMaxTTL := time.Duration(response.AccessTokenMaxTTL * int(time.Second))
|
||||
t.accessTokenRefreshedTime = time.Now()
|
||||
|
||||
t.SetToken(response.AccessToken, accessTokenTTL, accessTokenMaxTTL)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Fetches a new access token using client credentials
|
||||
func (t *MachineIdentityToken) FetchNewAccessToken() error {
|
||||
|
||||
loginResponse, err := api.CallUniversalMachineIdentityLogin(api.MachineIdentityUniversalAuthLoginRequest{
|
||||
ClientId: t.clientId,
|
||||
ClientSecret: t.clientSecret,
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
accessTokenTTL := time.Duration(loginResponse.ExpiresIn * int(time.Second))
|
||||
accessTokenMaxTTL := time.Duration(loginResponse.AccessTokenMaxTTL * int(time.Second))
|
||||
|
||||
if accessTokenTTL <= time.Duration(5)*time.Second {
|
||||
fmt.Println("\nInfisical Authentication: At this time, k8 operator does not support refresh of tokens with 5 seconds or less ttl. Please increase access token ttl and try again")
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
t.accessTokenFetchedTime = time.Now()
|
||||
t.SetToken(loginResponse.AccessToken, accessTokenTTL, accessTokenMaxTTL)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (t *MachineIdentityToken) SetToken(token string, accessTokenTTL time.Duration, accessTokenMaxTTL time.Duration) {
|
||||
t.mutex.Lock()
|
||||
defer t.mutex.Unlock()
|
||||
|
||||
t.accessToken = token
|
||||
t.accessTokenTTL = accessTokenTTL
|
||||
t.accessTokenMaxTTL = accessTokenMaxTTL
|
||||
}
|
||||
|
||||
func (t *MachineIdentityToken) GetToken() (string, error) {
|
||||
t.mutex.Lock()
|
||||
defer t.mutex.Unlock()
|
||||
|
||||
if t.accessToken == "" {
|
||||
return "", fmt.Errorf("no machine identity access token available")
|
||||
}
|
||||
|
||||
return t.accessToken, nil
|
||||
}
|
@ -12,6 +12,7 @@ import (
|
||||
"github.com/Infisical/infisical/k8-operator/packages/crypto"
|
||||
"github.com/Infisical/infisical/k8-operator/packages/model"
|
||||
"github.com/go-resty/resty/v2"
|
||||
infisical "github.com/infisical/go-sdk"
|
||||
)
|
||||
|
||||
type DecodedSymmetricEncryptionDetails = struct {
|
||||
@ -51,29 +52,26 @@ func GetServiceTokenDetails(infisicalToken string) (api.GetServiceTokenDetailsRe
|
||||
return serviceTokenDetails, nil
|
||||
}
|
||||
|
||||
func GetPlainTextSecretsViaUniversalAuth(accessToken string, etag string, secretScope v1alpha1.MachineIdentityScopeInWorkspace) ([]model.SingleEnvironmentVariable, model.RequestUpdateUpdateDetails, error) {
|
||||
func GetPlainTextSecretsViaMachineIdentity(infisicalClient infisical.InfisicalClientInterface, etag string, secretScope v1alpha1.MachineIdentityScopeInWorkspace) ([]model.SingleEnvironmentVariable, model.RequestUpdateUpdateDetails, error) {
|
||||
|
||||
httpClient := resty.New()
|
||||
httpClient.SetAuthScheme("Bearer")
|
||||
httpClient.SetAuthToken(accessToken)
|
||||
|
||||
secretsResponse, err := api.CallGetDecryptedSecretsV3(httpClient, api.GetDecryptedSecretsV3Request{
|
||||
secrets, err := infisicalClient.Secrets().List(infisical.ListSecretsOptions{
|
||||
ProjectSlug: secretScope.ProjectSlug,
|
||||
Environment: secretScope.EnvSlug,
|
||||
Recursive: secretScope.Recursive,
|
||||
SecretPath: secretScope.SecretsPath,
|
||||
IncludeImports: true,
|
||||
ExpandSecretReferences: true,
|
||||
ETag: etag,
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
return nil, model.RequestUpdateUpdateDetails{}, err
|
||||
}
|
||||
|
||||
var secrets []model.SingleEnvironmentVariable
|
||||
var environmentVariables []model.SingleEnvironmentVariable
|
||||
|
||||
for _, secret := range secretsResponse.Secrets {
|
||||
secrets = append(secrets, model.SingleEnvironmentVariable{
|
||||
for _, secret := range secrets {
|
||||
|
||||
environmentVariables = append(environmentVariables, model.SingleEnvironmentVariable{
|
||||
Key: secret.SecretKey,
|
||||
Value: secret.SecretValue,
|
||||
Type: secret.Type,
|
||||
@ -81,15 +79,11 @@ func GetPlainTextSecretsViaUniversalAuth(accessToken string, etag string, secret
|
||||
})
|
||||
}
|
||||
|
||||
// No need to do expansion for Machine Identity auth as this is handled on server-side.
|
||||
mergedSecrets := MergeRawImportedSecrets(secrets, secretsResponse.Imports)
|
||||
if err != nil {
|
||||
return nil, model.RequestUpdateUpdateDetails{}, err
|
||||
}
|
||||
newEtag := crypto.ComputeEtag([]byte(fmt.Sprintf("%v", environmentVariables)))
|
||||
|
||||
return mergedSecrets, model.RequestUpdateUpdateDetails{
|
||||
Modified: secretsResponse.Modified,
|
||||
ETag: secretsResponse.ETag,
|
||||
return environmentVariables, model.RequestUpdateUpdateDetails{
|
||||
Modified: etag != newEtag,
|
||||
ETag: newEtag,
|
||||
}, nil
|
||||
}
|
||||
|
||||
|
Reference in New Issue
Block a user