From 8e6dabe36c3abbe8b8a9b45d40c2ec0637fa86bb Mon Sep 17 00:00:00 2001 From: Alexander Steppke Date: Fri, 5 Feb 2021 15:47:11 +0100 Subject: [PATCH] feat: Harden NodeGroupRole by denying VPC actions. (#37) * feat: Harden NodeGroupRole by denying VPC actions. These actions are not necessary, since they are performed by the managed VPC CNI Addon, which uses its own role via IRSA. Therefore the Node Roles shouldn't be allowed to perform them. * Update README.md Co-authored-by: Jan Brauer --- README.md | 3 ++- src/constructs/super-eks.ts | 32 +++++++++++++++++++++++++++++++ test/constructs/super-eks.test.ts | 22 ++++++++++++++++++++- 3 files changed, 55 insertions(+), 2 deletions(-) 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*') }), + ), + }); +});