Merge remote-tracking branch 'origin' into groups-phase-3

This commit is contained in:
Tuan Dang
2024-04-23 21:47:22 -07:00
20 changed files with 688 additions and 26 deletions
@@ -0,0 +1,38 @@
name: Build patroni
on: [workflow_dispatch]
jobs:
patroni-image:
name: Build patroni
runs-on: ubuntu-latest
steps:
- name: ☁️ Checkout source
uses: actions/checkout@v3
with:
repository: 'zalando/patroni'
- name: Save commit hashes for tag
id: commit
uses: pr-mpt/actions-commit-hash@v2
- name: 🔧 Set up Docker Buildx
uses: docker/setup-buildx-action@v2
- name: 🐋 Login to Docker Hub
uses: docker/login-action@v2
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
- name: Set up Depot CLI
uses: depot/setup-action@v1
- name: 🏗️ Build backend and push to docker hub
uses: depot/build-push-action@v1
with:
project: 64mmf0n610
token: ${{ secrets.DEPOT_PROJECT_TOKEN }}
push: true
context: .
file: Dockerfile
tags: |
infisical/patroni:${{ steps.commit.outputs.short }}
infisical/patroni:latest
platforms: linux/amd64,linux/arm64
@@ -1,6 +1,8 @@
name: Build and release CLI name: Build and release CLI
on: on:
workflow_dispatch:
push: push:
# run only against tags # run only against tags
tags: tags:
@@ -14,6 +16,12 @@ jobs:
cli-integration-tests: cli-integration-tests:
name: Run tests before deployment name: Run tests before deployment
uses: ./.github/workflows/run-cli-tests.yml uses: ./.github/workflows/run-cli-tests.yml
secrets:
CLI_TESTS_UA_CLIENT_ID: ${{ secrets.CLI_TESTS_UA_CLIENT_ID }}
CLI_TESTS_UA_CLIENT_SECRET: ${{ secrets.CLI_TESTS_UA_CLIENT_SECRET }}
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 }}
goreleaser: goreleaser:
runs-on: ubuntu-20.04 runs-on: ubuntu-20.04
+13
View File
@@ -6,7 +6,20 @@ on:
paths: paths:
- "cli/**" - "cli/**"
workflow_dispatch:
workflow_call: workflow_call:
secrets:
CLI_TESTS_UA_CLIENT_ID:
required: true
CLI_TESTS_UA_CLIENT_SECRET:
required: true
CLI_TESTS_SERVICE_TOKEN:
required: true
CLI_TESTS_PROJECT_ID:
required: true
CLI_TESTS_ENV_SLUG:
required: true
jobs: jobs:
test: test:
+107
View File
@@ -942,6 +942,113 @@ describe.each([{ auth: AuthMode.JWT }, { auth: AuthMode.IDENTITY_ACCESS_TOKEN }]
const secrets = await getSecrets(seedData1.environment.slug, path); const secrets = await getSecrets(seedData1.environment.slug, path);
expect(secrets).toEqual([]); expect(secrets).toEqual([]);
}); });
test.each(testRawSecrets)("Bulk create secret raw in path $path", async ({ path, secret }) => {
const createSecretReqBody = {
projectSlug: seedData1.project.slug,
environment: seedData1.environment.slug,
secretPath: path,
secrets: [
{
secretKey: secret.key,
secretValue: secret.value,
secretComment: secret.comment
}
]
};
const createSecRes = await testServer.inject({
method: "POST",
url: `/api/v3/secrets/batch/raw`,
headers: {
authorization: `Bearer ${authToken}`
},
body: createSecretReqBody
});
expect(createSecRes.statusCode).toBe(200);
const createdSecretPayload = JSON.parse(createSecRes.payload);
expect(createdSecretPayload).toHaveProperty("secrets");
// fetch secrets
const secrets = await getSecrets(seedData1.environment.slug, path);
expect(secrets).toEqual(
expect.arrayContaining([
expect.objectContaining({
key: secret.key,
value: secret.value,
type: SecretType.Shared
})
])
);
await deleteRawSecret({ path, key: secret.key });
});
test.each(testRawSecrets)("Bulk update secret raw in path $path", async ({ secret, path }) => {
await createRawSecret({ path, ...secret });
const updateSecretReqBody = {
projectSlug: seedData1.project.slug,
environment: seedData1.environment.slug,
secretPath: path,
secrets: [
{
secretValue: "new-value",
secretKey: secret.key
}
]
};
const updateSecRes = await testServer.inject({
method: "PATCH",
url: `/api/v3/secrets/batch/raw`,
headers: {
authorization: `Bearer ${authToken}`
},
body: updateSecretReqBody
});
expect(updateSecRes.statusCode).toBe(200);
const updatedSecretPayload = JSON.parse(updateSecRes.payload);
expect(updatedSecretPayload).toHaveProperty("secrets");
// fetch secrets
const secrets = await getSecrets(seedData1.environment.slug, path);
expect(secrets).toEqual(
expect.arrayContaining([
expect.objectContaining({
key: secret.key,
value: "new-value",
version: 2,
type: SecretType.Shared
})
])
);
await deleteRawSecret({ path, key: secret.key });
});
test.each(testRawSecrets)("Bulk delete secret raw in path $path", async ({ path, secret }) => {
await createRawSecret({ path, ...secret });
const deletedSecretReqBody = {
projectSlug: seedData1.project.slug,
environment: seedData1.environment.slug,
secretPath: path,
secrets: [{ secretKey: secret.key }]
};
const deletedSecRes = await testServer.inject({
method: "DELETE",
url: `/api/v3/secrets/batch/raw`,
headers: {
authorization: `Bearer ${authToken}`
},
body: deletedSecretReqBody
});
expect(deletedSecRes.statusCode).toBe(200);
const deletedSecretPayload = JSON.parse(deletedSecRes.payload);
expect(deletedSecretPayload).toHaveProperty("secrets");
// fetch secrets
const secrets = await getSecrets(seedData1.environment.slug, path);
expect(secrets).toEqual([]);
});
} }
); );
@@ -42,6 +42,7 @@ export async function up(knex: Knex): Promise<void> {
await knex.transaction(async (tx) => { await knex.transaction(async (tx) => {
const duplicateRows = await tx(TableName.OrgMembership) const duplicateRows = await tx(TableName.OrgMembership)
.select("userId", "orgId") // Select the userId and orgId so we can group by them .select("userId", "orgId") // Select the userId and orgId so we can group by them
.whereNotNull("userId") // Ensure that the userId is not null
.count("* as cnt") // Count the number of rows for each userId and orgId, so we can make sure there are more than 1 row (a duplicate) .count("* as cnt") // Count the number of rows for each userId and orgId, so we can make sure there are more than 1 row (a duplicate)
.groupBy("userId", "orgId") .groupBy("userId", "orgId")
.havingRaw("count(*) > ?", [1]); // Using havingRaw for direct SQL expressions .havingRaw("count(*) > ?", [1]); // Using havingRaw for direct SQL expressions
@@ -495,7 +495,11 @@ export const secretApprovalRequestServiceFactory = ({
await projectDAL.checkProjectUpgradeStatus(projectId); await projectDAL.checkProjectUpgradeStatus(projectId);
const folder = await folderDAL.findBySecretPath(projectId, environment, secretPath); const folder = await folderDAL.findBySecretPath(projectId, environment, secretPath);
if (!folder) throw new BadRequestError({ message: "Folder not found", name: "GenSecretApproval" }); if (!folder)
throw new BadRequestError({
message: "Folder not found for the given environment slug & secret path",
name: "GenSecretApproval"
});
const folderId = folder.id; const folderId = folder.id;
const blindIndexCfg = await secretBlindIndexDAL.findOne({ projectId }); const blindIndexCfg = await secretBlindIndexDAL.findOne({ projectId });
+4
View File
@@ -282,6 +282,7 @@ export const RAW_SECRETS = {
}, },
CREATE: { CREATE: {
secretName: "The name of the secret to create.", secretName: "The name of the secret to create.",
projectSlug: "The slug of the project to create the secret in.",
environment: "The slug of the environment to create the secret in.", environment: "The slug of the environment to create the secret in.",
secretComment: "Attach a comment to the secret.", secretComment: "Attach a comment to the secret.",
secretPath: "The path to create the secret in.", secretPath: "The path to create the secret in.",
@@ -301,11 +302,13 @@ export const RAW_SECRETS = {
}, },
UPDATE: { UPDATE: {
secretName: "The name of the secret to update.", secretName: "The name of the secret to update.",
secretComment: "Update comment to the secret.",
environment: "The slug of the environment where the secret is located.", environment: "The slug of the environment where the secret is located.",
secretPath: "The path of the secret to update", secretPath: "The path of the secret to update",
secretValue: "The new value of the secret.", secretValue: "The new value of the secret.",
skipMultilineEncoding: "Skip multiline encoding for the secret value.", skipMultilineEncoding: "Skip multiline encoding for the secret value.",
type: "The type of the secret to update.", type: "The type of the secret to update.",
projectSlug: "The slug of the project to update the secret in.",
workspaceId: "The ID of the project to update the secret in." workspaceId: "The ID of the project to update the secret in."
}, },
DELETE: { DELETE: {
@@ -313,6 +316,7 @@ export const RAW_SECRETS = {
environment: "The slug of the environment where the secret is located.", environment: "The slug of the environment where the secret is located.",
secretPath: "The path of the secret.", secretPath: "The path of the secret.",
type: "The type of the secret to delete.", type: "The type of the secret to delete.",
projectSlug: "The slug of the project to delete the secret in.",
workspaceId: "The ID of the project where the secret is located." workspaceId: "The ID of the project where the secret is located."
} }
} as const; } as const;
@@ -1656,4 +1656,263 @@ export const registerSecretRouter = async (server: FastifyZodProvider) => {
return { secrets }; return { secrets };
} }
}); });
server.route({
method: "POST",
url: "/batch/raw",
config: {
rateLimit: secretsLimit
},
schema: {
description: "Create many secrets",
security: [
{
bearerAuth: []
}
],
body: z.object({
projectSlug: z.string().trim().describe(RAW_SECRETS.CREATE.projectSlug),
environment: z.string().trim().describe(RAW_SECRETS.CREATE.environment),
secretPath: z
.string()
.trim()
.default("/")
.transform(removeTrailingSlash)
.describe(RAW_SECRETS.CREATE.secretPath),
secrets: z
.object({
secretKey: z.string().trim().describe(RAW_SECRETS.CREATE.secretName),
secretValue: z
.string()
.transform((val) => (val.at(-1) === "\n" ? `${val.trim()}\n` : val.trim()))
.describe(RAW_SECRETS.CREATE.secretValue),
secretComment: z.string().trim().optional().default("").describe(RAW_SECRETS.CREATE.secretComment),
skipMultilineEncoding: z.boolean().optional().describe(RAW_SECRETS.CREATE.skipMultilineEncoding)
})
.array()
.min(1)
}),
response: {
200: z.object({
secrets: secretRawSchema.array()
})
}
},
onRequest: verifyAuth([AuthMode.JWT, AuthMode.API_KEY, AuthMode.SERVICE_TOKEN, AuthMode.IDENTITY_ACCESS_TOKEN]),
handler: async (req) => {
const { environment, projectSlug, secretPath, secrets: inputSecrets } = req.body;
const secrets = await server.services.secret.createManySecretsRaw({
actorId: req.permission.id,
actor: req.permission.type,
actorAuthMethod: req.permission.authMethod,
actorOrgId: req.permission.orgId,
secretPath,
environment,
projectSlug,
secrets: inputSecrets
});
await server.services.auditLog.createAuditLog({
projectId: secrets[0].workspace,
...req.auditLogInfo,
event: {
type: EventType.CREATE_SECRETS,
metadata: {
environment: req.body.environment,
secretPath: req.body.secretPath,
secrets: secrets.map((secret, i) => ({
secretId: secret.id,
secretKey: inputSecrets[i].secretKey,
secretVersion: secret.version
}))
}
}
});
await server.services.telemetry.sendPostHogEvents({
event: PostHogEventTypes.SecretCreated,
distinctId: getTelemetryDistinctId(req),
properties: {
numberOfSecrets: secrets.length,
workspaceId: secrets[0].workspace,
environment: req.body.environment,
secretPath: req.body.secretPath,
channel: getUserAgentType(req.headers["user-agent"]),
...req.auditLogInfo
}
});
return { secrets };
}
});
server.route({
method: "PATCH",
url: "/batch/raw",
config: {
rateLimit: secretsLimit
},
schema: {
description: "Update many secrets",
security: [
{
bearerAuth: []
}
],
body: z.object({
projectSlug: z.string().trim().describe(RAW_SECRETS.UPDATE.projectSlug),
environment: z.string().trim().describe(RAW_SECRETS.UPDATE.environment),
secretPath: z
.string()
.trim()
.default("/")
.transform(removeTrailingSlash)
.describe(RAW_SECRETS.UPDATE.secretPath),
secrets: z
.object({
secretKey: z.string().trim().describe(RAW_SECRETS.UPDATE.secretName),
secretValue: z
.string()
.transform((val) => (val.at(-1) === "\n" ? `${val.trim()}\n` : val.trim()))
.describe(RAW_SECRETS.UPDATE.secretValue),
secretComment: z.string().trim().optional().describe(RAW_SECRETS.UPDATE.secretComment),
skipMultilineEncoding: z.boolean().optional().describe(RAW_SECRETS.UPDATE.skipMultilineEncoding)
})
.array()
.min(1)
}),
response: {
200: z.object({
secrets: secretRawSchema.array()
})
}
},
onRequest: verifyAuth([AuthMode.JWT, AuthMode.API_KEY, AuthMode.SERVICE_TOKEN, AuthMode.IDENTITY_ACCESS_TOKEN]),
handler: async (req) => {
const { environment, projectSlug, secretPath, secrets: inputSecrets } = req.body;
const secrets = await server.services.secret.updateManySecretsRaw({
actorId: req.permission.id,
actor: req.permission.type,
actorAuthMethod: req.permission.authMethod,
actorOrgId: req.permission.orgId,
secretPath,
environment,
projectSlug,
secrets: inputSecrets
});
await server.services.auditLog.createAuditLog({
projectId: secrets[0].workspace,
...req.auditLogInfo,
event: {
type: EventType.UPDATE_SECRETS,
metadata: {
environment: req.body.environment,
secretPath: req.body.secretPath,
secrets: secrets.map((secret, i) => ({
secretId: secret.id,
secretKey: inputSecrets[i].secretKey,
secretVersion: secret.version
}))
}
}
});
await server.services.telemetry.sendPostHogEvents({
event: PostHogEventTypes.SecretUpdated,
distinctId: getTelemetryDistinctId(req),
properties: {
numberOfSecrets: secrets.length,
workspaceId: secrets[0].workspace,
environment: req.body.environment,
secretPath: req.body.secretPath,
channel: getUserAgentType(req.headers["user-agent"]),
...req.auditLogInfo
}
});
return { secrets };
}
});
server.route({
method: "DELETE",
url: "/batch/raw",
config: {
rateLimit: secretsLimit
},
schema: {
description: "Delete many secrets",
security: [
{
bearerAuth: []
}
],
body: z.object({
projectSlug: z.string().trim().describe(RAW_SECRETS.DELETE.projectSlug),
environment: z.string().trim().describe(RAW_SECRETS.DELETE.environment),
secretPath: z
.string()
.trim()
.default("/")
.transform(removeTrailingSlash)
.describe(RAW_SECRETS.DELETE.secretPath),
secrets: z
.object({
secretKey: z.string().trim().describe(RAW_SECRETS.DELETE.secretName)
})
.array()
.min(1)
}),
response: {
200: z.object({
secrets: secretRawSchema.array()
})
}
},
onRequest: verifyAuth([AuthMode.JWT, AuthMode.API_KEY, AuthMode.SERVICE_TOKEN, AuthMode.IDENTITY_ACCESS_TOKEN]),
handler: async (req) => {
const { environment, projectSlug, secretPath, secrets: inputSecrets } = req.body;
const secrets = await server.services.secret.deleteManySecretsRaw({
actorId: req.permission.id,
actor: req.permission.type,
actorAuthMethod: req.permission.authMethod,
actorOrgId: req.permission.orgId,
environment,
projectSlug,
secretPath,
secrets: inputSecrets
});
await server.services.auditLog.createAuditLog({
projectId: secrets[0].workspace,
...req.auditLogInfo,
event: {
type: EventType.DELETE_SECRETS,
metadata: {
environment: req.body.environment,
secretPath: req.body.secretPath,
secrets: secrets.map((secret, i) => ({
secretId: secret.id,
secretKey: inputSecrets[i].secretKey,
secretVersion: secret.version
}))
}
}
});
await server.services.telemetry.sendPostHogEvents({
event: PostHogEventTypes.SecretDeleted,
distinctId: getTelemetryDistinctId(req),
properties: {
numberOfSecrets: secrets.length,
workspaceId: secrets[0].workspace,
environment: req.body.environment,
secretPath: req.body.secretPath,
channel: getUserAgentType(req.headers["user-agent"]),
...req.auditLogInfo
}
});
return { secrets };
}
});
}; };
+10 -2
View File
@@ -575,7 +575,11 @@ export const createManySecretsRawFnFactory = ({
await projectDAL.checkProjectUpgradeStatus(projectId); await projectDAL.checkProjectUpgradeStatus(projectId);
const folder = await folderDAL.findBySecretPath(projectId, environment, secretPath); const folder = await folderDAL.findBySecretPath(projectId, environment, secretPath);
if (!folder) throw new BadRequestError({ message: "Folder not found", name: "Create secret" }); if (!folder)
throw new BadRequestError({
message: "Folder not found for the given environment slug & secret path",
name: "Create secret"
});
const folderId = folder.id; const folderId = folder.id;
const blindIndexCfg = await secretBlindIndexDAL.findOne({ projectId }); const blindIndexCfg = await secretBlindIndexDAL.findOne({ projectId });
@@ -680,7 +684,11 @@ export const updateManySecretsRawFnFactory = ({
await projectDAL.checkProjectUpgradeStatus(projectId); await projectDAL.checkProjectUpgradeStatus(projectId);
const folder = await folderDAL.findBySecretPath(projectId, environment, secretPath); const folder = await folderDAL.findBySecretPath(projectId, environment, secretPath);
if (!folder) throw new BadRequestError({ message: "Folder not found", name: "Update secret" }); if (!folder)
throw new BadRequestError({
message: "Folder not found for the given environment slug & secret path",
name: "Update secret"
});
const folderId = folder.id; const folderId = folder.id;
const blindIndexCfg = await secretBlindIndexDAL.findOne({ projectId }); const blindIndexCfg = await secretBlindIndexDAL.findOne({ projectId });
+178 -7
View File
@@ -33,9 +33,11 @@ import { TSecretQueueFactory } from "./secret-queue";
import { import {
TAttachSecretTagsDTO, TAttachSecretTagsDTO,
TCreateBulkSecretDTO, TCreateBulkSecretDTO,
TCreateManySecretRawDTO,
TCreateSecretDTO, TCreateSecretDTO,
TCreateSecretRawDTO, TCreateSecretRawDTO,
TDeleteBulkSecretDTO, TDeleteBulkSecretDTO,
TDeleteManySecretRawDTO,
TDeleteSecretDTO, TDeleteSecretDTO,
TDeleteSecretRawDTO, TDeleteSecretRawDTO,
TFnSecretBlindIndexCheckV2, TFnSecretBlindIndexCheckV2,
@@ -46,6 +48,7 @@ import {
TGetSecretsRawDTO, TGetSecretsRawDTO,
TGetSecretVersionsDTO, TGetSecretVersionsDTO,
TUpdateBulkSecretDTO, TUpdateBulkSecretDTO,
TUpdateManySecretRawDTO,
TUpdateSecretDTO, TUpdateSecretDTO,
TUpdateSecretRawDTO TUpdateSecretRawDTO
} from "./secret-types"; } from "./secret-types";
@@ -179,7 +182,11 @@ export const secretServiceFactory = ({
await projectDAL.checkProjectUpgradeStatus(projectId); await projectDAL.checkProjectUpgradeStatus(projectId);
const folder = await folderDAL.findBySecretPath(projectId, environment, path); const folder = await folderDAL.findBySecretPath(projectId, environment, path);
if (!folder) throw new BadRequestError({ message: "Folder not found", name: "Create secret" }); if (!folder)
throw new BadRequestError({
message: "Folder not found for the given environment slug & secret path",
name: "Create secret"
});
const folderId = folder.id; const folderId = folder.id;
const blindIndexCfg = await secretBlindIndexDAL.findOne({ projectId }); const blindIndexCfg = await secretBlindIndexDAL.findOne({ projectId });
@@ -275,7 +282,11 @@ export const secretServiceFactory = ({
} }
const folder = await folderDAL.findBySecretPath(projectId, environment, path); const folder = await folderDAL.findBySecretPath(projectId, environment, path);
if (!folder) throw new BadRequestError({ message: "Folder not found", name: "Create secret" }); if (!folder)
throw new BadRequestError({
message: "Folder not found for the given environment slug & secret path",
name: "Create secret"
});
const folderId = folder.id; const folderId = folder.id;
const blindIndexCfg = await secretBlindIndexDAL.findOne({ projectId }); const blindIndexCfg = await secretBlindIndexDAL.findOne({ projectId });
@@ -391,7 +402,11 @@ export const secretServiceFactory = ({
await projectDAL.checkProjectUpgradeStatus(projectId); await projectDAL.checkProjectUpgradeStatus(projectId);
const folder = await folderDAL.findBySecretPath(projectId, environment, path); const folder = await folderDAL.findBySecretPath(projectId, environment, path);
if (!folder) throw new BadRequestError({ message: "Folder not found", name: "Create secret" }); if (!folder)
throw new BadRequestError({
message: "Folder not found for the given environment slug & secret path",
name: "Create secret"
});
const folderId = folder.id; const folderId = folder.id;
const blindIndexCfg = await secretBlindIndexDAL.findOne({ projectId }); const blindIndexCfg = await secretBlindIndexDAL.findOne({ projectId });
@@ -559,7 +574,11 @@ export const secretServiceFactory = ({
subject(ProjectPermissionSub.Secrets, { environment, secretPath: path }) subject(ProjectPermissionSub.Secrets, { environment, secretPath: path })
); );
const folder = await folderDAL.findBySecretPath(projectId, environment, path); const folder = await folderDAL.findBySecretPath(projectId, environment, path);
if (!folder) throw new BadRequestError({ message: "Folder not found", name: "Create secret" }); if (!folder)
throw new BadRequestError({
message: "Folder not found for the given environment slug & secret path",
name: "Create secret"
});
const folderId = folder.id; const folderId = folder.id;
const secretBlindIndex = await interalGenSecBlindIndexByName(projectId, secretName); const secretBlindIndex = await interalGenSecBlindIndexByName(projectId, secretName);
@@ -655,7 +674,11 @@ export const secretServiceFactory = ({
await projectDAL.checkProjectUpgradeStatus(projectId); await projectDAL.checkProjectUpgradeStatus(projectId);
const folder = await folderDAL.findBySecretPath(projectId, environment, path); const folder = await folderDAL.findBySecretPath(projectId, environment, path);
if (!folder) throw new BadRequestError({ message: "Folder not found", name: "Create secret" }); if (!folder)
throw new BadRequestError({
message: "Folder not found for the given environment slug & secret path",
name: "Create secret"
});
const folderId = folder.id; const folderId = folder.id;
const blindIndexCfg = await secretBlindIndexDAL.findOne({ projectId }); const blindIndexCfg = await secretBlindIndexDAL.findOne({ projectId });
@@ -724,7 +747,11 @@ export const secretServiceFactory = ({
await projectDAL.checkProjectUpgradeStatus(projectId); await projectDAL.checkProjectUpgradeStatus(projectId);
const folder = await folderDAL.findBySecretPath(projectId, environment, path); const folder = await folderDAL.findBySecretPath(projectId, environment, path);
if (!folder) throw new BadRequestError({ message: "Folder not found", name: "Update secret" }); if (!folder)
throw new BadRequestError({
message: "Folder not found for the given environment slug & secret path",
name: "Update secret"
});
const folderId = folder.id; const folderId = folder.id;
const blindIndexCfg = await secretBlindIndexDAL.findOne({ projectId }); const blindIndexCfg = await secretBlindIndexDAL.findOne({ projectId });
@@ -810,7 +837,11 @@ export const secretServiceFactory = ({
await projectDAL.checkProjectUpgradeStatus(projectId); await projectDAL.checkProjectUpgradeStatus(projectId);
const folder = await folderDAL.findBySecretPath(projectId, environment, path); const folder = await folderDAL.findBySecretPath(projectId, environment, path);
if (!folder) throw new BadRequestError({ message: "Folder not found", name: "Create secret" }); if (!folder)
throw new BadRequestError({
message: "Folder not found for the given environment slug & secret path",
name: "Create secret"
});
const folderId = folder.id; const folderId = folder.id;
const blindIndexCfg = await secretBlindIndexDAL.findOne({ projectId }); const blindIndexCfg = await secretBlindIndexDAL.findOne({ projectId });
@@ -1036,6 +1067,143 @@ export const secretServiceFactory = ({
return decryptSecretRaw(secret, botKey); return decryptSecretRaw(secret, botKey);
}; };
const createManySecretsRaw = async ({
actorId,
projectSlug,
environment,
actor,
actorOrgId,
actorAuthMethod,
secretPath,
secrets: inputSecrets = []
}: TCreateManySecretRawDTO) => {
const project = await projectDAL.findProjectBySlug(projectSlug, actorOrgId);
if (!project) throw new BadRequestError({ message: "Project not found" });
const projectId = project.id;
const botKey = await projectBotService.getBotKey(projectId);
if (!botKey) throw new BadRequestError({ message: "Project bot not found", name: "bot_not_found_error" });
const secrets = await createManySecret({
projectId,
environment,
path: secretPath,
actor,
actorId,
actorOrgId,
actorAuthMethod,
secrets: inputSecrets.map(({ secretComment, secretKey, secretValue, skipMultilineEncoding }) => {
const secretKeyEncrypted = encryptSymmetric128BitHexKeyUTF8(secretKey, botKey);
const secretValueEncrypted = encryptSymmetric128BitHexKeyUTF8(secretValue || "", botKey);
const secretCommentEncrypted = encryptSymmetric128BitHexKeyUTF8(secretComment || "", botKey);
return {
secretName: secretKey,
skipMultilineEncoding,
secretKeyCiphertext: secretKeyEncrypted.ciphertext,
secretKeyIV: secretKeyEncrypted.iv,
secretKeyTag: secretKeyEncrypted.tag,
secretValueCiphertext: secretValueEncrypted.ciphertext,
secretValueIV: secretValueEncrypted.iv,
secretValueTag: secretValueEncrypted.tag,
secretCommentCiphertext: secretCommentEncrypted.ciphertext,
secretCommentIV: secretCommentEncrypted.iv,
secretCommentTag: secretCommentEncrypted.tag
};
})
});
await snapshotService.performSnapshot(secrets[0].folderId);
await secretQueueService.syncSecrets({ secretPath, projectId, environment });
return secrets.map((secret) => decryptSecretRaw({ ...secret, workspace: projectId, environment }, botKey));
};
const updateManySecretsRaw = async ({
actorId,
projectSlug,
environment,
actor,
actorOrgId,
actorAuthMethod,
secretPath,
secrets: inputSecrets = []
}: TUpdateManySecretRawDTO) => {
const project = await projectDAL.findProjectBySlug(projectSlug, actorOrgId);
if (!project) throw new BadRequestError({ message: "Project not found" });
const projectId = project.id;
const botKey = await projectBotService.getBotKey(projectId);
if (!botKey) throw new BadRequestError({ message: "Project bot not found", name: "bot_not_found_error" });
const secrets = await updateManySecret({
projectId,
environment,
path: secretPath,
actor,
actorId,
actorOrgId,
actorAuthMethod,
secrets: inputSecrets.map(({ secretComment, secretKey, secretValue, skipMultilineEncoding }) => {
const secretKeyEncrypted = encryptSymmetric128BitHexKeyUTF8(secretKey, botKey);
const secretValueEncrypted = encryptSymmetric128BitHexKeyUTF8(secretValue || "", botKey);
const secretCommentEncrypted = encryptSymmetric128BitHexKeyUTF8(secretComment || "", botKey);
return {
secretName: secretKey,
type: SecretType.Shared,
skipMultilineEncoding,
secretKeyCiphertext: secretKeyEncrypted.ciphertext,
secretKeyIV: secretKeyEncrypted.iv,
secretKeyTag: secretKeyEncrypted.tag,
secretValueCiphertext: secretValueEncrypted.ciphertext,
secretValueIV: secretValueEncrypted.iv,
secretValueTag: secretValueEncrypted.tag,
secretCommentCiphertext: secretCommentEncrypted.ciphertext,
secretCommentIV: secretCommentEncrypted.iv,
secretCommentTag: secretCommentEncrypted.tag
};
})
});
await snapshotService.performSnapshot(secrets[0].folderId);
await secretQueueService.syncSecrets({ secretPath, projectId, environment });
return secrets.map((secret) => decryptSecretRaw({ ...secret, workspace: projectId, environment }, botKey));
};
const deleteManySecretsRaw = async ({
actorId,
projectSlug,
environment,
actor,
actorOrgId,
actorAuthMethod,
secretPath,
secrets: inputSecrets = []
}: TDeleteManySecretRawDTO) => {
const project = await projectDAL.findProjectBySlug(projectSlug, actorOrgId);
if (!project) throw new BadRequestError({ message: "Project not found" });
const projectId = project.id;
const botKey = await projectBotService.getBotKey(projectId);
if (!botKey) throw new BadRequestError({ message: "Project bot not found", name: "bot_not_found_error" });
const secrets = await deleteManySecret({
projectId,
environment,
path: secretPath,
actor,
actorId,
actorOrgId,
actorAuthMethod,
secrets: inputSecrets.map(({ secretKey }) => ({ secretName: secretKey, type: SecretType.Shared }))
});
await snapshotService.performSnapshot(secrets[0].folderId);
await secretQueueService.syncSecrets({ secretPath, projectId, environment });
return secrets.map((secret) => decryptSecretRaw({ ...secret, workspace: projectId, environment }, botKey));
};
const getSecretVersions = async ({ const getSecretVersions = async ({
actorId, actorId,
actor, actor,
@@ -1280,6 +1448,9 @@ export const secretServiceFactory = ({
createSecretRaw, createSecretRaw,
updateSecretRaw, updateSecretRaw,
deleteSecretRaw, deleteSecretRaw,
createManySecretsRaw,
updateManySecretsRaw,
deleteManySecretsRaw,
getSecretVersions, getSecretVersions,
// external services function // external services function
fnSecretBulkDelete, fnSecretBulkDelete,
@@ -181,6 +181,39 @@ export type TDeleteSecretRawDTO = TProjectPermission & {
type: SecretType; type: SecretType;
}; };
export type TCreateManySecretRawDTO = Omit<TProjectPermission, "projectId"> & {
secretPath: string;
projectSlug: string;
environment: string;
secrets: {
secretKey: string;
secretValue: string;
secretComment?: string;
skipMultilineEncoding?: boolean;
}[];
};
export type TUpdateManySecretRawDTO = Omit<TProjectPermission, "projectId"> & {
secretPath: string;
projectSlug: string;
environment: string;
secrets: {
secretKey: string;
secretValue: string;
secretComment?: string;
skipMultilineEncoding?: boolean;
}[];
};
export type TDeleteManySecretRawDTO = Omit<TProjectPermission, "projectId"> & {
secretPath: string;
projectSlug: string;
environment: string;
secrets: {
secretKey: string;
}[];
};
export type TGetSecretVersionsDTO = Omit<TProjectPermission, "projectId"> & { export type TGetSecretVersionsDTO = Omit<TProjectPermission, "projectId"> & {
limit?: number; limit?: number;
offset?: number; offset?: number;
-4
View File
@@ -22,10 +22,6 @@ var folderCmd = &cobra.Command{
var getCmd = &cobra.Command{ var getCmd = &cobra.Command{
Use: "get", Use: "get",
Short: "Get folders in a directory", Short: "Get folders in a directory",
PersistentPreRun: func(cmd *cobra.Command, args []string) {
util.RequireLocalWorkspaceFile()
util.RequireLogin()
},
Run: func(cmd *cobra.Command, args []string) { Run: func(cmd *cobra.Command, args []string) {
environmentName, _ := cmd.Flags().GetString("env") environmentName, _ := cmd.Flags().GetString("env")
+4 -1
View File
@@ -7,6 +7,7 @@ import (
"crypto/sha256" "crypto/sha256"
"encoding/base64" "encoding/base64"
"fmt" "fmt"
"os"
"regexp" "regexp"
"sort" "sort"
"strings" "strings"
@@ -204,8 +205,10 @@ var secretsSetCmd = &cobra.Command{
// decrypt workspace key // decrypt workspace key
plainTextEncryptionKey := crypto.DecryptAsymmetric(encryptedWorkspaceKey, encryptedWorkspaceKeyNonce, encryptedWorkspaceKeySenderPublicKey, currentUsersPrivateKey) plainTextEncryptionKey := crypto.DecryptAsymmetric(encryptedWorkspaceKey, encryptedWorkspaceKeyNonce, encryptedWorkspaceKeySenderPublicKey, currentUsersPrivateKey)
infisicalTokenEnv := os.Getenv(util.INFISICAL_TOKEN_NAME)
// pull current secrets // pull current secrets
secrets, err := util.GetAllEnvironmentVariables(models.GetAllSecretsParameters{Environment: environmentName, SecretsPath: secretsPath}, "") secrets, err := util.GetAllEnvironmentVariables(models.GetAllSecretsParameters{Environment: environmentName, SecretsPath: secretsPath, InfisicalToken: infisicalTokenEnv}, "")
if err != nil { if err != nil {
util.HandleError(err, "unable to retrieve secrets") util.HandleError(err, "unable to retrieve secrets")
} }
+2 -5
View File
@@ -2,7 +2,6 @@ package util
import ( import (
"fmt" "fmt"
"os"
"strings" "strings"
"github.com/Infisical/infisical-merge/packages/api" "github.com/Infisical/infisical-merge/packages/api"
@@ -13,13 +12,11 @@ import (
func GetAllFolders(params models.GetAllFoldersParameters) ([]models.SingleFolder, error) { func GetAllFolders(params models.GetAllFoldersParameters) ([]models.SingleFolder, error) {
if params.InfisicalToken == "" {
params.InfisicalToken = os.Getenv(INFISICAL_TOKEN_NAME)
}
var foldersToReturn []models.SingleFolder var foldersToReturn []models.SingleFolder
var folderErr error var folderErr error
if params.InfisicalToken == "" && params.UniversalAuthAccessToken == "" { if params.InfisicalToken == "" && params.UniversalAuthAccessToken == "" {
RequireLogin()
RequireLocalWorkspaceFile()
log.Debug().Msg("GetAllFolders: Trying to fetch folders using logged in details") log.Debug().Msg("GetAllFolders: Trying to fetch folders using logged in details")
-4
View File
@@ -307,10 +307,6 @@ func FilterSecretsByTag(plainTextSecrets []models.SingleEnvironmentVariable, tag
} }
func GetAllEnvironmentVariables(params models.GetAllSecretsParameters, projectConfigFilePath string) ([]models.SingleEnvironmentVariable, error) { func GetAllEnvironmentVariables(params models.GetAllSecretsParameters, projectConfigFilePath string) ([]models.SingleEnvironmentVariable, error) {
if params.InfisicalToken == "" {
params.InfisicalToken = os.Getenv(INFISICAL_TOKEN_NAME)
}
isConnected := CheckIsConnectedToInternet() isConnected := CheckIsConnectedToInternet()
var secretsToReturn []models.SingleEnvironmentVariable var secretsToReturn []models.SingleEnvironmentVariable
// var serviceTokenDetails api.GetServiceTokenDetailsResponse // var serviceTokenDetails api.GetServiceTokenDetailsResponse
@@ -0,0 +1,8 @@
---
title: "Bulk Create"
openapi: "POST /api/v3/secrets/batch/raw"
---
<Tip>
This endpoint requires you to disable end-to-end encryption. For more information, you should consult this [note](https://infisical.com/docs/api-reference/overview/examples/note).
</Tip>
@@ -0,0 +1,8 @@
---
title: "Bulk Delete"
openapi: "DELETE /api/v3/secrets/batch/raw"
---
<Tip>
This endpoint requires you to disable end-to-end encryption. For more information, you should consult this [note](https://infisical.com/docs/api-reference/overview/examples/note).
</Tip>
@@ -0,0 +1,8 @@
---
title: "Bulk Update"
openapi: "PATCH /api/v3/secrets/batch/raw"
---
<Tip>
This endpoint requires you to disable end-to-end encryption. For more information, you should consult this [note](https://infisical.com/docs/api-reference/overview/examples/note).
</Tip>
+1 -1
View File
@@ -33,7 +33,7 @@ This approach enables you to fetch secrets from Infisical during Amplify build t
preBuild: preBuild:
commands: commands:
- sudo curl -1sLf 'https://dl.cloudsmith.io/public/infisical/infisical-cli/setup.rpm.sh' | sudo -E bash - sudo curl -1sLf 'https://dl.cloudsmith.io/public/infisical/infisical-cli/setup.rpm.sh' | sudo -E bash
- sudo yum install infisical - sudo yum -y install infisical
``` ```
</Step> </Step>
<Step title="Modify the build command"> <Step title="Modify the build command">
@@ -22,7 +22,7 @@ description: "Learn how to use Helm chart to install Infisical on your Kubernete
</Step> </Step>
<Step title="Select Infisical version"> <Step title="Select Infisical version">
By default, the Infisical version set in your helm chart will likely be outdated. By default, the Infisical version set in your helm chart will likely be outdated.
Choose the latest Infisical docker image tag from here [here](https://hub.docker.com/r/infisical/infisical/tags). Choose the latest Infisical docker image tag from [here](https://hub.docker.com/r/infisical/infisical/tags).
```yaml values.yaml ```yaml values.yaml