packages/constructs/L2/eks-constructs/lib/mdaa-kubectl-provider.ts (137 lines of code) (raw):

/*! * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. * SPDX-License-Identifier: Apache-2.0 */ import { KubectlV27Layer } from '@aws-cdk/lambda-layer-kubectl-v27'; import { Duration, Names, NestedStack, Stack } from 'aws-cdk-lib'; import { Cluster, ICluster, IKubectlProvider, KubectlProvider, KubectlProviderAttributes, KubectlProviderProps, } from 'aws-cdk-lib/aws-eks'; import { IRole } from 'aws-cdk-lib/aws-iam'; import { Code, Function, Runtime } from 'aws-cdk-lib/aws-lambda'; import { Provider } from 'aws-cdk-lib/custom-resources'; import { AwsCliLayer } from 'aws-cdk-lib/lambda-layer-awscli'; import { MdaaNagSuppressions } from '@aws-mdaa/construct'; //NOSONAR import { Construct } from 'constructs'; import * as path from 'path'; /** * Implementation of Kubectl Lambda */ export class CompliantKubectlProvider extends NestedStack implements IKubectlProvider { /** * Take existing provider or create new based on cluster * * @param scope Construct * @param cluster k8s cluster */ public static getOrCreate(scope: Construct, cluster: ICluster) { // if this is an "owned" cluster, it has a provider associated with it if (cluster instanceof Cluster) { return cluster._attachKubectlResourceScope(scope); } // if this is an imported cluster, it maybe has a predefined kubectl provider? if (cluster.kubectlProvider) { return cluster.kubectlProvider; } // if this is an imported cluster and there is no kubectl provider defined, we need to provision a custom resource provider in this stack // we will define one per stack for each cluster based on the cluster uniqueid const uid = `${Names.nodeUniqueId(cluster.node)}-CompliantKubectlProvider`; const stack = Stack.of(scope); let provider = stack.node.tryFindChild(uid) as KubectlProvider; if (!provider) { provider = new KubectlProvider(stack, uid, { cluster }); } return provider; } /** * Import an existing provider * * @param scope Construct * @param id an id of resource * @param attrs attributes for the provider */ public static fromKubectlProviderAttributes( scope: Construct, id: string, attrs: KubectlProviderAttributes, ): IKubectlProvider { return new ImportedKubectlProvider(scope, id, attrs); } /** * The custom resource provider's service token. */ public readonly serviceToken: string; /** * The IAM role to assume in order to perform kubectl operations against this cluster. */ public readonly roleArn: string; /** * The IAM execution role of the handler. */ public readonly handlerRole: IRole; public constructor(scope: Construct, id: string, props: KubectlProviderProps) { super(scope, id); const cluster = props.cluster; if (!cluster.kubectlRole) { throw new Error('"kubectlRole" is not defined, cannot issue kubectl commands against this cluster'); } if (cluster.kubectlPrivateSubnets && !cluster.kubectlSecurityGroup) { throw new Error('"kubectlSecurityGroup" is required if "kubectlSubnets" is specified'); } const memorySize = cluster.kubectlMemory ? cluster.kubectlMemory.toMebibytes() : 1024; // prettier-ignore const handler = new Function(this, 'Handler', { //NOSONAR false positive code: Code.fromAsset(path.join(__dirname, 'kubectl-handler')), //NOSONAR false positive runtime: Runtime.PYTHON_3_13, handler: 'index.handler', timeout: Duration.minutes(15), description: 'onEvent handler for EKS kubectl resource provider', memorySize, environment: { ...cluster.kubectlEnvironment, LOG_LEVEL: 'INFO', }, role: cluster.kubectlLambdaRole, // defined only when using private access vpc: cluster.kubectlPrivateSubnets ? cluster.vpc : undefined, securityGroups: cluster.kubectlPrivateSubnets && cluster.kubectlSecurityGroup ? [cluster.kubectlSecurityGroup] : undefined, vpcSubnets: cluster.kubectlPrivateSubnets ? { subnets: cluster.kubectlPrivateSubnets } : undefined, }); // allow user to customize the layers with the tools we need handler.addLayers(props.cluster.awscliLayer ?? new AwsCliLayer(this, 'AwsCliLayer')); handler.addLayers(props.cluster.kubectlLayer ?? new KubectlV27Layer(this, 'KubectlLayer')); this.handlerRole = handler.role!; const provider = new Provider(this, 'Provider', { onEventHandler: handler, vpc: cluster.kubectlPrivateSubnets ? cluster.vpc : undefined, vpcSubnets: cluster.kubectlPrivateSubnets ? { subnets: cluster.kubectlPrivateSubnets } : undefined, securityGroups: cluster.kubectlPrivateSubnets && cluster.kubectlSecurityGroup ? [cluster.kubectlSecurityGroup] : undefined, }); this.serviceToken = provider.serviceToken; this.roleArn = cluster.kubectlRole.roleArn; MdaaNagSuppressions.addCodeResourceSuppressions( this, [ { id: 'AwsSolutions-IAM4', reason: 'AWSLambdaBasicExecutionRole, AWSLambdaVPCAccessExecutionRole are least privilege.', }, { id: 'AwsSolutions-IAM5', reason: 'Resource names not known at deployment time.' }, { id: 'AwsSolutions-L1', reason: 'Function generated by EKS L2 construct.' }, { id: 'NIST.800.53.R5-LambdaConcurrency', reason: 'Function is used as Cfn Custom Resource only during deployment time. Concurrency managed via Cfn.', }, { id: 'NIST.800.53.R5-LambdaDLQ', reason: 'Function is used as Cfn Custom Resource only during deployment time. Error handling managed via Cfn.', }, { id: 'NIST.800.53.R5-IAMNoInlinePolicy', reason: 'Policy statements are specific to custom resource.' }, { id: 'HIPAA.Security-LambdaConcurrency', reason: 'Function is used as Cfn Custom Resource only during deployment time. Concurrency managed via Cfn.', }, { id: 'PCI.DSS.321-LambdaConcurrency', reason: 'Function is used as Cfn Custom Resource only during deployment time. Concurrency managed via Cfn.', }, { id: 'HIPAA.Security-LambdaDLQ', reason: 'Function is used as Cfn Custom Resource only during deployment time. Error handling managed via Cfn.', }, { id: 'PCI.DSS.321-LambdaDLQ', reason: 'Function is used as Cfn Custom Resource only during deployment time. Error handling managed via Cfn.', }, { id: 'HIPAA.Security-IAMNoInlinePolicy', reason: 'Policy statements are specific to custom resource.' }, { id: 'PCI.DSS.321-IAMNoInlinePolicy', reason: 'Policy statements are specific to custom resource.' }, ], true, ); } } class ImportedKubectlProvider extends Construct implements IKubectlProvider { /** * The custom resource provider's service token. */ public readonly serviceToken: string; /** * The IAM role to assume in order to perform kubectl operations against this cluster. */ public readonly roleArn: string; /** * The IAM execution role of the handler. */ public readonly handlerRole: IRole; constructor(scope: Construct, id: string, props: KubectlProviderAttributes) { super(scope, id); this.serviceToken = props.functionArn; this.roleArn = props.kubectlRoleArn; this.handlerRole = props.handlerRole; } }