diff --git a/API.md b/API.md index fbb44db6..e6f455a9 100644 --- a/API.md +++ b/API.md @@ -1735,7 +1735,7 @@ const multiStringParameterProps: MultiStringParameterProps = { ... } | type | aws-cdk-lib.aws_ssm.ParameterType | The type of the string parameter. | | encryptionKey | aws-cdk-lib.aws_kms.IKey | *No description.* | | keyPrefix | string | *No description.* | -| keySeperator | string | *No description.* | +| keySeparator | string | *No description.* | --- @@ -2075,10 +2075,10 @@ public readonly keyPrefix: string; --- -##### `keySeperator`Optional +##### `keySeparator`Optional ```typescript -public readonly keySeperator: string; +public readonly keySeparator: string; ``` - *Type:* string diff --git a/lambda/main.go b/lambda/main.go index e415ae2d..5aaca1a6 100644 --- a/lambda/main.go +++ b/lambda/main.go @@ -298,7 +298,7 @@ func (a AWS) syncSopsToSecretsmanager(ctx context.Context, event cfn.Event) (phy keys := v.MapKeys() keysOrder := func(i, j int) bool { return keys[i].Interface().(string) < keys[j].Interface().(string) } sort.Slice(keys, keysOrder) - for i, key := range keys { + for _, key := range keys { strKey := resourceProperties.ParameterKeyPrefix + key.String() log.Printf("Parameter: " + strKey) value := v.MapIndex(key).Interface() @@ -311,8 +311,10 @@ func (a AWS) syncSopsToSecretsmanager(ctx context.Context, event cfn.Event) (phy if err != nil { return tempArn, nil, err } - returnData[fmt.Sprintf("ParameterName[%v]", i)] = strKey + // A returnData map for each parameter is not created, because it would limit the number of possible parameters unnecessarily } + returnData["Prefix"] = resourceProperties.ParameterKeyPrefix + returnData["Count"] = len(keys) return tempArn, returnData, nil } else { log.Printf("Patching single string parameter") diff --git a/src/MultiStringParameter.ts b/src/MultiStringParameter.ts index 143c3e24..cc7f886c 100644 --- a/src/MultiStringParameter.ts +++ b/src/MultiStringParameter.ts @@ -17,7 +17,7 @@ interface JSONObject { } export interface MultiStringParameterProps extends SopsStringParameterProps { - readonly keySeperator?: string; + readonly keySeparator?: string; readonly keyPrefix?: string; } @@ -67,7 +67,7 @@ export class MultiStringParameter extends Construct { region: this.stack.region, }; this.keyPrefix = props.keyPrefix ?? '/'; - this.keySeparator = props.keySeperator ?? '/'; + this.keySeparator = props.keySeparator ?? '/'; const keys = this.parseFile(props.sopsFilePath!, this.keySeparator) .filter((key) => !key.startsWith('sops')) diff --git a/src/SopsSync.ts b/src/SopsSync.ts index 95f70732..f072964c 100644 --- a/src/SopsSync.ts +++ b/src/SopsSync.ts @@ -9,7 +9,12 @@ import { CustomResource, FileSystem, } from 'aws-cdk-lib'; -import { IGrantable, PolicyStatement } from 'aws-cdk-lib/aws-iam'; +import { + IGrantable, + IRole, + ManagedPolicy, + PolicyStatement, +} from 'aws-cdk-lib/aws-iam'; import { IKey, Key } from 'aws-cdk-lib/aws-kms'; import { SingletonFunction, Code, Runtime } from 'aws-cdk-lib/aws-lambda'; import { Asset } from 'aws-cdk-lib/aws-s3-assets'; @@ -342,16 +347,9 @@ export class SopsSync extends Construct { props.encryptionKey?.grantEncryptDecrypt(provider); } if (props.parameterNames) { - provider.addToRolePolicy( - new PolicyStatement({ - actions: ['ssm:PutParameter'], - resources: props.parameterNames.map( - (param) => - `arn:aws:ssm:${Stack.of(this).region}:${ - Stack.of(this).account - }:parameter${param.startsWith('/') ? param : `/${param}`}`, - ), - }), + this.createReducedParameterPolicy( + props.parameterNames, + provider.role, ); props.encryptionKey?.grantEncryptDecrypt(provider); } @@ -429,4 +427,77 @@ export class SopsSync extends Construct { }); this.versionId = cr.getAttString('VersionId'); } + + private createReducedParameterPolicy(parameters: string[], role: IRole) { + // Avoid too large policies + // The maximum size of a managed policy is 6.144 bytes -> 1 character = 1 byte + const maxPolicyBytes = 6000; // Keep some bytes as a buffer + const arnPrefixBytes = 55; // Content for "arn:aws:ssm:ap-southeast-3::parameter/ + let startAtParameter = 0; + let currentPolicyBytes = 300; // Reserve some byte space for basic stuff inside the policy + for (let i = 0; i < parameters.length; i += 1) { + if ( + // Check if the current parameter would fit into the policy + arnPrefixBytes + parameters[i].length + currentPolicyBytes < + maxPolicyBytes + ) { + // If so increase the byte counter + currentPolicyBytes = + arnPrefixBytes + parameters[i].length + currentPolicyBytes; + } else { + const parameterNamesChunk = parameters.slice( + startAtParameter, + i, //end of slice is not included + ); + startAtParameter = i; + currentPolicyBytes = 300; + // Create the policy for the selected chunk + const putPolicy = new ManagedPolicy( + this, + `SopsSecretParameterProviderManagedPolicyParameterAccess${i}`, + { + description: + 'Policy to grant parameter provider permissions to put parameter', + }, + ); + putPolicy.addStatements( + new PolicyStatement({ + actions: ['ssm:PutParameter'], + resources: parameterNamesChunk.map( + (param) => + `arn:aws:ssm:${Stack.of(this).region}:${ + Stack.of(this).account + }:parameter${param.startsWith('/') ? param : `/${param}`}`, + ), + }), + ); + role.addManagedPolicy(putPolicy); + } + } + const parameterNamesChunk = parameters.slice( + startAtParameter, + parameters.length, + ); + // Create the policy for the remaning elements + const putPolicy = new ManagedPolicy( + this, + `SopsSecretParameterProviderManagedPolicyParameterAccess${parameters.length}`, + { + description: + 'Policy to grant parameter provider permissions to put parameter', + }, + ); + putPolicy.addStatements( + new PolicyStatement({ + actions: ['ssm:PutParameter'], + resources: parameterNamesChunk.map( + (param) => + `arn:aws:ssm:${Stack.of(this).region}:${ + Stack.of(this).account + }:parameter${param.startsWith('/') ? param : `/${param}`}`, + ), + }), + ); + role.addManagedPolicy(putPolicy); + } } diff --git a/test-secrets/yaml/sopsfile-parameters-large.yaml b/test-secrets/yaml/sopsfile-parameters-large.yaml new file mode 100644 index 00000000..99761eca --- /dev/null +++ b/test-secrets/yaml/sopsfile-parameters-large.yaml @@ -0,0 +1,100 @@ +DefineSomeExtraExtraLongNameForParameterWhichHaveAExtraExtraLongNameToTestPolicySplitting1: value +DefineSomeExtraExtraLongNameForParameterWhichHaveAExtraExtraLongNameToTestPolicySplitting2: value +DefineSomeExtraExtraLongNameForParameterWhichHaveAExtraExtraLongNameToTestPolicySplitting3: value +DefineSomeExtraExtraLongNameForParameterWhichHaveAExtraExtraLongNameToTestPolicySplitting4: value +DefineSomeExtraExtraLongNameForParameterWhichHaveAExtraExtraLongNameToTestPolicySplitting5: value +DefineSomeExtraExtraLongNameForParameterWhichHaveAExtraExtraLongNameToTestPolicySplitting6: value +DefineSomeExtraExtraLongNameForParameterWhichHaveAExtraExtraLongNameToTestPolicySplitting7: value +DefineSomeExtraExtraLongNameForParameterWhichHaveAExtraExtraLongNameToTestPolicySplitting8: value +DefineSomeExtraExtraLongNameForParameterWhichHaveAExtraExtraLongNameToTestPolicySplitting9: value +DefineSomeExtraExtraLongNameForParameterWhichHaveAExtraExtraLongNameToTestPolicySplitting10: value +DefineSomeExtraExtraLongNameForParameterWhichHaveAExtraExtraLongNameToTestPolicySplitting11: value +DefineSomeExtraExtraLongNameForParameterWhichHaveAExtraExtraLongNameToTestPolicySplitting12: value +DefineSomeExtraExtraLongNameForParameterWhichHaveAExtraExtraLongNameToTestPolicySplitting13: value +DefineSomeExtraExtraLongNameForParameterWhichHaveAExtraExtraLongNameToTestPolicySplitting14: value +DefineSomeExtraExtraLongNameForParameterWhichHaveAExtraExtraLongNameToTestPolicySplitting15: value +DefineSomeExtraExtraLongNameForParameterWhichHaveAExtraExtraLongNameToTestPolicySplitting16: value +DefineSomeExtraExtraLongNameForParameterWhichHaveAExtraExtraLongNameToTestPolicySplitting17: value +DefineSomeExtraExtraLongNameForParameterWhichHaveAExtraExtraLongNameToTestPolicySplitting18: value +DefineSomeExtraExtraLongNameForParameterWhichHaveAExtraExtraLongNameToTestPolicySplitting19: value +DefineSomeExtraExtraLongNameForParameterWhichHaveAExtraExtraLongNameToTestPolicySplitting20: value +DefineSomeExtraExtraLongNameForParameterWhichHaveAExtraExtraLongNameToTestPolicySplitting21: value +DefineSomeExtraExtraLongNameForParameterWhichHaveAExtraExtraLongNameToTestPolicySplitting22: value +DefineSomeExtraExtraLongNameForParameterWhichHaveAExtraExtraLongNameToTestPolicySplitting23: value +DefineSomeExtraExtraLongNameForParameterWhichHaveAExtraExtraLongNameToTestPolicySplitting24: value +DefineSomeExtraExtraLongNameForParameterWhichHaveAExtraExtraLongNameToTestPolicySplitting25: value +DefineSomeExtraExtraLongNameForParameterWhichHaveAExtraExtraLongNameToTestPolicySplitting26: value +DefineSomeExtraExtraLongNameForParameterWhichHaveAExtraExtraLongNameToTestPolicySplitting27: value +DefineSomeExtraExtraLongNameForParameterWhichHaveAExtraExtraLongNameToTestPolicySplitting28: value +DefineSomeExtraExtraLongNameForParameterWhichHaveAExtraExtraLongNameToTestPolicySplitting29: value +DefineSomeExtraExtraLongNameForParameterWhichHaveAExtraExtraLongNameToTestPolicySplitting30: value +DefineSomeExtraExtraLongNameForParameterWhichHaveAExtraExtraLongNameToTestPolicySplitting31: value +DefineSomeExtraExtraLongNameForParameterWhichHaveAExtraExtraLongNameToTestPolicySplitting32: value +DefineSomeExtraExtraLongNameForParameterWhichHaveAExtraExtraLongNameToTestPolicySplitting33: value +DefineSomeExtraExtraLongNameForParameterWhichHaveAExtraExtraLongNameToTestPolicySplitting34: value +DefineSomeExtraExtraLongNameForParameterWhichHaveAExtraExtraLongNameToTestPolicySplitting35: value +DefineSomeExtraExtraLongNameForParameterWhichHaveAExtraExtraLongNameToTestPolicySplitting36: value +DefineSomeExtraExtraLongNameForParameterWhichHaveAExtraExtraLongNameToTestPolicySplitting37: value +DefineSomeExtraExtraLongNameForParameterWhichHaveAExtraExtraLongNameToTestPolicySplitting38: value +DefineSomeExtraExtraLongNameForParameterWhichHaveAExtraExtraLongNameToTestPolicySplitting39: value +DefineSomeExtraExtraLongNameForParameterWhichHaveAExtraExtraLongNameToTestPolicySplitting40: value +DefineSomeExtraExtraLongNameForParameterWhichHaveAExtraExtraLongNameToTestPolicySplitting41: value +DefineSomeExtraExtraLongNameForParameterWhichHaveAExtraExtraLongNameToTestPolicySplitting42: value +DefineSomeExtraExtraLongNameForParameterWhichHaveAExtraExtraLongNameToTestPolicySplitting43: value +DefineSomeExtraExtraLongNameForParameterWhichHaveAExtraExtraLongNameToTestPolicySplitting44: value +DefineSomeExtraExtraLongNameForParameterWhichHaveAExtraExtraLongNameToTestPolicySplitting45: value +DefineSomeExtraExtraLongNameForParameterWhichHaveAExtraExtraLongNameToTestPolicySplitting46: value +DefineSomeExtraExtraLongNameForParameterWhichHaveAExtraExtraLongNameToTestPolicySplitting47: value +DefineSomeExtraExtraLongNameForParameterWhichHaveAExtraExtraLongNameToTestPolicySplitting48: value +DefineSomeExtraExtraLongNameForParameterWhichHaveAExtraExtraLongNameToTestPolicySplitting49: value +DefineSomeExtraExtraLongNameForParameterWhichHaveAExtraExtraLongNameToTestPolicySplitting50: value +DefineSomeExtraExtraLongNameForParameterWhichHaveAExtraExtraLongNameToTestPolicySplitting51: value +DefineSomeExtraExtraLongNameForParameterWhichHaveAExtraExtraLongNameToTestPolicySplitting52: value +DefineSomeExtraExtraLongNameForParameterWhichHaveAExtraExtraLongNameToTestPolicySplitting53: value +DefineSomeExtraExtraLongNameForParameterWhichHaveAExtraExtraLongNameToTestPolicySplitting54: value +DefineSomeExtraExtraLongNameForParameterWhichHaveAExtraExtraLongNameToTestPolicySplitting55: value +DefineSomeExtraExtraLongNameForParameterWhichHaveAExtraExtraLongNameToTestPolicySplitting56: value +DefineSomeExtraExtraLongNameForParameterWhichHaveAExtraExtraLongNameToTestPolicySplitting57: value +DefineSomeExtraExtraLongNameForParameterWhichHaveAExtraExtraLongNameToTestPolicySplitting58: value +DefineSomeExtraExtraLongNameForParameterWhichHaveAExtraExtraLongNameToTestPolicySplitting59: value +DefineSomeExtraExtraLongNameForParameterWhichHaveAExtraExtraLongNameToTestPolicySplitting60: value +DefineSomeExtraExtraLongNameForParameterWhichHaveAExtraExtraLongNameToTestPolicySplitting61: value +DefineSomeExtraExtraLongNameForParameterWhichHaveAExtraExtraLongNameToTestPolicySplitting62: value +DefineSomeExtraExtraLongNameForParameterWhichHaveAExtraExtraLongNameToTestPolicySplitting63: value +DefineSomeExtraExtraLongNameForParameterWhichHaveAExtraExtraLongNameToTestPolicySplitting64: value +DefineSomeExtraExtraLongNameForParameterWhichHaveAExtraExtraLongNameToTestPolicySplitting65: value +DefineSomeExtraExtraLongNameForParameterWhichHaveAExtraExtraLongNameToTestPolicySplitting66: value +DefineSomeExtraExtraLongNameForParameterWhichHaveAExtraExtraLongNameToTestPolicySplitting67: value +DefineSomeExtraExtraLongNameForParameterWhichHaveAExtraExtraLongNameToTestPolicySplitting68: value +DefineSomeExtraExtraLongNameForParameterWhichHaveAExtraExtraLongNameToTestPolicySplitting69: value +DefineSomeExtraExtraLongNameForParameterWhichHaveAExtraExtraLongNameToTestPolicySplitting70: value +DefineSomeExtraExtraLongNameForParameterWhichHaveAExtraExtraLongNameToTestPolicySplitting71: value +DefineSomeExtraExtraLongNameForParameterWhichHaveAExtraExtraLongNameToTestPolicySplitting72: value +DefineSomeExtraExtraLongNameForParameterWhichHaveAExtraExtraLongNameToTestPolicySplitting73: value +DefineSomeExtraExtraLongNameForParameterWhichHaveAExtraExtraLongNameToTestPolicySplitting74: value +DefineSomeExtraExtraLongNameForParameterWhichHaveAExtraExtraLongNameToTestPolicySplitting75: value +DefineSomeExtraExtraLongNameForParameterWhichHaveAExtraExtraLongNameToTestPolicySplitting76: value +DefineSomeExtraExtraLongNameForParameterWhichHaveAExtraExtraLongNameToTestPolicySplitting77: value +DefineSomeExtraExtraLongNameForParameterWhichHaveAExtraExtraLongNameToTestPolicySplitting78: value +DefineSomeExtraExtraLongNameForParameterWhichHaveAExtraExtraLongNameToTestPolicySplitting79: value +DefineSomeExtraExtraLongNameForParameterWhichHaveAExtraExtraLongNameToTestPolicySplitting80: value +DefineSomeExtraExtraLongNameForParameterWhichHaveAExtraExtraLongNameToTestPolicySplitting81: value +DefineSomeExtraExtraLongNameForParameterWhichHaveAExtraExtraLongNameToTestPolicySplitting82: value +DefineSomeExtraExtraLongNameForParameterWhichHaveAExtraExtraLongNameToTestPolicySplitting83: value +DefineSomeExtraExtraLongNameForParameterWhichHaveAExtraExtraLongNameToTestPolicySplitting84: value +DefineSomeExtraExtraLongNameForParameterWhichHaveAExtraExtraLongNameToTestPolicySplitting85: value +DefineSomeExtraExtraLongNameForParameterWhichHaveAExtraExtraLongNameToTestPolicySplitting86: value +DefineSomeExtraExtraLongNameForParameterWhichHaveAExtraExtraLongNameToTestPolicySplitting87: value +DefineSomeExtraExtraLongNameForParameterWhichHaveAExtraExtraLongNameToTestPolicySplitting88: value +DefineSomeExtraExtraLongNameForParameterWhichHaveAExtraExtraLongNameToTestPolicySplitting89: value +DefineSomeExtraExtraLongNameForParameterWhichHaveAExtraExtraLongNameToTestPolicySplitting90: value +DefineSomeExtraExtraLongNameForParameterWhichHaveAExtraExtraLongNameToTestPolicySplitting91: value +DefineSomeExtraExtraLongNameForParameterWhichHaveAExtraExtraLongNameToTestPolicySplitting92: value +DefineSomeExtraExtraLongNameForParameterWhichHaveAExtraExtraLongNameToTestPolicySplitting93: value +DefineSomeExtraExtraLongNameForParameterWhichHaveAExtraExtraLongNameToTestPolicySplitting94: value +DefineSomeExtraExtraLongNameForParameterWhichHaveAExtraExtraLongNameToTestPolicySplitting95: value +DefineSomeExtraExtraLongNameForParameterWhichHaveAExtraExtraLongNameToTestPolicySplitting96: value +DefineSomeExtraExtraLongNameForParameterWhichHaveAExtraExtraLongNameToTestPolicySplitting97: value +DefineSomeExtraExtraLongNameForParameterWhichHaveAExtraExtraLongNameToTestPolicySplitting98: value +DefineSomeExtraExtraLongNameForParameterWhichHaveAExtraExtraLongNameToTestPolicySplitting99: value +DefineSomeExtraExtraLongNameForParameterWhichHaveAExtraExtraLongNameToTestPolicySplitting100: value diff --git a/test/secret.test.ts b/test/secret.test.ts index 6f2a9f62..0c85fe9d 100644 --- a/test/secret.test.ts +++ b/test/secret.test.ts @@ -549,6 +549,24 @@ test('Multiple parameters from yaml file', () => { }); template.hasResourceProperties('AWS::IAM::Policy', { + PolicyDocument: { + Statement: [ + { + Action: [ + 'kms:Decrypt', + 'kms:Encrypt', + 'kms:ReEncrypt*', + 'kms:GenerateDataKey*', + ], + Effect: 'Allow', + Resource: + 'arn:aws:kms:eu-central-1:111122223333:key/1234abcd-12ab-34cd-56ef-1234567890ab', + }, + ], + }, + }); + + template.hasResourceProperties('AWS::IAM::ManagedPolicy', { PolicyDocument: { Statement: [ { @@ -581,17 +599,6 @@ test('Multiple parameters from yaml file', () => { }, ], }, - { - Action: [ - 'kms:Decrypt', - 'kms:Encrypt', - 'kms:ReEncrypt*', - 'kms:GenerateDataKey*', - ], - Effect: 'Allow', - Resource: - 'arn:aws:kms:eu-central-1:111122223333:key/1234abcd-12ab-34cd-56ef-1234567890ab', - }, ], }, }); @@ -604,7 +611,7 @@ test('Multiple parameters from yaml file with custom key structure', () => { simpleName: false, sopsFilePath: 'test-secrets/yaml/sopsfile-complex-parameters.enc-age.yaml', keyPrefix: '_', - keySeperator: '.', + keySeparator: '.', encryptionKey: Key.fromKeyArn( stack, 'Key', @@ -623,6 +630,24 @@ test('Multiple parameters from yaml file with custom key structure', () => { }); template.hasResourceProperties('AWS::IAM::Policy', { + PolicyDocument: { + Statement: [ + { + Action: [ + 'kms:Decrypt', + 'kms:Encrypt', + 'kms:ReEncrypt*', + 'kms:GenerateDataKey*', + ], + Effect: 'Allow', + Resource: + 'arn:aws:kms:eu-central-1:111122223333:key/1234abcd-12ab-34cd-56ef-1234567890ab', + }, + ], + }, + }); + + template.hasResourceProperties('AWS::IAM::ManagedPolicy', { PolicyDocument: { Statement: [ { @@ -655,18 +680,31 @@ test('Multiple parameters from yaml file with custom key structure', () => { }, ], }, - { - Action: [ - 'kms:Decrypt', - 'kms:Encrypt', - 'kms:ReEncrypt*', - 'kms:GenerateDataKey*', - ], - Effect: 'Allow', - Resource: - 'arn:aws:kms:eu-central-1:111122223333:key/1234abcd-12ab-34cd-56ef-1234567890ab', - }, ], }, }); }); + +test('Large set of parameters to split in multiple policies', () => { + const app = new App(); + const stack = new Stack(app, 'ParameterIntegration'); + new MultiStringParameter(stack, 'SopsSecret1', { + simpleName: false, + sopsFilePath: 'test-secrets/yaml/sopsfile-parameters-large.yaml', + keyPrefix: '_', + keySeparator: '.', + encryptionKey: Key.fromKeyArn( + stack, + 'Key', + 'arn:aws:kms:eu-central-1:111122223333:key/1234abcd-12ab-34cd-56ef-1234567890ab', + ), + stringValue: ' ', + }); + const template = Template.fromStack(stack); + + template.hasResourceProperties('AWS::SSM::Parameter', { + Name: '_DefineSomeExtraExtraLongNameForParameterWhichHaveAExtraExtraLongNameToTestPolicySplitting1', + }); + + template.resourceCountIs('AWS::IAM::ManagedPolicy', 3); +});