Compare commits

...

4 Commits

7 changed files with 103 additions and 96 deletions

View File

@ -674,7 +674,8 @@ 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.",
shouldEnableDelete: "The flag to enable deletion of secrets"
} }
}, },
UPDATE: { UPDATE: {

View File

@ -8,7 +8,7 @@ import { writeLimit } from "@app/server/config/rateLimiter";
import { getTelemetryDistinctId } from "@app/server/lib/telemetry"; import { getTelemetryDistinctId } from "@app/server/lib/telemetry";
import { verifyAuth } from "@app/server/plugins/auth/verify-auth"; import { verifyAuth } from "@app/server/plugins/auth/verify-auth";
import { AuthMode } from "@app/services/auth/auth-type"; import { AuthMode } from "@app/services/auth/auth-type";
import { IntegrationMappingBehavior } from "@app/services/integration-auth/integration-list"; import { IntegrationMetadataSchema } from "@app/services/integration/integration-schema";
import { PostHogEventTypes, TIntegrationCreatedEvent } from "@app/services/telemetry/telemetry-types"; import { PostHogEventTypes, TIntegrationCreatedEvent } from "@app/services/telemetry/telemetry-types";
export const registerIntegrationRouter = async (server: FastifyZodProvider) => { export const registerIntegrationRouter = async (server: FastifyZodProvider) => {
@ -46,36 +46,7 @@ export const registerIntegrationRouter = async (server: FastifyZodProvider) => {
path: z.string().trim().optional().describe(INTEGRATION.CREATE.path), path: z.string().trim().optional().describe(INTEGRATION.CREATE.path),
region: z.string().trim().optional().describe(INTEGRATION.CREATE.region), region: z.string().trim().optional().describe(INTEGRATION.CREATE.region),
scope: z.string().trim().optional().describe(INTEGRATION.CREATE.scope), scope: z.string().trim().optional().describe(INTEGRATION.CREATE.scope),
metadata: z metadata: IntegrationMetadataSchema.default({})
.object({
secretPrefix: z.string().optional().describe(INTEGRATION.CREATE.metadata.secretPrefix),
secretSuffix: z.string().optional().describe(INTEGRATION.CREATE.metadata.secretSuffix),
initialSyncBehavior: z.string().optional().describe(INTEGRATION.CREATE.metadata.initialSyncBehavoir),
mappingBehavior: z
.nativeEnum(IntegrationMappingBehavior)
.optional()
.describe(INTEGRATION.CREATE.metadata.mappingBehavior),
shouldAutoRedeploy: z.boolean().optional().describe(INTEGRATION.CREATE.metadata.shouldAutoRedeploy),
secretGCPLabel: z
.object({
labelName: z.string(),
labelValue: z.string()
})
.optional()
.describe(INTEGRATION.CREATE.metadata.secretGCPLabel),
secretAWSTag: z
.array(
z.object({
key: z.string(),
value: z.string()
})
)
.optional()
.describe(INTEGRATION.CREATE.metadata.secretAWSTag),
kmsKeyId: z.string().optional().describe(INTEGRATION.CREATE.metadata.kmsKeyId),
shouldDisableDelete: z.boolean().optional().describe(INTEGRATION.CREATE.metadata.shouldDisableDelete)
})
.default({})
}), }),
response: { response: {
200: z.object({ 200: z.object({
@ -161,33 +132,7 @@ export const registerIntegrationRouter = async (server: FastifyZodProvider) => {
targetEnvironment: z.string().trim().describe(INTEGRATION.UPDATE.targetEnvironment), targetEnvironment: z.string().trim().describe(INTEGRATION.UPDATE.targetEnvironment),
owner: z.string().trim().describe(INTEGRATION.UPDATE.owner), owner: z.string().trim().describe(INTEGRATION.UPDATE.owner),
environment: z.string().trim().describe(INTEGRATION.UPDATE.environment), environment: z.string().trim().describe(INTEGRATION.UPDATE.environment),
metadata: z metadata: IntegrationMetadataSchema.optional()
.object({
secretPrefix: z.string().optional().describe(INTEGRATION.CREATE.metadata.secretPrefix),
secretSuffix: z.string().optional().describe(INTEGRATION.CREATE.metadata.secretSuffix),
initialSyncBehavior: z.string().optional().describe(INTEGRATION.CREATE.metadata.initialSyncBehavoir),
mappingBehavior: z.string().optional().describe(INTEGRATION.CREATE.metadata.mappingBehavior),
shouldAutoRedeploy: z.boolean().optional().describe(INTEGRATION.CREATE.metadata.shouldAutoRedeploy),
secretGCPLabel: z
.object({
labelName: z.string(),
labelValue: z.string()
})
.optional()
.describe(INTEGRATION.CREATE.metadata.secretGCPLabel),
secretAWSTag: z
.array(
z.object({
key: z.string(),
value: z.string()
})
)
.optional()
.describe(INTEGRATION.CREATE.metadata.secretAWSTag),
kmsKeyId: z.string().optional().describe(INTEGRATION.CREATE.metadata.kmsKeyId),
shouldDisableDelete: z.boolean().optional().describe(INTEGRATION.CREATE.metadata.shouldDisableDelete)
})
.optional()
}), }),
response: { response: {
200: z.object({ 200: z.object({

View File

@ -31,6 +31,7 @@ import { logger } from "@app/lib/logger";
import { TCreateManySecretsRawFn, TUpdateManySecretsRawFn } from "@app/services/secret/secret-types"; import { TCreateManySecretsRawFn, TUpdateManySecretsRawFn } from "@app/services/secret/secret-types";
import { TIntegrationDALFactory } from "../integration/integration-dal"; import { TIntegrationDALFactory } from "../integration/integration-dal";
import { IntegrationMetadataSchema } from "../integration/integration-schema";
import { import {
IntegrationInitialSyncBehavior, IntegrationInitialSyncBehavior,
IntegrationMappingBehavior, IntegrationMappingBehavior,
@ -1363,38 +1364,41 @@ const syncSecretsGitHub = async ({
} }
} }
for await (const encryptedSecret of encryptedSecrets) { const metadata = IntegrationMetadataSchema.parse(integration.metadata);
if ( if (metadata.shouldEnableDelete) {
!(encryptedSecret.name in secrets) && for await (const encryptedSecret of encryptedSecrets) {
!(appendices?.prefix !== undefined && !encryptedSecret.name.startsWith(appendices?.prefix)) && if (
!(appendices?.suffix !== undefined && !encryptedSecret.name.endsWith(appendices?.suffix)) !(encryptedSecret.name in secrets) &&
) { !(appendices?.prefix !== undefined && !encryptedSecret.name.startsWith(appendices?.prefix)) &&
switch (integration.scope) { !(appendices?.suffix !== undefined && !encryptedSecret.name.endsWith(appendices?.suffix))
case GithubScope.Org: { ) {
await octokit.request("DELETE /orgs/{org}/actions/secrets/{secret_name}", { switch (integration.scope) {
org: integration.owner as string, case GithubScope.Org: {
secret_name: encryptedSecret.name await octokit.request("DELETE /orgs/{org}/actions/secrets/{secret_name}", {
}); org: integration.owner as string,
break;
}
case GithubScope.Env: {
await octokit.request(
"DELETE /repositories/{repository_id}/environments/{environment_name}/secrets/{secret_name}",
{
repository_id: Number(integration.appId),
environment_name: integration.targetEnvironmentId as string,
secret_name: encryptedSecret.name secret_name: encryptedSecret.name
} });
); break;
break; }
} case GithubScope.Env: {
default: { await octokit.request(
await octokit.request("DELETE /repos/{owner}/{repo}/actions/secrets/{secret_name}", { "DELETE /repositories/{repository_id}/environments/{environment_name}/secrets/{secret_name}",
owner: integration.owner as string, {
repo: integration.app as string, repository_id: Number(integration.appId),
secret_name: encryptedSecret.name environment_name: integration.targetEnvironmentId as string,
}); secret_name: encryptedSecret.name
break; }
);
break;
}
default: {
await octokit.request("DELETE /repos/{owner}/{repo}/actions/secrets/{secret_name}", {
owner: integration.owner as string,
repo: integration.app as string,
secret_name: encryptedSecret.name
});
break;
}
} }
} }
} }

View File

@ -0,0 +1,35 @@
import { z } from "zod";
import { INTEGRATION } from "@app/lib/api-docs";
import { IntegrationMappingBehavior } from "../integration-auth/integration-list";
export const IntegrationMetadataSchema = z.object({
secretPrefix: z.string().optional().describe(INTEGRATION.CREATE.metadata.secretPrefix),
secretSuffix: z.string().optional().describe(INTEGRATION.CREATE.metadata.secretSuffix),
initialSyncBehavior: z.string().optional().describe(INTEGRATION.CREATE.metadata.initialSyncBehavoir),
mappingBehavior: z
.nativeEnum(IntegrationMappingBehavior)
.optional()
.describe(INTEGRATION.CREATE.metadata.mappingBehavior),
shouldAutoRedeploy: z.boolean().optional().describe(INTEGRATION.CREATE.metadata.shouldAutoRedeploy),
secretGCPLabel: z
.object({
labelName: z.string(),
labelValue: z.string()
})
.optional()
.describe(INTEGRATION.CREATE.metadata.secretGCPLabel),
secretAWSTag: z
.array(
z.object({
key: z.string(),
value: z.string()
})
)
.optional()
.describe(INTEGRATION.CREATE.metadata.secretAWSTag),
kmsKeyId: z.string().optional().describe(INTEGRATION.CREATE.metadata.kmsKeyId),
shouldDisableDelete: z.boolean().optional().describe(INTEGRATION.CREATE.metadata.shouldDisableDelete),
shouldEnableDelete: z.boolean().optional().describe(INTEGRATION.CREATE.metadata.shouldEnableDelete)
});

View File

@ -29,6 +29,7 @@ export type TCreateIntegrationDTO = {
}[]; }[];
kmsKeyId?: string; kmsKeyId?: string;
shouldDisableDelete?: boolean; shouldDisableDelete?: boolean;
shouldEnableDelete?: boolean;
}; };
} & Omit<TProjectPermission, "projectId">; } & Omit<TProjectPermission, "projectId">;
@ -54,6 +55,7 @@ export type TUpdateIntegrationDTO = {
}[]; }[];
kmsKeyId?: string; kmsKeyId?: string;
shouldDisableDelete?: boolean; shouldDisableDelete?: boolean;
shouldEnableDelete?: boolean;
}; };
} & Omit<TProjectPermission, "projectId">; } & Omit<TProjectPermission, "projectId">;

View File

@ -73,6 +73,7 @@ export const useCreateIntegration = () => {
}[]; }[];
kmsKeyId?: string; kmsKeyId?: string;
shouldDisableDelete?: boolean; shouldDisableDelete?: boolean;
shouldEnableDelete?: boolean;
}; };
}) => { }) => {
const { const {

View File

@ -33,6 +33,7 @@ import {
Input, Input,
Select, Select,
SelectItem, SelectItem,
Switch,
Tab, Tab,
TabList, TabList,
TabPanel, TabPanel,
@ -59,7 +60,7 @@ const schema = yup.object({
selectedSourceEnvironment: yup.string().trim().required("Project Environment is required"), selectedSourceEnvironment: yup.string().trim().required("Project Environment is required"),
secretPath: yup.string().trim().required("Secrets Path is required"), secretPath: yup.string().trim().required("Secrets Path is required"),
secretSuffix: yup.string().trim().optional(), secretSuffix: yup.string().trim().optional(),
shouldEnableDelete: yup.boolean().optional(),
scope: yup.mixed<TargetEnv>().oneOf(targetEnv.slice()).required(), scope: yup.mixed<TargetEnv>().oneOf(targetEnv.slice()).required(),
repoIds: yup.mixed().when("scope", { repoIds: yup.mixed().when("scope", {
@ -98,7 +99,6 @@ type FormData = yup.InferType<typeof schema>;
export default function GitHubCreateIntegrationPage() { export default function GitHubCreateIntegrationPage() {
const router = useRouter(); const router = useRouter();
const { mutateAsync } = useCreateIntegration(); const { mutateAsync } = useCreateIntegration();
const integrationAuthId = const integrationAuthId =
(queryString.parse(router.asPath.split("?")[1]).integrationAuthId as string) ?? ""; (queryString.parse(router.asPath.split("?")[1]).integrationAuthId as string) ?? "";
@ -120,7 +120,8 @@ export default function GitHubCreateIntegrationPage() {
defaultValues: { defaultValues: {
secretPath: "/", secretPath: "/",
scope: "github-repo", scope: "github-repo",
repoIds: [] repoIds: [],
shouldEnableDelete: false
} }
}); });
@ -177,7 +178,8 @@ export default function GitHubCreateIntegrationPage() {
app: targetApp.name, // repo name app: targetApp.name, // repo name
owner: targetApp.owner, // repo owner owner: targetApp.owner, // repo owner
metadata: { metadata: {
secretSuffix: data.secretSuffix secretSuffix: data.secretSuffix,
shouldEnableDelete: data.shouldEnableDelete
} }
}); });
}) })
@ -194,7 +196,8 @@ export default function GitHubCreateIntegrationPage() {
scope: data.scope, scope: data.scope,
owner: integrationAuthOrgs?.find((e) => e.orgId === data.orgId)?.name, owner: integrationAuthOrgs?.find((e) => e.orgId === data.orgId)?.name,
metadata: { metadata: {
secretSuffix: data.secretSuffix secretSuffix: data.secretSuffix,
shouldEnableDelete: data.shouldEnableDelete
} }
}); });
break; break;
@ -211,7 +214,8 @@ export default function GitHubCreateIntegrationPage() {
owner: repoOwner, owner: repoOwner,
targetEnvironmentId: data.envId, targetEnvironmentId: data.envId,
metadata: { metadata: {
secretSuffix: data.secretSuffix secretSuffix: data.secretSuffix,
shouldEnableDelete: data.shouldEnableDelete
} }
}); });
break; break;
@ -546,6 +550,21 @@ export default function GitHubCreateIntegrationPage() {
animate={{ opacity: 1, translateX: 0 }} animate={{ opacity: 1, translateX: 0 }}
exit={{ opacity: 0, translateX: 30 }} exit={{ opacity: 0, translateX: 30 }}
> >
<div className="ml-1 mb-5">
<Controller
control={control}
name="shouldEnableDelete"
render={({ field: { onChange, value } }) => (
<Switch
id="delete-github-option"
onCheckedChange={(isChecked) => onChange(isChecked)}
isChecked={value}
>
Delete secrets in Github that are not in Infisical
</Switch>
)}
/>
</div>
<Controller <Controller
control={control} control={control}
name="secretSuffix" name="secretSuffix"