allow specifying of aws kms key

This commit is contained in:
Vladyslav Matsiiako
2024-04-20 18:40:56 -07:00
parent 714a3186a9
commit d11ded9abc
16 changed files with 213 additions and 16 deletions
+2 -1
View File
@@ -590,7 +590,8 @@ export const INTEGRATION = {
initialSyncBehavoir: "Type of syncing behavoir with the integration.", initialSyncBehavoir: "Type of syncing behavoir with the integration.",
shouldAutoRedeploy: "Used by Render to trigger auto deploy.", shouldAutoRedeploy: "Used by Render to trigger auto deploy.",
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."
} }
}, },
UPDATE: { UPDATE: {
@@ -511,6 +511,39 @@ export const registerIntegrationAuthRouter = async (server: FastifyZodProvider)
} }
}); });
server.route({
method: "GET",
url: "/:integrationAuthId/aws-secrets-manager/kms-keys",
config: {
rateLimit: readLimit
},
onRequest: verifyAuth([AuthMode.JWT]),
schema: {
params: z.object({
integrationAuthId: z.string().trim()
}),
querystring: z.object({
region: z.string().trim()
}),
response: {
200: z.object({
kmsKeys: z.object({ id: z.string(), alias: z.string() }).array()
})
}
},
handler: async (req) => {
const kmsKeys = await server.services.integrationAuth.getAwsKmsKeys({
actorId: req.permission.id,
actor: req.permission.type,
actorAuthMethod: req.permission.authMethod,
actorOrgId: req.permission.orgId,
id: req.params.integrationAuthId,
region: req.query.region
});
return { kmsKeys };
}
});
server.route({ server.route({
method: "GET", method: "GET",
url: "/:integrationAuthId/qovery/projects", url: "/:integrationAuthId/qovery/projects",
@@ -65,7 +65,8 @@ 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)
}) })
.optional() .optional()
}), }),
@@ -1,5 +1,6 @@
import { ForbiddenError } from "@casl/ability"; import { ForbiddenError } from "@casl/ability";
import { Octokit } from "@octokit/rest"; import { Octokit } from "@octokit/rest";
import AWS from "aws-sdk";
import { SecretEncryptionAlgo, SecretKeyEncoding, TIntegrationAuths, TIntegrationAuthsInsert } from "@app/db/schemas"; import { SecretEncryptionAlgo, SecretKeyEncoding, TIntegrationAuths, TIntegrationAuthsInsert } from "@app/db/schemas";
import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service"; import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service";
@@ -23,6 +24,7 @@ import {
TGetIntegrationAuthTeamCityBuildConfigDTO, TGetIntegrationAuthTeamCityBuildConfigDTO,
THerokuPipelineCoupling, THerokuPipelineCoupling,
TIntegrationAuthAppsDTO, TIntegrationAuthAppsDTO,
TIntegrationAuthAwsKmsKeyDTO,
TIntegrationAuthBitbucketWorkspaceDTO, TIntegrationAuthBitbucketWorkspaceDTO,
TIntegrationAuthChecklyGroupsDTO, TIntegrationAuthChecklyGroupsDTO,
TIntegrationAuthGithubEnvsDTO, TIntegrationAuthGithubEnvsDTO,
@@ -534,6 +536,52 @@ export const integrationAuthServiceFactory = ({
return data.results.map(({ name, id: orgId }) => ({ name, orgId })); return data.results.map(({ name, id: orgId }) => ({ name, orgId }));
}; };
const getAwsKmsKeys = async ({
actorId,
actor,
actorOrgId,
actorAuthMethod,
id,
region
}: TIntegrationAuthAwsKmsKeyDTO) => {
const integrationAuth = await integrationAuthDAL.findById(id);
if (!integrationAuth) throw new BadRequestError({ message: "Failed to find integration" });
const { permission } = await permissionService.getProjectPermission(
actor,
actorId,
integrationAuth.projectId,
actorAuthMethod,
actorOrgId
);
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Read, ProjectPermissionSub.Integrations);
const botKey = await projectBotService.getBotKey(integrationAuth.projectId);
const { accessId, accessToken } = await getIntegrationAccessToken(integrationAuth, botKey);
AWS.config.update({
region,
credentials: {
accessKeyId: String(accessId),
secretAccessKey: accessToken
}
});
const kms = new AWS.KMS();
const aliases = await kms.listAliases({}).promise();
const keys = await kms.listKeys({}).promise();
const response = keys
.Keys!.map((key) => {
const keyAlias = aliases.Aliases!.find((alias) => key.KeyId === alias.TargetKeyId);
if (!keyAlias?.AliasName?.includes("alias/aws/") || keyAlias?.AliasName?.includes("alias/aws/secretsmanager")) {
return { id: String(key.KeyId), alias: String(keyAlias?.AliasName || key.KeyId) };
}
return { id: "null", alias: "null" };
})
.filter((elem) => elem.id !== "null");
return response;
};
const getQoveryProjects = async ({ const getQoveryProjects = async ({
actorId, actorId,
actor, actor,
@@ -1133,6 +1181,7 @@ export const integrationAuthServiceFactory = ({
getIntegrationApps, getIntegrationApps,
getVercelBranches, getVercelBranches,
getApps, getApps,
getAwsKmsKeys,
getGithubOrgs, getGithubOrgs,
getGithubEnvs, getGithubEnvs,
getChecklyGroups, getChecklyGroups,
@@ -63,6 +63,11 @@ export type TIntegrationAuthQoveryProjectDTO = {
orgId: string; orgId: string;
} & Omit<TProjectPermission, "projectId">; } & Omit<TProjectPermission, "projectId">;
export type TIntegrationAuthAwsKmsKeyDTO = {
id: string;
region: string;
} & Omit<TProjectPermission, "projectId">;
export type TIntegrationAuthQoveryEnvironmentsDTO = { export type TIntegrationAuthQoveryEnvironmentsDTO = {
id: string; id: string;
} & TProjectPermission; } & TProjectPermission;
@@ -582,6 +582,7 @@ const syncSecretsAWSSecretManager = async ({
new CreateSecretCommand({ new CreateSecretCommand({
Name: integration.app as string, Name: integration.app as string,
SecretString: JSON.stringify(secKeyVal), SecretString: JSON.stringify(secKeyVal),
KmsKeyId: metadata.kmsKeyId ? metadata.kmsKeyId : null,
Tags: metadata.secretAWSTag Tags: metadata.secretAWSTag
? metadata.secretAWSTag.map((tag: { key: string; value: string }) => ({ Key: tag.key, Value: tag.value })) ? metadata.secretAWSTag.map((tag: { key: string; value: string }) => ({ Key: tag.key, Value: tag.value }))
: [] : []
@@ -26,6 +26,7 @@ export type TCreateIntegrationDTO = {
key: string; key: string;
value: string; value: string;
}[]; }[];
kmsKeyId?: string;
}; };
} & Omit<TProjectPermission, "projectId">; } & Omit<TProjectPermission, "projectId">;
Binary file not shown.

Before

Width:  |  Height:  |  Size: 584 KiB

After

Width:  |  Height:  |  Size: 162 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 74 KiB

@@ -30,7 +30,7 @@ Prerequisites:
"ssm:DeleteParameter", "ssm:DeleteParameter",
"ssm:GetParametersByPath", "ssm:GetParametersByPath",
"ssm:DeleteParameters", "ssm:DeleteParameters",
"ssm:AddTagsToResource" "ssm:AddTagsToResource" // if you need to add tags to secrets
], ],
"Resource": "*" "Resource": "*"
} }
+14 -10
View File
@@ -29,7 +29,8 @@ Prerequisites:
"secretsmanager:GetSecretValue", "secretsmanager:GetSecretValue",
"secretsmanager:CreateSecret", "secretsmanager:CreateSecret",
"secretsmanager:UpdateSecret", "secretsmanager:UpdateSecret",
"secretsmanager:TagResource" "secretsmanager:TagResource", // if you need to add tags to secrets
"kms:ListKeys" // if you need to specify the KMS key
], ],
"Resource": "*" "Resource": "*"
} }
@@ -51,13 +52,6 @@ Prerequisites:
Press on the AWS Secrets Manager tile and input your AWS access key ID and secret access key from the previous step. Press on the AWS Secrets Manager tile and input your AWS access key ID and secret access key from the previous step.
![integration auth](../../images/integrations/aws/integrations-aws-secret-manager-auth.png) ![integration auth](../../images/integrations/aws/integrations-aws-secret-manager-auth.png)
<Info>
If this is your project's first cloud integration, then you'll have to grant
Infisical access to your project's environment variables. Although this step
breaks E2EE, it's necessary for Infisical to sync the environment variables to
the cloud platform.
</Info>
</Step> </Step>
<Step title="Start integration"> <Step title="Start integration">
Select how you want to integration to work by specifying a number of parameters: Select how you want to integration to work by specifying a number of parameters:
@@ -75,10 +69,20 @@ Prerequisites:
The secret name/path in AWS into which you want to sync the secrets from Infisical. The secret name/path in AWS into which you want to sync the secrets from Infisical.
</ParamField> </ParamField>
Then, press `Create Integration` to start syncing secrets to AWS Secrets Manager.
![integration create](../../images/integrations/aws/integrations-aws-secret-manager-create.png) ![integration create](../../images/integrations/aws/integrations-aws-secret-manager-create.png)
Optionally, you can add tags or specify the encryption key of all the secrets created via this integration:
<ParamField path="Secret Tag" type="string" optional>
The Key/Value of a tag that will be added to secrets in AWS. Please note that it is possible to add multiple tags via API.
</ParamField>
<ParamField path="Encryption Key" type="string" optional>
The alias/ID of the AWS KMS key used for encryption. Please note that keys should be enabled in order to work.
</ParamField>
![integration options](../../images/integrations/aws/integrations-aws-secret-manager-options.png)
Then, press `Create Integration` to start syncing secrets to AWS Secrets Manager.
<Info> <Info>
Infisical currently syncs environment variables to AWS Secrets Manager as Infisical currently syncs environment variables to AWS Secrets Manager as
key-value pairs under one secret. We're actively exploring ways to help users key-value pairs under one secret. We're actively exploring ways to help users
@@ -10,6 +10,7 @@ import {
Environment, Environment,
HerokuPipelineCoupling, HerokuPipelineCoupling,
IntegrationAuth, IntegrationAuth,
KmsKey,
NorthflankSecretGroup, NorthflankSecretGroup,
Org, Org,
Project, Project,
@@ -43,6 +44,14 @@ const integrationAuthKeys = {
[{ integrationAuthId }, "integrationAuthGithubOrgs"] as const, [{ integrationAuthId }, "integrationAuthGithubOrgs"] as const,
getIntegrationAuthGithubEnvs: (integrationAuthId: string, repoName: string, repoOwner: string) => getIntegrationAuthGithubEnvs: (integrationAuthId: string, repoName: string, repoOwner: string) =>
[{ integrationAuthId, repoName, repoOwner }, "integrationAuthGithubOrgs"] as const, [{ integrationAuthId, repoName, repoOwner }, "integrationAuthGithubOrgs"] as const,
getIntegrationAuthAwsKmsKeys: ({
integrationAuthId,
region
}: {
integrationAuthId: string,
region: string
}) =>
[{ integrationAuthId, region }, "integrationAuthAwsKmsKeyIds"] as const,
getIntegrationAuthQoveryOrgs: (integrationAuthId: string) => getIntegrationAuthQoveryOrgs: (integrationAuthId: string) =>
[{ integrationAuthId }, "integrationAuthQoveryOrgs"] as const, [{ integrationAuthId }, "integrationAuthQoveryOrgs"] as const,
getIntegrationAuthQoveryProjects: ({ getIntegrationAuthQoveryProjects: ({
@@ -217,6 +226,27 @@ const fetchIntegrationAuthQoveryOrgs = async (integrationAuthId: string) => {
return orgs; return orgs;
}; };
const fetchIntegrationAuthAwsKmsKeys = async ({
integrationAuthId,
region
}: {
integrationAuthId: string;
region: string;
}) => {
const {
data: { kmsKeys }
} = await apiRequest.get<{ kmsKeys: KmsKey[] }>(
`/api/v1/integration-auth/${integrationAuthId}/aws-secrets-manager/kms-keys`,
{
params: {
region
}
}
);
return kmsKeys;
};
const fetchIntegrationAuthQoveryProjects = async ({ const fetchIntegrationAuthQoveryProjects = async ({
integrationAuthId, integrationAuthId,
orgId orgId
@@ -544,6 +574,27 @@ export const useGetIntegrationAuthQoveryOrgs = (integrationAuthId: string) => {
}); });
}; };
export const useGetIntegrationAuthAwsKmsKeys = ({
integrationAuthId,
region
}: {
integrationAuthId: string;
region: string;
}) => {
return useQuery({
queryKey: integrationAuthKeys.getIntegrationAuthAwsKmsKeys({
integrationAuthId,
region
}),
queryFn: () =>
fetchIntegrationAuthAwsKmsKeys({
integrationAuthId,
region
}),
enabled: true
});
};
export const useGetIntegrationAuthQoveryProjects = ({ export const useGetIntegrationAuthQoveryProjects = ({
integrationAuthId, integrationAuthId,
orgId orgId
@@ -58,6 +58,11 @@ export type Project = {
projectId: string; projectId: string;
}; };
export type KmsKey = {
id: string;
alias: string;
};
export type Service = { export type Service = {
name: string; name: string;
serviceId: string; serviceId: string;
@@ -67,6 +67,7 @@ export const useCreateIntegration = () => {
key: string; key: string;
value: string; value: string;
}[]; }[];
kmsKeyId?: string;
}; };
}) => { }) => {
const { const {
@@ -14,6 +14,7 @@ import { motion } from "framer-motion";
import queryString from "query-string"; import queryString from "query-string";
import { useCreateIntegration } from "@app/hooks/api"; import { useCreateIntegration } from "@app/hooks/api";
import { useGetIntegrationAuthAwsKmsKeys } from "@app/hooks/api/integrationAuth/queries";
import { import {
Button, Button,
@@ -87,6 +88,7 @@ export default function AWSSecretManagerCreateIntegrationPage() {
const [targetSecretNameErrorText, setTargetSecretNameErrorText] = useState(""); const [targetSecretNameErrorText, setTargetSecretNameErrorText] = useState("");
const [tagKey, setTagKey] = useState(""); const [tagKey, setTagKey] = useState("");
const [tagValue, setTagValue] = useState(""); const [tagValue, setTagValue] = useState("");
const [kmsKeyId, setKmsKeyId] = useState("");
// const [path, setPath] = useState(''); // const [path, setPath] = useState('');
// const [pathErrorText, setPathErrorText] = useState(''); // const [pathErrorText, setPathErrorText] = useState('');
@@ -94,6 +96,19 @@ export default function AWSSecretManagerCreateIntegrationPage() {
const [isLoading, setIsLoading] = useState(false); const [isLoading, setIsLoading] = useState(false);
const [shouldTag, setShouldTag] = useState(false); const [shouldTag, setShouldTag] = useState(false);
const { data: integrationAuthAwsKmsKeys, isLoading: isIntegrationAuthAwsKmsKeysLoading } =
useGetIntegrationAuthAwsKmsKeys({
integrationAuthId: String(integrationAuthId),
region: selectedAWSRegion
});
useEffect(() => {
if (integrationAuthAwsKmsKeys) {
setKmsKeyId(String(integrationAuthAwsKmsKeys?.filter(key => key.alias === "alias/aws/secretsmanager")[0]?.id))
}
}, [integrationAuthAwsKmsKeys])
useEffect(() => { useEffect(() => {
if (workspace) { if (workspace) {
setSelectedSourceEnvironment(workspace.environments[0].slug); setSelectedSourceEnvironment(workspace.environments[0].slug);
@@ -132,7 +147,11 @@ export default function AWSSecretManagerCreateIntegrationPage() {
value: tagValue value: tagValue
}] }]
} }
: {}) : {}),
...((kmsKeyId && integrationAuthAwsKmsKeys?.filter(key => key.id === kmsKeyId)[0]?.alias !== "alias/aws/secretsmanager") ?
{
kmsKeyId
}: {})
} }
}); });
@@ -145,7 +164,7 @@ export default function AWSSecretManagerCreateIntegrationPage() {
} }
}; };
return integrationAuth && workspace && selectedSourceEnvironment ? ( return (integrationAuth && workspace && selectedSourceEnvironment && !isIntegrationAuthAwsKmsKeysLoading) ? (
<div className="flex h-full w-full flex-col items-center justify-center"> <div className="flex h-full w-full flex-col items-center justify-center">
<Head> <Head>
<title>Set Up AWS Secrets Manager Integration</title> <title>Set Up AWS Secrets Manager Integration</title>
@@ -285,6 +304,31 @@ export default function AWSSecretManagerCreateIntegrationPage() {
</FormControl> </FormControl>
</div> </div>
)} )}
<FormControl label="Encryption Key" className="mt-4">
<Select
value={kmsKeyId}
onValueChange={(e) => {
setKmsKeyId(e)
}}
className="w-full border border-mineshaft-500"
>
{integrationAuthAwsKmsKeys?.length ? (
integrationAuthAwsKmsKeys.map((key) => {
return (
<SelectItem
value={key.id as string}
key={`repo-id-${key.id}`}
className="w-[28.4rem] text-sm"
>
{key.alias}
</SelectItem>
);
})
) : (
<div />
)}
</Select>
</FormControl>
</motion.div> </motion.div>
</TabPanel> </TabPanel>
</Tabs> </Tabs>
@@ -317,7 +361,7 @@ export default function AWSSecretManagerCreateIntegrationPage() {
<title>Set Up AWS Secrets Manager Integration</title> <title>Set Up AWS Secrets Manager Integration</title>
<link rel="icon" href="/infisical.ico" /> <link rel="icon" href="/infisical.ico" />
</Head> </Head>
{isintegrationAuthLoading ? ( {(isintegrationAuthLoading || isIntegrationAuthAwsKmsKeysLoading) ? (
<img <img
src="/images/loading/loading.gif" src="/images/loading/loading.gif"
height={70} height={70}
@@ -9,4 +9,5 @@ export type Metadata = {
key: string; key: string;
value: string; value: string;
}[] }[]
kmsKeyId?: string;
} }