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

View File

@@ -590,7 +590,8 @@ export const INTEGRATION = {
initialSyncBehavoir: "Type of syncing behavoir with the integration.",
shouldAutoRedeploy: "Used by Render to trigger auto deploy.",
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: {

View File

@@ -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({
method: "GET",
url: "/:integrationAuthId/qovery/projects",

View File

@@ -65,7 +65,8 @@ export const registerIntegrationRouter = async (server: FastifyZodProvider) => {
})
)
.optional()
.describe(INTEGRATION.CREATE.metadata.secretAWSTag)
.describe(INTEGRATION.CREATE.metadata.secretAWSTag),
kmsKeyId: z.string().optional().describe(INTEGRATION.CREATE.metadata.kmsKeyId)
})
.optional()
}),

View File

@@ -1,5 +1,6 @@
import { ForbiddenError } from "@casl/ability";
import { Octokit } from "@octokit/rest";
import AWS from "aws-sdk";
import { SecretEncryptionAlgo, SecretKeyEncoding, TIntegrationAuths, TIntegrationAuthsInsert } from "@app/db/schemas";
import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service";
@@ -23,6 +24,7 @@ import {
TGetIntegrationAuthTeamCityBuildConfigDTO,
THerokuPipelineCoupling,
TIntegrationAuthAppsDTO,
TIntegrationAuthAwsKmsKeyDTO,
TIntegrationAuthBitbucketWorkspaceDTO,
TIntegrationAuthChecklyGroupsDTO,
TIntegrationAuthGithubEnvsDTO,
@@ -534,6 +536,52 @@ export const integrationAuthServiceFactory = ({
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 ({
actorId,
actor,
@@ -1133,6 +1181,7 @@ export const integrationAuthServiceFactory = ({
getIntegrationApps,
getVercelBranches,
getApps,
getAwsKmsKeys,
getGithubOrgs,
getGithubEnvs,
getChecklyGroups,

View File

@@ -63,6 +63,11 @@ export type TIntegrationAuthQoveryProjectDTO = {
orgId: string;
} & Omit<TProjectPermission, "projectId">;
export type TIntegrationAuthAwsKmsKeyDTO = {
id: string;
region: string;
} & Omit<TProjectPermission, "projectId">;
export type TIntegrationAuthQoveryEnvironmentsDTO = {
id: string;
} & TProjectPermission;

View File

@@ -582,6 +582,7 @@ const syncSecretsAWSSecretManager = async ({
new CreateSecretCommand({
Name: integration.app as string,
SecretString: JSON.stringify(secKeyVal),
KmsKeyId: metadata.kmsKeyId ? metadata.kmsKeyId : null,
Tags: metadata.secretAWSTag
? metadata.secretAWSTag.map((tag: { key: string; value: string }) => ({ Key: tag.key, Value: tag.value }))
: []

View File

@@ -26,6 +26,7 @@ export type TCreateIntegrationDTO = {
key: string;
value: string;
}[];
kmsKeyId?: string;
};
} & 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

View File

@@ -30,7 +30,7 @@ Prerequisites:
"ssm:DeleteParameter",
"ssm:GetParametersByPath",
"ssm:DeleteParameters",
"ssm:AddTagsToResource"
"ssm:AddTagsToResource" // if you need to add tags to secrets
],
"Resource": "*"
}

View File

@@ -29,7 +29,8 @@ Prerequisites:
"secretsmanager:GetSecretValue",
"secretsmanager:CreateSecret",
"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": "*"
}
@@ -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.
![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 title="Start integration">
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.
</ParamField>
Then, press `Create Integration` to start syncing secrets to AWS Secrets Manager.
![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>
Infisical currently syncs environment variables to AWS Secrets Manager as
key-value pairs under one secret. We're actively exploring ways to help users

View File

@@ -10,6 +10,7 @@ import {
Environment,
HerokuPipelineCoupling,
IntegrationAuth,
KmsKey,
NorthflankSecretGroup,
Org,
Project,
@@ -43,6 +44,14 @@ const integrationAuthKeys = {
[{ integrationAuthId }, "integrationAuthGithubOrgs"] as const,
getIntegrationAuthGithubEnvs: (integrationAuthId: string, repoName: string, repoOwner: string) =>
[{ integrationAuthId, repoName, repoOwner }, "integrationAuthGithubOrgs"] as const,
getIntegrationAuthAwsKmsKeys: ({
integrationAuthId,
region
}: {
integrationAuthId: string,
region: string
}) =>
[{ integrationAuthId, region }, "integrationAuthAwsKmsKeyIds"] as const,
getIntegrationAuthQoveryOrgs: (integrationAuthId: string) =>
[{ integrationAuthId }, "integrationAuthQoveryOrgs"] as const,
getIntegrationAuthQoveryProjects: ({
@@ -217,6 +226,27 @@ const fetchIntegrationAuthQoveryOrgs = async (integrationAuthId: string) => {
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 ({
integrationAuthId,
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 = ({
integrationAuthId,
orgId

View File

@@ -58,6 +58,11 @@ export type Project = {
projectId: string;
};
export type KmsKey = {
id: string;
alias: string;
};
export type Service = {
name: string;
serviceId: string;

View File

@@ -67,6 +67,7 @@ export const useCreateIntegration = () => {
key: string;
value: string;
}[];
kmsKeyId?: string;
};
}) => {
const {

View File

@@ -14,6 +14,7 @@ import { motion } from "framer-motion";
import queryString from "query-string";
import { useCreateIntegration } from "@app/hooks/api";
import { useGetIntegrationAuthAwsKmsKeys } from "@app/hooks/api/integrationAuth/queries";
import {
Button,
@@ -87,6 +88,7 @@ export default function AWSSecretManagerCreateIntegrationPage() {
const [targetSecretNameErrorText, setTargetSecretNameErrorText] = useState("");
const [tagKey, setTagKey] = useState("");
const [tagValue, setTagValue] = useState("");
const [kmsKeyId, setKmsKeyId] = useState("");
// const [path, setPath] = useState('');
// const [pathErrorText, setPathErrorText] = useState('');
@@ -94,6 +96,19 @@ export default function AWSSecretManagerCreateIntegrationPage() {
const [isLoading, setIsLoading] = 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(() => {
if (workspace) {
setSelectedSourceEnvironment(workspace.environments[0].slug);
@@ -132,7 +147,11 @@ export default function AWSSecretManagerCreateIntegrationPage() {
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">
<Head>
<title>Set Up AWS Secrets Manager Integration</title>
@@ -285,6 +304,31 @@ export default function AWSSecretManagerCreateIntegrationPage() {
</FormControl>
</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>
</TabPanel>
</Tabs>
@@ -317,7 +361,7 @@ export default function AWSSecretManagerCreateIntegrationPage() {
<title>Set Up AWS Secrets Manager Integration</title>
<link rel="icon" href="/infisical.ico" />
</Head>
{isintegrationAuthLoading ? (
{(isintegrationAuthLoading || isIntegrationAuthAwsKmsKeysLoading) ? (
<img
src="/images/loading/loading.gif"
height={70}

View File

@@ -9,4 +9,5 @@ export type Metadata = {
key: string;
value: string;
}[]
kmsKeyId?: string;
}