From 1a13d8fbb3ea4edd4ff8fb0a2608547f63b902f9 Mon Sep 17 00:00:00 2001 From: Adam Ruka Date: Tue, 22 Dec 2020 06:42:22 -0800 Subject: [PATCH 1/2] fix(codebuild): missing permissions for SecretsManager environment variables (#12121) When creating a CodeBuild Project that uses environment variables from SecretsManager, the Project fails execution with: ``` AccessDeniedException: User: arn:aws:sts::828671620168:assumed-role/role is not authorized to perform: secretsmanager:GetSecretValue on resource: arn:aws:secretsmanager:us-west-2:123456789012:secret:my-secret-GXyUCE ``` The solution is to automatically grant the Project's Role permissions to read all Secrets whose names were provided as environment variables. ---- *By submitting this pull request, I confirm that my contribution is made under the terms of the Apache-2.0 license* --- .../@aws-cdk/aws-codebuild/lib/project.ts | 34 ++++++++++++++--- .../aws-codebuild/test/test.project.ts | 38 +++++++++++++++++++ 2 files changed, 66 insertions(+), 6 deletions(-) diff --git a/packages/@aws-cdk/aws-codebuild/lib/project.ts b/packages/@aws-cdk/aws-codebuild/lib/project.ts index 597a816ca8fc1..dede5ddd9cf87 100644 --- a/packages/@aws-cdk/aws-codebuild/lib/project.ts +++ b/packages/@aws-cdk/aws-codebuild/lib/project.ts @@ -790,7 +790,7 @@ export class Project extends ProjectBase { this.projectName = this.getResourceNameAttribute(resource.ref); this.addToRolePolicy(this.createLoggingPermission()); - this.addParameterStorePermission(props); + this.addEnvVariablesPermissions(props.environmentVariables); // add permissions to create and use test report groups // with names starting with the project's name, // unless the customer explicitly opts out of it @@ -922,12 +922,13 @@ export class Project extends ProjectBase { }); } - private addParameterStorePermission(props: ProjectProps) { - if (!props.environmentVariables) { - return; - } + private addEnvVariablesPermissions(environmentVariables: { [name: string]: BuildEnvironmentVariable } | undefined): void { + this.addParameterStorePermissions(environmentVariables); + this.addSecretsManagerPermissions(environmentVariables); + } - const resources = Object.values(props.environmentVariables) + private addParameterStorePermissions(environmentVariables: { [name: string]: BuildEnvironmentVariable } | undefined): void { + const resources = Object.values(environmentVariables || {}) .filter(envVariable => envVariable.type === BuildEnvironmentVariableType.PARAMETER_STORE) .map(envVariable => // If the parameter name starts with / the resource name is not separated with a double '/' @@ -951,6 +952,27 @@ export class Project extends ProjectBase { })); } + private addSecretsManagerPermissions(environmentVariables: { [name: string]: BuildEnvironmentVariable } | undefined): void { + const resources = Object.values(environmentVariables || {}) + .filter(envVariable => envVariable.type === BuildEnvironmentVariableType.SECRETS_MANAGER) + .map(envVariable => Stack.of(this).formatArn({ + service: 'secretsmanager', + resource: 'secret', + // we don't know the exact ARN of the Secret just from its name, but we can get close + resourceName: `${envVariable.value}-??????`, + sep: ':', + })); + + if (resources.length === 0) { + return; + } + + this.addToRolePolicy(new iam.PolicyStatement({ + actions: ['secretsmanager:GetSecretValue'], + resources, + })); + } + private renderEnvironment( env: BuildEnvironment = {}, projectVars: { [name: string]: BuildEnvironmentVariable } = {}): CfnProject.EnvironmentProperty { diff --git a/packages/@aws-cdk/aws-codebuild/test/test.project.ts b/packages/@aws-cdk/aws-codebuild/test/test.project.ts index 81a09bd3b96f8..8a0cb370dd006 100644 --- a/packages/@aws-cdk/aws-codebuild/test/test.project.ts +++ b/packages/@aws-cdk/aws-codebuild/test/test.project.ts @@ -889,5 +889,43 @@ export = { test.done(); }, + + "grants the Project's Role read permissions to the SecretsManager environment variables"(test: Test) { + // GIVEN + const stack = new cdk.Stack(); + + // WHEN + new codebuild.PipelineProject(stack, 'Project', { + environmentVariables: { + 'ENV_VAR1': { + type: codebuild.BuildEnvironmentVariableType.SECRETS_MANAGER, + value: 'my-secret', + }, + }, + }); + + // THEN + expect(stack).to(haveResourceLike('AWS::IAM::Policy', { + 'PolicyDocument': { + 'Statement': arrayWith({ + 'Action': 'secretsmanager:GetSecretValue', + 'Effect': 'Allow', + 'Resource': { + 'Fn::Join': ['', [ + 'arn:', + { Ref: 'AWS::Partition' }, + ':secretsmanager:', + { Ref: 'AWS::Region' }, + ':', + { Ref: 'AWS::AccountId' }, + ':secret:my-secret-??????', + ]], + }, + }), + }, + })); + + test.done(); + }, }, }; From 579b9235706d6848847a258bbb607a9bff6a9e11 Mon Sep 17 00:00:00 2001 From: Ayush Goyal Date: Wed, 23 Dec 2020 00:16:42 +0530 Subject: [PATCH 2/2] feat(eks): option to disable manifest validation (#12012) Closes #11763 ---- *By submitting this pull request, I confirm that my contribution is made under the terms of the Apache-2.0 license* --- packages/@aws-cdk/aws-eks/README.md | 13 ++++ packages/@aws-cdk/aws-eks/lib/k8s-manifest.ts | 8 +++ .../lib/kubectl-handler/apply/__init__.py | 18 +++-- .../test/integ.eks-cluster.expected.json | 66 +++++++++++++------ .../aws-eks/test/integ.eks-cluster.ts | 16 +++++ 5 files changed, 96 insertions(+), 25 deletions(-) diff --git a/packages/@aws-cdk/aws-eks/README.md b/packages/@aws-cdk/aws-eks/README.md index d1b62a5c8a37f..01ae95842babb 100644 --- a/packages/@aws-cdk/aws-eks/README.md +++ b/packages/@aws-cdk/aws-eks/README.md @@ -856,6 +856,19 @@ new Cluster(this, 'MyCluster', { }); ``` +#### Manifests Validation + +The `kubectl` CLI supports applying a manifest by skipping the validation. +This can be accomplished by setting the `skipValidation` flag to `true` in the `KubernetesManifest` props. + +```ts +new eks.KubernetesManifest(this, 'HelloAppWithoutValidation', { + cluster: this.cluster, + manifest: [ deployment, service ], + skipValidation: true, +}); +``` + ### Helm Charts The `HelmChart` construct or `cluster.addHelmChart` method can be used diff --git a/packages/@aws-cdk/aws-eks/lib/k8s-manifest.ts b/packages/@aws-cdk/aws-eks/lib/k8s-manifest.ts index 5b5e31e0a6fff..205c28a9ee647 100644 --- a/packages/@aws-cdk/aws-eks/lib/k8s-manifest.ts +++ b/packages/@aws-cdk/aws-eks/lib/k8s-manifest.ts @@ -37,6 +37,13 @@ export interface KubernetesManifestOptions { * otherwise specified. */ readonly prune?: boolean; + + /** + * A flag to signify if the manifest validation should be skipped + * + * @default false + */ + readonly skipValidation?: boolean; } /** @@ -122,6 +129,7 @@ export class KubernetesManifest extends CoreConstruct { RoleArn: provider.roleArn, // TODO: bake into provider's environment PruneLabel: pruneLabel, Overwrite: props.overwrite, + SkipValidation: props.skipValidation, }, }); } diff --git a/packages/@aws-cdk/aws-eks/lib/kubectl-handler/apply/__init__.py b/packages/@aws-cdk/aws-eks/lib/kubectl-handler/apply/__init__.py index 7cdfdeb160197..80e9a7891481e 100644 --- a/packages/@aws-cdk/aws-eks/lib/kubectl-handler/apply/__init__.py +++ b/packages/@aws-cdk/aws-eks/lib/kubectl-handler/apply/__init__.py @@ -23,7 +23,8 @@ def apply_handler(event, context): manifest_text = props['Manifest'] role_arn = props['RoleArn'] prune_label = props.get('PruneLabel', None) - overwrite = props.get('Overwrite', False) + overwrite = props.get('Overwrite', 'false').lower() == 'true' + skip_validation = props.get('SkipValidation', 'false').lower() == 'true' # "log in" to the cluster cmd = [ 'aws', 'eks', 'update-kubeconfig', @@ -43,20 +44,25 @@ def apply_handler(event, context): logger.info("manifest written to: %s" % manifest_file) + kubectl_opts = [] + if skip_validation: + kubectl_opts.extend(['--validate=false']) + if request_type == 'Create': # if "overwrite" is enabled, then we use "apply" for CREATE operations # which technically means we can determine the desired state of an # existing resource. if overwrite: - kubectl('apply', manifest_file) + kubectl('apply', manifest_file, *kubectl_opts) else: # --save-config will allow us to use "apply" later - kubectl('create', manifest_file, '--save-config') + kubectl_opts.extend(['--save-config']) + kubectl('create', manifest_file, *kubectl_opts) elif request_type == 'Update': - opts = [] if prune_label is not None: - opts = ['--prune', '-l', prune_label] - kubectl('apply', manifest_file, *opts) + kubectl_opts.extend(['--prune', '-l', prune_label]) + + kubectl('apply', manifest_file, *kubectl_opts) elif request_type == "Delete": try: kubectl('delete', manifest_file) diff --git a/packages/@aws-cdk/aws-eks/test/integ.eks-cluster.expected.json b/packages/@aws-cdk/aws-eks/test/integ.eks-cluster.expected.json index cb1c0010b5d41..e3c699e7fa491 100644 --- a/packages/@aws-cdk/aws-eks/test/integ.eks-cluster.expected.json +++ b/packages/@aws-cdk/aws-eks/test/integ.eks-cluster.expected.json @@ -3867,7 +3867,7 @@ }, "/", { - "Ref": "AssetParameters9a25dd6d9e57e25d745576dc7750da1634d4b890ca4f11546b8c1cf5411957c2S3BucketBE7D619F" + "Ref": "AssetParametersc73abc34737d53a79bc2f339e8ae561af314b1fc67c51905129dcec3771ba09dS3Bucket133A4850" }, "/", { @@ -3877,7 +3877,7 @@ "Fn::Split": [ "||", { - "Ref": "AssetParameters9a25dd6d9e57e25d745576dc7750da1634d4b890ca4f11546b8c1cf5411957c2S3VersionKeyB0752C6A" + "Ref": "AssetParametersc73abc34737d53a79bc2f339e8ae561af314b1fc67c51905129dcec3771ba09dS3VersionKeyC4C77F70" } ] } @@ -3890,7 +3890,7 @@ "Fn::Split": [ "||", { - "Ref": "AssetParameters9a25dd6d9e57e25d745576dc7750da1634d4b890ca4f11546b8c1cf5411957c2S3VersionKeyB0752C6A" + "Ref": "AssetParametersc73abc34737d53a79bc2f339e8ae561af314b1fc67c51905129dcec3771ba09dS3VersionKeyC4C77F70" } ] } @@ -3912,11 +3912,11 @@ "Arn" ] }, - "referencetoawscdkeksclustertestAssetParameterse4ce1c625ef8590bc63f26160777b1c74421c8f5290dc5d15227810eedff2e6cS3Bucket13E8DC72Ref": { - "Ref": "AssetParameterse4ce1c625ef8590bc63f26160777b1c74421c8f5290dc5d15227810eedff2e6cS3BucketD473D2B6" + "referencetoawscdkeksclustertestAssetParametersbafd50ae9f214e496ff8c72c6425f93dca3ccd590e20963706d5d610d9c75757S3Bucket174F3576Ref": { + "Ref": "AssetParametersbafd50ae9f214e496ff8c72c6425f93dca3ccd590e20963706d5d610d9c75757S3Bucket008DBB35" }, - "referencetoawscdkeksclustertestAssetParameterse4ce1c625ef8590bc63f26160777b1c74421c8f5290dc5d15227810eedff2e6cS3VersionKeyEDAB3239Ref": { - "Ref": "AssetParameterse4ce1c625ef8590bc63f26160777b1c74421c8f5290dc5d15227810eedff2e6cS3VersionKey8213FD47" + "referencetoawscdkeksclustertestAssetParametersbafd50ae9f214e496ff8c72c6425f93dca3ccd590e20963706d5d610d9c75757S3VersionKeyE8595856Ref": { + "Ref": "AssetParametersbafd50ae9f214e496ff8c72c6425f93dca3ccd590e20963706d5d610d9c75757S3VersionKey97C3E1A0" }, "referencetoawscdkeksclustertestVpcPrivateSubnet1Subnet32A4EC2ARef": { "Ref": "VpcPrivateSubnet1Subnet536B997A" @@ -3969,6 +3969,34 @@ } } }, + "HelloAppWithoutValidation7C638ACB": { + "Type": "Custom::AWSCDK-EKS-KubernetesResource", + "Properties": { + "ServiceToken": { + "Fn::GetAtt": [ + "awscdkawseksKubectlProviderNestedStackawscdkawseksKubectlProviderNestedStackResourceA7AEBA6B", + "Outputs.awscdkeksclustertestawscdkawseksKubectlProviderframeworkonEventC681B49AArn" + ] + }, + "Manifest": "[{\"apiVersion\":\"v1\",\"kind\":\"ConfigMap\",\"data\":{\"hello\":\"world\"},\"metadata\":{\"name\":\"config-map\",\"labels\":{\"aws.cdk.eks/prune-c89cbcc5d9bdd35cfc69c0334c0a9af21d1e0e372e\":\"\"}},\"unknown\":{\"key\":\"value\"}}]", + "ClusterName": { + "Ref": "Cluster9EE0221C" + }, + "RoleArn": { + "Fn::GetAtt": [ + "ClusterCreationRole360249B6", + "Arn" + ] + }, + "PruneLabel": "aws.cdk.eks/prune-c89cbcc5d9bdd35cfc69c0334c0a9af21d1e0e372e", + "SkipValidation": true + }, + "DependsOn": [ + "ClusterKubectlReadyBarrier200052AF" + ], + "UpdateReplacePolicy": "Delete", + "DeletionPolicy": "Delete" + }, "CustomAWSCDKOpenIdConnectProviderCustomResourceProviderRole517FED65": { "Type": "AWS::IAM::Role", "Properties": { @@ -4575,17 +4603,17 @@ "Type": "String", "Description": "Artifact hash for asset \"daeb79e3cee39c9b902dc0d5c780223e227ed573ea60976252947adab5fb2be1\"" }, - "AssetParameterse4ce1c625ef8590bc63f26160777b1c74421c8f5290dc5d15227810eedff2e6cS3BucketD473D2B6": { + "AssetParametersbafd50ae9f214e496ff8c72c6425f93dca3ccd590e20963706d5d610d9c75757S3Bucket008DBB35": { "Type": "String", - "Description": "S3 bucket for asset \"e4ce1c625ef8590bc63f26160777b1c74421c8f5290dc5d15227810eedff2e6c\"" + "Description": "S3 bucket for asset \"bafd50ae9f214e496ff8c72c6425f93dca3ccd590e20963706d5d610d9c75757\"" }, - "AssetParameterse4ce1c625ef8590bc63f26160777b1c74421c8f5290dc5d15227810eedff2e6cS3VersionKey8213FD47": { + "AssetParametersbafd50ae9f214e496ff8c72c6425f93dca3ccd590e20963706d5d610d9c75757S3VersionKey97C3E1A0": { "Type": "String", - "Description": "S3 key for asset version \"e4ce1c625ef8590bc63f26160777b1c74421c8f5290dc5d15227810eedff2e6c\"" + "Description": "S3 key for asset version \"bafd50ae9f214e496ff8c72c6425f93dca3ccd590e20963706d5d610d9c75757\"" }, - "AssetParameterse4ce1c625ef8590bc63f26160777b1c74421c8f5290dc5d15227810eedff2e6cArtifactHashDEE5AB5C": { + "AssetParametersbafd50ae9f214e496ff8c72c6425f93dca3ccd590e20963706d5d610d9c75757ArtifactHashF584A7D8": { "Type": "String", - "Description": "Artifact hash for asset \"e4ce1c625ef8590bc63f26160777b1c74421c8f5290dc5d15227810eedff2e6c\"" + "Description": "Artifact hash for asset \"bafd50ae9f214e496ff8c72c6425f93dca3ccd590e20963706d5d610d9c75757\"" }, "AssetParametersb075459e6bf309093fbd4b9a9e576a5f172b91c14d84eedb0f069566f6abb0deS3Bucket14156880": { "Type": "String", @@ -4635,17 +4663,17 @@ "Type": "String", "Description": "Artifact hash for asset \"a69aadbed84d554dd9f2eb7987ffe5d8f76b53a86f1909059df07050e57bef0c\"" }, - "AssetParameters9a25dd6d9e57e25d745576dc7750da1634d4b890ca4f11546b8c1cf5411957c2S3BucketBE7D619F": { + "AssetParametersc73abc34737d53a79bc2f339e8ae561af314b1fc67c51905129dcec3771ba09dS3Bucket133A4850": { "Type": "String", - "Description": "S3 bucket for asset \"9a25dd6d9e57e25d745576dc7750da1634d4b890ca4f11546b8c1cf5411957c2\"" + "Description": "S3 bucket for asset \"c73abc34737d53a79bc2f339e8ae561af314b1fc67c51905129dcec3771ba09d\"" }, - "AssetParameters9a25dd6d9e57e25d745576dc7750da1634d4b890ca4f11546b8c1cf5411957c2S3VersionKeyB0752C6A": { + "AssetParametersc73abc34737d53a79bc2f339e8ae561af314b1fc67c51905129dcec3771ba09dS3VersionKeyC4C77F70": { "Type": "String", - "Description": "S3 key for asset version \"9a25dd6d9e57e25d745576dc7750da1634d4b890ca4f11546b8c1cf5411957c2\"" + "Description": "S3 key for asset version \"c73abc34737d53a79bc2f339e8ae561af314b1fc67c51905129dcec3771ba09d\"" }, - "AssetParameters9a25dd6d9e57e25d745576dc7750da1634d4b890ca4f11546b8c1cf5411957c2ArtifactHash332C2FA3": { + "AssetParametersc73abc34737d53a79bc2f339e8ae561af314b1fc67c51905129dcec3771ba09dArtifactHash7484ACD9": { "Type": "String", - "Description": "Artifact hash for asset \"9a25dd6d9e57e25d745576dc7750da1634d4b890ca4f11546b8c1cf5411957c2\"" + "Description": "Artifact hash for asset \"c73abc34737d53a79bc2f339e8ae561af314b1fc67c51905129dcec3771ba09d\"" }, "SsmParameterValueawsserviceeksoptimizedami118amazonlinux2recommendedimageidC96584B6F00A464EAD1953AFF4B05118Parameter": { "Type": "AWS::SSM::Parameter::Value", diff --git a/packages/@aws-cdk/aws-eks/test/integ.eks-cluster.ts b/packages/@aws-cdk/aws-eks/test/integ.eks-cluster.ts index d8522eb543619..ea02eb890073c 100644 --- a/packages/@aws-cdk/aws-eks/test/integ.eks-cluster.ts +++ b/packages/@aws-cdk/aws-eks/test/integ.eks-cluster.ts @@ -59,6 +59,8 @@ class EksClusterStack extends TestStack { this.assertSimpleManifest(); + this.assertManifestWithoutValidation(); + this.assertSimpleHelmChart(); this.assertSimpleCdk8sChart(); @@ -137,6 +139,20 @@ class EksClusterStack extends TestStack { // apply a kubernetes manifest this.cluster.addManifest('HelloApp', ...hello.resources); } + private assertManifestWithoutValidation() { + // apply a kubernetes manifest + new eks.KubernetesManifest(this, 'HelloAppWithoutValidation', { + cluster: this.cluster, + manifest: [{ + apiVersion: 'v1', + kind: 'ConfigMap', + data: { hello: 'world' }, + metadata: { name: 'config-map' }, + unknown: { key: 'value' }, + }], + skipValidation: true, + }); + } private assertNodeGroupX86() { // add a extra nodegroup this.cluster.addNodegroupCapacity('extra-ng', {