Hardening Dockerfile with Hadolint in GitLab CI

Hardening Dockerfile with Hadolint in GitLab CI

Hadolint is a popular Dockerfile linter that helps you build best practices Dockerfiles by pointing out potential issues and improvements in your Dockerfiles. Integrating Hadolint into your CI/CD pipeline can significantly harden your deployment process by ensuring that Dockerfiles adhere to best practices before they are built and deployed.

Before setting up the pipeline, it's beneficial to practice using Hadolint in our local environment.

How to install Hadolint

I'll be practicing Hadolint on MacOS. You can install Hadolint using Brew, but it's also available for other operating systems https://github.com/hadolint/hadolint/releases :

# brew install hadolint        

Check the version of Hadolint:

# hadolint --version
Haskell Dockerfile Linter 2.12.0        

Lint your Dockerfile Locally

For testing purposes, I used an old Dockerfile I had just lying around. Create the file with the command:

# cat Dockerfile
FROM quay.io/muradsamadov/openjdk:11.0.16-jre-slim
RUN addgroup --system javauser && adduser --system --shell /bin/false javauser && adduser javauser javauser
WORKDIR /app
COPY build/libs/*.jar /app/app.jar
RUN chown -R javauser:javauser /app
USER root
ENTRYPOINT ["java", "-XX:+UseContainerSupport", "-XX:MaxRAMPercentage=75", "-jar", "-Dspring.profiles.active=prod","./app.jar" ]        

Save and close the file.

Now, we can lint the file with Hadolint like so:

# hadolint Dockerfile
Dockerfile:6 DL3002 warning: Last USER should not be root        

Dockerfile:6 DL3002 warning: Last USER should not be root - The warning DL3002 specifically indicates that setting the final user as 'root' is discouraged for security reasons. Running processes as the root user within a container can pose a significant security risk because it grants extensive permissions and control over the system. If an attacker gains access to the container, having root privileges can make it easier for them to exploit or manipulate the system.

Changing from the "USER root" layer to "USER javauser" enhances security measures.

# cat Dockerfile
FROM quay.io/muradsamadov/openjdk:11.0.16-jre-slim
RUN addgroup --system javauser && adduser --system --shell /bin/false javauser && adduser javauser javauser
WORKDIR /app
COPY build/libs/*.jar /app/app.jar
RUN chown -R javauser:javauser /app
USER javauser
ENTRYPOINT ["java", "-XX:+UseContainerSupport", "-XX:MaxRAMPercentage=75", "-jar", "-Dspring.profiles.active=prod","./app.jar" ]        

Save and close the file and check again:

# hadolint Dockerfile        

If Hadolint returns an empty output, it means your Dockerfile is hardening.

Configuration file with Hadolint

Additionally, you can pass a custom configuration file in the command line with the?--config?option. In our example,?Hadolint?can warn you when images from untrusted repositories are being used in Dockerfiles, you can append the?trustedRegistries?keys to the configuration file, as shown below:

# cat hadolint.yaml
trustedRegistries:
  - custom-registry.cloud.com        

Let’s check:

# hadolint --config hadolint.yaml Dockerfile
Dockerfile:1 DL3026 error: Use only an allowed registry in the FROM image        

Modify the Dockerfile by replacing quay.io with custom-registry.cloud.com to update the registry.


Hadolint in GitLab CI

For GitLab CI you need a basic shell in your docker image so you have to use the debian based images of hadolint.

Add the following job to your project's?.gitlab-ci.yml:

lint_dockerfile:
  image: hadolint/hadolint:latest-debian
  script:
    - |
      if hadolint -f gitlab_codeclimate -c $HADOLINT Dockerfile | tee docker-lint-$CI_COMMIT_SHORT_SHA.json ;then
        echo -e "\nChecking Dockerfile hardening is successfull."
      else
        echo -e "\nChecking Dockerfile hardening has issues. Please check and fix it."
        exit 1
      fi
  artifacts:
    name: "$CI_JOB_NAME artifacts from $CI_PROJECT_NAME on $CI_COMMIT_TAG"
    expire_in: 1 day
    when: always
    reports:
      codequality:
        - "docker-lint-$CI_COMMIT_SHORT_SHA.json"
    paths:
      - "docker-lint-$CI_COMMIT_SHORT_SHA.json"
  tags:
  - dev        

Let's break it down:

lint_dockerfile: This is a job name that signifies the task of linting the Dockerfile.

image: Specifies the Docker image used for this job, which is the latest version of Hadolint from the 'hadolint/hadolint' repository on Debian.

script: Contains the commands to be executed. In this case:

  • It runs Hadolint with the parameters hadolint -f gitlab_codeclimate -c $HADOLINT Dockerfile.
  • It pipes the output to a file named docker-lint-$CI_COMMIT_SHORT_SHA.json.
  • Based on the linting output: If the linting succeeds (no issues found), it displays a success message. If issues are found, it displays a message to check and fix the Dockerfile and exits the process with an error code (exit 1).

$HADOLINT: I've configured the hadolint.yaml file, defining a variable named HADOLINT as a file type:

artifacts: Specifies the artifacts generated by the job. Artifacts are the files or reports created during the job execution. In this case:

  • name: Sets the name of the artifact. It includes the job name, project name, and commit tag.
  • expire_in: Defines how long these artifacts will be kept (1 day in this case).
  • when: Determines when to create artifacts (always ensures they're generated regardless of the job status).
  • reports: Defines the type of report being generated, which is a code quality report using the file docker-lint-$CI_COMMIT_SHORT_SHA.json.
  • paths: Specifies the paths to the artifacts to be stored.

tags: Specifies the tags for this job, in this case, it's tagged as 'dev'.

$CI_COMMIT_SHORT_SHA , $CI_JOB_NAME , $CI_PROJECT_NAME , $CI_COMMIT_TAG: these are predefined environment variables in GitLab CI/CD pipelines.

Our Dockerfile as below:

FROM quay.io/muradsamadov/openjdk:11.0.16-jre-slim
RUN addgroup --system javauser && adduser --system --shell /bin/false javauser && adduser javauser javauser
WORKDIR /app
COPY build/libs/*.jar /app/app.jar
RUN chown -R javauser:javauser /app
USER javauser
ENTRYPOINT ["java", "-XX:+UseContainerSupport", "-XX:MaxRAMPercentage=75", "-jar", "-Dspring.profiles.active=prod","./app.jar" ]        

Let’s trigger pipeline and the outcome indicates a failure:

Here, the error message displayed by Hadolint:

"check_name":"DL3026","description":"Use only an allowed registry in the FROM image”        

Let’s use custom-registry.cloud.com registry in Dockerfile and trigger again:

FROM custom-registry.cloud.com/openjdk:11.0.16-jre-slim
RUN addgroup --system javauser && adduser --system --shell /bin/false javauser && adduser javauser javauser
WORKDIR /app
COPY build/libs/*.jar /app/app.jar
RUN chown -R javauser:javauser /app
USER javauser
ENTRYPOINT ["java", "-XX:+UseContainerSupport", "-XX:MaxRAMPercentage=75", "-jar", "-Dspring.profiles.active=prod","./app.jar" ]        

The pipeline has successfully built, showing the message that confirms the successful check for Dockerfile hardening: Checking Dockerfile hardening is successfull

As above there's usually an "Job artifacts" section. In this section, you'll typically find a list of artifacts. You can click on the artifact you want to download and GitLab will provide an option to download it.

And that’s how you can easily lint your Dockerfiles from the command line. To find out more about how to use Hadolint, make sure to look through the help information (using the Hadolint?—help command) to see the different options available.

要查看或添加评论,请登录

Murad Samadov的更多文ç«