Descomplicando o Hardening de Imagens EC2
Um desafio bem comum enfrentado por profissionais nessa jornada DevOps é entregar serviços de forma segura sem comprometer a velocidade da entrega de seus workloads. É nesse momento em que o time de segurança deve brilhar!
A ideia desse post é apresentar uma maneira automatizada de entregar AMI’s seguras, ou seja, AMI’s que passaram minimamente pelos controles exigidos do CIS (Center of Internet Security).
Aliás, o que são AMI’s?
O serviço Amazon EC2 permite que façamos o provisionamento de máquinas ou instancias virtuais na nuvem da AWS.
EC2 AMIs oferecem a configuração necessária para iniciar uma instância EC2. Você pode usar AMIs para vários cenários, como na configuração de aplicativos, aplicação de políticas de segurança, configuração de ambientes de desenvolvimento, dentre outros casos de uso.
Basicamente você pode entender a AMI como um template de configuração a nível de sistema operacional da instancia EC2.
Falando mais especificamente da AWS, utilizamos AMI’s para praticamente todo serviço de computação dessa nuvem, seja no provisionamento de uma instância simples, auto-scaling groups, e até mesmo clusters ECS e EKS.
Garantir uma entrega dessas AMI’s de forma segura é essencial para proteção dos nossos workloads.
E por que utilizar uma imagem hardenizada via controles do CIS ?
Recomendo fortemente você dar uma olhada na página oficial do CIS para ter uma ideia mais ampla do que os controles de segurança propõem.
Falando especificamente da adoção do hardening de imagens via CIS, temos algumas vantagens:
- Estar em conformidade com as práticas e recomendações exigidas de segurança cibernética.
- Imagens hardenizadas via CIS garantem a mitigação de ameaças como negação de serviço, limites de confiança e autorização.
- Redução de custos na mitigação de vulnerabilidades uma vez que as imagens tendem a nascer de forma segura.
Como podemos garantir a entrega dessas AMI’s de maneira segura e automatizada?
É aqui que entra a magia da infraestrutura como código, meus amigos e minhas amigas!
Existem formas variadas de fazer essa entrega automatizada na nuvem da AWS, mas nesse post iremos nos limitar a seguinte estratégia: provisionar a imagem automaticamente e em tempo de provisionamento executarmos as configuração de segurança de nível 1 e 2 exigidas pelo CIS.
Para isso será necessário:
- Packer: para construção e definição da nossa AMI;
- Ansible: para executar as configurações do CIS de nível 1 e 2;
- Jenkins: para construção de um pipeline afim de automatizar essa operação.
Packer
O Packer é uma solução de código aberto da Hashicorp que se propõe exclusivamente na entrega de imagens nos mais variados provedores de nuvem pública. Confira mais detalhes da solução aqui.
Primeiramente temos que configurar o arquivo de manifesto do Packer, que é feito em json:
{
"variables": {
"build_name": "lab",
"aws_access_key": "{{env `AWS_ACCESS_KEY_ID`}}",
"aws_secret_key": "{{env `AWS_SECRET_ACCESS_KEY`}}",
"aws_region": "{{env `AWS_DEFAULT_REGION`}}",
"aws_instance_type": "t3.medium",
"vpc_id" : "vpc-00012345678910",
"subnet_id" : "subnet-00012345678910",
"source_ami" : "ami-00012345678910",
"ssh_username": "ubuntu",
"ami_name": "lab-img-cis-{{timestamp}}",
"tag_value" : "lab-cis-benchmark-ami"
},
"builders": [
{
"name": "aws",
"type": "amazon-ebs",
"access_key": "{{user `aws_access_key`}}",
"secret_key": "{{user `aws_secret_key`}}",
"vpc_id": "{{user `vpc_id`}}",
"subnet_id": "{{user `subnet_id`}}",
"region": "{{user `aws_region`}}",
"source_ami": "{{user `source_ami`}}",
"instance_type": "{{user `aws_instance_type`}}",
"ssh_username": "{{user `ssh_username`}}",
"ami_name": "{{user `ami_name`}}",
"tags": {
"Name": "{{user `tag_value`}}"
}
}
],
"provisioners":[
{
"type": "ansible",
"user": "ubuntu",
"playbook_file": "./ansible/playbook_to_harden_images.yml"
}
]
}
Existem alguns detalhes importantes sobre esse manifesto. As informações da primeira parte do arquivo, referem-se as variáveis que o Packer irá utilizar para provisionar uma instância temporária no ambiente para que ele possa aplicar as configurações determinadas na playbook do Ansible.
Podemos ver que trata-se de uma AMI para instancias que irão executar Ubuntu 20.04, por isso precisamos passar ao Packer a informação do usuário que ele irá utilizar para conectar na máquina temporária.
O Packer irá “invocar” o Ansible via a propriedade de proviosioners. Nessa propriedade passamos o path de onde está a playbook do Ansible que será executada.
O Ansible
O Ansible é uma solução bem popular há alguns anos. Simplificando muito, o propósito dessa solução é o gerenciamento de configuração do ambiente. Falando de infraestrutura como código, o Ansible talvez seja o carro chefe de uma estratégia automatizada.
Nesse caso de uso, o Ansible é utilizado para fazer toda a configuração exigida pelo nível 1 do CIS. Já existem algumas playbooks de hardening do nível 1 do CIS disponíveis em repositórios públicos do github que podemos utilizar para basear as nossas playbooks. Vou deixar aqui alguns desses repositórios:
- https://github.com/ansible-lockdown/RHEL7-CIS
- https://github.com/alivx/CIS-Ubuntu-20.04-Ansible
- https://github.com/florianutz/ubuntu2004_cis
- https://github.com/QLPD/ansible-cis-amazon-linux-2
CUIDADO!
Não utilize nada no seu ambiente sem antes verificar o conteúdo. Essas playbooks fazem alterações significativas na instância. Recomendo não rodar em instancias que já estejam em produção. A ideia é usá-las de base para que possamos montar a nossa.
Exemplo de playbook:
#playbook_to_harden_images.yml---
- hosts: default
become: true
roles:
- hardening_ubuntu_cis
A role que estamos chamando é onde está descrito todas as configurações do CIS. Neste link você encontra um exemplo de como são essas configurações:
Mais uma vez, recomendo você a não simplesmente fazer uma cópia dessas tasks, tente entender o que elas fazem e como elas devem ser utilizadas.
Jenkins
O último integrante da nossa stack é o Jenkins, o propósito dele aqui é simplesmente ser a nossa plataforma de automação, no sentido de nos dar um pipeline para que possamos rodar isso sempre que necessário de uma forma centralizada e automatizada. Aqui você poderia utilizar qualquer outra plataforma de CI/CD, como Azure DevOps, Github Actions, GitLab, dentre outros.
pipeline {
agent anystages {
stage ('Validar template Packer') {
steps {
sh 'echo "---Validando Packer----"'
sh 'packer validate hardeningami.json'
}
}
stage ('Build template Packer') {
steps {
sh 'echo "---Buildando o Packer----"'
sh 'packer build hardeningami.json'
}
}
}
}
Seguindo a estratégia apresentada, o repositório deverá ter no mínimo os seguintes arquivos:
- hardeningami.json
- playbook_to_harden_images.yml
- Jenkinsfile
A organização desses arquivos vai depender do seu ambiente e também da sua estratégia de implementação
Utilizando as AMIs
Uma estratégia automatizada de entregar AMI’s seguras eleva o poder do time de operações e segurança a outro patamar.
Uma vez que temos pipeline de entrega de AMI’s já podemos pensar em trabalhar com uma infraestrutura imutável, ou seja, uma infraestrutura baseada em versões. Imagine combinar por exemplo, esse pipeline de entrega de AMI’s com um pipeline de provisionamento de uma EC2, sim, é muito mais que possível! Isso daria um controle muito maior para o time de operações, além de dar um poder gigantesco na recuperação de workloads, fazendo o time de segurança muito feliz.
Conclusão
Vimos então que é possível sim entregar segurança na operação de infraestrutura de forma automatizada, deixando de lado aquela visão de que segurança é um bloqueador natural do DevOps. Estou aqui para mostrar que não!
Por esse post é isso pessoal, muito obrigado. Críticas e sugestões são sempre bem vindas.