实现Docker安全检查实用程序的方法和示例



哈Ha!



在现代现实中,由于容器化在开发过程中的作用日益增强,确保与容器相关联的各个阶段和实体的安全性并不是最后的问题。进行手动检查非常耗时,因此最好至少采取初始步骤来自动化此过程。



在本文中,我将分享用于实现多个Docker安全实用程序的现成脚本,以及有关如何部署小型演示台以测试此过程的说明。您可以使用这些资源来试验如何组织映像和Dockerfile指令的安全测试过程。显然,每个人的开发和实现基础结构都不同,因此下面我将给出几种可能的选择。



安全检查实用程序



有许多不同的帮助程序和脚本可以测试Docker基础架构的各个方面。其中一些已在上一篇文章(https://habr.com/ru/company/swordfish_security/blog/518758/#docker-security)中进行了描述,在此材料中,我想着重介绍其中三篇在开发过程中构建的Docker映像的安全性要求的一部分。此外,我还将展示一个示例,说明如何将这三个实用程序连接到一个管道中以执行安全检查。



Hadolint

https://github.com/hadolint/hadolint



一个相当简单的控制台实用程序,可以近似地帮助评估Dockerfile指令的正确性和安全性(例如,仅使用授权的映像注册表或使用sudo)。



Hadolint实用程序的输出



Dockle

https://github.com/goodwithtech/dockle



控制台实用程序,可与图像(或与图像的已保存tar归档文件一起使用)来检查特定图像的正确性和安全性,分析其层和配置-创建用户,指示哪些内容使用的是已安装的卷,是否存在空密码等。虽然检查的数量不是很大,并且基于DockerCIS(Internet安全中心)基准的一些检查和建议





Trivy

https://github.com/aquasecurity/trivy



该实用程序旨在发现两种类型的漏洞-操作系统构建问题(由Alpine,RedHat(EL),CentOS,Debian GNU,Ubuntu支持)和依赖项问题(Gemfile.lock,Pipfile)。锁定,composer.lock,package-lock.json,yarn.lock,Cargo.lock)。Trivy可以扫描存储库中的映像和本地映像,也可以使用Docker映像基于已传输的.tar文件进行扫描。







实用程序的实现选项



为了在孤立的条件下尝试描述的应用程序,我将提供在简化的过程中安装所有实用程序的说明。



主要思想是演示如何实现对在开发期间创建的Dockerfile和Docker映像的自动内容验证。



检查本身包括以下步骤:

  1. 检查的Dockerfile指令的正确性和安全性-使用Hadolint棉短绒
  2. 使用Dockle实用程序检查目标图像和中间图像的正确性和安全性
  3. 使用Trivy实用程序检查基本映像中的众所周知的漏洞(CVE)和许多依赖


在本文的进一步内容中,我将提供三个选项来实现这些步骤:

首先,通过使用GitLab的示例配置CI / CD管道(并描述了生成测试实例的过程)。

第二个是使用shell脚本。

第三是构建用于扫描Docker映像的Docker映像。

您可以选择最适合您的选项,将其转移到您的基础架构中,并使其适应您的需求。



所有必需的文件和其他说明也位于存储库中:https : //github.com/Swordfish-Security/docker_cicd



集成到GitLab CI / CD



在第一个选项中,我们将使用GitLab存储库系统的示例来研究如何实施安全检查。在这里,我们将逐步进行操作,并分析如何从零开始使用GitLab设置测试环境,创建扫描过程并运行实用程序以检查测试Dockerfile和随机映像-JuiceShop应用程序。



安装GitLab

1.安装Docker:

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


2.将当前用户添加到docker组,以便您可以通过sudo而不使用docker:

sudo addgroup <username> docker


3.查找您的IP:

ip addr


4.在容器中安装并运行GitLab,用您自己的主机名替换IP地址:

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


我们正在等待GitLab完成所有必要的安装过程(您可以通过日志文件的输出来监视该过程:docker logs -f gitlab)。



5.在浏览器中打开您的本地IP,然后看到一个页面,其中包含更改根用户密码的建议:



设置一个新密码并转到GitLab。



6.创建一个新项目,例如cicd-test,并使用开始文件README.md对其进行初始化



7.现在,我们需要安装GitLab Runner:一个可根据请求启动所有必要操作的代理。

下载最新版本(在这种情况下,适用于Linux 64位):

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


8.使它可执行:

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


9.为Runner添加OS用户并启动服务:

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


它看起来应该像这样:



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.现在,我们注册了Runner,以便它可以与我们的GitLab实例进行交互。

为此,请打开Settings-CI / CD页面(http:// // OUR_ IP_ADDRESS / root / cicd-test /-/ settings / ci_cd),然后在Runners选项卡上找到URL和注册令牌:



11.通过替换URL和Registration令牌注册Runner:

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"


结果,我们得到了现成的可运行的GitLab,其中需要添加有关启动实用程序的说明。在此演示案例中,我们没有构建应用程序及其容器化的步骤,但是在实际环境中,它们将在扫描步骤之前并生成图像和Dockerfile进行分析。



管道的配置



1.将文件mydockerfile.df(这是我们将要检查的一些测试Dockerfile)和GitLab CI / CD进程配置文件.gitlab-cicd.yml添加到存储库中,该文件列出了扫描程序的说明(注意文件名中的点) )。



YAML配置文件包含有关运行三个实用程序(Hadolint,Dockle和Trivy)的说明,这些实用程序将解析所选的Dockerfile和DOCKERFILE变量中指定的映像。所有必需的文件都可以从存储库中获取:https : //github.com/Swordfish-Security/docker_cicd/ mydockerfile.df中的



摘录(这是一个抽象文件,带有一组任意指令,仅用于演示该实用程序的工作方式)。直接链接到文件:mydockerfile.df



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


配置YAML如下所示(文件本身可以从以下直接链接获取:.gitlab-ci.yml):



.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


如有必要,您还可以将保存的图像扫描为.tar存档(但是,您将需要更改YAML文件中实用程序的输入参数)



NB: Trivy rpm git. RedHat-based .


2.将文件添加到存储库后,根据配置文件中的指示,GitLab将自动开始构建和扫描过程。在CI / CD→管道选项卡上,您可以查看说明的进度。



结果,我们有四个任务。其中三个直接处理扫描,最后一个(报告)从分散的文件中收集带有扫描结果的简单报告。



默认情况下,如果在映像或依赖项中发现了严重漏洞,则Trivy停止执行。同时,Hadolint始终将执行代码返回给Success,因为执行它的结果是总是有注释,这会导致构建停止。



根据您的特定要求,您可以配置退出代码,以便这些实用程序在检测到某些严重性问题时也可以停止构建过程。在我们的情况下,仅当Trivy检测到我们在.gitlab-ci.yml的SHOWSTOPPER变量中指定的严重漏洞时,构建才会停止





可以在每个扫描任务的日志中,直接在构件部分的json文件中或在简单的HTML报告中(以下有关更多内容)查看每个实用程序的操作结果:





3.为了以更易于理解的形式显示实用程序报告,使用了一个小的Python脚本。将三个json文件转换为一个带有缺陷表的HTML文件。

该脚本由单独的Report任务启动,其最终工件是带有报告的HTML文件。脚本源也位于存储库中,您可以根据需要,颜色等进行修改。





Shell脚本



第二种选择适用于需要在CI / CD系统之外检查Docker映像的情况,或者需要将所有指令的形式都可以在主机上直接执行的情况。可以在干净的虚拟(甚至真实)计算机上运行的现成的Shell脚本涵盖了此选项。该脚本遵循与上述gitlab-runner相同的指令。



为了使脚本成功运行,必须在系统上安装Docker,并且当前用户必须位于docker组中。



脚本本身可在此处获取:docker_sec_check.sh



在文件的开头,变量用于设置应扫描的图像以及哪些严重性缺陷将导致Trivy实用程序以指定的错误代码退出。



在脚本执行期间,所有实用程序都将下载到docker_tools目录,其工作结果将下载到docker_tools / json目录,带有报告的HTML将保存在results.html文件中



示例脚本输出
~/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




具有所有实用程序的Docker映像



作为第三种选择,我编译了两个简单的Dockerfile来使用安全实用程序创建映像。一个Dockerfile将帮助构建一个用于从存储库中扫描图像的集合,第二个(Dockerfile_tar)-构建一个用于扫描具有映像的tar文件的集合。



1.我们从存储库https://github.com/Swordfish-Security/docker_cicd/tree/master/Dockerfile中获取相应的Docker文件和脚本

2.运行以进行组装:

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


3.组装结束后,根据图像创建一个容器。同时,我们将DOCKERIMAGE环境变量与我们感兴趣的映像名称一起传递,并将要分析的Dockerfile从我们的机器安装到文件/ Dockerfile(请注意,此文件的绝对路径是必需的):

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


结果



我们仅介绍了一组用于扫描Docker工件的基本工具,我认为这些工具相当有效地涵盖了图像安全性要求的一部分。还有许多免费和付费的工具可以执行相同的检查,绘制精美的报告或仅在控制台模式下工作,涵盖容器管理系统等。这些工具及其集成方法的概述可能会在稍后出现。



本文介绍的这套工具的积极方面是,它们全部基于开放源代码构建,您可以尝试使用它们和其他类似工具来找到完全适合您的需求和基础结构功能的工具。当然,应该对所有发现的漏洞进行研究,以了解它们在特定条件下的适用性,但这是以后的大型文章的主题。



我希望本教程,脚本和实用程序将对您有所帮助,并成为在容器化领域创建更安全的基础结构的起点。



All Articles