Skip to content

Commit

Permalink
Ack addon must support inline policies for EKS
Browse files Browse the repository at this point in the history
The EKS ack controller doesn't have a recommended managed policy, only an inline one, and the currently provided managed policy in blueprints doesn't seem to give any eks permissions, nor does any other managed policy seem to be appropriate. The best option is to support inline policies. There may be other ack controllers with similar issues, but EKS is the one I'm aware of.

Signed-off-by: Jack Kleeman <[email protected]>
  • Loading branch information
jackkleeman committed Feb 14, 2024
1 parent cc7aae3 commit 821ff04
Show file tree
Hide file tree
Showing 2 changed files with 149 additions and 124 deletions.
257 changes: 135 additions & 122 deletions lib/addons/ack/index.ts
Original file line number Diff line number Diff line change
@@ -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<Construct> {
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<AckAddOnProps> = {...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;
}
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<Construct> {
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<AckAddOnProps> = {...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;
}
16 changes: 14 additions & 2 deletions lib/addons/ack/serviceMappings.ts
Original file line number Diff line number Diff line change
@@ -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[]
}

/**
Expand Down Expand Up @@ -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",
Expand Down

0 comments on commit 821ff04

Please sign in to comment.