Skip to main content

Quatro etapas para reforçar a segurança do Amazon EKS

Escrito por
Headshot of Kamil Potrec

Kamil Potrec

blog feature amazon eks security

21 de julho de 2021

0 minutos de leitura

Na primeira parte desta série de artigos, exploramos como implantar o Amazon EKS com Terraform e vimos como proteger a implementação inicial de RBAC e o Instance Metadata Service. Neste segundo artigo, vamos conhecer outras práticas recomendadas para reforçar a segurança do Amazon EKS, incluindo a importância de funções de IAM dedicadas para entrega contínua, de uma arquitetura mult conta para isolar clusters do Amazon EKS e de criptografar seus segredos no plano de controle. Por fim, mostraremos como incorporar ferramentas de análise estática aos seus pipelines de CD para detectar esses problemas antes que cheguem aos ambientes de produção.

Acompanhe quem implantou o cluster

O serviço Amazon EKS se integra ao serviço Identity Access Management para autenticar usuários no cluster. As decisões de autorização são realizadas pelo modelo nativo de RBAC do Kubernetes. A configuração de RBAC é mapeada para as identidades do IAM por meio do aws-auth ConfigMap. No entanto, há um mapeamento especial que não aparece em nenhum arquivo de configuração ou definição. A entidade do IAM usada para criar o cluster é mapeada automaticamente para o system:masters, um grupo integrado do Kubernetes. Isso está bem documentado na documentação do usuário, mas ainda tem implicações importantes para a auditabilidade das permissões do cluster.

Para demonstrar o impacto dessa configuração, vamos excluir todas as Roles e ClusterRoles do nosso cluster de demonstração.

dev@pwnbox:~$ kubectl delete clusterroles --all
clusterrole.rbac.authorization.k8s.io "admin" deleted
clusterrole.rbac.authorization.k8s.io "aws-node" deleted
clusterrole.rbac.authorization.k8s.io "cluster-admin" deleted
clusterrole.rbac.authorization.k8s.io "edit" deleted
clusterrole.rbac.authorization.k8s.io "eks:addon-manager" deleted
clusterrole.rbac.authorization.k8s.io "eks:fargate-manager" deleted
clusterrole.rbac.authorization.k8s.io "eks:node-bootstrapper" deleted
clusterrole.rbac.authorization.k8s.io "eks:node-manager" deleted
clusterrole.rbac.authorization.k8s.io "eks:podsecuritypolicy:privileged" deleted
<OMITTED>
dev@pwnbox:~$ kubectl delete roles --all-namespaces --all
role.rbac.authorization.k8s.io "system:controller:bootstrap-signer" deleted
role.rbac.authorization.k8s.io "eks:addon-manager" deleted
role.rbac.authorization.k8s.io "eks:certificate-controller" deleted
role.rbac.authorization.k8s.io "eks:fargate-manager" deleted
role.rbac.authorization.k8s.io "eks:node-manager" deleted
<OMITTED>
dev@pwnbox:~$ kubectl auth can-i --list
Resources                                       Non-Resource URLs   Resource Names   Verbs
*.*                                               []                  []               [*]
                                                [*]                 []               [*]
selfsubjectaccessreviews.authorization.k8s.io   []                  []               [create]
selfsubjectrulesreviews.authorization.k8s.io    []                  []               [create]
                                                [/api/*]            []               [get]
                                                [/api]              []               [get]
                                                [/apis/*]           []               [get]
                                                [/apis]             []               [get]
                                                [/healthz]          []               [get]
                                                [/healthz]          []               [get]
                                                [/livez]            []               [get]
                                                [/livez]            []               [get]
                                                [/openapi/*]        []               [get]
                                                [/openapi]          []               [get]
                                                [/readyz]           []               [get]
                                                [/readyz]           []               [get]
                                                [/version/]         []               [get]
                                                [/version/]         []               [get]
                                                [/version]          []               [get]
                                                [/version]          []               [get]
dev@pwnbox:~$ kubectl get configmap --all-namespaces
NAMESPACE     NAME                                 DATA   AGE
kube-system   coredns                              1      7h59m
kube-system   cp-vpc-resource-controller           0      7h58m
kube-system   eks-certificates-controller          0      7h59m
kube-system   extension-apiserver-authentication   6      7h59m
kube-system   kube-proxy                           1      7h59m
kube-system   kube-proxy-config                    1      7h59m

O Amazon EKS usa autenticação de token por webhook para se integrar ao IAM. Podemos ver a configuração nos argumentos do servidor da API.

FLAG: --authentication-token-webhook-cache-ttl="7m0s"
FLAG: --authentication-token-webhook-config-file="/etc/kubernetes/authenticator/apiserver-webhook-kubeconfig.yaml"
FLAG: --authentication-token-webhook-version="v1beta1"

O AWS IAM tem o conceito de identificadores exclusivos, que podem ser usados em serviços específicos para evitar configurações incorretas causadas pela reutilização de nomes. A documentação do Amazon EKS não deixa claro se a permissão system:masters está vinculada ao identificador exclusivo da entidade ou ao seu nome amigável. Para verificar se a remoção da entidade original revogaria todas as permissões no cluster, excluímos a função temporária e a recriamos.

dev@pwnbox:~$ aws iam get-role --role-name ci-eks-test
{
    "Role": {
        "Path": "/",
        "RoleName": "ci-eks-test",
        "RoleId": "AROAYREY3WYOOSHLPO3W6",
        "Arn": "arn:aws:iam::123456789012:role/ci-eks-test",
        "CreateDate": "2021-06-20T21:09:01+00:00",
        "AssumeRolePolicyDocument": {
            "Version": "2012-10-17",
            "Statement": [
                {
                    "Effect": "Allow",
                    "Principal": {
                        "AWS": "arn:aws:iam::123456789012:user/ci"
                    },
                    "Action": "sts:AssumeRole",
                    "Condition": {}
                }
            ]
        },
        "MaxSessionDuration": 3600,
        "RoleLastUsed": {}
    }
}
dev@pwnbox:~$ kubectl auth can-i --list
Resources                                       Non-Resource URLs   Resource Names     Verbs
*.*                                             []                  []                 [*]
                                                [*]                 []                 [*]
selfsubjectaccessreviews.authorization.k8s.io   []                  []                 [create]
selfsubjectrulesreviews.authorization.k8s.io    []                  []                 [create]
                                                [/api/*]            []                 [get]
                                                [/api]              []                 [get]
                                                [/apis/*]           []                 [get]
                                                [/apis]             []                 [get]
                                                [/healthz]          []                 [get]
                                                [/healthz]          []                 [get]
                                                [/livez]            []                 [get]
                                                [/livez]            []                 [get]
                                                [/openapi/*]        []                 [get]
                                                [/openapi]          []                 [get]
                                                [/readyz]           []                 [get]
                                                [/readyz]           []                 [get]
                                                [/version/]         []                 [get]
                                                [/version/]         []                 [get]
                                                [/version]          []                 [get]
                                                [/version]          []                 [get]
podsecuritypolicies.policy                      []                  [eks.privileged]   [use]

dev@pwnbox:~$ aws iam get-role --role-name ci-eks-test
{
    "Role": {
        "Path": "/",
        "RoleName": "ci-eks-test",
        "RoleId": "AROAYREY3WYOAK6QMGSUT",
        "Arn": "arn:aws:iam::123456789012:role/ci-eks-test",
        "CreateDate": "2021-06-20T21:14:28+00:00",
        "AssumeRolePolicyDocument": {
            "Version": "2012-10-17",
            "Statement": [
                {
                    "Effect": "Allow",
                    "Principal": {
                        "AWS": "arn:aws:iam::123456789012:user/ci"
                    },
                    "Action": "sts:AssumeRole",
                    "Condition": {}
                }
            ]
        },
        "MaxSessionDuration": 3600,
        "RoleLastUsed": {}
    }
}
dev@pwnbox:~$ kubectl auth can-i --list
Resources                                       Non-Resource URLs   Resource Names     Verbs
*.*                                             []                  []                 [*]
                                                [*]                 []                 [*]
selfsubjectaccessreviews.authorization.k8s.io   []                  []                 [create]
selfsubjectrulesreviews.authorization.k8s.io    []                  []                 [create]
                                                [/api/*]            []                 [get]
                                                [/api]              []                 [get]
                                                [/apis/*]           []                 [get]
                                                [/apis]             []                 [get]
                                                [/healthz]          []                 [get]
                                                [/healthz]          []                 [get]
                                                [/livez]            []                 [get]
                                                [/livez]            []                 [get]
                                                [/openapi/*]        []                 [get]
                                                [/openapi]          []                 [get]
                                                [/readyz]           []                 [get]
                                                [/readyz]           []                 [get]
                                                [/version/]         []                 [get]
                                                [/version/]         []                 [get]
                                                [/version]          []                 [get]
                                                [/version]          []                 [get]
podsecuritypolicies.policy                      []                  [eks.privileged]   [use]

Como você pode ver, agora temos uma função completamente nova, identificada por um RoleId diferente. No entanto, o serviço de autenticação do Amazon EKS usa o nome amigável da função para nos conceder as mesmas permissões de system:masters.

A maneira mais eficaz de atenuar esse problema é usar um sistema centralizado de implantação, utilizar uma função dedicada para as implantações e evitar ao máximo o uso de credenciais pessoais. Ferramentas como o aws-vault podem ajudar você a alternar entre diferentes funções com segurança.

Agora, vamos ver como os nós se autenticam no plano de controle.

Use contas separadas para isolar os clusters

O Amazon EKS exige que todos os nós tenham um perfil de instância associado às duas políticas a seguir: AmazonEKSWorkerNodePolicy e AmazonEC2ContainerRegistryReadOnly.

dev@pwnbox:$ aws iam get-policy-version --policy-arn arn:aws:iam::aws:policy/AmazonEKSWorkerNodePolicy --version-id v1
{
    "PolicyVersion": {
        "Document": {
            "Version": "2012-10-17",
            "Statement": [
                {
                    "Action": [
                        "ec2:DescribeInstances",
                        "ec2:DescribeRouteTables",
                        "ec2:DescribeSecurityGroups",
                        "ec2:DescribeSubnets",
                        "ec2:DescribeVolumes",
                        "ec2:DescribeVolumesModifications",
                        "ec2:DescribeVpcs",
                        "eks:DescribeCluster"
                    ],
                    "Resource": "*",
                    "Effect": "Allow"
                }
            ]
        },
        "VersionId": "v1",
        "IsDefaultVersion": true,
        "CreateDate": "2018-05-27T21:09:01+00:00"
    }
}

dev@pwnbox:$ aws iam get-policy-version --policy-arn arn:aws:iam::aws:policy/AmazonEC2ContainerRegistryReadOnly --version-id v3
{
    "PolicyVersion": {
        "Document": {
            "Version": "2012-10-17",
            "Statement": [
                {
                    "Effect": "Allow",
                    "Action": [
                        "ecr:GetAuthorizationToken",
                        "ecr:BatchCheckLayerAvailability",
                        "ecr:GetDownloadUrlForLayer",
                        "ecr:GetRepositoryPolicy",
                        "ecr:DescribeRepositories",
                        "ecr:ListImages",
                        "ecr:DescribeImages",
                        "ecr:BatchGetImage",
                        "ecr:GetLifecyclePolicy",
                        "ecr:GetLifecyclePolicyPreview",
                        "ecr:ListTagsForResource",
                        "ecr:DescribeImageScanFindings"
                    ],
                    "Resource": "*"
                }
            ]
        },
        "VersionId": "v3",
        "IsDefaultVersion": true,
        "CreateDate": "2019-12-10T20:56:32+00:00"
    }
}

Por padrão, essas políticas concedem privilégios a todos os recursos da conta da AWS. Isso significa que um pod comprometido nesse cluster poderá enumerar informações sobre qualquer recurso, VPC, sub-rede e imagem da conta.

Na verdade, o nó do Amazon EKS precisa acessar mais uma política gerenciada, chamada AmazonEKS_CNI_Policy. Essa política permite que o nó aloque endereços IP e execute qualquer funcionalidade relacionada à rede.

dev@pwnbox:$ aws iam get-policy-version --policy-arn arn:aws:iam::aws:policy/AmazonEKS_CNI_Policy --version-id v4
{
    "PolicyVersion": {
        "Document": {
            "Version": "2012-10-17",
            "Statement": [
                {
                    "Effect": "Allow",
                    "Action": [
                        "ec2:AssignPrivateIpAddresses",
                        "ec2:AttachNetworkInterface",
                        "ec2:CreateNetworkInterface",
                        "ec2:DeleteNetworkInterface",
                        "ec2:DescribeInstances",
                        "ec2:DescribeTags",
                        "ec2:DescribeNetworkInterfaces",
                        "ec2:DescribeInstanceTypes",
                        "ec2:DetachNetworkInterface",
                        "ec2:ModifyNetworkInterfaceAttribute",
                        "ec2:UnassignPrivateIpAddresses"
                    ],
                    "Resource": "*"
                },
                {
                    "Effect": "Allow",
                    "Action": [
                        "ec2:CreateTags"
                    ],
                    "Resource": [
                        "arn:aws:ec2:*:*:network-interface/*"
                    ]
                }
            ]
        },
        "VersionId": "v4",
        "IsDefaultVersion": true,
        "CreateDate": "2020-04-20T20:52:01+00:00"
    }
}

Essa política permite que quem a chama modifique a configuração de rede de qualquer instância da conta. A própria Amazon recomenda que essa política seja associada diretamente à conta de serviço do Kubernetes aws-node. Por padrão, o módulo do Terraform associa essa política ao perfil do IAM. Podemos mudar esse comportamento definindo o atributo attach_worker_cni_policy como false.

Criptografe seus Secrets

Um dos componentes do plano de controle gerenciados pelo serviço Amazon EKS é o armazenamento de chave-valor etcd. O servidor da API do Kubernetes o utiliza para armazenar todos os objetos e configurações. Por padrão, o Kubernetes não criptografa os dados armazenados nesse repositório de chave-valor. A AWS é responsável pela segurança dos componentes do plano de controle, mas ainda podemos aplicar uma estratégia de defesa em profundidade para aumentar a segurança dos nossos dados.

O serviço AWS KMS é um dos provedores de criptografia que podem oferecer criptografia de envelope para objetos do tipo secrets armazenados no cluster. Ao definir a opção `cluster_encryption_config`, podemos especificar uma chave do KMS que será usada para criptografar chaves intermediárias de criptografia de dados, que, por sua vez, serão usadas para criptografar objetos específicos. Este artigo do blog do Amazon EKS, publicado pela AWS, apresenta uma ótima visão geral do processo. A partir daí, quem tiver acesso a um cluster etcd não poderá ler nossos segredos sem também ter acesso à nossa chave do KMS. Todo o processo é totalmente transparente para o usuário final.

Detecte problemas no pipeline de implantação

A infraestrutura como código nos permite descrever de forma declarativa o estado desejado do cluster do Amazon EKS. Com isso, podemos detectar estaticamente alguns desses problemas antes de qualquer implantação.

No Terraform, podemos gerar um plano da configuração que será implantada.

dev@pwnbox:$ terraform plan

Terraform used the selected providers to generate the following execution plan. Resource actions are indicated with the following symbols:
  + create
 <= read (data resources)

Terraform will perform the following actions:

  # data.aws_eks_cluster.cluster will be read during apply
  # (config refers to values not yet known)
 <= data "aws_eks_cluster" "cluster"  {
      + arn                       = (known after apply)
      + certificate_authority     = (known after apply)
      + created_at                = (known after apply)
      + enabled_cluster_log_types = (known after apply)
      + endpoint                  = (known after apply)
      + id                        = (known after apply)
      + identity                  = (known after apply)
      + kubernetes_network_config = (known after apply)
      + name                      = (known after apply)
      + platform_version          = (known after apply)
      + role_arn                  = (known after apply)
      + status                    = (known after apply)
      + tags                      = (known after apply)
      + version                   = (known after apply)
      + vpc_config                = (known after apply)
    }

<OMITTED>

# module.vpc.aws_vpc.this[0] will be created
  + resource "aws_vpc" "this" {
      + arn                              = (known after apply)
      + assign_generated_ipv6_cidr_block = false
      + cidr_block                       = "10.0.0.0/16"
      + default_network_acl_id           = (known after apply)
      + default_route_table_id           = (known after apply)
      + default_security_group_id        = (known after apply)
      + dhcp_options_id                  = (known after apply)
      + enable_classiclink               = (known after apply)
      + enable_classiclink_dns_support   = (known after apply)
      + enable_dns_hostnames             = false
      + enable_dns_support               = true
      + id                               = (known after apply)
      + instance_tenancy                 = "default"
      + ipv6_association_id              = (known after apply)
      + ipv6_cidr_block                  = (known after apply)
      + main_route_table_id              = (known after apply)
      + owner_id                         = (known after apply)
      + tags                             = {
          + "Name" = "eks-threat-modelling-ddddaaaa"
        }
      + tags_all                         = {
          + "Name" = "eks-threat-modelling-ddddaaaa"
        }
    }

Plan: 44 to add, 0 to change, 0 to destroy.

Changes to Outputs:
  + cluster_endpoint          = (known after apply)
  + cluster_name              = (known after apply)
  + cluster_security_group_id = (known after apply)
  + config_map_aws_auth       = []
  + kubectl_config            = (known after apply)
  + region                    = "eu-west-1

Esse plano pode ser convertido para o formato JSON, o que nos permite fazer uma análise estática de todos os atributos.

dev@pwnbox:$ terraform show -json tfplan > tfplan.json
dev@pwnbox:$ jq '.resource_changes[] | select((.type == "aws_eks_cluster") and .mode == "managed").change.after' tfplan.json
{
  "enabled_cluster_log_types": [
    "audit",
    "authenticator"
  ],
  "encryption_config": [
    {
      "provider": [
        {}
      ],
      "resources": [
        "secrets"
      ]
    }
  ],
  "kubernetes_network_config": [
    {}
  ],
  "name": "eks-threat-modelling-ddddaaaa",
  "tags": null,
  "timeouts": {
    "create": "30m",
    "delete": "15m",
    "update": null
  },
  "version": "1.19",
  "vpc_config": [
    {
      "endpoint_private_access": true,
      "endpoint_public_access": false,
      "public_access_cidrs": [
        "0.0.0.0/0"
      ]
    }
  ]
}

Depois de entender a estrutura do plano do Terraform, podemos usar o Open Policy Agent e sua linguagem declarativa para escrever testes simples.

package play

deny[msg]{
    input.vpc_config[0].endpoint_public_access == true
    msg := sprintf("The public endpoint is enabled on the cluster", [])
}

Você pode experimentar este exemplo simples no Rego Playground online.

Escrever essas regras pode ser trabalhoso. É preciso analisar as estruturas de saída do Terraform, processar os arquivos, manter bibliotecas e apresentar os resultados de forma útil. É aí que produtos como o Snyk IaC podem ajudar. Veja um exemplo da análise de segurança realizada na nossa implantação padrão.

dev@pwnbox:$ snyk iac test tfplan.json

Testing tfplan.json...

Infrastructure as code issues:
  ✗ EKS cluster allows public access [High Severity] [SNYK-CC-TF-94] in EKS
    introduced by aws_eks_cluster[this] > vpc_config

  ✗ Non-Encrypted root block device [Medium Severity] [SNYK-CC-TF-53] in EC2
    introduced by aws_launch_configuration[workers] > root_block_device > encrypted

  ✗ Public IPs are automatically mapped to instances [Low Severity] [SNYK-CC-AWS-427] in VPC
    introduced by aws_subnet[public_0] > map_public_ip_on_launch

  ✗ EKS control plane logging insufficient [Low Severity] [SNYK-CC-TF-131] in EKS
    introduced by aws_eks_cluster[this] > enabled_cluster_log_types

  ✗ Public IPs are automatically mapped to instances [Low Severity] [SNYK-CC-AWS-427] in VPC
    introduced by aws_subnet[public_2] > map_public_ip_on_launch

  ✗ Public IPs are automatically mapped to instances [Low Severity] [SNYK-CC-AWS-427] in VPC
    introduced by aws_subnet[public_1] > map_public_ip_on_launch

Organization:      p0tr3c
Type:              Terraform
Target file:       tfplan.json
Project name:      hardened
Open source:       no
Project path:      tfplan.json

Tested tfplan.json for known issues, found 6 issues

dev@pwnbox:/$

Com a análise de IaC da Snyk, você pode detectar problemas na configuração do Terraform antes de implantar em produção, ajudando a manter seus ambientes de produção seguros. https://app.snyk.io — crie uma conta grátis! Além disso, você também pode experimentar a ferramenta gratuita de verificação de código da Snyk para ter uma ideia rápida da segurança do seu código.

Proteja a infraestrutura desde a origem

A Snyk automatiza a segurança e a conformidade de IaC nos fluxos de trabalho e detecta recursos com configurações divergentes ou ausentes.

Publicado em:

Leia mais

blog feature pypi spoof
Article

A Snyk AI Security Platform completa, grátis para mantenedores de projetos open source

Mantenedores de projetos open source estão sobrecarregados com relatórios reais de vulnerabilidades e precisam de ajuda para priorizar, corrigir e lançar correções mais rápido. O Secure Developer Program da Snyk dá aos projetos qualificados acesso gratuito à Snyk AI Security Platform.

Article

Versões maliciosas de node-ipc publicadas no npm após suspeita de comprometimento da conta de um mantenedor

Em 14 de maio de 2026, várias versões maliciosas do popular pacote npm node-ipc foram publicadas no registro do npm. As informações públicas disponíveis identificam node...

blog feature toolkit
Blog

Versão maliciosa do pacote elementary-data no PyPI rouba credenciais de nuvem de engenheiros de dados

Atacantes exploraram uma vulnerabilidade de injeção de script no GitHub Actions para publicar uma versão maliciosa da CLI Python elementary-data (v0.23.3), com um backdoor que rouba credenciais e tinha como alvo perfis do dbt, chaves de provedores de nuvem e segredos SSH em ambientes de engenharia de dados.