Skip to content

Commit

Permalink
Eks cluster encryption (#2793)
Browse files Browse the repository at this point in the history
  • Loading branch information
dcmcand authored Oct 24, 2024
2 parents 215bbd6 + 60d2a96 commit 7594a92
Show file tree
Hide file tree
Showing 7 changed files with 110 additions and 0 deletions.
24 changes: 24 additions & 0 deletions src/_nebari/provider/cloud/amazon_web_services.py
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,30 @@ def instances(region: str) -> Dict[str, str]:
return {t: t for t in instance_types}


@functools.lru_cache()
def kms_key_arns(region: str) -> Dict[str, dict]:
"""Return dict of available/enabled KMS key IDs and associated KeyMetadata for the AWS region."""
session = aws_session(region=region)
client = session.client("kms")
paginator = client.get_paginator("list_keys")
fields = [
"Arn",
"KeyUsage",
"KeySpec",
# "KeyState",
# "Origin",
# "KeyManager",
# "EncryptionAlgorithms",
# "MultiRegion",
]
kms_keys = [
client.describe_key(KeyId=j["KeyId"]).get("KeyMetadata")
for i in paginator.paginate()
for j in i["Keys"]
]
return {i["KeyId"]: {k: i[k] for k in fields} for i in kms_keys if i["Enabled"]}


def aws_get_vpc_id(name: str, namespace: str, region: str) -> Optional[str]:
"""Return VPC ID for the EKS cluster namedd `{name}-{namespace}`."""
cluster_name = f"{name}-{namespace}"
Expand Down
34 changes: 34 additions & 0 deletions src/_nebari/stages/infrastructure/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -182,6 +182,7 @@ class AWSInputVars(schema.Base):
eks_endpoint_access: Optional[
Literal["private", "public", "public_and_private"]
] = "public"
eks_kms_arn: Optional[str] = None
node_groups: List[AWSNodeGroupInputVars]
availability_zones: List[str]
vpc_cidr_block: str
Expand Down Expand Up @@ -498,6 +499,7 @@ class AmazonWebServicesProvider(schema.Base):
eks_endpoint_access: Optional[
Literal["private", "public", "public_and_private"]
] = "public"
eks_kms_arn: Optional[str] = None
existing_subnet_ids: Optional[List[str]] = None
existing_security_group_id: Optional[str] = None
vpc_cidr_block: str = "10.10.0.0/16"
Expand Down Expand Up @@ -554,6 +556,37 @@ def _check_input(cls, data: Any) -> Any:
f"Amazon Web Services instance {node_group.instance} not one of available instance types={available_instances}"
)

# check if kms key is valid
available_kms_keys = amazon_web_services.kms_key_arns(data["region"])
if "eks_kms_arn" in data and data["eks_kms_arn"] is not None:
key_id = [
id for id in available_kms_keys.keys() if id in data["eks_kms_arn"]
]
if (
len(key_id) == 1
and available_kms_keys[key_id[0]]["Arn"] == data["eks_kms_arn"]
):
key_id = key_id[0]
# Symmetric KMS keys with Encrypt and decrypt key-usage have the SYMMETRIC_DEFAULT key-spec
# EKS cluster encryption requires a Symmetric key that is set to encrypt and decrypt data
if available_kms_keys[key_id]["KeySpec"] != "SYMMETRIC_DEFAULT":
if available_kms_keys[key_id]["KeyUsage"] == "GENERATE_VERIFY_MAC":
raise ValueError(
f"Amazon Web Services KMS Key with ID {key_id} does not have KeyUsage set to 'Encrypt and decrypt' data"
)
elif available_kms_keys[key_id]["KeyUsage"] != "ENCRYPT_DECRYPT":
raise ValueError(
f"Amazon Web Services KMS Key with ID {key_id} is not of type Symmetric, and KeyUsage not set to 'Encrypt and decrypt' data"
)
else:
raise ValueError(
f"Amazon Web Services KMS Key with ID {key_id} is not of type Symmetric"
)
else:
raise ValueError(
f"Amazon Web Services KMS Key with ARN {data['eks_kms_arn']} not one of available/enabled keys={[v['Arn'] for v in available_kms_keys.values()]}"
)

return data


Expand Down Expand Up @@ -843,6 +876,7 @@ def input_vars(self, stage_outputs: Dict[str, Dict[str, Any]]):
name=self.config.escaped_project_name,
environment=self.config.namespace,
eks_endpoint_access=self.config.amazon_web_services.eks_endpoint_access,
eks_kms_arn=self.config.amazon_web_services.eks_kms_arn,
existing_subnet_ids=self.config.amazon_web_services.existing_subnet_ids,
existing_security_group_id=self.config.amazon_web_services.existing_security_group_id,
region=self.config.amazon_web_services.region,
Expand Down
1 change: 1 addition & 0 deletions src/_nebari/stages/infrastructure/template/aws/main.tf
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,7 @@ module "kubernetes" {

endpoint_public_access = var.eks_endpoint_access == "private" ? false : true
endpoint_private_access = var.eks_endpoint_access == "public" ? false : true
eks_kms_arn = var.eks_kms_arn
public_access_cidrs = var.eks_public_access_cidrs
permissions_boundary = var.permissions_boundary
}
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,20 @@ resource "aws_eks_cluster" "main" {
public_access_cidrs = var.public_access_cidrs
}

# Only set encryption_config if eks_kms_arn is not null
dynamic "encryption_config" {
for_each = var.eks_kms_arn != null ? [1] : []
content {
provider {
key_arn = var.eks_kms_arn
}
resources = ["secrets"]
}
}

depends_on = [
aws_iam_role_policy_attachment.cluster-policy,
aws_iam_role_policy_attachment.cluster_encryption,
]

tags = merge({ Name = var.name }, var.tags)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,33 @@ resource "aws_iam_role_policy_attachment" "cluster-policy" {
role = aws_iam_role.cluster.name
}

data "aws_iam_policy_document" "cluster_encryption" {
count = var.eks_kms_arn != null ? 1 : 0
statement {
actions = [
"kms:Encrypt",
"kms:Decrypt",
"kms:ListGrants",
"kms:DescribeKey"
]
resources = [var.eks_kms_arn]
}
}

resource "aws_iam_policy" "cluster_encryption" {
count = var.eks_kms_arn != null ? 1 : 0
name = "${var.name}-eks-encryption-policy"
description = "IAM policy for EKS cluster encryption"
policy = data.aws_iam_policy_document.cluster_encryption[count.index].json
}

# Grant the EKS Cluster role KMS permissions if a key-arn is specified
resource "aws_iam_role_policy_attachment" "cluster_encryption" {
count = var.eks_kms_arn != null ? 1 : 0
policy_arn = aws_iam_policy.cluster_encryption[count.index].arn
role = aws_iam_role.cluster.name
}

# =======================================================
# Kubernetes Node Group Policies
# =======================================================
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,12 @@ variable "endpoint_private_access" {
default = false
}

variable "eks_kms_arn" {
description = "kms key arn for EKS cluster encryption_config"
type = string
default = null
}

variable "public_access_cidrs" {
type = list(string)
default = ["0.0.0.0/0"]
Expand Down
6 changes: 6 additions & 0 deletions src/_nebari/stages/infrastructure/template/aws/variables.tf
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,12 @@ variable "eks_endpoint_private_access" {
default = false
}

variable "eks_kms_arn" {
description = "kms key arn for EKS cluster encryption_config"
type = string
default = null
}

variable "eks_public_access_cidrs" {
type = list(string)
default = ["0.0.0.0/0"]
Expand Down

0 comments on commit 7594a92

Please sign in to comment.