Métodos e exemplos de implementação de utilitários de verificação de segurança Docker



Olá, Habr!



Na realidade moderna, devido ao papel crescente da contentorização nos processos de desenvolvimento, a questão de garantir a segurança das várias etapas e entidades associadas aos contentores não está em último lugar. Realizar verificações no modo manual é demorado, portanto, seria bom realizar pelo menos as etapas iniciais para automatizar esse processo.



Neste artigo, compartilharei scripts prontos para implementar vários utilitários de segurança do Docker e instruções sobre como implantar um pequeno suporte de demonstração para testar esse processo. Você pode usar os recursos para experimentar como organizar o processo de teste de segurança para imagens e instruções do Dockerfile. É claro que a infraestrutura de desenvolvimento e implementação é diferente para cada pessoa, então a seguir darei várias opções possíveis.



Utilitários de verificação de segurança



Existem muitos aplicativos auxiliares e scripts diferentes que testam vários aspectos da infraestrutura do Docker. Alguns deles já foram descritos no artigo anterior ( https://habr.com/ru/company/swordfish_security/blog/518758/#docker-security ), e neste material gostaria de destacar três deles que abrangem os principais parte dos requisitos de segurança para imagens Docker que são construídas durante o desenvolvimento. Além disso, também mostrarei um exemplo de como esses três utilitários podem ser conectados em um pipeline para realizar verificações de segurança.



Hadolint

https://github.com/hadolint/hadolint



Um utilitário de console bastante simples que ajuda, como uma primeira aproximação, a avaliar a exatidão e a segurança das instruções do Dockerfile (por exemplo, usando apenas registros de imagem autorizados ou usando sudo).



Saída do utilitário Hadolint



Dockle

https://github.com/goodwithtech/dockle



Utilitário de console que funciona com uma imagem (ou com um arquivo tar salvo de uma imagem) que verifica a exatidão e segurança de uma imagem específica como tal, analisando suas camadas e configuração - quais usuários são criados, quais instruções usado quais volumes são montados, a presença de uma senha vazia, etc. Enquanto o número de verificações não é muito grande e é baseado em várias de suas próprias verificações e recomendações do CIS (Center for Internet Security) Benchmark para Docker.





Trivial

https://github.com/aquasecurity/trivy



Este utilitário visa encontrar dois tipos de vulnerabilidades - problemas de construção do sistema operacional (suportado por Alpine, RedHat (EL), CentOS, Debian GNU, Ubuntu) e problemas de dependência (Gemfile.lock, Pipfile. lock, composer.lock, package-lock.json, yarn.lock, Cargo.lock). Trivy pode digitalizar a imagem no repositório e a imagem local, bem como digitalizar com base no arquivo .tar transferido com a imagem Docker.







Opções de implementação para utilitários



Para experimentar os aplicativos descritos em condições isoladas, irei fornecer instruções para a instalação de todos os utilitários em um processo simplificado.



A ideia principal é demonstrar como você pode implementar a validação automática de conteúdo de imagens Dockerfile e Docker que são criadas durante o desenvolvimento.



A verificação em si consiste nas seguintes etapas:

  1. Verificar a exatidão e segurança das instruções do Dockerfile - usando o Hadolint linter
  2. Verificar a exatidão e segurança das imagens alvo e intermediárias - usando o utilitário Dockle
  3. Verificação de vulnerabilidades conhecidas (CVEs) na imagem de base e uma série de dependências com o utilitário Trivy


Mais adiante no artigo, darei três opções para implementar essas etapas:

Primeiro, configurando o pipeline de CI / CD usando o exemplo do GitLab (com uma descrição do processo de geração de uma instância de teste).

O segundo é usar um script de shell.

A terceira é com a construção de uma imagem Docker para digitalizar 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 no repositório: https://github.com/Swordfish-Security/docker_cicd



Integração com GitLab CI / CD



Na primeira opção, veremos como você pode implementar verificações de segurança usando o exemplo do sistema de repositório GitLab. Aqui, passaremos pelas etapas e analisaremos como configurar um ambiente de teste com o GitLab do zero, criar um processo de digitalização e executar utilitários para verificar um Dockerfile de teste e uma imagem aleatória - o aplicativo JuiceShop.



Instalando o GitLab

1. Instale o Docker:

sudo apt-get update && sudo apt-get install docker.io


2. Adicione o usuário atual ao grupo do docker para que você possa trabalhar com o docker não via sudo:

sudo addgroup <username> docker


3. Encontre seu IP:

ip addr


4. Instale e execute o GitLab em um contêiner, substituindo o endereço IP no nome do host 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:latest


Estamos aguardando que o GitLab conclua todos os procedimentos de instalação necessários (você pode acompanhar o processo por meio da saída do arquivo de log: docker logs -f gitlab).



5. Abra seu IP local no navegador e veja uma página com uma proposta para alterar a senha do usuário root:



Defina uma nova senha e vá para GitLab.



6. Crie um novo projeto, por exemplo cicd-test e inicialize-o com o arquivo inicial README.md :



7. Agora precisamos instalar o GitLab Runner: um agente que irá lançar todas as operações necessárias mediante solicitação.

Baixe a versão mais recente (neste caso, para Linux de 64 bits):

sudo curl -L --output /usr/local/bin/gitlab-runner https://gitlab-runner-downloads.s3.amazonaws.com/latest/binaries/gitlab-runner-linux-amd64


8. Torne-o executável:

sudo chmod +x /usr/local/bin/gitlab-runner


9. Adicione o usuário do sistema operacional para o Runner e inicie 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 start


Deve ser parecido com isto:



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 fazer isso, abra a página Configurações-CI / CD (http: // OUR_IP_ADDRESS / root / cicd-test / - / settings / ci_cd) e na guia Runners encontramos o URL e token de registro:



11. Registre o Runner substituindo o URL e o token de registro:

sudo gitlab-runner register \
--non-interactive \
--url "http://<URL>/" \
--registration-token "<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 pronto, no qual precisamos adicionar instruções para iniciar nossos utilitários. Neste caso de demonstração, não temos etapas para construir o aplicativo e sua conteinerização, mas em um ambiente real elas irão preceder as etapas de digitalização e gerar imagens e Dockerfile para análise.



Configuração do pipeline



1. Adicione ao repositório os arquivos mydockerfile.df (este é um Dockerfile de teste que verificaremos) e o arquivo de configuração do processo GitLab CI / CD .gitlab-cicd.yml , que lista instruções para scanners (observe 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 especificada na variável DOCKERFILE. Todos os arquivos necessários podem ser obtidos do repositório: https://github.com/Swordfish-Security/docker_cicd/



Excerpt from mydockerfile.df (este é um arquivo abstrato com um conjunto de instruções arbitrárias apenas para demonstrar como o utilitário funciona). Link direto para o arquivo: mydockerfile.df



Conteúdo de 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 root


A configuração YAML se parece com isto (o próprio arquivo pode ser obtido do link direto aqui: .gitlab-ci.yml ):



Conteúdo .Gitlab-ci.yml
variables:
    DOCKER_HOST: "tcp://docker:2375/"
    DOCKERFILE: "mydockerfile.df" # name of the Dockerfile to analyse   
    DOCKERIMAGE: "bkimminich/juice-shop" # name of the Docker image to analyse
    # DOCKERIMAGE: "knqyf263/cve-2018-11235" # test Docker image with several CRITICAL CVE
    SHOWSTOPPER_PRIORITY: "CRITICAL" # what level of criticality will fail Trivy job
    TRIVYCACHE: "$CI_PROJECT_DIR/.cache" # where to cache Trivy database of vulnerabilities for faster reuse
    ARTIFACT_FOLDER: "$CI_PROJECT_DIR"
 
services:
    - docker:dind # to be able to build docker images inside the Runner
 
stages:
    - scan
    - report
    - publish
 
HadoLint:
    # Basic lint analysis of Dockerfile instructions
    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 will always exit with 0 exit code
    - ./hadolint-Linux-x86_64 -f json $DOCKERFILE > $ARTIFACT_FOLDER/hadolint_results.json || exit 0
 
    artifacts:
        when: always # return artifacts even after job failure       
        paths:
        - $ARTIFACT_FOLDER/hadolint_results.json
 
Dockle:
    # Analysing best practices about docker image (users permissions, instructions followed when image was built, 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 # return artifacts even after job failure       
        paths:
        - $ARTIFACT_FOLDER/dockle_results.json
 
Trivy:
    # Analysing docker image and package dependencies against several CVE bases
    stage: scan   
    image: docker:git
 
    script:
    # getting the latest 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
     
    # displaying all vulnerabilities w/o failing the build
    - ./trivy -d --cache-dir $TRIVYCACHE -f json -o $ARTIFACT_FOLDER/trivy_results.json --exit-code 0 $DOCKERIMAGE    
    
    # write vulnerabilities info to stdout in human readable format (reading pure json is not fun, eh?). You can remove this if you don't need this.
    - ./trivy -d --cache-dir $TRIVYCACHE --exit-code 0 $DOCKERIMAGE    
 
    # failing the build if the SHOWSTOPPER priority is found
    - ./trivy -d --cache-dir $TRIVYCACHE --exit-code 1 --severity $SHOWSTOPPER_PRIORITY --quiet $DOCKERIMAGE
         
    artifacts:
        when: always # return artifacts even after job failure
        paths:
        - $ARTIFACT_FOLDER/trivy_results.json
 
    cache:
        paths:
        - .cache
 
Report:
    # combining tools outputs into one 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.html


Se necessário, você também pode digitalizar as imagens salvas como um arquivo .tar (no entanto, você precisará alterar os parâmetros de entrada para os utilitários no arquivo YAML)



NB: Trivy rpm git. RedHat-based .


2. Após adicionar os 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 digitalização. Na guia CI / CD → Pipelines, você pode ver o andamento das instruções.



Como resultado, temos quatro tarefas. Três deles estão diretamente envolvidos na verificação e o último (Relatório) coleta um relatório simples de arquivos dispersos com resultados de verificação.



Por padrão, o Trivy para de executar se vulnerabilidades CRÍTICAS são encontradas na imagem ou nas dependências. Ao mesmo tempo, Hadolint sempre retorna Success o código de execução, pois como resultado de sua execução sempre há comentários, o que leva à parada da construção.



Dependendo de seus requisitos específicos, você pode configurar o código de saída para que esses utilitários parem o processo de construção quando detectarem problemas de uma determinada gravidade. Em nosso caso, a construção irá parar apenas se Trivy detectar uma vulnerabilidade crítica que especificamos na variável SHOWSTOPPER em .gitlab-ci.yml .





O resultado da operação de cada utilitário pode ser visto no log de cada tarefa de varredura, diretamente nos arquivos json na seção de artefatos ou em um relatório HTML simples (mais sobre isso a seguir):





3. Para apresentar os relatórios de utilitários em uma forma um pouco mais legível, um pequeno script Python é usado para converter três arquivos json em um arquivo HTML com uma tabela de defeitos.

Esse script é iniciado por uma tarefa de Relatório separada e seu artefato final é um arquivo HTML com um relatório. O código-fonte do script também está no repositório e você pode adaptá-lo às suas necessidades, cores, etc.





Script shell



A segunda opção é adequada para os casos em que você precisa verificar as imagens do Docker fora do sistema CI / CD ou precisa ter todas as instruções em um formato que possa ser executado diretamente no host. Esta opção é coberta por um script de shell pronto que pode ser executado em uma máquina virtual limpa (ou mesmo real). O script segue as mesmas instruções do gitlab-runner acima.



Para que o script funcione com êxito, o Docker deve estar instalado no sistema e o usuário atual deve estar no grupo do docker.



O script em si pode ser obtido aqui: docker_sec_check.sh



No início do arquivo, variáveis ​​são usadas para definir qual imagem deve ser verificada e quais defeitos críticos farão com que saia do utilitário Trivy com o código de erro especificado.



Durante a execução do script, todos os utilitários serão baixados para o diretório docker_tools , os resultados de seu trabalho - para o diretório docker_tools / json , e o HTML com o relatório estará no arquivo results.html .



Exemplo de saída de script
~/docker_cicd$ ./docker_sec_check.sh

[+] Setting environment variables
[+] Installing required packages
[+] Preparing necessary directories
[+] Fetching sample Dockerfile
2020-10-20 10:40:00 (45.3 MB/s) - ‘Dockerfile’ saved [8071/8071]
[+] Pulling image to scan
latest: Pulling from bkimminich/juice-shop
[+] Running Hadolint
...
Dockerfile:205 DL3015 Avoid additional packages by specifying `--no-install-recommends`
Dockerfile:248 DL3002 Last USER should not be root
...
[+] Running Dockle
...
WARN    - DKL-DI-0006: Avoid latest tag
        * Avoid 'latest' tag
INFO    - CIS-DI-0005: Enable Content trust for Docker
        * export DOCKER_CONTENT_TRUST=1 before docker pull/build
...
[+] Running Trivy
juice-shop/frontend/package-lock.json
=====================================
Total: 3 (UNKNOWN: 0, LOW: 1, MEDIUM: 0, HIGH: 2, CRITICAL: 0)

+---------------------+------------------+----------+---------+-------------------------+
|       LIBRARY       | VULNERABILITY ID | SEVERITY | VERSION |             TITLE       |
+---------------------+------------------+----------+---------+-------------------------+
| object-path         | CVE-2020-15256   | HIGH     | 0.11.4  | Prototype pollution in  |
|                     |                  |          |         | object-path             |
+---------------------+------------------+          +---------+-------------------------+
| tree-kill           | CVE-2019-15599   |          | 1.2.2   | Code Injection          |
+---------------------+------------------+----------+---------+-------------------------+
| webpack-subresource | CVE-2020-15262   | LOW      | 1.4.1   | Unprotected dynamically |
|                     |                  |          |         | loaded chunks           |
+---------------------+------------------+----------+---------+-------------------------+

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)

...
[+] Removing left-overs
[+] Making the output look pretty
[+] Converting JSON results
[+] Writing results HTML
[+] Clean exit ============================================================
[+] Everything is done. Find the resulting HTML report in results.html




Imagem Docker com todos os utilitários



Como terceira alternativa, compilei dois Dockerfiles simples para criar uma imagem com utilitários de segurança. Um Dockerfile ajudará a construir um conjunto para escanear uma imagem do repositório, o segundo (Dockerfile_tar) - construir um conjunto para escanear um arquivo tar com uma imagem.



1. Pegamos o arquivo Docker correspondente e os scripts do repositório https://github.com/Swordfish-Security/docker_cicd/tree/master/Dockerfile .

2. Execute-o para montagem:

docker build -t dscan:image -f docker_security.df .


3. Após o final da montagem, crie um container a partir da imagem. Ao mesmo tempo, passamos a variável de ambiente DOCKERIMAGE com o nome da imagem na qual estamos interessados ​​e montamos o Dockerfile que queremos analisar de nossa máquina para o arquivo / Dockerfile (observe que um caminho absoluto para este arquivo é necessário):

docker run --rm -v $(pwd)/results:/results -v $(pwd)/docker_security.df:/Dockerfile -e DOCKERIMAGE="bkimminich/juice-shop" dscan:image



[+] Setting environment variables
[+] Running Hadolint
/Dockerfile:3 DL3006 Always tag the version of an image explicitly
[+] Running Dockle
WARN    - DKL-DI-0006: Avoid latest tag
        * Avoid 'latest' tag
INFO    - CIS-DI-0005: Enable Content trust for Docker
        * export DOCKER_CONTENT_TRUST=1 before docker pull/build
INFO    - CIS-DI-0006: Add HEALTHCHECK instruction to the container image
        * not found HEALTHCHECK statement
INFO    - DKL-LI-0003: Only put necessary files
        * unnecessary file : juice-shop/node_modules/sqlite3/Dockerfile
        * unnecessary file : juice-shop/node_modules/sqlite3/tools/docker/architecture/linux-arm64/Dockerfile
        * unnecessary file : juice-shop/node_modules/sqlite3/tools/docker/architecture/linux-arm/Dockerfile
[+] Running Trivy
...
juice-shop/package-lock.json
============================
Total: 20 (UNKNOWN: 0, LOW: 1, MEDIUM: 6, HIGH: 8, CRITICAL: 5)
...
[+] Making the output look pretty
[+] Starting the main module ============================================================
[+] Converting JSON results
[+] Writing results HTML
[+] Clean exit ============================================================
[+] Everything is done. Find the resulting HTML report in results.html


resultados



Cobrimos apenas um conjunto básico de ferramentas para escanear artefatos do Docker, que, em minha opinião, cobre com bastante eficácia uma parte decente dos requisitos de segurança de imagem. Existem muitas outras ferramentas gratuitas e pagas que podem realizar as mesmas verificações, gerar lindos relatórios ou trabalhar puramente no modo console, cobrir sistemas de gerenciamento de contêineres, etc. Uma visão geral dessas ferramentas e como integrá-las pode aparecer um pouco mais tarde.



O lado positivo do conjunto de ferramentas, que é descrito no artigo, é que todas elas são construídas em código-fonte aberto e você pode experimentá-las e outras ferramentas semelhantes para descobrir o que se adapta exatamente aos seus requisitos e recursos de infraestrutura. Claro, todas as vulnerabilidades que serão encontradas devem ser estudadas para aplicabilidade em condições específicas, mas este é um tópico para um grande artigo futuro.



Espero que este tutorial, scripts e utilitários ajudem você e se tornem um ponto de partida para a criação de uma infraestrutura mais segura no campo da conteinerização.



All Articles