From b8c8fc0eb21188c3357ce9ce14bb8dd58405e6fc Mon Sep 17 00:00:00 2001 From: Colin Copeland Date: Tue, 25 Apr 2023 08:55:38 -0400 Subject: [PATCH] Add EksPublicAccessCidrs parameter and enable EKS control plane logging (#117) * add EKS public_access_cidrs * enable api, audit, and authenticator EKS control plane logging * update changelog * allow bastion access to Kubernetes API endpoint * use eks.LaunchTemplateSpecification to enforce HttpTokens-based metadata * update changelog * set HttpPutResponseHopLimit=3 --- CHANGELOG.rst | 5 +++- stack/bastion.py | 12 +++++++++ stack/eks.py | 63 ++++++++++++++++++++++++++++++++++++++++++++---- 3 files changed, 74 insertions(+), 6 deletions(-) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 8b9fea6..d809942 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -10,7 +10,10 @@ Change Log * Add ``EksClusterName`` parameter to control name of EKS cluster. If upgrading, set this to STACK_NAME-cluster to match existing name. * Drop support for RDS PostgreSQL 9.x * Upgrade to troposphere v4.2.0 - +* Add ``EksPublicAccessCidrs`` parameter to optionally restrict access to your public Kubernetes API endpoint using CIDR blocks. If defined, both public and private endpoint access enabled as detailed in `API server endpoint access options `_. +* Enable ``api``, ``audit``, and ``authenticator`` log types for `EKS control plane logging `_. +* Allow bastion access to Kubernetes API endpoint +* Add ``eks.LaunchTemplateSpecification`` to enforce `HttpTokens-based metadata `_ `2.1.2`_ (2022-03-10) --------------------- diff --git a/stack/bastion.py b/stack/bastion.py index 0747514..6eb3b83 100644 --- a/stack/bastion.py +++ b/stack/bastion.py @@ -238,6 +238,18 @@ if USE_EKS: from .eks import cluster backend_server_id = GetAtt(cluster, "ClusterSecurityGroupId") + # Allow bastion access to Kubernetes API endpoint + container_security_group_k8s_ingress = ec2.SecurityGroupIngress( + 'ContainerSecurityGroupKubernetesBastionIngress', + template=template, + GroupId=backend_server_id, + IpProtocol='tcp', + FromPort=443, + ToPort=443, + SourceSecurityGroupId=Ref(bastion_security_group), + Condition=bastion_type_set, + Description="Kubernetes API endpoint", + ) else: from .security_groups import container_security_group backend_server_id = Ref(container_security_group) diff --git a/stack/eks.py b/stack/eks.py index bfefc07..a3edf0d 100644 --- a/stack/eks.py +++ b/stack/eks.py @@ -14,7 +14,7 @@ iam ) -from .common import cmk_arn +from .common import cmk_arn, use_aes256_encryption, use_cmk_arn from .containers import ( container_instance_role, container_instance_type, @@ -69,7 +69,7 @@ AllowedValues=["true", "false"], Default="false", ), - group="Global", + group="Elastic Kubernetes Service (EKS)", label="Enable EKS EncryptionConfig", )) use_eks_encryption_config_cond = "EnableEksEncryptionConfigCond" @@ -78,6 +78,20 @@ Not(Equals(Ref(cmk_arn), "")) )) +# https://docs.aws.amazon.com/eks/latest/userguide/cluster-endpoint.html#modify-endpoint-access +public_access_cidrs = Ref(template.add_parameter( + Parameter( + "EksPublicAccessCidrs", + Description="The CIDR blocks that are allowed access to your cluster's public Kubernetes API server endpoint.", # noqa + Type="CommaDelimitedList", + Default="", + ), + group="Elastic Kubernetes Service (EKS)", + label="Kubernetes API public access CIDRs", +)) +restrict_eks_api_access_cond = "RestrictEksApiAccessCond" +template.add_condition(restrict_eks_api_access_cond, Not(Equals(Join("", public_access_cidrs), ""))) + # Unlike most other resources in the stack, we specify the cluster name # via a stack parameter so it's easy to find and so it cannot be accidentally # recreated (for example if the ResourcesVpcConfig is changed). @@ -87,7 +101,7 @@ Description="The unique name to give to your cluster.", # noqa Type="String", ), - group="Global", + group="Elastic Kubernetes Service (EKS)", label="Cluster name", )) @@ -95,6 +109,15 @@ "EksCluster", template=template, Name=cluster_name, + Logging=eks.Logging( + ClusterLogging=eks.ClusterLogging( + EnabledTypes=[ + eks.LoggingTypeConfig(Type="api"), + eks.LoggingTypeConfig(Type="audit"), + eks.LoggingTypeConfig(Type="authenticator"), + ] + ) + ), ResourcesVpcConfig=eks.ResourcesVpcConfig( SubnetIds=[ # For load balancers @@ -105,6 +128,9 @@ Ref(private_subnet_b), ], SecurityGroupIds=[Ref(eks_security_group)], + EndpointPrivateAccess=If(restrict_eks_api_access_cond, True, False), + EndpointPublicAccess=True, + PublicAccessCidrs=If(restrict_eks_api_access_cond, public_access_cidrs, NoValue), ), EncryptionConfig=If( use_eks_encryption_config_cond, @@ -114,6 +140,32 @@ RoleArn=GetAtt(eks_service_role, "Arn"), ) +# https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-ec2-launchtemplate.html +nodegroup_launch_template = ec2.LaunchTemplate( + "NodegroupLaunchTemplate", + template=template, + LaunchTemplateData=ec2.LaunchTemplateData( + BlockDeviceMappings=[ + ec2.LaunchTemplateBlockDeviceMapping( + DeviceName="/dev/xvda", + Ebs=ec2.EBSBlockDevice( + DeleteOnTermination=True, + Encrypted=use_aes256_encryption, + KmsKeyId=If(use_cmk_arn, Ref(cmk_arn), Ref("AWS::NoValue")), + VolumeType="gp2", + VolumeSize=container_volume_size, + ), + ), + ], + InstanceType=container_instance_type, + MetadataOptions=ec2.MetadataOptions( + HttpTokens="required", + # Why 3? See note: https://github.com/adamchainz/ec2-metadata#instance-metadata-service-version-2 + HttpPutResponseHopLimit=3, + ), + ) +) + eks.Nodegroup( # https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-eks-nodegroup.html "Nodegroup", @@ -125,9 +177,10 @@ ClusterName=Ref(cluster), # The NodeRole must be specified as an ARN. NodeRole=GetAtt(container_instance_role, "Arn"), + LaunchTemplate=eks.LaunchTemplateSpecification( + Id=Ref(nodegroup_launch_template), + ), # The rest are optional. - DiskSize=container_volume_size, - InstanceTypes=[container_instance_type], ScalingConfig=eks.ScalingConfig( DesiredSize=desired_container_instances, MaxSize=max_container_instances,