Merge pull request #2394 from Infisical/misc/update-kms-of-existing-params-for-integration

misc: ensure that selected kms key in aws param integration is followed
This commit is contained in:
Sheen
2024-09-10 12:51:06 +08:00
committed by GitHub
2 changed files with 71 additions and 24 deletions
@@ -567,8 +567,8 @@ const syncSecretsAWSParameterStore = async ({
}); });
ssm.config.update(config); ssm.config.update(config);
const metadata = z.record(z.any()).parse(integration.metadata || {}); const metadata = IntegrationMetadataSchema.parse(integration.metadata);
const awsParameterStoreSecretsObj: Record<string, AWS.SSM.Parameter> = {}; const awsParameterStoreSecretsObj: Record<string, AWS.SSM.Parameter & { KeyId?: string }> = {};
logger.info( logger.info(
`getIntegrationSecrets: integration sync triggered for ssm with [projectId=${projectId}] [environment=${integration.environment.slug}] [secretPath=${integration.secretPath}] [shouldDisableDelete=${metadata.shouldDisableDelete}]` `getIntegrationSecrets: integration sync triggered for ssm with [projectId=${projectId}] [environment=${integration.environment.slug}] [secretPath=${integration.secretPath}] [shouldDisableDelete=${metadata.shouldDisableDelete}]`
); );
@@ -598,18 +598,57 @@ const syncSecretsAWSParameterStore = async ({
nextToken = parameters.NextToken; nextToken = parameters.NextToken;
} }
logger.info( let areParametersKmsKeysFetched = false;
`getIntegrationSecrets: all fetched keys from AWS SSM [projectId=${projectId}] [environment=${
integration.environment.slug if (metadata.kmsKeyId) {
}] [secretPath=${integration.secretPath}] [awsParameterStoreSecretsObj=${Object.keys( // we put this inside a try catch so that existing integrations without the ssm:DescribeParameters
awsParameterStoreSecretsObj // AWS permission will not break
).join(",")}]` try {
); let hasNextDescribePage = true;
logger.info( let describeNextToken: string | undefined;
`getIntegrationSecrets: all secrets from Infisical to send to AWS SSM [projectId=${projectId}] [environment=${
integration.environment.slug while (hasNextDescribePage) {
}] [secretPath=${integration.secretPath}] [secrets=${Object.keys(secrets).join(",")}]` const parameters = await ssm
); .describeParameters({
MaxResults: 10,
NextToken: describeNextToken,
ParameterFilters: [
{
Key: "Path",
Option: "OneLevel",
Values: [integration.path as string]
}
]
})
.promise();
if (parameters.Parameters) {
parameters.Parameters.forEach((parameter) => {
if (parameter.Name) {
const secKey = parameter.Name.substring((integration.path as string).length);
awsParameterStoreSecretsObj[secKey].KeyId = parameter.KeyId;
}
});
}
areParametersKmsKeysFetched = true;
hasNextDescribePage = Boolean(parameters.NextToken);
describeNextToken = parameters.NextToken;
}
} catch (error) {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
if ((error as any).code === "AccessDeniedException") {
logger.error(
`AWS Parameter Store Error [integration=${integration.id}]: double check AWS account permissions (refer to the Infisical docs)`
);
}
response = {
isSynced: false,
syncMessage: (error as AWSError)?.message || "Error syncing with AWS Parameter Store"
};
}
}
// Identify secrets to create // Identify secrets to create
// don't use Promise.all() and promise map here // don't use Promise.all() and promise map here
// it will cause rate limit // it will cause rate limit
@@ -620,7 +659,7 @@ const syncSecretsAWSParameterStore = async ({
// -> create secret // -> create secret
if (secrets[key].value) { if (secrets[key].value) {
logger.info( logger.info(
`getIntegrationSecrets: create secret in AWS SSM for [projectId=${projectId}] [environment=${integration.environment.slug}] [secretPath=${integration.secretPath}] [key=${key}]` `getIntegrationSecrets: create secret in AWS SSM for [projectId=${projectId}] [environment=${integration.environment.slug}] [secretPath=${integration.secretPath}]`
); );
await ssm await ssm
.putParameter({ .putParameter({
@@ -648,7 +687,7 @@ const syncSecretsAWSParameterStore = async ({
} catch (err) { } catch (err) {
logger.error( logger.error(
err, err,
`getIntegrationSecrets: create secret in AWS SSM for failed [projectId=${projectId}] [environment=${integration.environment.slug}] [secretPath=${integration.secretPath}] [key=${key}]` `getIntegrationSecrets: create secret in AWS SSM for failed [projectId=${projectId}] [environment=${integration.environment.slug}] [secretPath=${integration.secretPath}]`
); );
// eslint-disable-next-line @typescript-eslint/no-explicit-any // eslint-disable-next-line @typescript-eslint/no-explicit-any
if ((err as any).code === "AccessDeniedException") { if ((err as any).code === "AccessDeniedException") {
@@ -667,16 +706,23 @@ const syncSecretsAWSParameterStore = async ({
// case: secret exists in AWS parameter store // case: secret exists in AWS parameter store
} else { } else {
logger.info( logger.info(
`getIntegrationSecrets: update secret in AWS SSM for [projectId=${projectId}] [environment=${integration.environment.slug}] [secretPath=${integration.secretPath}] [key=${key}]` `getIntegrationSecrets: update secret in AWS SSM for [projectId=${projectId}] [environment=${integration.environment.slug}] [secretPath=${integration.secretPath}]`
); );
// -> update secret
if (awsParameterStoreSecretsObj[key].Value !== secrets[key].value) { const shouldUpdateKms =
areParametersKmsKeysFetched &&
Boolean(metadata.kmsKeyId) &&
awsParameterStoreSecretsObj[key].KeyId !== metadata.kmsKeyId;
// we ensure that the KMS key configured in the integration is applied for ALL parameters on AWS
if (shouldUpdateKms || awsParameterStoreSecretsObj[key].Value !== secrets[key].value) {
await ssm await ssm
.putParameter({ .putParameter({
Name: `${integration.path}${key}`, Name: `${integration.path}${key}`,
Type: "SecureString", Type: "SecureString",
Value: secrets[key].value, Value: secrets[key].value,
Overwrite: true Overwrite: true,
...(metadata.kmsKeyId && { KeyId: metadata.kmsKeyId })
}) })
.promise(); .promise();
} }
@@ -698,7 +744,7 @@ const syncSecretsAWSParameterStore = async ({
} catch (err) { } catch (err) {
logger.error( logger.error(
err, err,
`getIntegrationSecrets: update secret in AWS SSM for failed [projectId=${projectId}] [environment=${integration.environment.slug}] [secretPath=${integration.secretPath}] [key=${key}]` `getIntegrationSecrets: update secret in AWS SSM for failed [projectId=${projectId}] [environment=${integration.environment.slug}] [secretPath=${integration.secretPath}]`
); );
// eslint-disable-next-line @typescript-eslint/no-explicit-any // eslint-disable-next-line @typescript-eslint/no-explicit-any
if ((err as any).code === "AccessDeniedException") { if ((err as any).code === "AccessDeniedException") {
@@ -728,11 +774,11 @@ const syncSecretsAWSParameterStore = async ({
for (const key in awsParameterStoreSecretsObj) { for (const key in awsParameterStoreSecretsObj) {
if (Object.hasOwn(awsParameterStoreSecretsObj, key)) { if (Object.hasOwn(awsParameterStoreSecretsObj, key)) {
logger.info( logger.info(
`getIntegrationSecrets: inside of shouldDisableDelete AWS SSM [projectId=${projectId}] [environment=${integration.environment.slug}] [secretPath=${integration.secretPath}] [key=${key}] [step=2]` `getIntegrationSecrets: inside of shouldDisableDelete AWS SSM [projectId=${projectId}] [environment=${integration.environment.slug}] [secretPath=${integration.secretPath}] [step=2]`
); );
if (!(key in secrets)) { if (!(key in secrets)) {
logger.info( logger.info(
`getIntegrationSecrets: inside of shouldDisableDelete AWS SSM [projectId=${projectId}] [environment=${integration.environment.slug}] [secretPath=${integration.secretPath}] [key=${key}] [step=3]` `getIntegrationSecrets: inside of shouldDisableDelete AWS SSM [projectId=${projectId}] [environment=${integration.environment.slug}] [secretPath=${integration.secretPath}] [step=3]`
); );
// case: // case:
// -> delete secret // -> delete secret
@@ -742,7 +788,7 @@ const syncSecretsAWSParameterStore = async ({
}) })
.promise(); .promise();
logger.info( logger.info(
`getIntegrationSecrets: inside of shouldDisableDelete AWS SSM [projectId=${projectId}] [environment=${integration.environment.slug}] [secretPath=${integration.secretPath}] [key=${key}] [step=4]` `getIntegrationSecrets: inside of shouldDisableDelete AWS SSM [projectId=${projectId}] [environment=${integration.environment.slug}] [secretPath=${integration.secretPath}] [step=4]`
); );
} }
await new Promise((resolve) => { await new Promise((resolve) => {
@@ -30,6 +30,7 @@ Prerequisites:
"ssm:DeleteParameter", "ssm:DeleteParameter",
"ssm:GetParameters", "ssm:GetParameters",
"ssm:GetParametersByPath", "ssm:GetParametersByPath",
"ssm:DescribeParameters",
"ssm:DeleteParameters", "ssm:DeleteParameters",
"ssm:AddTagsToResource", // if you need to add tags to secrets "ssm:AddTagsToResource", // if you need to add tags to secrets
"kms:ListKeys", // if you need to specify the KMS key "kms:ListKeys", // if you need to specify the KMS key