diff --git a/README.md b/README.md index 2eaeaab..c18fd22 100644 --- a/README.md +++ b/README.md @@ -10,10 +10,11 @@ __super-eks__ solves this problem by making a few choices for you as outlined be - :white_check_mark: Forwarding logs to CloudWatch Logs with [fluent-bit](https://github.com/aws/aws-for-fluent-bit) - :white_check_mark: Ingress management with the [AWS Load Balancer Controller](https://github.com/kubernetes-sigs/aws-load-balancer-controller) - :white_check_mark: Isolated node groups, one for the shipped components, the other one for your workloads +- :white_check_mark: Hardened node setup, deny nodes altering the VPC setup. +- :white_check_mark: Default to [managed cluster add-ons](https://docs.aws.amazon.com/eks/latest/userguide/update-cluster.html#update-cluster-add-ons) where possible. ### :world_map: Roadmap -- :hammer_and_wrench: Hardened node setup - :hammer_and_wrench: Monitoring with Prometheus and CloudWatch - :hammer_and_wrench: Backup solution for cluster recovery - :hammer_and_wrench: Authentication/authorization for workloads with Amazon Cognito diff --git a/src/constructs/super-eks.ts b/src/constructs/super-eks.ts index 548487d..4244191 100644 --- a/src/constructs/super-eks.ts +++ b/src/constructs/super-eks.ts @@ -80,6 +80,7 @@ export class SuperEks extends cdk.Construct { this.additionalNodegroups.push(this.addSuperEksNodegroup()); this.addManagedVpcCniAddon(); + this.hardenNodes(); this.configureExternalDNS(); this.configureAwsLoadBalancerController(); @@ -122,6 +123,37 @@ export class SuperEks extends cdk.Construct { this.additionalNodegroups.forEach((nodegroup) => {nodegroup.node.addDependency(vpcCniAddon); }); } + private hardenNodes() { + const policy = new iam.Policy(this, 'NodeHardeningPolicy', { + statements: [ + new iam.PolicyStatement({ + effect: iam.Effect.DENY, + actions: [ + 'ec2:AssignPrivateIpAddresses', + 'ec2:AttachNetworkInterface', + 'ec2:CreateNetworkInterface', + 'ec2:DeleteNetworkInterface', + 'ec2:DetachNetworkInterface', + 'ec2:ModifyNetworkInterfaceAttribute', + 'ec2:UnassignPrivateIpAddresses', + ], + resources: ['*'], + }), + new iam.PolicyStatement({ + effect: iam.Effect.DENY, + actions: ['ec2:CreateTags'], + resources: ['arn:aws:ec2:*:*:network-interface/*'], + }), + ], + }); + + if (this.cluster.defaultNodegroup?.role) { + policy.attachToRole(this.cluster.defaultNodegroup.role); + } + + this.additionalNodegroups.forEach((nodeGroup) => policy.attachToRole(nodeGroup.role)); + } + private configureExternalDNS(): void { new ExternalDNS(this, 'ExternalDNS', { cluster: this.cluster, diff --git a/test/constructs/super-eks.test.ts b/test/constructs/super-eks.test.ts index 2f51d8d..b598197 100644 --- a/test/constructs/super-eks.test.ts +++ b/test/constructs/super-eks.test.ts @@ -1,5 +1,5 @@ import '@aws-cdk/assert/jest'; -import { ABSENT, arrayWith, ResourcePart, stringLike } from '@aws-cdk/assert'; +import { arrayWith, objectLike, stringLike, ResourcePart, ABSENT } from '@aws-cdk/assert'; import * as route53 from '@aws-cdk/aws-route53'; import * as cdk from '@aws-cdk/core'; @@ -103,3 +103,23 @@ test('It installs external-dns', () => { Chart: 'external-dns', }); }); + +test('It hardens all Nodes', () => { + const app = new cdk.App(); + const stack = new cdk.Stack(app, 'Stack', { + env: { region: 'eu-central-1', account: '1234567891011' }, + }); + // WHEN + new SuperEks(stack, 'TestCluster', { + hostedZone: route53.HostedZone.fromHostedZoneId(stack, 'HostedZone', '123'), + }); + + // THEN + expect(stack).toHaveResource('AWS::IAM::Policy', { + PolicyName: stringLike('*NodeHardeningPolicy*'), + Roles: arrayWith( + objectLike( { Ref: stringLike('*EksClusterNodegroupSuperEksNodegroupNodeGroupRole*') }), + objectLike( { Ref: stringLike('*EksClusterNodegroupDefaultCapacityNodeGroupRole*') }), + ), + }); +});