diff --git a/lib/addons/ack/index.ts b/lib/addons/ack/index.ts index e8c6d929d..65dbf8e73 100644 --- a/lib/addons/ack/index.ts +++ b/lib/addons/ack/index.ts @@ -1,122 +1,135 @@ -import { ManagedPolicy } from 'aws-cdk-lib/aws-iam'; -import { Construct } from 'constructs'; -import merge from "ts-deepmerge"; -import { ClusterInfo, Values } from "../../spi"; -import "reflect-metadata"; -import { createNamespace, setPath, supportsX86 } from "../../utils"; -import { HelmAddOn, HelmAddOnProps, HelmAddOnUserProps } from "../helm-addon"; -import { AckServiceName, serviceMappings } from './serviceMappings'; - -export * from "./serviceMappings"; - -/** - * User provided option for the Helm Chart - */ -export interface AckAddOnProps extends HelmAddOnUserProps { - /** - * Required identified, must be unique within the parent stack scope. - */ - id?: string; - /** - * Default Service Name - * @default iam - */ - serviceName: AckServiceName; - /** - * Managed IAM Policy of the ack controller - * @default IAMFullAccess - */ - managedPolicyName?: string; - /** - * To Create Namespace using CDK. This should be done only for the first time. - */ - createNamespace?: boolean; - /** - * To create Service Account - */ - saName?: string; -} - -/** - * Default props to be used when creating the Helm chart - */ -const defaultProps: AckAddOnProps = { - namespace: "ack-system", - values: {}, - createNamespace: true, - serviceName: AckServiceName.IAM, - id: "iam-ack" -}; - -/** - * Main class to instantiate the Helm chart - */ -@Reflect.metadata("ordered", true) -@supportsX86 -export class AckAddOn extends HelmAddOn { - - readonly options: AckAddOnProps; - readonly id? : string; - - constructor(props?: AckAddOnProps) { - super(populateDefaults(defaultProps, props) as HelmAddOnProps); - this.options = this.props as AckAddOnProps; - this.id = this.options.id; - } - - - deploy(clusterInfo: ClusterInfo): Promise { - const cluster = clusterInfo.cluster; - - const sa = cluster.addServiceAccount(`${this.options.chart}-sa`, { - namespace: this.options.namespace, - name: this.options.saName, - }); - - let values: Values = populateValues(this.options,cluster.stack.region); - values = merge(values, this.props.values ?? {}); - - if(this.options.createNamespace == true){ - // Let CDK Create the Namespace - const namespace = createNamespace(this.options.namespace! , cluster); - sa.node.addDependency(namespace); - } - - sa.role.addManagedPolicy(ManagedPolicy.fromAwsManagedPolicyName(this.options.managedPolicyName!)); - const chart = this.addHelmChart(clusterInfo, values); - chart.node.addDependency(sa); - return Promise.resolve(chart); - } -} - -/** - * populateValues populates the appropriate values used to customize the Helm chart - * @param helmOptions User provided values to customize the chart - */ -function populateValues(helmOptions: AckAddOnProps, awsRegion: string): Values { - const values = helmOptions.values ?? {}; - setPath(values, "aws.region", awsRegion); - setPath(values,"serviceAccount.create", false); - setPath(values,"serviceAccount.name", helmOptions.saName); - return values; -} - -/** - * populate parameters passed or the default values from service Mappings. - */ -function populateDefaults(defaultProps: AckAddOnProps, props?: AckAddOnProps): AckAddOnProps { - let tempProps : Partial = {...props ?? {}}; // since props may be empty - tempProps.id = tempProps.id ?? defaultProps.id; - tempProps.serviceName = tempProps.serviceName ?? defaultProps.serviceName; - tempProps.name = tempProps.name ?? serviceMappings[tempProps.serviceName!]!.chart; - tempProps.namespace = tempProps.namespace ?? defaultProps.namespace; - tempProps.chart = tempProps.chart ?? serviceMappings[tempProps.serviceName!]?.chart; - tempProps.version = tempProps.version ?? serviceMappings[tempProps.serviceName!]?.version; - const repositoryUrl = "oci://public.ecr.aws/aws-controllers-k8s"; - tempProps.release = tempProps.release ?? tempProps.chart; - tempProps.repository = tempProps.repository ?? `${repositoryUrl}/${tempProps.name}`; - tempProps.managedPolicyName = tempProps.managedPolicyName ?? serviceMappings[tempProps.serviceName!]?.managedPolicyName; - tempProps.createNamespace = tempProps.createNamespace ?? defaultProps.createNamespace; - tempProps.saName = tempProps.saName ?? `${tempProps.chart}-sa`; - return tempProps as AckAddOnProps; -} \ No newline at end of file +import { ManagedPolicy, Policy, PolicyStatement } from 'aws-cdk-lib/aws-iam'; +import { Construct } from 'constructs'; +import merge from "ts-deepmerge"; +import { ClusterInfo, Values } from "../../spi"; +import "reflect-metadata"; +import { createNamespace, setPath, supportsX86 } from "../../utils"; +import { HelmAddOn, HelmAddOnProps, HelmAddOnUserProps } from "../helm-addon"; +import { AckServiceName, serviceMappings } from './serviceMappings'; + +export * from "./serviceMappings"; + +/** + * User provided option for the Helm Chart + */ +export interface AckAddOnProps extends HelmAddOnUserProps { + /** + * Required identified, must be unique within the parent stack scope. + */ + id?: string; + /** + * Default Service Name + * @default iam + */ + serviceName: AckServiceName; + /** + * Managed IAM Policy of the ack controller + * @default IAMFullAccess + */ + managedPolicyName?: string; + /** + * Inline IAM Policy for the ack controller + * @default undefined + */ + inlinePolicyStatements?: PolicyStatement[]; + /** + * To Create Namespace using CDK. This should be done only for the first time. + */ + createNamespace?: boolean; + /** + * To create Service Account + */ + saName?: string; +} + +/** + * Default props to be used when creating the Helm chart + */ +const defaultProps: AckAddOnProps = { + namespace: "ack-system", + values: {}, + createNamespace: true, + serviceName: AckServiceName.IAM, + id: "iam-ack" +}; + +/** + * Main class to instantiate the Helm chart + */ +@Reflect.metadata("ordered", true) +@supportsX86 +export class AckAddOn extends HelmAddOn { + + readonly options: AckAddOnProps; + readonly id? : string; + + constructor(props?: AckAddOnProps) { + super(populateDefaults(defaultProps, props) as HelmAddOnProps); + this.options = this.props as AckAddOnProps; + this.id = this.options.id; + } + + + deploy(clusterInfo: ClusterInfo): Promise { + const cluster = clusterInfo.cluster; + + const sa = cluster.addServiceAccount(`${this.options.chart}-sa`, { + namespace: this.options.namespace, + name: this.options.saName, + }); + + let values: Values = populateValues(this.options,cluster.stack.region); + values = merge(values, this.props.values ?? {}); + + if(this.options.createNamespace == true){ + // Let CDK Create the Namespace + const namespace = createNamespace(this.options.namespace! , cluster); + sa.node.addDependency(namespace); + } + + if (this.options.managedPolicyName) { + sa.role.addManagedPolicy(ManagedPolicy.fromAwsManagedPolicyName(this.options.managedPolicyName!)); + } + if (this.options.inlinePolicyStatements && this.options.inlinePolicyStatements.length > 0) { + sa.role.attachInlinePolicy(new Policy(cluster.stack, "inline-policy", { + statements: this.options.inlinePolicyStatements + })); + } + const chart = this.addHelmChart(clusterInfo, values); + chart.node.addDependency(sa); + return Promise.resolve(chart); + } +} + +/** + * populateValues populates the appropriate values used to customize the Helm chart + * @param helmOptions User provided values to customize the chart + */ +function populateValues(helmOptions: AckAddOnProps, awsRegion: string): Values { + const values = helmOptions.values ?? {}; + setPath(values, "aws.region", awsRegion); + setPath(values,"serviceAccount.create", false); + setPath(values,"serviceAccount.name", helmOptions.saName); + return values; +} + +/** + * populate parameters passed or the default values from service Mappings. + */ +function populateDefaults(defaultProps: AckAddOnProps, props?: AckAddOnProps): AckAddOnProps { + let tempProps : Partial = {...props ?? {}}; // since props may be empty + tempProps.id = tempProps.id ?? defaultProps.id; + tempProps.serviceName = tempProps.serviceName ?? defaultProps.serviceName; + tempProps.name = tempProps.name ?? serviceMappings[tempProps.serviceName!]!.chart; + tempProps.namespace = tempProps.namespace ?? defaultProps.namespace; + tempProps.chart = tempProps.chart ?? serviceMappings[tempProps.serviceName!]?.chart; + tempProps.version = tempProps.version ?? serviceMappings[tempProps.serviceName!]?.version; + const repositoryUrl = "oci://public.ecr.aws/aws-controllers-k8s"; + tempProps.release = tempProps.release ?? tempProps.chart; + tempProps.repository = tempProps.repository ?? `${repositoryUrl}/${tempProps.name}`; + tempProps.managedPolicyName = tempProps.managedPolicyName ?? serviceMappings[tempProps.serviceName!]?.managedPolicyName; + tempProps.inlinePolicyStatements = tempProps.inlinePolicyStatements ?? serviceMappings[tempProps.serviceName!]?.inlinePolicyStatements; + tempProps.createNamespace = tempProps.createNamespace ?? defaultProps.createNamespace; + tempProps.saName = tempProps.saName ?? `${tempProps.chart}-sa`; + return tempProps as AckAddOnProps; +} diff --git a/lib/addons/ack/serviceMappings.ts b/lib/addons/ack/serviceMappings.ts index 9d49190c3..45c94f727 100644 --- a/lib/addons/ack/serviceMappings.ts +++ b/lib/addons/ack/serviceMappings.ts @@ -1,10 +1,13 @@ +import {PolicyStatement} from "aws-cdk-lib/aws-iam"; + /** * Chart Mapping for fields such as chart, version, managed IAM policy. */ export interface AckChartMapping { chart: string, version: string, - managedPolicyName: string + managedPolicyName?: string + inlinePolicyStatements?: PolicyStatement[] } /** @@ -125,7 +128,16 @@ export const serviceMappings : {[key in AckServiceName]?: AckChartMapping } = { [AckServiceName.EKS]: { chart: "eks-chart", version: "1.0.5", - managedPolicyName: "AmazonEKSClusterPolicy" + managedPolicyName: "AmazonEKSClusterPolicy", + inlinePolicyStatements: [PolicyStatement.fromJson({ + "Effect": "Allow", + "Action": [ + "eks:*", + "iam:GetRole", + "iam:PassRole" + ], + "Resource": "*" + })] }, [AckServiceName.APPLICATIONAUTOSCALING]: { chart: "applicationautoscaling-chart",