From e0068c4d5dcadcf35c761960cd4a1986f8dcb163 Mon Sep 17 00:00:00 2001 From: Nuno Date: Sat, 3 Aug 2024 16:55:18 +0200 Subject: [PATCH] Feature/harden container security following OWASP best practices (#3614) * Harden container security * Update changelog --- CHANGELOG.md | 6 ++++++ Dockerfile | 8 +++++--- docker/docker-compose.build.yml | 7 ++++--- docker/docker-compose.dev.yml | 5 +++-- docker/docker-compose.yml | 29 +++++++++++++++++++++++++---- 5 files changed, 43 insertions(+), 12 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8726c4468..a019343ef 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,12 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## Unreleased + +### Changed + +- Hardened container security by switching to a non-root user, setting the filesystem to read-only, and dropping unnecessary capabilities + ## 2.100.0 - 2024-08-03 ### Added diff --git a/Dockerfile b/Dockerfile index 3f4554523..8ca86a308 100644 --- a/Dockerfile +++ b/Dockerfile @@ -11,7 +11,7 @@ COPY ./package.json package.json COPY ./package-lock.json package-lock.json COPY ./prisma/schema.prisma prisma/schema.prisma -RUN apt update && apt install -y \ +RUN apt-get update && apt-get install -y --no-install-suggests \ g++ \ git \ make \ @@ -50,16 +50,18 @@ RUN npm run database:generate-typings # Image to run, copy everything needed from builder FROM node:20-slim - LABEL org.opencontainers.image.source="https://github.com/ghostfolio/ghostfolio" +ENV NODE_ENV=production -RUN apt update && apt install -y \ +RUN apt-get update && apt-get install -y --no-install-suggests \ curl \ openssl \ && rm -rf /var/lib/apt/lists/* COPY --from=builder /ghostfolio/dist/apps /ghostfolio/apps COPY ./docker/entrypoint.sh /ghostfolio/entrypoint.sh +RUN chown -R node:node /ghostfolio WORKDIR /ghostfolio/apps/api EXPOSE ${PORT:-3333} +USER node CMD [ "/ghostfolio/entrypoint.sh" ] diff --git a/docker/docker-compose.build.yml b/docker/docker-compose.build.yml index 28990692d..636d9a4e3 100644 --- a/docker/docker-compose.build.yml +++ b/docker/docker-compose.build.yml @@ -6,7 +6,6 @@ services: - ../.env environment: DATABASE_URL: postgresql://${POSTGRES_USER}:${POSTGRES_PASSWORD}@postgres:5432/${POSTGRES_DB}?connect_timeout=300&sslmode=prefer - NODE_ENV: production REDIS_HOST: redis REDIS_PASSWORD: ${REDIS_PASSWORD} ports: @@ -21,8 +20,9 @@ services: interval: 10s timeout: 5s retries: 5 + postgres: - image: postgres:15 + image: docker.io/library/postgres:15 env_file: - ../.env healthcheck: @@ -32,8 +32,9 @@ services: retries: 5 volumes: - postgres:/var/lib/postgresql/data + redis: - image: redis:alpine + image: docker.io/library/redis:alpine env_file: - ../.env command: ['redis-server', '--requirepass', $REDIS_PASSWORD] diff --git a/docker/docker-compose.dev.yml b/docker/docker-compose.dev.yml index 47944378c..91f8f2c09 100644 --- a/docker/docker-compose.dev.yml +++ b/docker/docker-compose.dev.yml @@ -1,6 +1,6 @@ services: postgres: - image: postgres:15 + image: docker.io/library/postgres:15 container_name: postgres restart: unless-stopped env_file: @@ -9,8 +9,9 @@ services: - ${POSTGRES_PORT:-5432}:5432 volumes: - postgres:/var/lib/postgresql/data + redis: - image: redis:alpine + image: docker.io/library/redis:alpine container_name: redis restart: unless-stopped env_file: diff --git a/docker/docker-compose.yml b/docker/docker-compose.yml index 3e708e918..23d9d6358 100644 --- a/docker/docker-compose.yml +++ b/docker/docker-compose.yml @@ -1,12 +1,16 @@ services: ghostfolio: - image: ghostfolio/ghostfolio:latest + image: docker.io/ghostfolio/ghostfolio:latest init: true + read_only: true + cap_drop: + - ALL + security_opt: + - no-new-privileges:true env_file: - ../.env environment: DATABASE_URL: postgresql://${POSTGRES_USER}:${POSTGRES_PASSWORD}@postgres:5432/${POSTGRES_DB}?connect_timeout=300&sslmode=prefer - NODE_ENV: production REDIS_HOST: redis REDIS_PASSWORD: ${REDIS_PASSWORD} ports: @@ -21,8 +25,19 @@ services: interval: 10s timeout: 5s retries: 5 + postgres: - image: postgres:15 + image: docker.io/library/postgres:15 + cap_drop: + - ALL + cap_add: + - CHOWN + - DAC_READ_SEARCH + - FOWNER + - SETGID + - SETUID + security_opt: + - no-new-privileges:true env_file: - ../.env healthcheck: @@ -32,8 +47,14 @@ services: retries: 5 volumes: - postgres:/var/lib/postgresql/data + redis: - image: redis:alpine + image: docker.io/library/redis:alpine + user: '999:1000' + cap_drop: + - ALL + security_opt: + - no-new-privileges:true env_file: - ../.env command: ['redis-server', '--requirepass', $REDIS_PASSWORD]