Scan de vulnerabilidades em imagens de containers

1 – Introdução

Embora seja tecnologia antiga (o FreeBSD já implementava algo similar em sua versão 4.0, em 2000, chamado Jails), somente nos últimos 6 anos (este texto foi escrito em 2020) vimos a popularização do uso de containers para o deploy de ambientes homogêneos de aplicações, garantindo que tanto Desenvolvimento quanto QA e Produção estejam exatamente iguais em termos de pacotes necessários para se rodar a aplicação, crescer. Mas esse crescimento não pode ser considerado lento. Foi uma explosão.

Uma das vantagens do uso de containers é o isolamento total do seu ambiente operacional de forma que se um atacante consegue comprometer esse ambiente bastará destruí-lo e lançar outro com a vulnerabilidade corrigida, sem necessidade de se lançar uma nova instância ou VM e repetir toda a sua configuração.

Surgiu então o DockerHub, onde as companhias produtoras de sistemas operacionais (mesmo das distribuições linux) lançaram suas imagens, menores e otimizadas. Desenvolvedores e Sysadmin do todo o mundo começaram a criar suas próprias imagens, à partir das imagens base. Outras pessoas começaram a consumir essas imagens. O uso de containers popularizou-se e surgiram sistemas como CoreOS, RancherOS, Swarm e Kubernetes. E então, as pessoas que produziram imagens para os mais variados usos esqueceram-se delas e elas nunca mais foram atualizadas e sistemas inteiros subiram (e sobem) em instâncias rodando a mais nova versão do Sistema Operacional, com todo o hardening de segurança definido pela empresa aplicado e mesmo assim são utilizadas para mineração de criptomoedas. Onde está o problema?

Várias pessoas assumem que uma imagem Docker e seus containers são seguros por padrão, o que, infelizmente, não é o caso. Há algumas coisas que afetam a segurança da sua imagem Docker. Quais pacotes estão instalados na imagem, bibliotecas usadas pela sua aplicação ou mesmo a imagem base, todos esses componentes podem introduzir vulnerabilidades na sua aplicação. Vários deles são facilmente detectáveis e de simples correção, entretanto.

Ouvi uma frase em um evento da IBM onde o apresentador disse que “o DockerHub é a forma mais eficiente de você conseguir uma imagem de container vulnerável” e onde ele mostrava o OpenShift já fazendo um scan automático das imagens e indicando quais eram as suas vulnerabilidades, buscando as bases das principais distribuições Linux e locais como o CVE-Mitre. Mas, se estou utilizando um ambiente como o ElasticBeanstalk da AWS ou um Kubernetes, como faço isso? E se meu pipeline de CI/CD faz build constante da imagem?

Há uma ferramenta de segurança muito útil que permite que desenvolvedores, times de QA, times de segurança e mesmo Sysop testarem, identificarem e endereçarem vulnerabilidades nas imagens que são usadas para criar aplicações. Chama-se Anchore Engine.

Vamos ver como instalar e usar Anchore Image Vulnerability Scanner a seguir. Geralmente há vários métodos de implementação, mas abordaremos os dois seguintes:

  • AnchoreCLI command-line;
  • Jenkins Anchore Container Image Scanner plugin.

Vamos ver como instalar, configurar e iniciar o scanner, configurar e usar a ferramenta de linha de comando AnchoreCLI bem como o plugin para Jenkins. Você aprenderá como adicionar imagens para o scan, executá-lo e ver relatórios.

Ao final deste artigo você terá aprendido o seguinte:

  • Instalar e configurar o Anchore Engine;
  • Instalar, configurar e usar o AnchoreCLI;
  • Configurar e usar o plugin Anchore Container Image Scanner para Jenkins

Para conduzir esse tutorial necessitaremos de uma máquina local ou virtual com Ubuntu 18.04, com Docker, docker-compose e Jenkins instalado e rodando. Recomendo uma instância AWS EC2 t2.medium. Garanta que você tem permissões de sudo ou root nessa máquina.

Configurando e utilizando o Anchore Image Scanner em linha de comando

2 – Configurar o diretório de trabalho e baixar os arquivos de configuração do Anchore.

Crie um diretório de trabalho para os seus arquivos do Anchore. Neste diretório você criará dois sub-diretórios: um para a configuração e outro para a base de dados:

mkdir -p anchore/{config,db}

Acesse o diretório anchore. Faremos o download de dois arquivos de configuração (docker-compose.yaml e config.yaml) à partir do projeto do Github.

Para baixar o arquivo docker-compose.yaml faça:

curl https://docs.anchore.com/current/docs/engine/quickstart/docker-compose.yaml > docker-compose.yaml

Então baixe o arquivo config.yaml para o diretório anchore/config:

curl https://raw.githubusercontent.com/anchore/anchore-engine/master/scripts/docker-compose/config.yaml -o ~/anchore/config/config.yaml

O arquivo config.yaml possui as configurações básicas que o serviço de engine do Anchore requer para rodar. Temos vários parâmetros como log level, porta, usuário, senha e todos podem ser ajustados para atender as requisições do sistema.

É uma boa prática de segurança mudar a senha e você pode fazer isso simplesmente editando o arquivo config.yaml. Entretanto, nesse artigo, usaremos as configurações padrão.

Com o diretório de trabalho e arquivos de configuração em seus lugares, o sistema está pronto para a instalação do Anchore.

3 – Instalar e iniciar o Anchore Engine

Usaremos Docker compose para instalar e iniciar o Anchore Engine e seu banco de dados. À partir do diretório anchore faça:

docker-compose up -d

Isto fará o download (pull) das imagens Docker do Anchore automaticamente e criará o seu banco de dados no diretório anchore/database/ previamente criado. Quando completo, o comando iniciará o Anchore engine.

Depois de instalar e iniciar o Anchore com sucesso, você pode analisar imagens Docker usando a ferramenta de linha de comando AnchoreCLI. Naturalmente, você precisa primeiro instalá-la.

4 – Instalar e configurar AnchoreCLI

A ferramenta de linha de comando AnchoreCLI é instalada através do sistema PIP do Python. Primeiro instalaremos o pacote python-pip, que será usada para instalar o AnchoreCLI diretamente do seu código-fonte, já instalando-o e deixando-o pronto para ser configurado de acordo com nosso Anchore Engine.

Para instalar o pacote python-pip, execute:

sudo apt-get update
sudo apt-get install python-pip
sudo pip install --upgrade setuptools

Em seguida, instale o AnchoreCLI usando python-pip

pip install anchorecli

Este comando executará o download e a instalação dos arquivos do AnchoreCLI. Após a instalação você deve recarregar as suas configurações de profile usando o seguinte comando:

source ~/.profile

Para verificar se a instalação foi efetuada com sucesso, use o comando abaixo para obter a versão do AnchoreCLI:

anchore-cli --version

Para verificar o status do sistema com o AnchoreCLI, use o seguinte comando:

anchore-cli --url http://localhost:8228/v1 --u admin --p foobar system status

Note que você deve passar a URL do Anchore engine, o username e a password.

Por padrão, o AnchoreCLI tentará acessar o Anchore Engine sem autenticação. Entretanto, isso não irá funcionar e então você precisa passar as credenciais para o Anchore Engine a cada comando que você executa. Ao invés disso, a alternativa é definir esses parâmetros como variáveis de ambiente. O comando abaixo definirá a URL do Anchore Engine junto à porta 8228, o usuário e a senha definidos no arquivo de configuração:

export ANCHORE_CLI_URL=http://localhost:8228/v1
export ANCHORE_CLI_USER=admin
export ANCHORE_CLI_PASS=foobar

Recomendamos manter essas variáveis configuradas nos arquivos .profile ou em seus arquivos de ambientes gerenciados por produtos como autoenv.

5 – Adicionar e analizar imagens

Agora que temos o Anchore Engine rodando e o AnchoreCLI configurado, vamos adicionar e analisar imagens Docker para descobrir vulnerabilidades nelas. Analizaremos duas imagens: openjdk:8-jre-alpine, que possui vulnerabilidades e debian:latest que não as possui. Adicionalmente, analisaremos algumas outras imagens de JDK.

Primeiro, precisamos adicionar imagens ao Anchore Engine. Fazemos com AnchoreCLI, assim:

anchore-cli image add openjdk:8-jre-alpine
anchore-cli image add docker.io/library/debian:latest
anchore-cli image add openjdk:10-jdk
anchore-cli image add openjdk:11-jdk

Após adicionar a imagem ao Anchore Engine, a análise inicia imediatamente. Se há várias imagens carregadas, elas são postas em uma fila e analizadas uma por vez. Você pode verificar o status do progresso e ver a lista de imagens carregadas junto a suas análises. Para ver a lista de imagens, fazemos:

anchore-cli image list

Teremos então a seguinte saída:

mrbits@garganthua:~/anchore$ anchore-cli image list
Full Tag                         Image Digest                                                            Analysis Status
docker.io/openjdk:10-jdk         sha256:923d074ef1f4f0dceef68d9bad8be19c918d9ca8180a26b037e00576f24c2cb4 analyzed
docker.io/openjdk:11-jdk         sha256:9923c0853475007397ed5c91438c12262476d99372d4cd4d7d44d05e9af5c077 analyzed
docker.io/openjdk:8-jre-alpine   sha256:b2ad93b079b1495488cc01375de799c402d45086015a120c105ea00e1be0fd52 analyzed

Dependendo do número de imagens, seu tamanho e tempo passado desde suas adição, você terá o status analyzed para análises completas, analyzing para aquelas em progresso e not analyzed para as imagens aguardando análise na fila.

6 – Visualizar o resultado da análise

Assim que a análise estiver completa, você pode verificar os resultados e ver os dados de vulnerabilidades, avaliação de policy e outros problemas que o Anchore Engine encontrou.

Para verificar os resultados de vulnerabilidades da imagem openjdk:8-jre-alpine faça:

anchore-cli image vuln openjdk:8-jre-alpine all

A saída será algo assim:

mrbits@garganthua:~/anchore$ anchore-cli image vuln openjdk:8-jre-alpine all
Vulnerability    IDPackage               Severity  Fix          CVE Refs Vulnerability URL
CVE-2018-1000654 libtasn1-4.13-r0        High      4.14-r0      http://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2018-1000654
CVE-2019-12900   libbz2-1.0.6-r6         High      1.0.6-r7     http://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2019-12900
CVE-2019-14697   musl-1.1.20-r4          High      1.1.20-r5    http://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2019-14697
CVE-2019-14697   musl-utils-1.1.20-r4    High      1.1.20-r5    http://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2019-14697
CVE-2019-8457    sqlite-libs-3.26.0-r3   High      3.28.0-r0    http://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2019-8457
...

O relatório mostra o identificador CVE, o pacote vulnerável, sua severidade e se há ou não uma correção para ela. Para nossa imagem openjdk:8-jre-alpine, a análise mostra que há cinco vulnerabilidades HIGH e mais algumas de níveis médio e não importantes (negligible), que não são mostradas na saída acima.

Para ver as vulnerabilidades de uma imagem estável como a debian:latest, faça:

anchore-cli image vuln docker.io/library/debian:latest all

Veremos uma saída similar a essa:

Vulnerability     IDPackage        Severity   Fix   CVE RefsVulnerability URL
CVE-2005-2541     tar-1.30+dfsg-6  Negligible None  https://security-tracker.debian.org/tracker/CVE-2005-2541
CVE-2019-1010022  libc-bin-2.28-10 Negligible None  https://security-tracker.debian.org/tracker/CVE-2019-1010022
CVE-2019-1010022  libc6-2.28-10    Negligible None  https://security-tracker.debian.org/tracker/CVE-2019-1010022
CVE-2019-1010023  libc-bin-2.28-10 Negligible None  https://security-tracker.debian.org/tracker/CVE-2019-1010023
CVE-2019-1010023  libc6-2.28-10    Negligible None  https://security-tracker.debian.org/tracker/CVE-2019-1010023
CVE-2019-1010024  libc-bin-2.28-10 Negligible None  https://security-tracker.debian.org/tracker/CVE-2019-1010024

Como podemos ver pelo relatório, a imagem debian:latest tem vulnerabilidades não importantes e nenhuma correção para elas.

Para ver os resultados da avaliação de políticas para a imagem openjdk:8-jre-alpine, faça:

anchore-cli evaluate check openjdk:8-jre-alpine

Sua saída, cujo resultado mostra uma falha (fail) é:

Image Digest: sha256:b2ad93b079b1495488cc01375de799c402d45086015a120c105ea00e1be0fd52
Full Tag: docker.io/openjdk:8-jre-alpine
Status: fail
Last Eval: 2019-09-20T12:03:32Z
Policy ID: 2c53a13c-1765-11e8-82ef-23527761d060

A imagem viola uma política específica (Policy ID: 2c53a13c-1765-11e8-82ef-23527761d060) e portanto retorna um status de falha.

Agora que vimos como o Anchore Engine responde após detectar uma violação de política, é hora de ver como ele se comporta com a nossa imagem estável debian:latest. Para isso, rodamos o seguinte comando:

anchore-cli evaluate check docker.io/library/debian:latest --detail

A saída do comando acima se parecerá com:

Image Digest: sha256:d3351d5bb795302c8207de4db9e0b6eb23bcbfd9cae5a90b89ca01f49d0f792d
Full Tag: docker.io/library/debian:latest
Image ID: c2c03a296d2329a4f3ab72a7bf38b78a8a80108204d326b0139d6af700e152d1
Status: pass
Last Eval: 2019-09-20T12:00:06Z
Policy ID: 2c53a13c-1765-11e8-82ef-23527761d060
Final Action: warn
Final Action Reason: policy_evaluation
Gate  TriggerDetail Status
dockerfileinstructionDockerfile directive 'HEALTHCHECK' not found, matching condition 'not_exists' checkwarn

O resultado mostra um status de Pass e uma Final Action como Warn por causa da informação que não não casada com uma diretiva do Dockerfile. Ele não falha, mas requer verificação e tratamento do problema.

Configurando e usando o plugin do Anchore Image Scanner no Jenkins

Testes são parte importante das rotinas de integração contínua e não pode haver deploy em produção se a aplicação não for exaustivamente testada. Entretanto, o que vemos são testes unitários, comportamentais, de integração e regressão sendo feitos, enquanto testes de segurança são relegados a segundo plano.

Nesta parte do artigo trataremos da inclusão de um passo de teste de vulnerabilidades em imagens docker em nosso pipeline, antes da imagem ir para o nosso registry.

7 – Adicionar e configurar o plugin do Anchore Image Scanner no Jenkins

Neste passo, integraremos o Anchore Engine com nosso Jenkins server. Jenkins é um serviço open-source baseado em java para automatização de uma larga gama de tarefas repetitivas no ciclo de desenvolvimento de software.

O plugin do Anchore está disponível no Jenkins mas não é instalado por padrão. Vamos instalá-lo:

  • Faça login no Jenkins usando um web browser;
  • Vá ao menu Jenkins;
  • Localize e selecione Manage Jenkins;
  • Vá para Manage Plugins;
  • Na tab Available, role a tela até Build Tools e selecione o plugin do Anchore Container Image Scanner;
  • Clique em Install without restart option.

Após a instalação do plugin do Anchore Container Image Scanner, o próximo passo é configurar as credenciais. Para isso, siga os passos abaixo:

  • Vá ao menu Jenkins e selecione a opção Manage Jenkins;
  • Abra a opção Configure system;
  • Localize a configuração do Anchore;
  • Selecione Engine Mode;
  • Insira os detalhes do Anchore Engine (URL, username, password, e a porta 8228);
  • Clique em Save.

8 – Adicionar e verificar imagens

Clique em New Item no Jenkins Dashboard no menu à esquerda. Uma janela com várias opções ser abrirá. Insira o nome desejado para o projeto.

Neste projeto usaremos o assistente de Pipeline. Selecione esta opção e clique em OK.

Estamos prontos para verificar nossas imagens. No nosso estudo usaremos as imagens que já estão no registry Docker que é acessível pelo Anchore Engine.

Para isso você deve adicionar ao pipeline o script onde especificaremos a imagem a ser verificada.

9 – Adicionar o script ao pipeline

Role a tela até a seção de Pipeline e adicione o script seguinte para especificar qual imagem deve ser verificada. Começaremos com a openjdk:8-jre-alpine que contém vulnerabilidades.

node {
def imageLine = 'openjdk:8-jre-alpine'`
writeFile file: 'anchore_images', text: imageLine`
anchore name: 'anchore_images'`
}

Clique em Save.

10 – Executar o build e verificar o relatório

No menu do Jenkins clique em Build Now

Isso iniciará o processo de build, que levará alguns minutos, dependendo do tamanho da imagem. Após terminar, um número e um botão colorido aparecerão abaixo da Build History. Ele terá a cor vermelha para Fail ou azul para Pass. Clicar no botão nos mostrará mais resultados.

11 – Verificar os resultados

Clique no  Build # para ver mais detalhes. Isso abrirá uma janela de Console Output indicando uma falha: Anchore Report (FAIL).

Este relatório detalhado indica se a análise falhou (Fail) ou passou (Pass) e nos traz vários dados mostrando as vulnerabilidades, avisos e outros, baseados em nossa configuração. Por padrão o plugin é configurado para falhar e parar um build sempre que haja vulnerabilidades. Abaixo temos um exemplo da tela de relatório de políticas e seguranca.

Abaixo vemos a imagem do relatório de segurança da imagem vulnerável.

Se agora analizamos a imagem debian:latest, sem vulnerabilidades, temos os resultado abaixo, para políticas e vulnerabilidades:

Conclusão

Anchore Container Image Scanner é uma ferramenta de análise poderosa que identifica uma grande quantidade de vulnerabilidades e problemas de políticas em imagens Docker. Possui várias opções de customização e pode ser configurada em como responder quando detecta problemas durante uma análise, como por exemplo a capacidade de falhar o build em um pipeline quando uma vulnerabilidade grave é encontrada.

Esse processo aumentará o tempo de execução do nosso pipeline por adicionar um passo de teste de vulnerabilidades logo após o build, mas teremos a garantia de que nossa aplicação rodará de maneira mais segura do que se esse teste não existisse.

Outras ferramentas

Há várias ferramentas disponíveis para executar análises de vulnerabilidades em imagens Docker. Entre elas podemos citar o Snyk e o Clair.

Da mesma maneira que podemos integrar a análise de vulnerabilidades de imagens docker no Jenkins, é possível fazê-lo facilmente em outras ferramentas. Sua integração com Code Pipeline da AWS é simples e há documentação ampla.

Como introdução a outras ferramentas, a AWS possui um artigo muito bom sobre verificação de vulnerabilidades em imagens Docker usando Clair, Amazon ECS, ECR e AWS CodePipeline.

Referências