Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Tag based filtering for secret endpoint #2242

Merged
merged 8 commits into from
Aug 9, 2024
Merged
3 changes: 2 additions & 1 deletion backend/src/lib/api-docs/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -596,7 +596,8 @@ export const RAW_SECRETS = {
"The slug of the project to list secrets from. This parameter is only applicable by machine identities.",
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."
includeImports: "Weather to include imported secrets or not.",
tagSlugs: "The comma seperated tag slugs to filter secrets"
},
CREATE: {
secretName: "The name of the secret to create.",
Expand Down
11 changes: 9 additions & 2 deletions backend/src/server/routes/v3/secret-router.ts
Original file line number Diff line number Diff line change
Expand Up @@ -180,7 +180,13 @@ export const registerSecretRouter = async (server: FastifyZodProvider) => {
.enum(["true", "false"])
.default("false")
.transform((value) => value === "true")
.describe(RAW_SECRETS.LIST.includeImports)
.describe(RAW_SECRETS.LIST.includeImports),
tagSlugs: z
.string()
.describe(RAW_SECRETS.LIST.tagSlugs)
.optional()
// split by comma and trim the strings
.transform((el) => (el ? el.split(",").map((i) => i.trim()) : []))
}),
response: {
200: z.object({
Expand Down Expand Up @@ -251,7 +257,8 @@ export const registerSecretRouter = async (server: FastifyZodProvider) => {
projectId: workspaceId,
path: secretPath,
includeImports: req.query.include_imports,
recursive: req.query.recursive
recursive: req.query.recursive,
tagSlugs: req.query.tagSlugs
});

await server.services.auditLog.createAuditLog({
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -429,6 +429,7 @@ export const secretV2BridgeServiceFactory = ({
actorAuthMethod,
includeImports,
recursive,
tagSlugs = [],
expandSecretReferences: shouldExpandSecretReferences
}: TGetSecretsDTO) => {
const { permission } = await permissionService.getProjectPermission(
Expand Down Expand Up @@ -496,6 +497,9 @@ export const secretV2BridgeServiceFactory = ({
: ""
})
);
const filteredSecrets = tagSlugs.length
? decryptedSecrets.filter((secret) => Boolean(secret.tags?.find((el) => tagSlugs.includes(el.slug))))
: decryptedSecrets;
const expandSecretReferences = expandSecretReferencesFactory({
projectId,
folderDAL,
Expand All @@ -504,7 +508,7 @@ export const secretV2BridgeServiceFactory = ({
});

if (shouldExpandSecretReferences) {
const secretsGroupByPath = groupBy(decryptedSecrets, (i) => i.secretPath);
const secretsGroupByPath = groupBy(filteredSecrets, (i) => i.secretPath);
for (const secretPathKey in secretsGroupByPath) {
if (Object.hasOwn(secretsGroupByPath, secretPathKey)) {
const secretsGroupByKey = secretsGroupByPath[secretPathKey].reduce(
Expand All @@ -530,7 +534,7 @@ export const secretV2BridgeServiceFactory = ({

if (!includeImports) {
return {
secrets: decryptedSecrets
secrets: filteredSecrets
};
}

Expand Down Expand Up @@ -558,7 +562,7 @@ export const secretV2BridgeServiceFactory = ({
});

return {
secrets: decryptedSecrets,
secrets: filteredSecrets,
imports: importedSecrets
};
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ export type TGetSecretsDTO = {
environment: string;
includeImports?: boolean;
recursive?: boolean;
tagSlugs?: string[];
} & TProjectPermission;

export type TGetASecretDTO = {
Expand Down
13 changes: 9 additions & 4 deletions backend/src/services/secret/secret-service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -964,7 +964,8 @@ export const secretServiceFactory = ({
environment,
includeImports,
expandSecretReferences,
recursive
recursive,
tagSlugs = []
}: TGetSecretsRawDTO) => {
const { botKey, shouldUseSecretV2Bridge } = await projectBotService.getBotKey(projectId);
if (shouldUseSecretV2Bridge) {
Expand All @@ -978,7 +979,8 @@ export const secretServiceFactory = ({
path,
recursive,
actorAuthMethod,
includeImports
includeImports,
tagSlugs
});
return { secrets, imports };
}
Expand All @@ -998,6 +1000,9 @@ export const secretServiceFactory = ({
});

const decryptedSecrets = secrets.map((el) => decryptSecretRaw(el, botKey));
const filteredSecrets = tagSlugs.length
? decryptedSecrets.filter((secret) => Boolean(secret.tags?.find((el) => tagSlugs.includes(el.slug))))
: decryptedSecrets;
const processedImports = (imports || [])?.map(({ secrets: importedSecrets, ...el }) => {
const decryptedImportSecrets = importedSecrets.map((sec) =>
decryptSecretRaw(
Expand Down Expand Up @@ -1106,14 +1111,14 @@ export const secretServiceFactory = ({
};

// expand secrets
await batchSecretsExpand(decryptedSecrets);
await batchSecretsExpand(filteredSecrets);

// expand imports by batch
await Promise.all(processedImports.map((processedImport) => batchSecretsExpand(processedImport.secrets)));
}

return {
secrets: decryptedSecrets,
secrets: filteredSecrets,
imports: processedImports
};
};
Expand Down
1 change: 1 addition & 0 deletions backend/src/services/secret/secret-types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -149,6 +149,7 @@ export type TGetSecretsRawDTO = {
environment: string;
includeImports?: boolean;
recursive?: boolean;
tagSlugs?: string[];
} & TProjectPermission;

export type TGetASecretRawDTO = {
Expand Down
4 changes: 4 additions & 0 deletions cli/packages/api/api.go
Original file line number Diff line number Diff line change
Expand Up @@ -404,6 +404,10 @@ func CallGetRawSecretsV3(httpClient *resty.Client, request GetRawSecretsV3Reques
SetQueryParam("environment", request.Environment).
SetQueryParam("secretPath", request.SecretPath)

if request.TagSlugs != "" {
req.SetQueryParam("tagSlugs", request.TagSlugs)
}

if request.IncludeImport {
req.SetQueryParam("include_imports", "true")
}
Expand Down
1 change: 1 addition & 0 deletions cli/packages/api/model.go
Original file line number Diff line number Diff line change
Expand Up @@ -574,6 +574,7 @@ type GetRawSecretsV3Request struct {
SecretPath string `json:"secretPath"`
IncludeImport bool `json:"include_imports"`
Recursive bool `json:"recursive"`
TagSlugs string `json:"tagSlugs,omitempty"`
}

type GetRawSecretsV3Response struct {
Expand Down
2 changes: 1 addition & 1 deletion cli/packages/cmd/agent.go
Original file line number Diff line number Diff line change
Expand Up @@ -312,7 +312,7 @@ func ParseAgentConfig(configFile []byte) (*Config, error) {

func secretTemplateFunction(accessToken string, existingEtag string, currentEtag *string) func(string, string, string) ([]models.SingleEnvironmentVariable, error) {
return func(projectID, envSlug, secretPath string) ([]models.SingleEnvironmentVariable, error) {
res, err := util.GetPlainTextSecretsV3(accessToken, projectID, envSlug, secretPath, false, false)
res, err := util.GetPlainTextSecretsV3(accessToken, projectID, envSlug, secretPath, false, false, "")
if err != nil {
return nil, err
}
Expand Down
13 changes: 7 additions & 6 deletions cli/packages/util/secrets.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ import (
"github.com/zalando/go-keyring"
)

func GetPlainTextSecretsViaServiceToken(fullServiceToken string, environment string, secretPath string, includeImports bool, recursive bool) ([]models.SingleEnvironmentVariable, error) {
func GetPlainTextSecretsViaServiceToken(fullServiceToken string, environment string, secretPath string, includeImports bool, recursive bool, tagSlugs string) ([]models.SingleEnvironmentVariable, error) {
serviceTokenParts := strings.SplitN(fullServiceToken, ".", 4)
if len(serviceTokenParts) < 4 {
return nil, fmt.Errorf("invalid service token entered. Please double check your service token and try again")
Expand Down Expand Up @@ -53,6 +53,7 @@ func GetPlainTextSecretsViaServiceToken(fullServiceToken string, environment str
SecretPath: secretPath,
IncludeImport: includeImports,
Recursive: recursive,
TagSlugs: tagSlugs,
})

if err != nil {
Expand All @@ -76,7 +77,7 @@ func GetPlainTextSecretsViaServiceToken(fullServiceToken string, environment str

}

func GetPlainTextSecretsV3(accessToken string, workspaceId string, environmentName string, secretsPath string, includeImports bool, recursive bool) (models.PlaintextSecretResult, error) {
func GetPlainTextSecretsV3(accessToken string, workspaceId string, environmentName string, secretsPath string, includeImports bool, recursive bool, tagSlugs string) (models.PlaintextSecretResult, error) {
httpClient := resty.New()
httpClient.SetAuthToken(accessToken).
SetHeader("Accept", "application/json")
Expand All @@ -86,7 +87,7 @@ func GetPlainTextSecretsV3(accessToken string, workspaceId string, environmentNa
Environment: environmentName,
IncludeImport: includeImports,
Recursive: recursive,
// TagSlugs: tagSlugs,
TagSlugs: tagSlugs,
}

if secretsPath != "" {
Expand Down Expand Up @@ -281,7 +282,7 @@ func GetAllEnvironmentVariables(params models.GetAllSecretsParameters, projectCo
}

res, err := GetPlainTextSecretsV3(loggedInUserDetails.UserCredentials.JTWToken, infisicalDotJson.WorkspaceId,
params.Environment, params.SecretsPath, params.IncludeImport, params.Recursive)
params.Environment, params.SecretsPath, params.IncludeImport, params.Recursive, params.TagSlugs)
log.Debug().Msgf("GetAllEnvironmentVariables: Trying to fetch secrets JTW token [err=%s]", err)

if err == nil {
Expand All @@ -303,15 +304,15 @@ func GetAllEnvironmentVariables(params models.GetAllSecretsParameters, projectCo
} else {
if params.InfisicalToken != "" {
log.Debug().Msg("Trying to fetch secrets using service token")
secretsToReturn, errorToReturn = GetPlainTextSecretsViaServiceToken(params.InfisicalToken, params.Environment, params.SecretsPath, params.IncludeImport, params.Recursive)
secretsToReturn, errorToReturn = GetPlainTextSecretsViaServiceToken(params.InfisicalToken, params.Environment, params.SecretsPath, params.IncludeImport, params.Recursive, params.TagSlugs)
} else if params.UniversalAuthAccessToken != "" {

if params.WorkspaceId == "" {
PrintErrorMessageAndExit("Project ID is required when using machine identity")
}

log.Debug().Msg("Trying to fetch secrets using universal auth")
res, err := GetPlainTextSecretsV3(params.UniversalAuthAccessToken, params.WorkspaceId, params.Environment, params.SecretsPath, params.IncludeImport, params.Recursive)
res, err := GetPlainTextSecretsV3(params.UniversalAuthAccessToken, params.WorkspaceId, params.Environment, params.SecretsPath, params.IncludeImport, params.Recursive, params.TagSlugs)

errorToReturn = err
secretsToReturn = res.Secrets
Expand Down
Loading