Skip to content

Commit

Permalink
Merge pull request #3060 from Infisical/daniel/query-secrets-by-metadata
Browse files Browse the repository at this point in the history
feat(api): list secrets filter by metadata
  • Loading branch information
DanielHougaard authored Feb 7, 2025
2 parents c9d7559 + 70c9761 commit 9d66659
Show file tree
Hide file tree
Showing 6 changed files with 95 additions and 1 deletion.
4 changes: 3 additions & 1 deletion backend/src/lib/api-docs/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -688,7 +688,9 @@ export const RAW_SECRETS = {
environment: "The slug of the environment to list secrets from.",
secretPath: "The secret path to list secrets from.",
includeImports: "Weather to include imported secrets or not.",
tagSlugs: "The comma separated tag slugs to filter secrets."
tagSlugs: "The comma separated tag slugs to filter secrets.",
metadataFilter:
"The secret metadata key-value pairs to filter secrets by. When querying for multiple metadata pairs, the query is treated as an AND operation. Secret metadata format is key=value1,value=value2|key=value3,value=value4."
},
CREATE: {
secretName: "The name of the secret to create.",
Expand Down
61 changes: 61 additions & 0 deletions backend/src/server/routes/v3/secret-router.ts
Original file line number Diff line number Diff line change
Expand Up @@ -181,6 +181,66 @@ export const registerSecretRouter = async (server: FastifyZodProvider) => {
}
],
querystring: z.object({
metadataFilter: z
.string()
.optional()
.transform((val) => {
if (!val) return undefined;

const result: { key?: string; value?: string }[] = [];
const pairs = val.split("|");

for (const pair of pairs) {
const keyValuePair: { key?: string; value?: string } = {};
const parts = pair.split(/[,=]/);

for (let i = 0; i < parts.length; i += 2) {
const identifier = parts[i].trim().toLowerCase();
const value = parts[i + 1]?.trim();

if (identifier === "key" && value) {
keyValuePair.key = value;
} else if (identifier === "value" && value) {
keyValuePair.value = value;
}
}

if (keyValuePair.key && keyValuePair.value) {
result.push(keyValuePair);
}
}

return result.length ? result : undefined;
})
.superRefine((metadata, ctx) => {
if (metadata && !Array.isArray(metadata)) {
ctx.addIssue({
code: z.ZodIssueCode.custom,
message:
"Invalid secretMetadata format. Correct format is key=value1,value=value2|key=value3,value=value4."
});
}

if (metadata) {
if (metadata.length > 10) {
ctx.addIssue({
code: z.ZodIssueCode.custom,
message: "You can only filter by up to 10 metadata fields"
});
}

for (const item of metadata) {
if (!item.key && !item.value) {
ctx.addIssue({
code: z.ZodIssueCode.custom,
message:
"Invalid secretMetadata format, key or value must be provided. Correct format is key=value1,value=value2|key=value3,value=value4."
});
}
}
}
})
.describe(RAW_SECRETS.LIST.metadataFilter),
workspaceId: z.string().trim().optional().describe(RAW_SECRETS.LIST.workspaceId),
workspaceSlug: z.string().trim().optional().describe(RAW_SECRETS.LIST.workspaceSlug),
environment: z.string().trim().optional().describe(RAW_SECRETS.LIST.environment),
Expand Down Expand Up @@ -281,6 +341,7 @@ export const registerSecretRouter = async (server: FastifyZodProvider) => {
actorAuthMethod: req.permission.authMethod,
projectId: workspaceId,
path: secretPath,
metadataFilter: req.query.metadataFilter,
includeImports: req.query.include_imports,
recursive: req.query.recursive,
tagSlugs: req.query.tagSlugs
Expand Down
15 changes: 15 additions & 0 deletions backend/src/services/secret-v2-bridge/secret-v2-bridge-dal.ts
Original file line number Diff line number Diff line change
Expand Up @@ -414,6 +414,20 @@ export const secretV2BridgeDALFactory = (db: TDbClient) => {
`${TableName.SecretTag}.id`
)
.leftJoin(TableName.ResourceMetadata, `${TableName.SecretV2}.id`, `${TableName.ResourceMetadata}.secretId`)
.where((qb) => {
if (filters?.metadataFilter && filters.metadataFilter.length > 0) {
filters.metadataFilter.forEach((meta) => {
void qb.whereExists((subQuery) => {
void subQuery
.select("secretId")
.from(TableName.ResourceMetadata)
.whereRaw(`"${TableName.ResourceMetadata}"."secretId" = "${TableName.SecretV2}"."id"`)
.where(`${TableName.ResourceMetadata}.key`, meta.key)
.where(`${TableName.ResourceMetadata}.value`, meta.value);
});
});
}
})
.select(
selectAllTableCols(TableName.SecretV2),
db.raw(
Expand Down Expand Up @@ -481,6 +495,7 @@ export const secretV2BridgeDALFactory = (db: TDbClient) => {
}
]
});

return data;
} catch (error) {
throw new DatabaseError({ error, name: "get all secret" });
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,10 @@ export type TGetSecretsDTO = {
includeImports?: boolean;
recursive?: boolean;
tagSlugs?: string[];
metadataFilter?: {
key?: string;
value?: string;
}[];
orderBy?: SecretsOrderBy;
orderDirection?: OrderByDirection;
offset?: number;
Expand Down Expand Up @@ -310,6 +314,7 @@ export type TFindSecretsByFolderIdsFilter = {
orderDirection?: OrderByDirection;
search?: string;
tagSlugs?: string[];
metadataFilter?: { key?: string; value?: string }[];
includeTagsInSearch?: boolean;
keys?: string[];
};
Expand Down
7 changes: 7 additions & 0 deletions backend/src/services/secret/secret-service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1263,6 +1263,13 @@ export const secretServiceFactory = ({
name: "bot_not_found_error"
});

if (paramsV2.metadataFilter) {
throw new BadRequestError({
message: "Please upgrade your project to filter secrets by metadata",
name: "SecretMetadataNotSupported"
});
}

const { secrets, imports } = await getSecrets({
actorId,
projectId,
Expand Down
4 changes: 4 additions & 0 deletions backend/src/services/secret/secret-types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -182,6 +182,10 @@ export type TGetSecretsRawDTO = {
includeImports?: boolean;
recursive?: boolean;
tagSlugs?: string[];
metadataFilter?: {
key?: string;
value?: string;
}[];
orderBy?: SecretsOrderBy;
orderDirection?: OrderByDirection;
offset?: number;
Expand Down

0 comments on commit 9d66659

Please sign in to comment.