O blog da AWS
Security groups para pods do Amazon EKS
Por Mike Stefaniak e Sri Saran Balaji
Traduzido por Tiago Reichert e Guilherme França
Os Security Groups, agindo como firewalls de rede em nível de instância, estão entre os blocos de construção mais importantes e comumente usados em qualquer implantação em nuvem na AWS. Não foi nenhuma surpresa para nós que a integração de Security Groups com pods Kubernetes surgiu como um dos recursos do Amazon Elastic Kubernetes Service (Amazon EKS) mais solicitados, conforme visto em nosso roadmap público.
Neste blog post, cobrimos casos de uso endereçados atribuindo Security Group a pods, examinamos os bastidores para ver como o recurso é implementado e terminamos com um tutorial de exemplo.
Protegendo aplicações na camada de rede
Os aplicativos em contêineres frequentemente exigem acesso a outros serviços em execução no cluster, bem como a serviços externos da AWS, como Amazon Relational Database Service (Amazon RDS) ou Amazon ElastiCache. Na AWS, o controle do acesso a nível da rede entre os serviços geralmente é realizado por meio de EC2 Security Groups. Antes desta funcionalidade, você só podia atribuir Security Groups a nível do nó, e todos os pod em um nó compartilhavam os mesmos Security Groups. Para contornar essa limitação, você tinha que criar grupos de nós separados por aplicativo e configurar regras de afinidade complicadas para agendar pods nos nós certos. Esse processo ineficiente é difícil de gerenciar em escala e pode resultar em nós subutilizados, conforme mostrado abaixo.
As IAM roles for service accounts resolvem esse desafio de segurança em nível de pod na camada de autenticação, mas os requisitos de conformidade de muitas organizações também exigem segmentação de rede como uma defesa adicional. As políticas de rede do Kubernetes fornecem uma opção para controlar o tráfego de rede dentro do cluster, mas não oferecem suporte ao controle de acesso a recursos AWS fora do cluster. Além disso, para organizações que estão passando por esforços de modernização de aplicativos migrando serviços baseados em máquina virtual para contêineres no Kubernetes, pode ser mais simples reutilizar o conhecimento operacional, ferramentas e experiência em torno das políticas de grupo de segurança existentes, em vez de reimplementar regras como políticas de rede do Kubernetes. Isso é especialmente verdadeiro se sua equipe de segurança criou programas de conformidade em torno de Security Groups.
Os Security Groups para pods facilitam a obtenção de conformidade com a segurança da rede, executando aplicativos com diversos requisitos de segurança de rede em recursos de computação compartilhados. Regras de segurança de rede que abrangem pod a pod e pod para tráfego de serviço externo da AWS podem ser definidas em um único lugar com EC2 Security Group e aplicadas a aplicativos com APIs nativas do Kubernetes. Depois de aplicar os Security Groups no nível do pod, a arquitetura do seu aplicativo e do grupo de nós pode ser simplificada conforme mostrado abaixo.
Como funciona
Como parte desta funcionalidade, os clusters Amazon EKS têm dois novos componentes em execução no control plane do Kubernetes: um webhook e controlador de recursos para a Amazon Virtual Private Cloud (Amazon VPC) associado ao seu cluster. O webhook é responsável por adicionar limites e solicitações aos pods que requerem Security Groups. O controlador é responsável por gerenciar as interfaces de rede associadas a esses pods. Para facilitar esse recurso, cada nó de trabalho será associado a uma única interface de rede trunk e várias interfaces de rede branch. A interface trunk atua como uma interface de rede padrão anexada à instância. O controlador de recursos VPC então associa interfaces de branch à interface trunk. Isso aumenta o número de interfaces de rede que podem ser conectadas por instância. Como os Security Groups são especificados com interfaces de rede, agora podemos agendar pods que exigem Security Groups específicos nessas interfaces de rede adicionais alocadas para nós de trabalho. Vamos detalhar como esse recurso funciona em mais detalhes nas 3 fases abaixo.
Fase 1: Inicialização do nó e publicação dos limites da interface branch
Depois de habilitado com uma variável de configuração no plugin Amazon VPC CNI, o daemon de gerenciamento de endereço IP (ipamd) adicionará um label Kubernetes aos tipos de instância compatíveis. O controlador de recursos VPC anunciará as interfaces de rede branch como recursos estendidos nesses nós do cluster. A capacidade da interface branch é um acréscimo aos limites de endereços IP secundários existentes dos tipos de instâncias. Por exemplo, uma c5.4xlarge pode ter até 234 endereços IP secundários atribuídos a interfaces de rede padrão e até 54 interfaces de rede branch. O trunk/branch de ENI está disponível na maioria das famílias de instâncias baseadas no AWS Nitro, incluindo m5, m6g, c5, c6g, r5, r6g, g4 e p3. Se suas cargas de trabalho não precisam ser isoladas usando Security Groups específicos, nenhuma mudança é necessária para que você continue a executá-las usando endereços IP secundários em ENIs compartilhadas.
Fase 2: Agendamento de pods em nós
Para cargas de trabalho que exigem Security Groups específicos, adotamos uma abordagem nativa do Kubernetes e adicionamos uma nova Custom Resource Definition (CRD). Os administradores de cluster podem especificar quais Security Groups devem ser atribuídos aos pods por meio do SecurityGroupPolicy CRD. Em um namespace, você pode selecionar pods com base em labels de pod ou com base em labels de service account associados a um pod. Para os pods correspondentes, você também define os IDs de Security Groups a serem aplicados.
O webhook observa os recursos personalizados do SecurityGroupPolicy quanto a quaisquer alterações e injeta automaticamente pods correspondentes com a solicitação de recurso estendido necessária para que o pod seja agendado em um nó com capacidade de interface de rede branch disponível. Assim que o pod for agendado, o controlador de recursos criará e anexará uma interface branch à interface trunk. Após a conexão bem-sucedida, o controlador adiciona uma anotação ao objeto pod com os detalhes da interface branch.
O controlador de recursos VPC requer permissões EC2 para modificar os recursos VPC conforme exigido pelos pods em seu cluster. Para tornar isso mais simples, criamos uma AWS managed policy: AmazonEKSVPCResourceController. Dado que o controlador é executado no control plane do Kubernetes, você precisa anexar esta policy a role IAM associada ao seu cluster.
Fase 3: Configurando a rede para o pod
Durante esta fase, o plug-in VPC CNI configura a rede para o pod. O plug-in consulta o ipamd para ler os detalhes da interface de rede branch e, em seguida, consulta o Kubernetes API server para ler a anotação do pod. Assim que a anotação do pod estiver disponível, o CNI criará um dispositivo virtual LAN (vlan) a partir da interface do trunk. Este dispositivo é usado apenas por este pod de interface branch e não é compartilhado com nenhum outro pod no host. O CNI irá então criar uma tabela de rotas com rotas padrão usando o dispositivo vlan e associar um dispositivo virtual ethernet device (veth) do pod para esta interface. Por fim, o plugin CNI adiciona regras de iptables para que todo o tráfego que flui para este host veth e vlan use esta tabela de rota.
Como começar
No tutorial a seguir, iremos explorar passo a passo um típico caso de uso onde definir Security Groups diretamente aos pods pode ser útil, permitindo que apenas alguns dos pods executando em um mesmo nó acessem uma base de dados no Amazon RDS. Nesse exemplo, nós combinamos IAM roles for service accounts com Security Groups a nível de pods para uma estratégia de segurança em camadas.
Crie um cluster do EKS
Use o eksctl para criar um cluster. Certifique-se que está usando pelo menos a versão 0.27.0 para seguir com esse exemplo. Copie o conteúdo a seguir e salve o mesmo em um arquivo chamado cluster.yaml:
apiVersion: eksctl.io/v1alpha5 kind: ClusterConfig metadata: name: sgp-cluster region: us-west-2 iam: withOIDC: true managedNodeGroups: - name: sample-ng instanceType: m5.xlarge desiredCapacity: 1 privateNetworking: true eksctl create cluster -f cluster.yml Obtenha o ID da VPC criada pelo eksctl juntamente com o cluster. VPCID=$(aws eks describe-cluster --name sgp-cluster \ --query "cluster.resourcesVpcConfig.vpcId" \ --output text) echo $VPCID
Criar uma base de dados Postgres usando o Amazon RDS
Antes de criar o Amazon RDS, vamos criar um security group que será usado pelas aplicações que precisam de acesso a bases de dados.
RDSSG=$(aws ec2 create-security-group --group-name RDSDbAccessSG \ --description \ "Security group para aplicar em aplicações que precisam de acesso ao RDS"\ --vpc-id $VPCID \ --query "GroupId" \ --output text) # Permitir tráfego de saída a partir do security group aws ec2 authorize-security-group-egress --group-id $RDSSG \ --cidr 0.0.0.0/0 echo $RDSSG
O proximo passo é seguir as instruções do RDS para liberar acesso a sua base de dados criando um outro Security Group. Quando chegar no passo 7 para Inbound Rules, especifique o Security Group criado no passo anterior como a origem.
Agora, siga as instruções do RDS para criar uma base de dados PostgreSQL (certifique-se de especificar a mesma VPC usada no seu cluster EKS). Use o Security Group que você acabou de criar para sua instância de banco de dados. Habilite a opção de Autenticação do banco de dados do IAM e crie uma conta na base de dados que use a autenticação com IAM.
Habilite os pods a terem suas próprias interfaces de rede
Você precisará usar pelo menos a versão 1.7 do plugin Amazon VPC CNI para usar Security Groups nos pods. A documentação do Amazon EKS contém instruções de como obter a versão usada e atualizar caso necessário. Quando confirmar que seu cluster tem a versão necessária do VPC CNI, execute o comendo a seguir para ativar ENIs para os pods:
kubectl set env daemonset -n kube-system aws-node ENABLE_POD_ENI=true
Criar uma Service Account para pods que precisam de acesso ao RDS
Copie a configuração a seguir e troque a policy de exemplo com a ARN criada durante a criação da base de dados RDS. Salve essa configuração em um arquivo chamado serviceaccount.yaml:
apiVersion: eksctl.io/v1alpha5 kind: ClusterConfig metadata: name: sgp-cluster region: us-west-2 iam: withOIDC: true serviceAccounts: - metadata: name: rds-db-access namespace: default labels: {role: "backend"} attachPolicyARNs: - "arn:aws:iam::123456789012:policy/my-policy" eksctl create iamserviceaccount --config-file=serviceaccount.yaml
Aplique uma política SecurityGroupPolicy ao cluster
Obtenha os IDs dos nossos dois Security Groups que iremos adicionar a nossa política SecurityGroupPolicy. O primeiro Security Group que queremos adicionar é o Security Group do cluster EKS, que permite que pods executados com interfaces de rede secundárias comuniquem com outros pods do cluster como por exemplo o CoreDNS. O segundo Security Group é o que foi criado anteriormente para as aplicações que precisam acessar nossa base de dados RDS.
CLUSTERSG=$(aws eks describe-cluster --name sgp-cluster \ --query "cluster.resourcesVpcConfig.clusterSecurityGroupId" \ --output text) # print security group IDs echo $CLUSTERSG $RDSSG
Copie a configuração a seguir, substitua os IDs dos Security Groups com os valores obtidos acima, salve em um arquivo chamado sgp-policy.yaml:
apiVersion: vpcresources.k8s.aws/v1beta1 kind: SecurityGroupPolicy metadata: name: my-sg-policy spec: serviceAccountSelector: matchLabels: role: backend securityGroups: groupIds: - sg-yyyyyy - sg-zzzzzz kubectl apply -f sgp-policy.yaml
O SecurityGroupPolicy é um CustomResourceDefinition a nível de namespace. Aqui, nós criamos uma política que associa os Security Groups especificados a qualquer pod no namespace default que estejam associados a uma Service Account contendo a label role com valor igual a backend. Note que as políticas SecurityGroupPolicy apenas se aplicam a novos pods, e não afetam pods previamente executados.
Crie uma aplicação de exemplo para se conectar no RDS
Nesse passo, nós iremos criar um container de uma aplicação Python de exemplo que se conecta a nossa base de dados Postgres e mostra a versão em caso de sucesso, ou uma mensagem de erro. Esse exemplo é modelado a partir da documentação do RDS. Salve o seguinte conteúdo no arquivo postgres_test_iam.py.
import os import boto3 import psycopg2 HOST = os.getenv('HOST') PORT = "5432" USER = os.getenv('USER') REGION = "us-west-2" DBNAME = os.getenv('DATABASE') session = boto3.Session() client = boto3.client('rds', region_name=REGION) token = client.generate_db_auth_token(DBHostname=HOST, Port=PORT, DBUsername=USER, Region=REGION) conn = None try: conn = psycopg2.connect(host=HOST, port=PORT, database=DBNAME, user=USER, password=token, connect_timeout=3) cur = conn.cursor() cur.execute("""SELECT version()""") query_results = cur.fetchone() print(query_results) cur.close() except Exception as e: print("Database connection failed due to {}".format(e)) finally: if conn is not None: conn.close() Salve o seguinte conteúdo como um Dockerfile. FROM python:3.8.5-slim-buster ADD postgres_test_iam.py / RUN pip install psycopg2-binary boto3 CMD [ "python", "-u", "./postgres_test_iam.py" ]
Vamos agora construir o container e enviá-lo ao Amazon ECR. Use o ID da sua conta nos comandos a seguir.
docker build -t postgres-test . aws ecr create-repository --repository-name postgres-test-demo aws ecr get-login-password | docker login --username AWS —password-stdin 123456789012.dkr.ecr.us-west-2.amazonaws.com docker tag postgres-test 123456789012.dkr.ecr.us-west-2.amazonaws.com/postgres-test-demo:latest docker push
Faça o deploy da aplicação
Vamos agora fazer o deploy da nossa aplicação e testar que somente os pods desejáveis podem acessar nossa base de dados RDS. Salve o seguinte como postgres-test.yaml. Substitua as variáveis de ambiente HOST, DATABASE e USER pelos valores do passo de criação da base de dados.
apiVersion: v1 kind: Pod metadata: name: postgres-test spec: serviceAccountName: rds-db-access containers: - name: postgres-test image: 123456789012.dkr.ecr.us-west-2.amazonaws.com/postgres-test-demo:latest env: - name: HOST value: "REPLACE_DATABASE_HOSTNAME" - name: DATABASE value: "REPLACE_DATABASE_NAME" - name: USER value: "REPLACE_USER" kubectl apply -f postgres-test.yaml
Confirme através dos logs que esse pod pode acessar nossa base de dados RDS.
kubectl logs postgres-test ('PostgreSQL 12.3 on x86_64-pc-linux-gnu, compiled by gcc (GCC) 4.8.5 20150623 (Red Hat 4.8.5-11), 64-bit',)
Sucesso! Agora para um teste completo, vamos fazer uma pequena modificação na configuração do nosso pod removendo a Service Account. Esse pod não cairá mais nas condições da nossa SecurityGroupPolicy, e não conseguirá mais acessar a base de dados. Lembre-se que nós criamos nosso cluster com apenas um nó, portanto esse pod será executado no mesmo nó que o pod anterior. Salve o seguinte conteúdo no arquivo postgres-test-no-sa.yaml.
apiVersion: v1 kind: Pod metadata: name: postgres-test-no-sa spec: containers: - name: postgres-test image: 123456789012.dkr.ecr.us-west-2.amazonaws.com/postgres-test-demo:latest env: - name: HOST value: "REPLACE_DATABASE_HOSTNAME" - name: DATABASE value: "REPLACE_DATABASE_NAME" - name: USER value: "REPLACE_USER" kubectl apply -f postgres-test-no-sa.yaml kubectl logs postgres-test-no-sa Database connection failed due to timeout expired
É isso! Dois pods em um mesmo nó, mas apenas um pode acessar nossa base de dados.
Conclusão
Existem diversos pontos que devem ser considerados quando falamos de executar um cluster Kubernetes de forma segura. Toda organização tem suas próprias políticas de segurança e conformidade, algumas dessas políticas estão fortemente ligadas a Security Groups. Se você faz parte dessa categoria, associar Security Groups diretamente aos pods pode simplificar os padrões atuais de deployment de suas aplicações e facilitar a migração de workloads EC2 para o Amazon EKS.
Nesse post, nós mostramos como Security Group a nível de pods podem ser combinados com IAM roles for Service Accounts para prover uma estratégia de segurança em camadas tanto na camada de rede quanto na de autenticação. O recurso de Security Groups para pods está disponível hoje nos novos clusters Amazon EKS executando a versão 1.17 do Kubernetes. O suporte para clusters previamente existentes será liberado nas próximas semanas. Saiba mais na documentação do Amazon EKS.
Sobre os tradutores
Tiago Miguel Reichert é Arquiteto de Soluções na AWS Brasil e tem grande interesse por automação e cultura DevOps.
Guilherme França é Arquiteto de Soluções na AWS entusiasmado com Infraestrutura como Código e automatização de operações.