mirror of
https://github.com/Infisical/infisical.git
synced 2025-03-25 14:05:03 +00:00
Compare commits
13 Commits
update-fol
...
maidul-122
Author | SHA1 | Date | |
---|---|---|---|
8f3883c7d4 | |||
38cfb7fd41 | |||
246b8728a4 | |||
00415e1a87 | |||
b067751027 | |||
f2b3b7b726 | |||
1ba7a31e0d | |||
233a4f7d77 | |||
44ff1abd74 | |||
08cb105fe4 | |||
aa5cd0fd0f | |||
a339c473d5 | |||
718cabe49b |
.github/workflows
backend
e2e-test/routes/v3
src
db/migrations
lib/api-docs
server/routes/v3
services/secret
docs
api-reference/endpoints/secrets
integrations/cloud
frontend/src/pages/org/[id]/overview
38
.github/workflows/build-patroni-docker-img.yml
vendored
Normal file
38
.github/workflows/build-patroni-docker-img.yml
vendored
Normal file
@ -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
|
||||
|
||||
|
@ -942,6 +942,113 @@ describe.each([{ auth: AuthMode.JWT }, { auth: AuthMode.IDENTITY_ACCESS_TOKEN }]
|
||||
const secrets = await getSecrets(seedData1.environment.slug, path);
|
||||
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) => {
|
||||
const duplicateRows = await tx(TableName.OrgMembership)
|
||||
.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)
|
||||
.groupBy("userId", "orgId")
|
||||
.havingRaw("count(*) > ?", [1]); // Using havingRaw for direct SQL expressions
|
||||
|
@ -282,6 +282,7 @@ export const RAW_SECRETS = {
|
||||
},
|
||||
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.",
|
||||
secretComment: "Attach a comment to the secret.",
|
||||
secretPath: "The path to create the secret in.",
|
||||
@ -301,11 +302,13 @@ export const RAW_SECRETS = {
|
||||
},
|
||||
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.",
|
||||
secretPath: "The path of the secret to update",
|
||||
secretValue: "The new value of the secret.",
|
||||
skipMultilineEncoding: "Skip multiline encoding for the secret value.",
|
||||
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."
|
||||
},
|
||||
DELETE: {
|
||||
@ -313,6 +316,7 @@ export const RAW_SECRETS = {
|
||||
environment: "The slug of the environment where the secret is located.",
|
||||
secretPath: "The path of the secret.",
|
||||
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."
|
||||
}
|
||||
} as const;
|
||||
|
@ -1656,4 +1656,263 @@ export const registerSecretRouter = async (server: FastifyZodProvider) => {
|
||||
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 };
|
||||
}
|
||||
});
|
||||
};
|
||||
|
@ -33,9 +33,11 @@ import { TSecretQueueFactory } from "./secret-queue";
|
||||
import {
|
||||
TAttachSecretTagsDTO,
|
||||
TCreateBulkSecretDTO,
|
||||
TCreateManySecretRawDTO,
|
||||
TCreateSecretDTO,
|
||||
TCreateSecretRawDTO,
|
||||
TDeleteBulkSecretDTO,
|
||||
TDeleteManySecretRawDTO,
|
||||
TDeleteSecretDTO,
|
||||
TDeleteSecretRawDTO,
|
||||
TFnSecretBlindIndexCheckV2,
|
||||
@ -46,6 +48,7 @@ import {
|
||||
TGetSecretsRawDTO,
|
||||
TGetSecretVersionsDTO,
|
||||
TUpdateBulkSecretDTO,
|
||||
TUpdateManySecretRawDTO,
|
||||
TUpdateSecretDTO,
|
||||
TUpdateSecretRawDTO
|
||||
} from "./secret-types";
|
||||
@ -1064,6 +1067,143 @@ export const secretServiceFactory = ({
|
||||
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 ({
|
||||
actorId,
|
||||
actor,
|
||||
@ -1308,6 +1448,9 @@ export const secretServiceFactory = ({
|
||||
createSecretRaw,
|
||||
updateSecretRaw,
|
||||
deleteSecretRaw,
|
||||
createManySecretsRaw,
|
||||
updateManySecretsRaw,
|
||||
deleteManySecretsRaw,
|
||||
getSecretVersions,
|
||||
// external services function
|
||||
fnSecretBulkDelete,
|
||||
|
@ -181,6 +181,39 @@ export type TDeleteSecretRawDTO = TProjectPermission & {
|
||||
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"> & {
|
||||
limit?: number;
|
||||
offset?: number;
|
||||
|
8
docs/api-reference/endpoints/secrets/create-many.mdx
Normal file
8
docs/api-reference/endpoints/secrets/create-many.mdx
Normal file
@ -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>
|
8
docs/api-reference/endpoints/secrets/delete-many.mdx
Normal file
8
docs/api-reference/endpoints/secrets/delete-many.mdx
Normal file
@ -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>
|
8
docs/api-reference/endpoints/secrets/update-many.mdx
Normal file
8
docs/api-reference/endpoints/secrets/update-many.mdx
Normal file
@ -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>
|
@ -33,7 +33,7 @@ This approach enables you to fetch secrets from Infisical during Amplify build t
|
||||
preBuild:
|
||||
commands:
|
||||
- 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 title="Modify the build command">
|
||||
|
@ -594,7 +594,7 @@ const OrganizationPage = withPermission(
|
||||
</div>
|
||||
)}
|
||||
<div className="mb-4 flex flex-col items-start justify-start px-6 py-6 pb-0 text-3xl">
|
||||
{window.location.origin.includes("https://app.infisical.com") || window.location.origin.includes("http://localhost:8080") && (
|
||||
{(window.location.origin.includes("https://app.infisical.com") || window.location.origin.includes("http://localhost:8080")) && (
|
||||
<div
|
||||
className={`${
|
||||
!updateClosed ? "block" : "hidden"
|
||||
@ -602,10 +602,10 @@ const OrganizationPage = withPermission(
|
||||
>
|
||||
<FontAwesomeIcon icon={faWarning} className="p-6 text-4xl text-primary" />
|
||||
<div className="text-sm">
|
||||
<span className="text-lg font-semibold">Scheduled maintenance on April 13th 2024 </span>{" "}
|
||||
<span className="text-lg font-semibold">Scheduled maintenance on May 11th 2024 </span>{" "}
|
||||
<br />
|
||||
Infisical will undergo scheduled maintenance for approximately 1 hour on Saturday, April 13th, 11am EST. During these hours, read
|
||||
operations will continue to function normally but no resources will be editable.
|
||||
Infisical will undergo scheduled maintenance for approximately 2 hour on Saturday, May 11th, 11am EST. During these hours, read
|
||||
operations to Infisical will continue to function normally but no resources will be editable.
|
||||
No action is required on your end — your applications will continue to fetch secrets.
|
||||
<br />
|
||||
</div>
|
||||
|
Reference in New Issue
Block a user