diff --git a/libs/aws-cdk-stack/package.json b/libs/aws-cdk-stack/package.json index 47212aa..e7bc623 100644 --- a/libs/aws-cdk-stack/package.json +++ b/libs/aws-cdk-stack/package.json @@ -1,6 +1,6 @@ { "name": "@flowaccount/aws-cdk-stack", - "version": "2.0.2", + "version": "2.0.3", "dependencies": { "constructs": "^10.3.0", "aws-cdk": "^2.143.1", diff --git a/libs/aws-cdk-stack/src/lib/stacks/aws-ecs-cluster.ts b/libs/aws-cdk-stack/src/lib/stacks/aws-ecs-cluster.ts index 624352e..284a73a 100644 --- a/libs/aws-cdk-stack/src/lib/stacks/aws-ecs-cluster.ts +++ b/libs/aws-cdk-stack/src/lib/stacks/aws-ecs-cluster.ts @@ -9,7 +9,12 @@ import { ECSCapacityProvider } from './ecs-capacity-provider'; import { ManagedPolicyStack } from './managed-policy-stack'; import { RoleStack } from './role-stack'; import { VpcStack } from './vpc'; -import { IECSStackEnvironmentConfig } from '../types'; +import { + IECSStackEnvironmentConfig, + PolicyModel, + PolicyStackProperties, + PolicyStatementModel, +} from '../types'; /** * This class is used to create an ECS cluster stack by specifying the VPC and Subnets @@ -63,14 +68,18 @@ export const createStack = (configuration: IECSStackEnvironmentConfig) => { { name: configuration.ecs.instanceRole.name, assumedBy: configuration.ecs.instanceRole.assumedBy, + existingRole: configuration.ecs.instanceRole.existingRole ?? false, } ).output.role; // instance policy - new ManagedPolicyStack(_app, `${configuration.ecs.instancePolicy.name}`, { - ...configuration.ecs.instancePolicy, - roles: [_instanceRole], - }).output.policy; + createInstancePolicy( + _app, + configuration.ecs.instancePolicy.name, + configuration.ecs.instancePolicy, + [_instanceRole], + configuration.stage + ); // task execution role and policy const _taskExecutionRole: IRole = new RoleStack( @@ -175,3 +184,67 @@ export const createStack = (configuration: IECSStackEnvironmentConfig) => { // }) }); }; + +const createInstancePolicy = ( + app: App, + instancePolicyName: string, + extendedPolicy: PolicyModel, + instanceRoles?: IRole[], + stage?: string +) => { + const ec2Policy: PolicyStatementModel = { + actions: [ + 'ec2:DescribeInstances', + 'ec2:DescribeRegions', + 'ec2:DescribeSecurityGroups', + 'ec2:DescribeSubnets', + 'ec2:DescribeVpcs', + ], + resources: ['*'], + }; + + const ssmPolicy: PolicyStatementModel = { + actions: [ + 'ssm:DescribeAssociation', + 'ssm:GetDeployablePatchSnapshotForInstance', + 'ssm:GetDocument', + 'ssm:DescribeDocument', + 'ssm:GetManifest', + 'ssm:GetParameter', + 'ssm:GetParameters', + 'ssm:ListAssociations', + 'ssm:ListInstanceAssociations', + 'ssm:PutInventory', + 'ssm:PutComplianceItems', + 'ssm:PutConfigurePackageResult', + 'ssm:UpdateAssociationStatus', + 'ssm:UpdateInstanceAssociationStatus', + 'ssm:UpdateInstanceInformation', + `ssmmessages:CreateControlChannel`, + `ssmmessages:CreateDataChannel`, + `ssmmessages:OpenControlChannel`, + `ssmmessages:OpenDataChannel`, + `ec2messages:AcknowledgeMessage`, + `ec2messages:DeleteMessage`, + `ec2messages:FailMessage`, + `ec2messages:GetEndpoint`, + `ec2messages:GetMessages`, + `ec2messages:SendReply`, + ], + resources: ['*'], + }; + + const statements: PolicyStatementModel[] = [ec2Policy, ssmPolicy]; + statements.push(...extendedPolicy.statements); + + const instancePolicyProps: PolicyStackProperties = { + statements: statements, + name: extendedPolicy.name + ? extendedPolicy.name + : `default-${stage}-cluster-policy`, + roles: instanceRoles, + }; + + return new ManagedPolicyStack(app, instancePolicyName, instancePolicyProps) + .output.policy; +}; diff --git a/libs/aws-cdk-stack/src/lib/stacks/ecs-autoscaling-group.ts b/libs/aws-cdk-stack/src/lib/stacks/ecs-autoscaling-group.ts index 50bbff2..c0a6072 100644 --- a/libs/aws-cdk-stack/src/lib/stacks/ecs-autoscaling-group.ts +++ b/libs/aws-cdk-stack/src/lib/stacks/ecs-autoscaling-group.ts @@ -6,7 +6,6 @@ import { CfnLaunchTemplate, SecurityGroup, SubnetType, - AmazonLinuxImage, } from 'aws-cdk-lib/aws-ec2'; import { CfnInstanceProfile, IRole } from 'aws-cdk-lib/aws-iam'; import { @@ -53,6 +52,7 @@ EOF amazon-linux-extras disable docker && amazon-linux-extras install -y ecs && systemctl enable --now --no-block ecs docker plugin install rexray/ebs EBS_REGION=${stackProps.env.region} --grant-all-permissions `; + if (stackProps.s3MountConfig) { _userData = ` #!/bin/bash @@ -85,6 +85,7 @@ sudo s3cmd sync s3://${stackProps.s3MountConfig.bucketName} ${stackProps.s3Mount `; } + const _securityGroup = new SecurityGroup( this, stackProps.asgModel.asg.instanceSecurityGroup.name, @@ -94,11 +95,13 @@ sudo s3cmd sync s3://${stackProps.s3MountConfig.bucketName} ${stackProps.s3Mount securityGroupName: stackProps.asgModel.asg.instanceSecurityGroup.name, } ); + stackProps.asgModel.asg.instanceSecurityGroup.inboudRule.forEach( (_rule) => { _securityGroup.addIngressRule(_rule.peer, _rule.connection); } ); + const _instanceProfile = new CfnInstanceProfile( this, stackProps.asgModel.asg.instanceProfileName, @@ -107,6 +110,7 @@ sudo s3cmd sync s3://${stackProps.s3MountConfig.bucketName} ${stackProps.s3Mount instanceProfileName: stackProps.asgModel.asg.instanceProfileName, } ); + const _launchTemplate: CfnLaunchTemplate = new CfnLaunchTemplate( this, stackProps.asgModel.launchTemplate.name, @@ -129,6 +133,8 @@ sudo s3cmd sync s3://${stackProps.s3MountConfig.bucketName} ${stackProps.s3Mount volumeType: stackProps.asgModel.launchTemplate.volumeType, volumeSize: stackProps.asgModel.launchTemplate.volumeSize, deleteOnTermination: true, + encrypted: stackProps.asgModel.launchTemplate.encrypted, + kmsKeyId: stackProps.asgModel.launchTemplate.kmsKeyId, }, deviceName: '/dev/xvda', }, @@ -171,7 +177,7 @@ sudo s3cmd sync s3://${stackProps.s3MountConfig.bucketName} ${stackProps.s3Mount SpotAllocationStrategy: 'capacity-optimized', }, }); - asgGroup.addDependsOn(_launchTemplate); + asgGroup.addDependency(_launchTemplate); this._autoScalingGroup = asgGroup; // }); @@ -180,12 +186,10 @@ sudo s3cmd sync s3://${stackProps.s3MountConfig.bucketName} ${stackProps.s3Mount }); // ECS Cluster and Auto Scaling Group // let protection: string = stackProps.asgModel.asg.protectionFromScaleIn==true? 'ENABLED': 'DISABLED' - let protection: string; - if (stackProps.asgModel.asg.protectionFromScaleIn) { - protection = 'ENABLED'; - } else { - protection = 'DISABLED'; - } + const protection: string = stackProps.asgModel.asg.protectionFromScaleIn + ? 'ENABLED' + : 'DISABLED'; + const myCapacityProvider = new CfnCapacityProvider( this, `${asgGroup.autoScalingGroupName}`, @@ -201,6 +205,7 @@ sudo s3cmd sync s3://${stackProps.s3MountConfig.bucketName} ${stackProps.s3Mount name: `${asgGroup.autoScalingGroupName}`, } ); - myCapacityProvider.addDependsOn(asgGroup); + + myCapacityProvider.addDependency(asgGroup); } } diff --git a/libs/aws-cdk-stack/src/lib/stacks/managed-policy-stack.ts b/libs/aws-cdk-stack/src/lib/stacks/managed-policy-stack.ts index cff9375..2c735aa 100644 --- a/libs/aws-cdk-stack/src/lib/stacks/managed-policy-stack.ts +++ b/libs/aws-cdk-stack/src/lib/stacks/managed-policy-stack.ts @@ -1,5 +1,6 @@ -import { ManagedPolicy, PolicyStatement } from 'aws-cdk-lib/aws-iam'; -import { Stack } from 'aws-cdk-lib/core'; import { Construct } from 'constructs'; +import { ManagedPolicy, PolicyStatement, IRole } from 'aws-cdk-lib/aws-iam'; +import { Stack } from 'aws-cdk-lib/core'; +import { Construct } from 'constructs'; import { logger } from '@nx/devkit'; import { PolicyStackProperties } from '../types'; @@ -7,6 +8,7 @@ export class ManagedPolicyStack extends Stack { public readonly output: { policy: ManagedPolicy; }; + constructor(scope: Construct, id: string, _props: PolicyStackProperties) { super(scope, id, _props); @@ -22,6 +24,7 @@ export class ManagedPolicyStack extends Stack { } }); } + const _policyStatements: PolicyStatement[] = []; _props.statements?.forEach((statement) => { const policyStatement = new PolicyStatement(); @@ -33,12 +36,18 @@ export class ManagedPolicyStack extends Stack { }); _policyStatements.push(policyStatement); }); + logger.debug(`creating policy:${_props.name}`); const _policy = new ManagedPolicy(this, _props.name, { managedPolicyName: _props.name, statements: _policyStatements, - roles: _props.roles, }); + + logger.debug(`attach policy to roles`); + _props.roles?.forEach((role: IRole) => { + _policy.attachToRole(role); + }); + this.output = { policy: _policy }; } } diff --git a/libs/aws-cdk-stack/src/lib/stacks/role-stack.ts b/libs/aws-cdk-stack/src/lib/stacks/role-stack.ts index 819ab20..6ef4773 100644 --- a/libs/aws-cdk-stack/src/lib/stacks/role-stack.ts +++ b/libs/aws-cdk-stack/src/lib/stacks/role-stack.ts @@ -11,15 +11,21 @@ export class RoleStack extends Stack { super(scope, id, _props); if (_props.existingRole) { - const role = Role.fromRoleName(this, `${_props.name}`, _props.name, {}); - this.output = { role: role }; + const existingRole: IRole = Role.fromRoleName( + this, + _props.name, + _props.name, + {} + ); + existingRole; + this.output = { role: existingRole }; } else { logger.debug(`creating role -- ${_props.name}`); - const role = new Role(this, `${_props.name}`, { + const newRole = new Role(this, `${_props.name}`, { roleName: _props.name, assumedBy: new CompositePrincipal(..._props.assumedBy), }); - this.output = { role: role }; + this.output = { role: newRole }; } } } diff --git a/libs/aws-cdk-stack/src/lib/types/index.ts b/libs/aws-cdk-stack/src/lib/types/index.ts index cafb26c..2befd6f 100644 --- a/libs/aws-cdk-stack/src/lib/types/index.ts +++ b/libs/aws-cdk-stack/src/lib/types/index.ts @@ -341,7 +341,9 @@ export abstract class ECSStackEnvironmentConfig { export class RoleModel { name: string; assumedBy: PrincipalBase[]; + existingRole?: boolean; } + export class AutoScalingGroupModel { launchTemplate: { name: string; @@ -351,6 +353,8 @@ export class AutoScalingGroupModel { version: number | string; volumeType: string; volumeSize: number; + encrypted?: boolean; + kmsKeyId?: string; }; asg: { name: string; @@ -365,24 +369,29 @@ export class AutoScalingGroupModel { instanceSecurityGroup: SecurityGroupsModel; }; } + class SecurityGroupsInboudRuleModel { peer: IPeer; connection: Port; } + class SecurityGroupsModel { name: string; inboudRule: SecurityGroupsInboudRuleModel[]; } + export class PolicyStatementModel { actions: string[]; resources: string[]; conditions?: Conditions; } + export class PolicyModel { statements?: PolicyStatementModel[]; statement?: PolicyStatementModel; name: string; } + export class ECSModel { instancePolicy: PolicyModel; instanceRole: RoleModel; @@ -478,6 +487,7 @@ export class TagModel { key: string; value: string; } + export class S3MountConfig { bucketName: string; localPath: string; diff --git a/package.json b/package.json index d95d1d7..82687da 100644 --- a/package.json +++ b/package.json @@ -35,7 +35,7 @@ "help": "nx help", "workspace-generator": "nx workspace-generator", "e2e-registry": "yarn verdaccio --config ./tools/scripts/local-registry/config.yml --listen 4872", - "publish-global": "yarn nx build $1 && cd dist/libs/$1 && npm publish", + "publish-global": "publish() { yarn nx build $1 && cd dist/libs/$1 && npm publish; }; publish ", "publish-beta": "ts-node tools/scripts/publish-beta", "publish-local": "cp .npmrc.local .npmrc && run-p \"rimraf tmp\" e2e-registry \"ts-node ./tools/scripts/publish-all 99.99.99 local\"", "semantic-release": "semantic-release",