mirror of
https://github.com/awatertrevi/infisical.git
synced 2026-09-22 13:39:35 +00:00
feat: add option to maks and protect gitlab secrets
This commit is contained in:
@@ -674,7 +674,9 @@ export const INTEGRATION = {
|
|||||||
secretGCPLabel: "The label for GCP secrets.",
|
secretGCPLabel: "The label for GCP secrets.",
|
||||||
secretAWSTag: "The tags for AWS secrets.",
|
secretAWSTag: "The tags for AWS secrets.",
|
||||||
kmsKeyId: "The ID of the encryption key from AWS KMS.",
|
kmsKeyId: "The ID of the encryption key from AWS KMS.",
|
||||||
shouldDisableDelete: "The flag to disable deletion of secrets in AWS Parameter Store."
|
shouldDisableDelete: "The flag to disable deletion of secrets in AWS Parameter Store.",
|
||||||
|
shouldMaskSecrets: "The flag to determine the visibility of secrets to sync. Used by Gitlab",
|
||||||
|
shouldProtectSecrets: "The flag to determine usage rules of secrets to sync. Used by Gitlab"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
UPDATE: {
|
UPDATE: {
|
||||||
|
|||||||
@@ -73,7 +73,9 @@ export const registerIntegrationRouter = async (server: FastifyZodProvider) => {
|
|||||||
.optional()
|
.optional()
|
||||||
.describe(INTEGRATION.CREATE.metadata.secretAWSTag),
|
.describe(INTEGRATION.CREATE.metadata.secretAWSTag),
|
||||||
kmsKeyId: z.string().optional().describe(INTEGRATION.CREATE.metadata.kmsKeyId),
|
kmsKeyId: z.string().optional().describe(INTEGRATION.CREATE.metadata.kmsKeyId),
|
||||||
shouldDisableDelete: z.boolean().optional().describe(INTEGRATION.CREATE.metadata.shouldDisableDelete)
|
shouldDisableDelete: z.boolean().optional().describe(INTEGRATION.CREATE.metadata.shouldDisableDelete),
|
||||||
|
shouldMaskSecrets: z.boolean().optional().describe(INTEGRATION.CREATE.metadata.shouldMaskSecrets),
|
||||||
|
shouldProtectSecrets: z.boolean().optional().describe(INTEGRATION.CREATE.metadata.shouldProtectSecrets)
|
||||||
})
|
})
|
||||||
.default({})
|
.default({})
|
||||||
}),
|
}),
|
||||||
|
|||||||
@@ -1917,13 +1917,13 @@ const syncSecretsGitLab = async ({
|
|||||||
return allEnvVariables;
|
return allEnvVariables;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const metadata = z.record(z.any()).parse(integration.metadata);
|
||||||
const allEnvVariables = await getAllEnvVariables(integration?.appId as string, accessToken);
|
const allEnvVariables = await getAllEnvVariables(integration?.appId as string, accessToken);
|
||||||
const getSecretsRes: GitLabSecret[] = allEnvVariables
|
const getSecretsRes: GitLabSecret[] = allEnvVariables
|
||||||
.filter((secret: GitLabSecret) => secret.environment_scope === integration.targetEnvironment)
|
.filter((secret: GitLabSecret) => secret.environment_scope === integration.targetEnvironment)
|
||||||
.filter((gitLabSecret) => {
|
.filter((gitLabSecret) => {
|
||||||
let isValid = true;
|
let isValid = true;
|
||||||
|
|
||||||
const metadata = z.record(z.any()).parse(integration.metadata);
|
|
||||||
if (metadata.secretPrefix && !gitLabSecret.key.startsWith(metadata.secretPrefix)) {
|
if (metadata.secretPrefix && !gitLabSecret.key.startsWith(metadata.secretPrefix)) {
|
||||||
isValid = false;
|
isValid = false;
|
||||||
}
|
}
|
||||||
@@ -1943,8 +1943,8 @@ const syncSecretsGitLab = async ({
|
|||||||
{
|
{
|
||||||
key,
|
key,
|
||||||
value: secrets[key].value,
|
value: secrets[key].value,
|
||||||
protected: false,
|
protected: Boolean(metadata.shouldProtectSecrets),
|
||||||
masked: false,
|
masked: Boolean(metadata.shouldMaskSecrets),
|
||||||
raw: false,
|
raw: false,
|
||||||
environment_scope: integration.targetEnvironment
|
environment_scope: integration.targetEnvironment
|
||||||
},
|
},
|
||||||
@@ -1961,7 +1961,9 @@ const syncSecretsGitLab = async ({
|
|||||||
`${gitLabApiUrl}/v4/projects/${integration?.appId}/variables/${existingSecret.key}?filter[environment_scope]=${integration.targetEnvironment}`,
|
`${gitLabApiUrl}/v4/projects/${integration?.appId}/variables/${existingSecret.key}?filter[environment_scope]=${integration.targetEnvironment}`,
|
||||||
{
|
{
|
||||||
...existingSecret,
|
...existingSecret,
|
||||||
value: secrets[existingSecret.key].value
|
value: secrets[existingSecret.key].value,
|
||||||
|
protected: Boolean(metadata.shouldProtectSecrets),
|
||||||
|
masked: Boolean(metadata.shouldMaskSecrets)
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
headers: {
|
headers: {
|
||||||
|
|||||||
@@ -29,6 +29,8 @@ export type TCreateIntegrationDTO = {
|
|||||||
}[];
|
}[];
|
||||||
kmsKeyId?: string;
|
kmsKeyId?: string;
|
||||||
shouldDisableDelete?: boolean;
|
shouldDisableDelete?: boolean;
|
||||||
|
shouldMaskSecrets?: boolean;
|
||||||
|
shouldProtectSecrets?: boolean;
|
||||||
};
|
};
|
||||||
} & Omit<TProjectPermission, "projectId">;
|
} & Omit<TProjectPermission, "projectId">;
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,6 @@
|
|||||||
/* eslint-disable no-await-in-loop */
|
/* eslint-disable no-await-in-loop */
|
||||||
|
import { AxiosError } from "axios";
|
||||||
|
|
||||||
import { getConfig } from "@app/lib/config/env";
|
import { getConfig } from "@app/lib/config/env";
|
||||||
import { decryptSymmetric128BitHexKeyUTF8 } from "@app/lib/crypto";
|
import { decryptSymmetric128BitHexKeyUTF8 } from "@app/lib/crypto";
|
||||||
import { daysToMillisecond, secondsToMillis } from "@app/lib/dates";
|
import { daysToMillisecond, secondsToMillis } from "@app/lib/dates";
|
||||||
@@ -567,11 +569,14 @@ export const secretQueueFactory = ({
|
|||||||
isSynced: true
|
isSynced: true
|
||||||
});
|
});
|
||||||
} catch (err: unknown) {
|
} 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, {
|
await integrationDAL.updateById(integration.id, {
|
||||||
lastSyncJobId: job.id,
|
lastSyncJobId: job.id,
|
||||||
lastUsed: new Date(),
|
lastUsed: new Date(),
|
||||||
syncMessage: (err as Error)?.message,
|
syncMessage: message,
|
||||||
isSynced: false
|
isSynced: false
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -73,6 +73,8 @@ export const useCreateIntegration = () => {
|
|||||||
}[];
|
}[];
|
||||||
kmsKeyId?: string;
|
kmsKeyId?: string;
|
||||||
shouldDisableDelete?: boolean;
|
shouldDisableDelete?: boolean;
|
||||||
|
shouldMaskSecrets?: boolean;
|
||||||
|
shouldProtectSecrets?: boolean;
|
||||||
};
|
};
|
||||||
}) => {
|
}) => {
|
||||||
const {
|
const {
|
||||||
|
|||||||
@@ -25,6 +25,7 @@ import {
|
|||||||
ModalContent,
|
ModalContent,
|
||||||
Select,
|
Select,
|
||||||
SelectItem,
|
SelectItem,
|
||||||
|
Switch,
|
||||||
Tab,
|
Tab,
|
||||||
TabList,
|
TabList,
|
||||||
TabPanel,
|
TabPanel,
|
||||||
@@ -58,7 +59,9 @@ const schema = yup.object({
|
|||||||
targetAppId: yup.string().required("GitLab project is required"),
|
targetAppId: yup.string().required("GitLab project is required"),
|
||||||
targetEnvironment: yup.string(),
|
targetEnvironment: yup.string(),
|
||||||
secretPrefix: yup.string(),
|
secretPrefix: yup.string(),
|
||||||
secretSuffix: yup.string()
|
secretSuffix: yup.string(),
|
||||||
|
shouldMaskSecrets: yup.boolean(),
|
||||||
|
shouldProtectSecrets: yup.boolean()
|
||||||
});
|
});
|
||||||
|
|
||||||
type FormData = yup.InferType<typeof schema>;
|
type FormData = yup.InferType<typeof schema>;
|
||||||
@@ -138,7 +141,9 @@ export default function GitLabCreateIntegrationPage() {
|
|||||||
targetAppId,
|
targetAppId,
|
||||||
targetEnvironment,
|
targetEnvironment,
|
||||||
secretPrefix,
|
secretPrefix,
|
||||||
secretSuffix
|
secretSuffix,
|
||||||
|
shouldMaskSecrets,
|
||||||
|
shouldProtectSecrets
|
||||||
}: FormData) => {
|
}: FormData) => {
|
||||||
try {
|
try {
|
||||||
setIsLoading(true);
|
setIsLoading(true);
|
||||||
@@ -156,7 +161,9 @@ export default function GitLabCreateIntegrationPage() {
|
|||||||
secretPath,
|
secretPath,
|
||||||
metadata: {
|
metadata: {
|
||||||
secretPrefix,
|
secretPrefix,
|
||||||
secretSuffix
|
secretSuffix,
|
||||||
|
shouldMaskSecrets,
|
||||||
|
shouldProtectSecrets
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -390,6 +397,38 @@ export default function GitLabCreateIntegrationPage() {
|
|||||||
exit={{ opacity: 0, translateX: 30 }}
|
exit={{ opacity: 0, translateX: 30 }}
|
||||||
className="pb-[14.25rem]"
|
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">
|
||||||
|
Mask variables (requires secret values to match regex)
|
||||||
|
</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}
|
||||||
|
>
|
||||||
|
Protect secrets (only use in protected branches and tags)
|
||||||
|
</Switch>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
<Controller
|
<Controller
|
||||||
control={control}
|
control={control}
|
||||||
name="secretPrefix"
|
name="secretPrefix"
|
||||||
|
|||||||
Reference in New Issue
Block a user