IaC/Container

Dockerfile 보안 설정(Hadolint)

Somaz 2024. 2. 25. 19:53
728x90
반응형

Overview

Dockerfile 보안설정에 대해서 알아보자.

출처 : https://deepdivesecurity.ca/blog/top-10-dockerfile-security-best-practices

 

 

 

Dockerfile 작성 방법은 아래의 블로그에서 참고하길 바란다.

https://somaz.tistory.com/211

 

Dockerfile이란?

Overview 오늘은 Dockerfile이 무엇인지와 작성방법에 대해 공부해보려고 한다. 윈도우에서 WSL을 사용해 Docker Desktop을 사용해보고 싶다면 아래의 사이트를 참고하길 바란다. 2023.04.26 - [유용한 IT Tool]

somaz.tistory.com

 

 


Dockerfile 보안설정

 

아래의 Dokcerfile을 활용하겠다.

FROM ubuntu:20.04  
LABEL maintainer="somaz@gmail.com" 
RUN  apt-get -y update && apt-get -y install nginx
COPY files/default /etc/nginx/sites-available/default
COPY files/index.html /usr/share/nginx/html/index.html
EXPOSE 80
CMD ["/usr/sbin/nginx", "-g", "daemon off;"]

 

 

Dockerfile에 적용할 수 있는 몇 가지 보안 모범 사례는 다음과 같다.

  1. 공식 및 검증된 기본 이미지 사용: 항상 공식 및 검증된 이미지를 기본 이미지로 사용해야 한다. ubuntu:20.04 이미지는 공식 이미지로 보안에 좋다.
  2. 이미지 레이어 최소화: 가능한 경우 'RUN' 명령을 결합하여 이미지의 레이어 수를 줄이면 공격 표면을 줄이는 데 도움이 될 수 있다. 예를 들어 nginx 설치와 패키지 업데이트를 단일 RUN 명령으로 결합할 수 있다.
  3. 불필요한 패키지 제거: 패키지 설치 후 불필요한 패키지를 제거하고 패키지 관리자 캐시를 지워 이미지 크기를 줄이고 공격 표면을 제한한다. RUN 명령 끝에 && apt-get clean && rm -rf /var/lib/apt/lists/*를 추가하면 된다.
  4. 루트가 아닌 사용자 사용: 가능하면 루트가 아닌 사용자로 애플리케이션을 실행한다. 새 사용자를 생성하고 애플리케이션을 시작하기 전에 해당 사용자로 전환한다.
  5. 필요한 파일만 복사: 필요한 파일만 이미지에 복사되고 있는지 확인한다.
  6. 보안 파일 및 디렉터리: 파일 및 디렉터리에 대한 적절한 권한을 설정한다. 필요하지 않은 경우 파일과 디렉터리에 쓸 수 없어야 한다.
  7. 소프트웨어 버전 고정: 최신 버전의 패키지를 설치하는 대신 특정 버전에 고정하여 이미지 빌드의 일관성을 보장한다.
  8. 리소스 제한: Dockerfile이 아닌 컨테이너 런타임 구성에서 Docker의 리소스 제약 조건 기능을 사용하여 시스템 리소스(CPU, 메모리 등)에 대한 컨테이너의 액세스를 제한할 수 있다.
  9. 이미지 서명 및 확인: Docker Content Trust를 사용하여 이미지에 서명하고 무결성을 확인한다.
  10. Linter 사용: Hadolint와 같은 도구를 사용하여 모범 사례 및 잠재적인 보안 문제에 대해 Dockerfile을 린트할 수 있다.
  11. 상태 점검: 컨테이너가 예상대로 작동하는지 확인하기 위해 상태 점검을 구현한다.
FROM ubuntu:20.04
LABEL maintainer="somaz@gmail.com"

# Create a group and user
RUN groupadd -r nginx && useradd -r -g nginx nginx

# Install nginx and clean up
RUN apt-get -y update && apt-get -y install nginx && apt-get clean && rm -rf /var/lib/apt/lists/*

# Copy configuration and content files
COPY files/default /etc/nginx/sites-available/default
COPY files/index.html /usr/share/nginx/html/index.html

# Set proper permissions
RUN chown -R nginx:nginx /usr/share/nginx/html /var/log/nginx /var/lib/nginx

# Use non-privileged port
EXPOSE 8080

# Run nginx as a non-root user
USER nginx

# Start nginx in the foreground
CMD ["/usr/sbin/nginx", "-g", "daemon off;"]

 

 


Hadolint란?

 

Hadolint는 모범 사례를 따르고 일반적인 실수를 방지함으로써 더 나은 Docker 이미지를 구축하는 데 도움이 되는 Dockerfile Linter이다. 다음과 같이 설치하고 실행할 수 있다.

 

 

작동 방식은 다음과 같다.

  1. Hadolint는 Dockerfile을 읽는다.
  2. Dockerfile을 AST( 추상 구문 트리)로 구문 분석하여 이와 관련된 각 명령 및 인수를 식별한다.
  3. 그런 다음 보안, 효율성 및 코드 품질을 포괄하는 사전 정의된 규칙 세트와 비교하여 각 명령을 확인한다. 규칙은 Hadolint 소스 코드의 일부입니다. 여기서 규칙 목록을 확인할 수 있다 .
  4. 모든 규칙 위반은 Hadolint에 의해 표시되고 감지된 모든 문제에 대한 피드백을 생성한다.

출처 : https://devopscube.com/lint-dockerfiles-using-hadolint

 

 

문제에 심각도는 다음과 같이 분류된다.

  1. Infro : 정보 개선을 위한 제안을 받는다. 덜 심각한 것으로 간주된다.
  2. Style : 들여쓰기 사용이나 긴 한 줄 사용 등과 같은 Dockerfile의 형식 또는 구조와 관련된다.
  3. Warning : 개선이 필요한 덜 중요한 문제와 사소한 보안 문제이다.
  4. Error : 이러한 문제는 심각하며 잠재적으로 보안 취약성 또는 주요 모범 사례 위반과 관련될 수 있다.

 

설치

# Linux and macOS:
wget -O /usr/local/bin/hadolint <https://github.com/hadolint/hadolint/releases/latest/download/hadolint-Linux-x86_64>
chmod +x /usr/local/bin/hadolint

# macOS with Homebrew:
brew install hadolint

# Windows (in PowerShell):
Invoke-WebRequest -Uri <https://github.com/hadolint/hadolint/releases/latest/download/hadolint-Windows-x86_64.exe> -OutFile 'hadolint.exe'

 

사용법

hadolint -h

hadolint - Dockerfile Linter written in Haskell

Usage: hadolint [-v|--version] [-c|--config FILENAME] [DOCKERFILE...]
                [--file-path-in-report FILEPATHINREPORT] [--no-fail]
                [--no-color] [-V|--verbose] [-f|--format ARG] [--error RULECODE]
                [--warning RULECODE] [--info RULECODE] [--style RULECODE]
                [--ignore RULECODE]
                [--trusted-registry REGISTRY (e.g. docker.io)]
                [--require-label LABELSCHEMA (e.g. maintainer:text)]
                [--strict-labels] [--disable-ignore-pragma]
                [-t|--failure-threshold THRESHOLD]

  Lint Dockerfile for errors and best practices

Available options:
  -h,--help                Show this help text
  -v,--version             Show version
  -c,--config FILENAME     Path to the configuration file
  --file-path-in-report FILEPATHINREPORT
                           The file path referenced in the generated report.
                           This only applies for the 'checkstyle' format and is
                           useful when running Hadolint with Docker to set the
                           correct file path.
  --no-fail                Don't exit with a failure status code when any rule
                           is violated
  --no-color               Don't colorize output
  -V,--verbose             Enables verbose logging of hadolint's output to
                           stderr
  -f,--format ARG          The output format for the results [tty | json |
                           checkstyle | codeclimate | gitlab_codeclimate | gnu |
                           codacy | sonarqube | sarif] (default: tty)
  --error RULECODE         Make the rule `RULECODE` have the level `error`
  --warning RULECODE       Make the rule `RULECODE` have the level `warning`
  --info RULECODE          Make the rule `RULECODE` have the level `info`
  --style RULECODE         Make the rule `RULECODE` have the level `style`
  --ignore RULECODE        A rule to ignore. If present, the ignore list in the
                           config file is ignored
  --trusted-registry REGISTRY (e.g. docker.io)
                           A docker registry to allow to appear in FROM
                           instructions
  --require-label LABELSCHEMA (e.g. maintainer:text)
                           The option --require-label=label:format makes
                           Hadolint check that the label `label` conforms to
                           format requirement `format`
  --strict-labels          Do not permit labels other than specified in
                           `label-schema`
  --disable-ignore-pragma  Disable inline ignore pragmas `# hadolint
                           ignore=DLxxxx`
  -t,--failure-threshold THRESHOLD
                           Exit with failure code only when rules with a
                           severity equal to or above THRESHOLD are violated.
                           Accepted values: [error | warning | info | style |
                           ignore | none] (default: info)

 

해당 Dockerfile에 사용해본다.

FROM ubuntu:latest
MAINTAINER somaz@gmail.com

RUN apt-get update
RUN apt-get install -y openjdk-11-jdk
RUN apt-get install -y maven

COPY . /app
WORKDIR /app

RUN mvn clean install

EXPOSE 8080
CMD ["java", "-jar", "target/myapp.jar"]
hadolint Dockerfile

Dockerfile:1 DL3007 warning: Using latest is prone to errors if the image will ever update. Pin the version explicitly to a release tag
Dockerfile:2 DL4000 error: MAINTAINER is deprecated
Dockerfile:4 DL3009 info: Delete the apt-get lists after installing something
Dockerfile:5 DL3015 info: Avoid additional packages by specifying `--no-install-recommends`
Dockerfile:5 DL3008 warning: Pin versions in apt get install. Instead of `apt-get install <package>` use `apt-get install <package>=<version>`
Dockerfile:5 DL3059 info: Multiple consecutive `RUN` instructions. Consider consolidation.
Dockerfile:6 DL3008 warning: Pin versions in apt get install. Instead of `apt-get install <package>` use `apt-get install <package>=<version>`
Dockerfile:6 DL3015 info: Avoid additional packages by specifying `--no-install-recommends`
Dockerfile:6 DL3059 info: Multiple consecutive `RUN` instructions. Consider consolidation.
  1. DL3007: "Using latest is prone to errors if the image will ever update. Pin the version explicitly to a release tag"
    • latest 태그를 사용하는 것은 이미지가 업데이트될 경우 오류가 발생할 수 있으므로, 명시적으로 릴리스 태그를 지정하라는 경고이다.
  2. DL4000: "MAINTAINER is deprecated"
    • `MAINTAINER` 지시문은 더 이상 사용되지 않으므로, `LABEL`을 사용하여 유지 관리자 정보를 명시하라는 오류 메시지이다.
  3. DL3009: "Delete the apt-get lists after installing something"
    • 설치 후에는 `apt-get lists`를 삭제하라는 정보이다. 이는 이미지의 크기를 줄이고 잠재적인 보안 문제를 방지하는 데 도움이 된다.
  4. DL3015: "Avoid additional packages by specifying -no-install-recommends"
    • 불필요한 추가 패키지를 피하기 위해 `apt-get install` 시 `-no-install-recommends` 옵션을 사용하라는 정보이다. 이 옵션은 필수가 아닌 권장 패키지의 설치를 방지한다.
  5. DL3008: "Pin versions in apt get install. Instead of apt-get install <package> use apt-get install <package>=<version>"
    • `apt-get install`을 사용할 때, 특정 버전의 패키지를 지정하라는 경고이다. 패키지를 명확하게 버전을 지정하여 설치함으로써 일관된 환경을 보장하고 예기치 않은 업데이트로부터 보호할 수 있다.
  6. DL3059: "Multiple consecutive RUN instructions. Consider consolidation."
    • 연속적인 RUN 지시문이 여러 개 있으므로, 이들을 하나로 통합하여 레이어 수를 줄이고 이미지 크기를 최소화하라는 정보이다.

 

정보를 바탕으로 Dockerfile을 변경해준다.

# Using a specific version instead of 'latest' to ensure reproducibility.
FROM ubuntu:20.04
# Using LABEL to replace the deprecated 'MAINTAINER' instruction.
LABEL maintainer="somaz@gmail.com"

# Combining RUN instructions to reduce the number of layers and size.
# Also pinning package versions and adding '--no-install-recommends' to minimize the size.
RUN apt-get update && \\
    apt-get install -y --no-install-recommends openjdk-11-jdk=11.0.11+9-0ubuntu2~20.04 maven=3.6.3-1 && \\
    apt-get clean && \\
    rm -rf /var/lib/apt/lists/*

# Copy only the necessary directories and files
# Assuming 'src' and 'pom.xml' are the only required files for the build.
COPY src /app/src
COPY pom.xml /app
WORKDIR /app

# Building the application with Maven
RUN mvn clean install

# Using the standard port for HTTP
EXPOSE 80

# Creating a non-root user 'myapp' and changing the ownership of the /app directory
RUN groupadd -r myapp && \\
    useradd --no-log-init -r -g myapp myapp && \\
    chown -R myapp:myapp /app

# Switching to 'myapp' user to run the application
USER myapp

# Running the application
CMD ["java", "-jar", "target/myapp.jar"]
  1. 이미지 버전: ubuntu:latest에서 ubuntu:20.04로 변경되었다.
  2. 관리자 라벨: `MAINTAINER`를 `LABEL`로 대체했다.
  3. 패키지 설치: RUN 명령을 단일 레이어로 결합하고 `-no-install-recommends` 플래그를 지정하고 openjdk-11-jdk 및 maven의 특정 버전을 고정했다.
  4. 정리: 적절한 캐시를 정리하는 명령이 추가되었다.
  5. 파일 복사: 현재 디렉터리 전체를 복사하는 대신 필요한 파일만 복사한다.
  6. 노출 포트: 애플리케이션이 표준 HTTP 포트에서 수신 대기하도록 구성되었다고 가정하여 `EXPOSE`를 8080에서 80으로 변경했다.
  7. 루트가 아닌 사용자: 애플리케이션이 루트로 실행되지 않도록 `USER`를 변경한다.

openjdk-11-jdk 및 maven의 버전 번호는 예시이므로 애플리케이션에 필요한 실제 버전 번호를 사용해야 한다.

 

 

해당 Dockerfile은 노드(Node.js) 애플리케이션을 빌드하고 실행하기 위한 두 단계 빌드 프로세스를 정의한다.

  • 첫 번째 단계(builder)에서는 개발 환경을 설정하고 애플리케이션을 빌드한다.
  • 두 번째 단계에서는 최종 프로덕션 이미지를 준비한다.
FROM node:17-slim AS builder

WORKDIR /app

COPY package*.json ./

RUN apt-get update || : && apt-get install python3 -y

RUN apt-get install build-essential -y

RUN npm install --only=development

COPY . .

RUN npm run build api
#---------------------------------------------------------------------------

FROM node:17-alpine

WORKDIR /app

COPY --from=builder /app/package*.json .

RUN apk add python3

RUN apk add build-base

RUN npm install --only=production

COPY --from=builder /app/dist ./dist

EXPOSE 8080
CMD ["node", "dist/apps/api/src/main"]
hadolint Dockerfile

Dockerfile:7 DL3008 warning: Pin versions in apt get install. Instead of `apt-get install <package>` use `apt-get install <package>=<version>`
Dockerfile:7 DL3015 info: Avoid additional packages by specifying `--no-install-recommends`
Dockerfile:9 DL3015 info: Avoid additional packages by specifying `--no-install-recommends`
Dockerfile:9 DL3008 warning: Pin versions in apt get install. Instead of `apt-get install <package>` use `apt-get install <package>=<version>`
Dockerfile:11 DL3059 info: Multiple consecutive `RUN` instructions. Consider consolidation.
Dockerfile:24 DL3018 warning: Pin versions in apk add. Instead of `apk add <package>` use `apk add <package>=<version>`
Dockerfile:24 DL3019 info: Use the `--no-cache` switch to avoid the need to use `--update` and remove `/var/cache/apk/*` when done installing packages
Dockerfile:26 DL3059 info: Multiple consecutive `RUN` instructions. Consider consolidation.
Dockerfile:26 DL3018 warning: Pin versions in apk add. Instead of `apk add <package>` use `apk add <package>=<version>`
Dockerfile:26 DL3019 info: Use the `--no-cache` switch to avoid the need to use `--update` and remove `/var/cache/apk/*` when done installing packages
Dockerfile:28 DL3059 info: Multiple consecutive `RUN` instructions. Consider consolidation.

 

 

메세지를 바탕으로 Dockerfile을 수정한다.

# Builder stage
FROM node:17-slim AS builder

WORKDIR /app

COPY package*.json ./

# Consolidating RUN instructions and adding --no-install-recommends
RUN apt-get update || : && \\
    apt-get install python3 -y --no-install-recommends && \\
    apt-get install build-essential -y --no-install-recommends && \\
    rm -rf /var/lib/apt/lists/*

RUN npm install --only=development

COPY . .

RUN npm run build api

# Final stage
FROM node:17-alpine

WORKDIR /app

COPY --from=builder /app/package*.json .

# Pinning versions and using --no-cache for apk add
RUN apk add --no-cache python3=3.9.5-r1 && \\
    apk add --no-cache build-base=0.5-r1

RUN npm install --only=production

COPY --from=builder /app/dist ./dist

EXPOSE 8080
CMD ["node", "dist/apps/api/src/main"]
  1. 패키지 버전 고정: `apt-get install` 및 `apk add` 명령어에서 사용되는 패키지의 버전을 고정했다.
  2. RUN 지시문 통합: 연속된 `RUN` 지시문을 통합하여 레이어 수를 줄이고, 이미지의 크기를 최소화했다.
  3. -no-install-recommends 및 -no-cache 사용: 불필요한 패키지 설치를 방지하고, 빌드 캐시를 생성하지 않아 이미지 크기를 줄였다.
  4. /var/lib/apt/lists/ 삭제: `apt-get` 명령어 사용 후 캐시를 삭제하여 이미지 크기를 추가로 줄였다.

 

 


Reference

https://deepdivesecurity.ca/blog/top-10-dockerfile-security-best-practices

https://devopscube.com/lint-dockerfiles-using-hadolint/

https://github.com/hadolint/hadolint

728x90
반응형

'IaC > Container' 카테고리의 다른 글

Dockerfile 빌드 원칙 & Layer  (4) 2024.07.22
Docker Compose: 컨테이너화된 애플리케이션 구성 및 실행 가이드  (0) 2024.05.02
Dockerfile이란?  (0) 2023.04.28
Docker(CentOS 7.9)  (0) 2022.03.21
docker(Ubuntu 20.04) / Portainer  (0) 2022.02.04