mirror of
https://github.com/awatertrevi/infisical.git
synced 2026-09-22 13:39:35 +00:00
misc: addressed comments
This commit is contained in:
@@ -3,17 +3,17 @@ import { Knex } from "knex";
|
||||
import { TableName } from "../schemas";
|
||||
|
||||
export async function up(knex: Knex): Promise<void> {
|
||||
if (!(await knex.schema.hasColumn(TableName.Project, "secretDetectionIgnoreKeys"))) {
|
||||
if (!(await knex.schema.hasColumn(TableName.Project, "secretDetectionIgnoreValues"))) {
|
||||
await knex.schema.alterTable(TableName.Project, (t) => {
|
||||
t.specificType("secretDetectionIgnoreKeys", "text[]");
|
||||
t.specificType("secretDetectionIgnoreValues", "text[]");
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export async function down(knex: Knex): Promise<void> {
|
||||
if (await knex.schema.hasColumn(TableName.Project, "secretDetectionIgnoreKeys")) {
|
||||
if (await knex.schema.hasColumn(TableName.Project, "secretDetectionIgnoreValues")) {
|
||||
await knex.schema.alterTable(TableName.Project, (t) => {
|
||||
t.dropColumn("secretDetectionIgnoreKeys");
|
||||
t.dropColumn("secretDetectionIgnoreValues");
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -31,7 +31,7 @@ export const ProjectsSchema = z.object({
|
||||
secretSharing: z.boolean().default(true),
|
||||
showSnapshotsLegacy: z.boolean().default(false),
|
||||
defaultProduct: z.string().nullable().optional(),
|
||||
secretDetectionIgnoreKeys: z.string().array().nullable().optional()
|
||||
secretDetectionIgnoreValues: z.string().array().nullable().optional()
|
||||
});
|
||||
|
||||
export type TProjects = z.infer<typeof ProjectsSchema>;
|
||||
|
||||
@@ -1410,6 +1410,7 @@ export const secretApprovalRequestServiceFactory = ({
|
||||
|
||||
const project = await projectDAL.findById(projectId);
|
||||
await scanSecretPolicyViolations(
|
||||
projectId,
|
||||
secretPath,
|
||||
[
|
||||
...(data[SecretOperations.Create] || []),
|
||||
@@ -1418,7 +1419,7 @@ export const secretApprovalRequestServiceFactory = ({
|
||||
secretKey: el.secretKey,
|
||||
secretValue: el.secretValue as string
|
||||
})),
|
||||
project.secretDetectionIgnoreKeys || []
|
||||
project.secretDetectionIgnoreValues || []
|
||||
);
|
||||
|
||||
// for created secret approval change
|
||||
|
||||
@@ -165,28 +165,29 @@ export const parseScanErrorMessage = (err: unknown): string => {
|
||||
};
|
||||
|
||||
export const scanSecretPolicyViolations = async (
|
||||
projectId: string,
|
||||
secretPath: string,
|
||||
secrets: { secretKey: string; secretValue: string }[],
|
||||
ignoreKeys: string[]
|
||||
ignoreValues: string[]
|
||||
) => {
|
||||
const appCfg = getConfig();
|
||||
|
||||
if (!appCfg.PARAMS_FOLDER_SECRET_DETECTION_ENABLED) {
|
||||
return;
|
||||
}
|
||||
const paramFolderSecretDetectionPaths = appCfg.PARAMS_FOLDER_SECRET_DETECTION_PATHS?.map((el) => el.secretPath) ?? [];
|
||||
const isPathMatched = paramFolderSecretDetectionPaths.some((pattern) =>
|
||||
picomatch.isMatch(secretPath, pattern, { strictSlashes: false })
|
||||
|
||||
const match = appCfg.PARAMS_FOLDER_SECRET_DETECTION_PATHS?.find(
|
||||
(el) => el.projectId === projectId && picomatch.isMatch(secretPath, el.secretPath, { strictSlashes: false })
|
||||
);
|
||||
|
||||
if (!isPathMatched) {
|
||||
if (!match) {
|
||||
return;
|
||||
}
|
||||
|
||||
const tempFolder = await createTempFolder();
|
||||
try {
|
||||
const scanPromises = secrets
|
||||
.filter((secret) => !ignoreKeys.includes(secret.secretKey))
|
||||
.filter((secret) => !ignoreValues.includes(secret.secretValue))
|
||||
.map(async (secret) => {
|
||||
const secretFilePath = join(tempFolder, `${crypto.nativeCrypto.randomUUID()}.txt`);
|
||||
await writeTextToFile(secretFilePath, `${secret.secretKey}=${secret.secretValue}`);
|
||||
|
||||
@@ -705,7 +705,7 @@ export const PROJECTS = {
|
||||
secretSharing: "Enable or disable secret sharing for the project.",
|
||||
showSnapshotsLegacy: "Enable or disable legacy snapshots for the project.",
|
||||
defaultProduct: "The default product in which the project will open",
|
||||
secretDetectionIgnoreKeys: "The list of secret keys to ignore for secret detection."
|
||||
secretDetectionIgnoreValues: "The list of secret values to ignore for secret detection."
|
||||
},
|
||||
GET_KEY: {
|
||||
workspaceId: "The ID of the project to get the key from."
|
||||
|
||||
@@ -211,10 +211,9 @@ const envSchema = z
|
||||
.optional()
|
||||
.transform((val) => {
|
||||
if (!val) return undefined;
|
||||
return JSON.parse(val) as { secretPath: string }[];
|
||||
return JSON.parse(val) as { secretPath: string; projectId: string }[];
|
||||
})
|
||||
),
|
||||
PARAMS_FOLDER_SECRET_DETECTION_ENABLED: zodStrBool.default("false"),
|
||||
|
||||
// HSM
|
||||
HSM_LIB_PATH: zpStr(z.string().optional()),
|
||||
@@ -353,7 +352,8 @@ const envSchema = z
|
||||
isHsmConfigured:
|
||||
Boolean(data.HSM_LIB_PATH) && Boolean(data.HSM_PIN) && Boolean(data.HSM_KEY_LABEL) && data.HSM_SLOT !== undefined,
|
||||
samlDefaultOrgSlug: data.DEFAULT_SAML_ORG_SLUG,
|
||||
SECRET_SCANNING_ORG_WHITELIST: data.SECRET_SCANNING_ORG_WHITELIST?.split(",")
|
||||
SECRET_SCANNING_ORG_WHITELIST: data.SECRET_SCANNING_ORG_WHITELIST?.split(","),
|
||||
PARAMS_FOLDER_SECRET_DETECTION_ENABLED: (data.PARAMS_FOLDER_SECRET_DETECTION_PATHS?.length ?? 0) > 0
|
||||
}));
|
||||
|
||||
export type TEnvConfig = Readonly<z.infer<typeof envSchema>>;
|
||||
|
||||
@@ -265,7 +265,7 @@ export const SanitizedProjectSchema = ProjectsSchema.pick({
|
||||
hasDeleteProtection: true,
|
||||
secretSharing: true,
|
||||
showSnapshotsLegacy: true,
|
||||
secretDetectionIgnoreKeys: true
|
||||
secretDetectionIgnoreValues: true
|
||||
});
|
||||
|
||||
export const SanitizedTagSchema = SecretTagsSchema.pick({
|
||||
|
||||
@@ -370,7 +370,10 @@ export const registerProjectRouter = async (server: FastifyZodProvider) => {
|
||||
secretSharing: z.boolean().optional().describe(PROJECTS.UPDATE.secretSharing),
|
||||
showSnapshotsLegacy: z.boolean().optional().describe(PROJECTS.UPDATE.showSnapshotsLegacy),
|
||||
defaultProduct: z.nativeEnum(ProjectType).optional().describe(PROJECTS.UPDATE.defaultProduct),
|
||||
secretDetectionIgnoreKeys: z.array(z.string()).optional().describe(PROJECTS.UPDATE.secretDetectionIgnoreKeys)
|
||||
secretDetectionIgnoreValues: z
|
||||
.array(z.string())
|
||||
.optional()
|
||||
.describe(PROJECTS.UPDATE.secretDetectionIgnoreValues)
|
||||
}),
|
||||
response: {
|
||||
200: z.object({
|
||||
@@ -394,7 +397,7 @@ export const registerProjectRouter = async (server: FastifyZodProvider) => {
|
||||
slug: req.body.slug,
|
||||
secretSharing: req.body.secretSharing,
|
||||
showSnapshotsLegacy: req.body.showSnapshotsLegacy,
|
||||
secretDetectionIgnoreKeys: req.body.secretDetectionIgnoreKeys
|
||||
secretDetectionIgnoreValues: req.body.secretDetectionIgnoreValues
|
||||
},
|
||||
actorAuthMethod: req.permission.authMethod,
|
||||
actorId: req.permission.id,
|
||||
|
||||
@@ -667,9 +667,9 @@ export const projectServiceFactory = ({
|
||||
}
|
||||
}
|
||||
|
||||
if (update.secretDetectionIgnoreKeys && !hasRole(ProjectMembershipRole.Admin)) {
|
||||
if (update.secretDetectionIgnoreValues && !hasRole(ProjectMembershipRole.Admin)) {
|
||||
throw new ForbiddenRequestError({
|
||||
message: "Only admins can update secret detection ignore keys"
|
||||
message: "Only admins can update secret detection ignore values"
|
||||
});
|
||||
}
|
||||
|
||||
@@ -683,7 +683,7 @@ export const projectServiceFactory = ({
|
||||
secretSharing: update.secretSharing,
|
||||
defaultProduct: update.defaultProduct,
|
||||
showSnapshotsLegacy: update.showSnapshotsLegacy,
|
||||
secretDetectionIgnoreKeys: update.secretDetectionIgnoreKeys
|
||||
secretDetectionIgnoreValues: update.secretDetectionIgnoreValues
|
||||
});
|
||||
|
||||
return updatedProject;
|
||||
|
||||
@@ -96,7 +96,7 @@ export type TUpdateProjectDTO = {
|
||||
slug?: string;
|
||||
secretSharing?: boolean;
|
||||
showSnapshotsLegacy?: boolean;
|
||||
secretDetectionIgnoreKeys?: string[];
|
||||
secretDetectionIgnoreValues?: string[];
|
||||
};
|
||||
} & Omit<TProjectPermission, "projectId">;
|
||||
|
||||
|
||||
@@ -301,6 +301,7 @@ export const secretV2BridgeServiceFactory = ({
|
||||
|
||||
const project = await projectDAL.findById(projectId);
|
||||
await scanSecretPolicyViolations(
|
||||
projectId,
|
||||
secretPath,
|
||||
[
|
||||
{
|
||||
@@ -308,7 +309,7 @@ export const secretV2BridgeServiceFactory = ({
|
||||
secretValue: inputSecret.secretValue
|
||||
}
|
||||
],
|
||||
project.secretDetectionIgnoreKeys || []
|
||||
project.secretDetectionIgnoreValues || []
|
||||
);
|
||||
|
||||
const { nestedReferences, localReferences } = getAllSecretReferences(inputSecret.secretValue);
|
||||
@@ -525,6 +526,7 @@ export const secretV2BridgeServiceFactory = ({
|
||||
if (secretValue) {
|
||||
const project = await projectDAL.findById(projectId);
|
||||
await scanSecretPolicyViolations(
|
||||
projectId,
|
||||
secretPath,
|
||||
[
|
||||
{
|
||||
@@ -532,7 +534,7 @@ export const secretV2BridgeServiceFactory = ({
|
||||
secretValue
|
||||
}
|
||||
],
|
||||
project.secretDetectionIgnoreKeys || []
|
||||
project.secretDetectionIgnoreValues || []
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1616,7 +1618,7 @@ export const secretV2BridgeServiceFactory = ({
|
||||
throw new BadRequestError({ message: `Secret already exist: ${secrets.map((el) => el.key).join(",")}` });
|
||||
|
||||
const project = await projectDAL.findById(projectId);
|
||||
await scanSecretPolicyViolations(secretPath, inputSecrets, project.secretDetectionIgnoreKeys || []);
|
||||
await scanSecretPolicyViolations(projectId, secretPath, inputSecrets, project.secretDetectionIgnoreValues || []);
|
||||
|
||||
// get all tags
|
||||
const sanitizedTagIds = inputSecrets.flatMap(({ tagIds = [] }) => tagIds);
|
||||
@@ -1960,6 +1962,7 @@ export const secretV2BridgeServiceFactory = ({
|
||||
|
||||
const project = await projectDAL.findById(projectId);
|
||||
await scanSecretPolicyViolations(
|
||||
projectId,
|
||||
secretPath,
|
||||
secretsToUpdate
|
||||
.filter((el) => el.secretValue)
|
||||
@@ -1967,7 +1970,7 @@ export const secretV2BridgeServiceFactory = ({
|
||||
secretKey: el.newSecretName || el.secretKey,
|
||||
secretValue: el.secretValue as string
|
||||
})),
|
||||
project.secretDetectionIgnoreKeys || []
|
||||
project.secretDetectionIgnoreValues || []
|
||||
);
|
||||
|
||||
const bulkUpdatedSecrets = await fnSecretBulkUpdate({
|
||||
|
||||
@@ -282,7 +282,7 @@ export const useUpdateProject = () => {
|
||||
newSlug,
|
||||
secretSharing,
|
||||
showSnapshotsLegacy,
|
||||
secretDetectionIgnoreKeys
|
||||
secretDetectionIgnoreValues
|
||||
}) => {
|
||||
const { data } = await apiRequest.patch<{ workspace: Workspace }>(
|
||||
`/api/v1/workspace/${projectID}`,
|
||||
@@ -292,7 +292,7 @@ export const useUpdateProject = () => {
|
||||
slug: newSlug,
|
||||
secretSharing,
|
||||
showSnapshotsLegacy,
|
||||
secretDetectionIgnoreKeys
|
||||
secretDetectionIgnoreValues
|
||||
}
|
||||
);
|
||||
return data.workspace;
|
||||
|
||||
@@ -40,7 +40,7 @@ export type Workspace = {
|
||||
hasDeleteProtection: boolean;
|
||||
secretSharing: boolean;
|
||||
showSnapshotsLegacy: boolean;
|
||||
secretDetectionIgnoreKeys: string[];
|
||||
secretDetectionIgnoreValues: string[];
|
||||
};
|
||||
|
||||
export type WorkspaceEnv = {
|
||||
@@ -82,7 +82,7 @@ export type UpdateProjectDTO = {
|
||||
newSlug?: string;
|
||||
secretSharing?: boolean;
|
||||
showSnapshotsLegacy?: boolean;
|
||||
secretDetectionIgnoreKeys?: string[];
|
||||
secretDetectionIgnoreValues?: string[];
|
||||
};
|
||||
|
||||
export type UpdatePitVersionLimitDTO = { projectSlug: string; pitVersionLimit: number };
|
||||
|
||||
@@ -4,7 +4,7 @@ import { AutoCapitalizationSection } from "../AutoCapitalizationSection";
|
||||
import { BackfillSecretReferenceSecretion } from "../BackfillSecretReferenceSection";
|
||||
import { EnvironmentSection } from "../EnvironmentSection";
|
||||
import { PointInTimeVersionLimitSection } from "../PointInTimeVersionLimitSection";
|
||||
import { SecretDetectionIgnoreKeysSection } from "../SecretDetectionIgnoreKeysSection/SecretDetectionIgnoreKeysSection";
|
||||
import { SecretDetectionIgnoreValuesSection } from "../SecretDetectionIgnoreValuesSection/SecretDetectionIgnoreValuesSection";
|
||||
import { SecretSharingSection } from "../SecretSharingSection";
|
||||
import { SecretSnapshotsLegacySection } from "../SecretSnapshotsLegacySection";
|
||||
import { SecretTagsSection } from "../SecretTagsSection";
|
||||
@@ -20,7 +20,7 @@ export const SecretSettingsTab = () => {
|
||||
<SecretSharingSection />
|
||||
<SecretSnapshotsLegacySection />
|
||||
<PointInTimeVersionLimitSection />
|
||||
{config.paramsFolderSecretDetectionEnabled && <SecretDetectionIgnoreKeysSection />}
|
||||
{config.paramsFolderSecretDetectionEnabled && <SecretDetectionIgnoreValuesSection />}
|
||||
<BackfillSecretReferenceSecretion />
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -12,9 +12,9 @@ import { useUpdateProject } from "@app/hooks/api";
|
||||
import { ProjectMembershipRole } from "@app/hooks/api/roles/types";
|
||||
|
||||
const formSchema = z.object({
|
||||
ignoreKeys: z
|
||||
ignoreValues: z
|
||||
.object({
|
||||
key: z.string().trim().min(1, "Secret key name is required")
|
||||
value: z.string().trim().min(1, "Secret value is required")
|
||||
})
|
||||
.array()
|
||||
.default([])
|
||||
@@ -22,7 +22,7 @@ const formSchema = z.object({
|
||||
|
||||
type TForm = z.infer<typeof formSchema>;
|
||||
|
||||
export const SecretDetectionIgnoreKeysSection = () => {
|
||||
export const SecretDetectionIgnoreValuesSection = () => {
|
||||
const { currentWorkspace } = useWorkspace();
|
||||
const { membership } = useProjectPermission();
|
||||
const { mutateAsync: updateProject } = useUpdateProject();
|
||||
@@ -35,37 +35,39 @@ export const SecretDetectionIgnoreKeysSection = () => {
|
||||
} = useForm<TForm>({
|
||||
resolver: zodResolver(formSchema),
|
||||
defaultValues: {
|
||||
ignoreKeys: []
|
||||
ignoreValues: []
|
||||
}
|
||||
});
|
||||
|
||||
const ignoreKeysFormFields = useFieldArray({
|
||||
const ignoreValuesFormFields = useFieldArray({
|
||||
control,
|
||||
name: "ignoreKeys"
|
||||
name: "ignoreValues"
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
const existingIgnoreKeys = currentWorkspace?.secretDetectionIgnoreKeys || [];
|
||||
const existingIgnoreValues = currentWorkspace?.secretDetectionIgnoreValues || [];
|
||||
reset({
|
||||
ignoreKeys:
|
||||
existingIgnoreKeys.length > 0 ? existingIgnoreKeys.map((key) => ({ key })) : [{ key: "" }] // Show one empty field by default
|
||||
ignoreValues:
|
||||
existingIgnoreValues.length > 0
|
||||
? existingIgnoreValues.map((value) => ({ value }))
|
||||
: [{ value: "" }] // Show one empty field by default
|
||||
});
|
||||
}, [currentWorkspace?.secretDetectionIgnoreKeys, reset]);
|
||||
}, [currentWorkspace?.secretDetectionIgnoreValues, reset]);
|
||||
|
||||
const handleIgnoreKeysSubmit = async ({ ignoreKeys }: TForm) => {
|
||||
const handleIgnoreValuesSubmit = async ({ ignoreValues }: TForm) => {
|
||||
try {
|
||||
await updateProject({
|
||||
projectID: currentWorkspace.id,
|
||||
secretDetectionIgnoreKeys: ignoreKeys.map((item) => item.key)
|
||||
secretDetectionIgnoreValues: ignoreValues.map((item) => item.value)
|
||||
});
|
||||
|
||||
createNotification({
|
||||
text: "Successfully updated secret detection ignore keys",
|
||||
text: "Successfully updated secret detection ignore values",
|
||||
type: "success"
|
||||
});
|
||||
} catch {
|
||||
createNotification({
|
||||
text: "Failed updating secret detection ignore keys",
|
||||
text: "Failed updating secret detection ignore values",
|
||||
type: "error"
|
||||
});
|
||||
}
|
||||
@@ -78,41 +80,41 @@ export const SecretDetectionIgnoreKeysSection = () => {
|
||||
return (
|
||||
<div className="mb-6 rounded-lg border border-mineshaft-600 bg-mineshaft-900 p-4">
|
||||
<div className="flex w-full items-center justify-between">
|
||||
<p className="text-xl font-semibold">Secret Detection Ignore Keys</p>
|
||||
<p className="text-xl font-semibold">Secret Detection Ignore Values</p>
|
||||
</div>
|
||||
<p className="mb-4 mt-2 max-w-2xl text-sm text-gray-400">
|
||||
Define secret keys that should be ignored when scanning parameter folders for misplaced
|
||||
secrets. These keys will not trigger policy violation alerts even if they contain sensitive
|
||||
data.
|
||||
Define secret values that should be ignored when scanning parameter folders for misplaced
|
||||
secrets. These values will not trigger policy violation alerts even if they contain
|
||||
sensitive data.
|
||||
</p>
|
||||
|
||||
<form onSubmit={handleSubmit(handleIgnoreKeysSubmit)} autoComplete="off">
|
||||
<form onSubmit={handleSubmit(handleIgnoreValuesSubmit)} autoComplete="off">
|
||||
<div className="mb-4">
|
||||
<p className="mb-3 text-sm font-medium text-gray-300">Ignored Secret Keys</p>
|
||||
<p className="mb-3 text-sm font-medium text-gray-300">Ignored Secret Values</p>
|
||||
<div className="flex flex-col space-y-2">
|
||||
{ignoreKeysFormFields.fields.map(({ id: ignoreKeyFieldId }, i) => (
|
||||
<div key={ignoreKeyFieldId} className="flex items-end space-x-2">
|
||||
{ignoreValuesFormFields.fields.map(({ id: ignoreValueFieldId }, i) => (
|
||||
<div key={ignoreValueFieldId} className="flex items-end space-x-2">
|
||||
<div className="flex-grow">
|
||||
{i === 0 && <span className="text-xs text-mineshaft-400">Secret Key Name</span>}
|
||||
{i === 0 && <span className="text-xs text-mineshaft-400">Secret Value</span>}
|
||||
<Controller
|
||||
control={control}
|
||||
name={`ignoreKeys.${i}.key`}
|
||||
name={`ignoreValues.${i}.value`}
|
||||
render={({ field, fieldState: { error } }) => (
|
||||
<FormControl
|
||||
isError={Boolean(error?.message)}
|
||||
errorText={error?.message}
|
||||
className="mb-0"
|
||||
>
|
||||
<Input {...field} placeholder="PUBLIC_API_KEY" isDisabled={!isAdmin} />
|
||||
<Input {...field} placeholder="sk-1234567890abcdef" isDisabled={!isAdmin} />
|
||||
</FormControl>
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
<IconButton
|
||||
ariaLabel="delete ignore key"
|
||||
ariaLabel="delete ignore value"
|
||||
className="bottom-0.5 h-9"
|
||||
variant="outline_bg"
|
||||
onClick={() => ignoreKeysFormFields.remove(i)}
|
||||
onClick={() => ignoreValuesFormFields.remove(i)}
|
||||
isDisabled={!isAdmin}
|
||||
>
|
||||
<FontAwesomeIcon icon={faTrash} />
|
||||
@@ -124,10 +126,10 @@ export const SecretDetectionIgnoreKeysSection = () => {
|
||||
leftIcon={<FontAwesomeIcon icon={faPlus} />}
|
||||
size="xs"
|
||||
variant="outline_bg"
|
||||
onClick={() => ignoreKeysFormFields.append({ key: "" })}
|
||||
onClick={() => ignoreValuesFormFields.append({ value: "" })}
|
||||
isDisabled={!isAdmin}
|
||||
>
|
||||
Add Ignore Key
|
||||
Add Ignore Value
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
Reference in New Issue
Block a user