
Olá, Habr!
Na realidade moderna, devido ao papel crescente da contenção nos processos de desenvolvimento, a questão da segurança em várias etapas e entidades relacionadas a contêineres é de grande importância. Realizar verificações manualmente é uma tarefa trabalhosa, portanto seria bom dar pelo menos os primeiros passos em direção à automação desse processo.
Neste artigo, vou compartilhar scripts prontos para implementar várias ferramentas de segurança do Docker e instruções sobre como implantar um pequeno ambiente de demonstração para verificar esse processo. Os materiais podem ser utilizados para experimentar como organizar o processo de teste de segurança de imagens e instruções do Dockerfile. É claro que a infraestrutura de desenvolvimento e implantação de cada um é diferente, então abaixo apresentarei algumas opções possíveis.
Ferramentas de verificação de segurança
Existem muitos aplicativos auxiliares e scripts diferentes que realizam verificações sobre diversos aspectos da infraestrutura do Docker. Parte deles já foi descrita no artigo anterior (), e neste material eu gostaria de me concentrar em três deles, que cobrem a maior parte dos requisitos de segurança das imagens Docker criadas durante o processo de desenvolvimento. Além disso, também mostrarei um exemplo de como essas três ferramentas podem ser conectadas em um pipeline para realizar verificações de segurança.
Hadolint
Uma ferramenta de linha de comando bastante simples que ajuda a avaliar preliminarmente a correção e a segurança das instruções dos Dockerfiles (por exemplo, o uso somente de repositórios de imagens permitidos ou o uso do sudo).

Dockle
Uma ferramenta de linha de comando que trabalha com uma imagem (ou com um arquivo tar salvo da imagem), que verifica a correção e a segurança de uma imagem específica, analisando suas camadas e configuração – quais usuários foram criados, quais instruções são usadas, quais volumes estão conectados, a presença de senhas vazias etc. Até agora, o número de verificações não é muito grande e baseia-se em algumas verificações e recomendações próprias. para o Docker.

Trivy
Esta ferramenta visa identificar vulnerabilidades de dois tipos: problemas de compilação de sistemas operacionais (suportados Alpine, RedHat (EL), CentOS, Debian GNU, Ubuntu) e problemas em dependências (Gemfile.lock, Pipfile.lock, composer.lock, package-lock.json, yarn.lock, Cargo.lock). O Trivy pode escanear tanto uma imagem no repositório quanto uma imagem local, além de realizar a varredura com base em um arquivo .tar fornecido com a imagem Docker.

Opções de implementação das ferramentas
Para experimentar as aplicações descritas em condições isoladas, fornecerei instruções sobre como instalar todas as ferramentas dentro de um processo simplificado.
A ideia principal é demonstrar como é possível implementar uma verificação automática do conteúdo do Dockerfile e das imagens Docker que são criadas durante o desenvolvimento.
A verificação consiste nos seguintes passos:
- Verificação da validade e segurança das instruções do Dockerfile — com a ferramenta linter Hadolint
- Verificação da validade e segurança das imagens finais e intermediárias — com a ferramenta Dockle
- Verificação da existência de vulnerabilidades conhecidas (CVE) na imagem base e em várias dependências — com a ferramenta Trivy
Em seguida, no artigo, oferecerei três opções para implementar esses passos:
A primeira — através da configuração do pipeline CI/CD com o exemplo do GitLab (incluindo a descrição do processo de criação de uma instância de teste).
A segunda — utilizando um script shell.
A terceira — construindo uma imagem Docker para escanear imagens Docker.
Você pode escolher a opção que mais lhe convém, transferi-la para sua infraestrutura e adaptá-la às suas necessidades.
Todos os arquivos necessários e instruções adicionais também estão disponíveis no repositório:
Integração no GitLab CI/CD
Na primeira opção, veremos como implementar verificações de segurança usando o sistema de repositórios do GitLab. Aqui, seguiremos os passos e explicaremos como instalar do zero um ambiente de teste com o GitLab, criar o processo de escaneamento e realizar a execução das ferramentas para verificar o Dockerfile de teste e uma imagem aleatória — a aplicação JuiceShop.
Instalação do GitLab
1. Instale o Docker:
sudo apt-get update && sudo apt-get install docker.io2. Adicione o usuário atual ao grupo docker, para poder trabalhar com o Docker sem sudo:
sudo addgroup docker3. Encontre seu IP:
ip addr4. Instale e execute o GitLab em um contêiner, substituindo o endereço IP no hostname pelo seu:
docker run --detach
--hostname 192.168.1.112
--publish 443:443 --publish 80:80
--name gitlab
--restart always
--volume /srv/gitlab/config:/etc/gitlab
--volume /srv/gitlab/logs:/var/log/gitlab
--volume /srv/gitlab/data:/var/opt/gitlab
gitlab/gitlab-ce:latestAguardamos enquanto o GitLab executa todos os procedimentos necessários para a instalação (é possível acompanhar o processo através da saída do log: docker logs -f gitlab).
5. Abra seu IP local no navegador e veja a página solicitando para alterar a senha do usuário root:

Definimos uma nova senha e acessamos o GitLab.
6. Criamos um novo projeto, por exemplo cicd-test, e o inicializamos com um arquivo de início. README.md:

7. Agora precisamos instalar o GitLab Runner: o agente que executará todas as operações necessárias por demanda.
Baixamos a versão mais recente (neste caso — para Linux 64 bits):
sudo curl -L --output /usr/local/bin/gitlab-runner https://gitlab-runner-downloads.s3.amazonaws.com/latest/binaries/gitlab-runner-linux-amd648. Tornamos-o executável:
sudo chmod +x /usr/local/bin/gitlab-runner9. Adicionamos o usuário do sistema para o Runner e iniciamos o serviço:
sudo useradd --comment 'GitLab Runner' --create-home gitlab-runner --shell /bin/bash
sudo gitlab-runner install --user=gitlab-runner --working-directory=/home/gitlab-runner
sudo gitlab-runner startDeve resultar em algo assim:
local@osboxes:~$ sudo gitlab-runner install --user=gitlab-runner --working-directory=/home/gitlab-runner
Runtime platform arch=amd64 os=linux pid=8438 revision=0e5417a3 version=12.0.1
local@osboxes:~$ sudo gitlab-runner start
Runtime platform arch=amd64 os=linux pid=8518 revision=0e5417a3 version=12.0.1 10. Agora registramos o Runner para que ele possa interagir com nossa instância do GitLab.
Para isso, abrimos a página Configurações-CI/CD (http://OUR_ IP_ADDRESS/root/cicd-test/-/settings/ci_cd) e na aba Runners encontramos o URL e o Token de Registro:

11. Registramos o Runner, inserindo o URL e o Token de Registro:
sudo gitlab-runner register
--non-interactive
--url "http:///"
--registration-token ""
--executor "docker"
--docker-privileged
--docker-image alpine:latest
--description "docker-runner"
--tag-list "docker,privileged"
--run-untagged="true"
--locked="false"
--access-level="not_protected"Como resultado, obtemos um GitLab funcional, onde precisamos adicionar instruções para iniciar nossas utilidades. Neste caso de demonstração, não temos etapas para compilar o aplicativo e seu empacotamento em contêineres, mas em um ambiente real, essas etapas precederão as etapas de varredura e formarão imagens e Dockerfile para análise.
Configuração do pipeline
1. Adicionaremos arquivos ao repositório mydockerfile.df (este é um Dockerfile de teste que iremos verificar) e o arquivo de configuração do processo GitLab CI/CD .gitlab-cicd.yml, que lista as instruções para os scanners (note o ponto no nome do arquivo).
O arquivo de configuração YAML contém instruções para executar três utilitários (Hadolint, Dockle e Trivy), que analisarão o Dockerfile selecionado e a imagem definida na variável DOCKERFILE. Todos os arquivos necessários podem ser obtidos no repositório:
Trecho de mydockerfile.df (este é um arquivo abstrato com um conjunto de instruções arbitrárias apenas para demonstrar o funcionamento do utilitário). O link direto para o arquivo:
Conteúdo do mydockerfile.df
FROM amd64/node:10.16.0-alpine@sha256:f59303fb3248e5d992586c76cc83e1d3700f641cbcd7c0067bc7ad5bb2e5b489 AS tsbuild
COPY package.json .
COPY yarn.lock .
RUN yarn install
COPY lib lib
COPY tsconfig.json tsconfig.json
COPY tsconfig.app.json tsconfig.app.json
RUN yarn build
FROM amd64/ubuntu:18.04@sha256:eb70667a801686f914408558660da753cde27192cd036148e58258819b927395
LABEL maintainer="Rhys Arkins <rhys@arkins.net>"
LABEL name="renovate"
...
COPY php.ini /usr/local/etc/php/php.ini
RUN cp -a /tmp/piik/* /var/www/html/
RUN rm -rf /tmp/piwik
RUN chown -R www-data /var/www/html
ADD piwik-cli-setup /piwik-cli-setup
ADD reset.php /var/www/html/
## ENTRYPOINT ##
ADD entrypoint.sh /entrypoint.sh
ENTRYPOINT ["/entrypoint.sh"]
USER rootA configuração YAML se parece com isto (o próprio arquivo pode ser obtido através do link direto aqui: ):
Conteúdo do .gitlab-ci.yml
variáveis:
DOCKER_HOST: "tcp://docker:2375/"
DOCKERFILE: "mydockerfile.df" # nome do Dockerfile a ser analisado
DOCKERIMAGE: "bkimminich/juice-shop" # nome da imagem Docker a ser analisada
# DOCKERIMAGE: "knqyf263/cve-2018-11235" # imagem Docker de teste com várias CVEs CRÍTICAS
SHOWSTOPPER_PRIORITY: "CRÍTICO" # que nível de criticidade irá falhar o trabalho do Trivy
TRIVYCACHE: "$CI_PROJECT_DIR/.cache" # onde armazenar o banco de dados de vulnerabilidades do Trivy para reutilização mais rápida
ARTIFACT_FOLDER: "$CI_PROJECT_DIR"
serviços:
- docker:dind # para poder construir imagens Docker dentro do Runner
estágios:
- scan
- report
- publish
HadoLint:
# Análise básica de lint das instruções do Dockerfile
stage: scan
image: docker:git
after_script:
- cat $ARTIFACT_FOLDER/hadolint_results.json
script:
- export VERSION=$(wget -q -O - https://api.github.com/repos/hadolint/hadolint/releases/latest | grep '"tag_name":' | sed -E 's/.*"v([^"]+)".*//1/')
- wget https://github.com/hadolint/hadolint/releases/download/v${VERSION}/hadolint-Linux-x86_64 && chmod +x hadolint-Linux-x86_64
# NB: hadolint sempre sairá com o código de saída 0
- ./hadolint-Linux-x86_64 -f json $DOCKERFILE > $ARTIFACT_FOLDER/hadolint_results.json || exit 0
artifacts:
when: always # retornar artefatos mesmo após falha do trabalho
paths:
- $ARTIFACT_FOLDER/hadolint_results.json
Dockle:
# Analisando melhores práticas sobre imagem Docker (permissões de usuários, instruções seguidas ao construir a imagem, etc.)
stage: scan
image: docker:git
after_script:
- cat $ARTIFACT_FOLDER/dockle_results.json
script:
- export VERSION=$(wget -q -O - https://api.github.com/repos/goodwithtech/dockle/releases/latest | grep '"tag_name":' | sed -E 's/.*"v([^"]+)".*//1/')
- wget https://github.com/goodwithtech/dockle/releases/download/v${VERSION}/dockle_${VERSION}_Linux-64bit.tar.gz && tar zxf dockle_${VERSION}_Linux-64bit.tar.gz
- ./dockle --exit-code 1 -f json --output $ARTIFACT_FOLDER/dockle_results.json $DOCKERIMAGE
artifacts:
when: always # retornar artefatos mesmo após falha do trabalho
paths:
- $ARTIFACT_FOLDER/dockle_results.json
Trivy:
# Analisando imagem Docker e dependências de pacotes contra várias bases de CVE
stage: scan
image: docker:git
script:
# obtendo o último Trivy
- apk add rpm
- export VERSION=$(wget -q -O - https://api.github.com/repos/knqyf263/trivy/releases/latest | grep '"tag_name":' | sed -E 's/.*"v([^"]+)".*//1/')
- wget https://github.com/knqyf263/trivy/releases/download/v${VERSION}/trivy_${VERSION}_Linux-64bit.tar.gz && tar zxf trivy_${VERSION}_Linux-64bit.tar.gz
# exibindo todas as vulnerabilidades sem falhar a build
- ./trivy -d --cache-dir $TRIVYCACHE -f json -o $ARTIFACT_FOLDER/trivy_results.json --exit-code 0 $DOCKERIMAGE
# escrevendo informações sobre vulnerabilidades em stdout em formato legível (ler json puro não é divertido, hein?). Você pode remover isso se não precisar.
- ./trivy -d --cache-dir $TRIVYCACHE --exit-code 0 $DOCKERIMAGE
# falhar a build se a prioridade SHOWSTOPPER for encontrada
- ./trivy -d --cache-dir $TRIVYCACHE --exit-code 1 --severity $SHOWSTOPPER_PRIORITY --quiet $DOCKERIMAGE
artifacts:
when: always # retornar artefatos mesmo após falha do trabalho
paths:
- $ARTIFACT_FOLDER/trivy_results.json
cache:
paths:
- .cache
Report:
# combinando saídas de ferramentas em um HTML
stage: report
when: always
image: python:3.5
script:
- mkdir json
- cp $ARTIFACT_FOLDER/*.json ./json/
- pip install json2html
- wget https://raw.githubusercontent.com/shad0wrunner/docker_cicd/master/convert_json_results.py
- python ./convert_json_results.py
artifacts:
paths:
- results.htmlSe necessário, você também pode escanear e salvar imagens como um arquivo .tar (no entanto, será necessário alterar os parâmetros de entrada para as ferramentas no arquivo YAML).
NB: O Trivy requer que estejam instalados rpm e git. Caso contrário, ele retornará erros ao escanear imagens baseadas em RedHat e ao obter atualizações do banco de dados de vulnerabilidades.
2. Após adicionar arquivos ao repositório, de acordo com as instruções em nosso arquivo de configuração, o GitLab iniciará automaticamente o processo de construção e escaneamento. Na aba CI/CD → Pipelines, você poderá ver o progresso da execução das instruções.
Como resultado, temos quatro tarefas. Três delas se ocupam do escaneamento em si e a última (Relatório) compila um relatório simples a partir de arquivos dispersos com os resultados do escaneamento.

Por padrão, o Trivy interrompe sua execução se encontrar vulnerabilidades CRÍTICAS na imagem ou nas dependências. Ao mesmo tempo, o Hadolint sempre retorna um código de sucesso, pois sua execução sempre gera observações, o que leva à interrupção da construção.
Dependendo dos requisitos específicos, é possível configurar o código de saída para que essas ferramentas interrompam também o processo de construção ao detectar problemas de certa criticidade. No nosso caso, a construção será interrompida apenas se o Trivy detectar uma vulnerabilidade com a criticidade que especificamos na variável SHOWSTOPPER em .gitlab-ci.yml.

O resultado do trabalho de cada ferramenta pode ser visto no log de cada tarefa de escaneamento, diretamente nos arquivos json na seção de artefatos ou em um relatório HTML simples (mais sobre isso adiante):

3. Para apresentar os relatórios das ferramentas de uma forma um pouco mais legível, é utilizado um pequeno script em Python para converter três arquivos json em um único arquivo HTML com uma tabela de defeitos.
Esse script é executado como uma tarefa separada de Relatório, e seu artefato final é um arquivo HTML com o relatório. O código-fonte do script também está no repositório e pode ser adaptado para suas necessidades, cores, etc.

Shell-script
A segunda opção é adequada para situações em que é necessário verificar imagens Docker fora de um sistema CI/CD ou quando é preciso ter todas as instruções em um formato que possa ser executado diretamente no host. Esta opção é coberta por um script shell pronto, que pode ser executado em uma máquina virtual (ou até mesmo real) limpa. O script executa as mesmas instruções que o gitlab-runner descrito acima.
Para que o script funcione corretamente, o Docker deve estar instalado no sistema e o usuário atual deve estar no grupo docker.
Você pode obter o script aqui:
No início do arquivo, as variáveis definem qual imagem deve ser escaneada e quais níveis de criticidade dos defeitos farão com que o utilitário Trivy saia com o código de erro especificado.
Durante a execução do script, todas as utilidades serão baixadas para o diretório docker_tools, e os resultados do seu trabalho estarão no diretório docker_tools/json, e o HTML com o relatório estará no arquivo results.html.
Exemplo de saída do script
~\/docker_cicd$ .\/docker_sec_check.sh
[+] Definindo variáveis de ambiente
[+] Instalando pacotes necessários
[+] Preparando diretórios necessários
[+] Buscando Dockerfile de exemplo
2020-10-20 10:40:00 (45.3 MB/s) - ‘Dockerfile’ salvo [8071/8071]
[+] Baixando imagem para escanear
latest: Pulling from bkimminich\/juice-shop
[+] Executando Hadolint
...
Dockerfile:205 DL3015 Evite pacotes adicionais especificando `--no-install-recommends`
Dockerfile:248 DL3002 O último USUÁRIO não deve ser root
...
[+] Executando Dockle
...
WARN - DKL-DI-0006: Evite a tag latest
* Evite a tag 'latest'
INFO - CIS-DI-0005: Ative a confiança de conteúdo para Docker
* export DOCKER_CONTENT_TRUST=1 antes de docker pull/build
...
[+] Executando Trivy
juice-shop\/frontend\/package-lock.json
=====================================
Total: 3 (UNKNOWN: 0, LOW: 1, MEDIUM: 0, HIGH: 2, CRITICAL: 0)
+---------------------+------------------+----------+---------+-------------------------+
| BIBLIOTECA | ID DE VULNERABILIDADE | SEVERIDADE | VERSÃO | TITULO |
+---------------------+------------------+----------+---------+-------------------------+
| object-path | CVE-2020-15256 | ALTA | 0.11.4 | Poluição de protótipo em |
| | | | | object-path |
+---------------------+------------------+ +---------+-------------------------+
| tree-kill | CVE-2019-15599 | | 1.2.2 | Injeção de código |
+---------------------+------------------+----------+---------+-------------------------+
| webpack-subresource | CVE-2020-15262 | BAIXA | 1.4.1 | Chunks carregados dinamicamente não protegidos |
| | | | | |
+---------------------+------------------+----------+---------+-------------------------+
juice-shop\/package-lock.json
============================
Total: 20 (UNKNOWN: 0, LOW: 1, MEDIUM: 6, HIGH: 8, CRITICAL: 5)
...
juice-shop\/package-lock.json
============================
Total: 5 (CRITICAL: 5)
...
[+] Removendo sobras
[+] Melhorando a aparência da saída
[+] Convertendo resultados em JSON
[+] Escrevendo resultados HTML
[+] Saída limpa ============================================================
[+] Tudo concluído. Encontre o relatório HTML resultante em results.htmlImagem Docker com todas as utilidades
Como uma terceira alternativa, elaborei dois simples Dockerfiles para criar uma imagem com utilidades de segurança. Um Dockerfile ajudará a montar um conjunto para escanear a imagem do repositório, o segundo (Dockerfile_tar) - a montar um conjunto para escanear um arquivo tar com a imagem.
1. Pegue o Dockerfile apropriado e os scripts do repositório .
2. Execute-o para compilar:
docker build -t dscan:image -f docker_security.df .
3. Após a conclusão da compilação, crie um contêiner a partir da imagem. Para isso, passe a variável de ambiente DOCKERIMAGE com o nome da imagem que nos interessa e monte o Dockerfile que queremos analisar, do nosso computador para o arquivo /Dockerfile (note que é necessário o caminho absoluto para este arquivo):
docker run --rm -v $(pwd)/results:/results -v $(pwd)/docker_security.df:/Dockerfile -e DOCKERIMAGE="bkimminich/juice-shop" dscan:image
[+] Definindo variáveis de ambiente
[+] Executando Hadolint
/Dockerfile:3 DL3006 Sempre marque a versão de uma imagem explicitamente
[+] Executando Dockle
WARN - DKL-DI-0006: Evite a tag latest
* Evite a tag 'latest'
INFO - CIS-DI-0005: Habilite a confiança do conteúdo para Docker
* export DOCKER_CONTENT_TRUST=1 antes de docker pull/build
INFO - CIS-DI-0006: Adicione a instrução HEALTHCHECK à imagem do contêiner
* declaração HEALTHCHECK não encontrada
INFO - DKL-LI-0003: Mantenha apenas os arquivos necessários
* arquivo desnecessário: juice-shop/node_modules/sqlite3/Dockerfile
* arquivo desnecessário: juice-shop/node_modules/sqlite3/tools/docker/architecture/linux-arm64/Dockerfile
* arquivo desnecessário: juice-shop/node_modules/sqlite3/tools/docker/architecture/linux-arm/Dockerfile
[+] Executando Trivy
...
juice-shop/package-lock.json
============================
Total: 20 (UNKNOWN: 0, LOW: 1, MEDIUM: 6, HIGH: 8, CRITICAL: 5)
...
[+] Tornando a saída mais bonita
[+] Iniciando o módulo principal ============================================================
[+] Convertendo resultados JSON
[+] Escrevendo resultados em HTML
[+] Saída limpa ============================================================
[+] Tudo pronto. Encontre o relatório HTML resultante em results.htmlResultados
Consideramos apenas um conjunto básico de utilidades para escanear artefatos Docker, que, na minha opinião, cobre de forma bastante eficaz uma parte considerável dos requisitos de segurança das imagens. Existem ainda muitos outros instrumentos pagos e gratuitos que podem realizar as mesmas verificações, gerar relatórios bonitos ou funcionar puramente em modo console, abrangendo sistemas de gerenciamento de contêineres, etc. Uma visão geral desses instrumentos e formas de integração pode surgir um pouco mais tarde.
Uma das vantagens do conjunto de ferramentas descrito no artigo é que todas são baseadas em código aberto, permitindo que você experimente com elas e com outras ferramentas semelhantes para encontrar o que realmente se adapta às suas necessidades e peculiaridades da infraestrutura. Com certeza, todas as vulnerabilidades descobertas devem ser analisadas quanto à aplicabilidade em condições específicas, mas esse é um tema para um grande artigo futuro.
Espero que este guia, scripts e utilitários sejam úteis e se tornem um ponto de partida para a criação de uma infraestrutura mais segura na área de containerização.
Fonte: habr.com
