Skip to content

[FEATURE] Improve container security and harden your supply chain #109

@pfarikrispy

Description

@pfarikrispy

Summary

  • container image is not using multi-layers
  • packages (hadolint, trivy) are pulled insecurely thru an install Bash script
  • python dependencies are not handled very well
  • deps are pinned by version, not pinned by SHA digest
  • you're running a full (albeit Slim) Debian version instead of just a runtime for python

Problem Statement

The current setup is susceptible to an easy supply chain attack, the container can be tainted when remote Bash scripts are compromised, and the final container image is bloated when only a python executable is needed.

Proposed Solution

I would like to suggest a Dockerfile like this:

Security could be further improved by relying on hardened images from echo.ai, dhi.io or chainguard and their competitors.

# STAGE 1: Extract Hadolint Binary (Pinned by Digest)
FROM hadolint/hadolint@sha256:7f999c0b784a95777a102c0b64d1f2e915155f86640523e1003666f7f2b988f5 AS hadolint-stage

# STAGE 2: Extract Trivy Binary (Pinned by Digest)
FROM aquasec/trivy@sha256:4d60c4a4521759600e12d46e8c7512d7c07e997455a297924d55b084937a4e6e AS trivy-stage

# STAGE 3: Secure Python Builder (Pinned by Digest)
FROM python@sha256:7b57b98150499e9842f2ed73602f357591c0e3c8868bf030432bc13346cf6e15 AS builder

# Install build-time dependencies
RUN apt-get update && apt-get install -y --no-install-recommends \
    build-essential \ # when needed
    git \
    && rm -rf /var/lib/apt/lists/*

WORKDIR /app

RUN python -m venv /opt/venv
ENV PATH="/opt/venv/bin:$PATH"

RUN pip install --no-cache-dir --upgrade pip setuptools wheel

# Supply Chain Protection: Install dependencies with strict hash verification
# Create hashes using: pip-compile --generate-hashes <setup.py or pyproject.toml>
COPY requirements.txt .
RUN pip install --no-cache-dir --require-hashes -r requirements.txt

COPY . .
RUN pip install --no-cache-dir --no-deps .

# STAGE 4: Final Distroless Runtime (Pinned by Digest)
FROM gcr.io/distroless/python3-debian12@sha256:5646cf896dafe95def30420defa8077fc8ee71ef5578e2c018c2572aae0541e5

COPY --from=builder /opt/venv /opt/venv
COPY --from=hadolint-stage /usr/bin/hadolint /usr/local/bin/hadolint
COPY --from=trivy-stage /usr/local/bin/trivy /usr/local/bin/trivy

ENV PATH="/opt/venv/bin:$PATH"
ENV PYTHONUNBUFFERED=1

WORKDIR /github/workspace

ENTRYPOINT ["python", "-m", "docksec"]

Alternatives Considered

Looking at the entrypoint script, you may need to have that present and you can't simply run docksec.

  1. implement the parameter handling cleanly in your python program
  2. revert the distroless final image to a python Slim image but use a non-root (!) user
# Alternative Final Stage if entrypoint.sh is mandatory:
FROM python:3.12-slim AS final

# Create a dedicated non-root user/group
RUN groupadd -g 10001 appuser && \
    useradd -u 10001 -g appuser -s /sbin/nologin -c "App User" appuser

COPY --from=hadolint-stage /usr/bin/hadolint /usr/local/bin/hadolint
COPY --from=trivy-stage /usr/local/bin/trivy /usr/local/bin/trivy
COPY --from=builder /opt/venv /opt/venv

ENV PATH="/opt/venv/bin:$PATH"

COPY entrypoint.sh /entrypoint.sh
RUN chmod +x /entrypoint.sh && chown appuser:appuser /entrypoint.sh

# Drop privileges completely
USER appuser

ENTRYPOINT ["/entrypoint.sh"]

Even better for python, would be to switch using uv for dependency mgmt:

# STAGE 1: Extract Binaries (Pinned)
FROM hadolint/hadolint@sha256:7f999c0b784a95777a102c0b64d1f2e915155f86640523e1003666f7f2b988f5 AS hadolint
FROM aquasec/trivy@sha256:4d60c4a4521759600e12d46e8c7512d7c07e997455a297924d55b084937a4e6e AS trivy
FROM ghcr.io/astral-sh/uv:0.5@sha256:4624738479e0f6f059e09d57a22ef45b78f4477b949f91a5ec407a51357f8976 AS uv

# STAGE 2: Builder
FROM python@sha256:7b57b98150499e9842f2ed73602f357591c0e3c8868bf030432bc13346cf6e15 AS builder

# Copy uv from its official image
COPY --from=uv /uv /bin/uv

WORKDIR /app

# Enable bytecode compilation and optimization
ENV UV_COMPILE_BYTECODE=1 
# Prevent uv from using the internet for deps; ensure we rely on the lockfile
ENV UV_FROZEN=1 

COPY pyproject.toml uv.lock ./

# Install dependencies into /opt/venv
RUN uv venv /opt/venv && \
    . /opt/venv/bin/activate && \
    uv sync --frozen --no-dev

# Copy source code and install
COPY . .
RUN . /opt/venv/bin/activate && uv pip install --no-deps .

# STAGE 3: Final Distroless Runtime
FROM gcr.io/distroless/python3-debian12@sha256:5646cf896dafe95def30420defa8077fc8ee71ef5578e2c018c2572aae0541e5

COPY --from=builder /opt/venv /opt/venv
COPY --from=hadolint /usr/bin/hadolint /usr/local/bin/hadolint
COPY --from=trivy /usr/local/bin/trivy /usr/local/bin/trivy

ENV PATH="/opt/venv/bin:$PATH"
WORKDIR /github/workspace

ENTRYPOINT ["python", "-m", "docksec"]

Use Case

Who would benefit from this feature?

  • Individual developers
  • DevOps teams
  • Security teams
  • CI/CD pipelines
  • Enterprise users
  • Other:

Acceptance Criteria

What does done look like?

  • docksec parameters still work
  • final image is smaller and more secure (CVE)
  • implement uv and add a 5-day back-off for newer updates
[tool.uv]
# Ignore any package version published within the last 5 days
exclude-newer = "5 days"

I'm more than happy to create a PR but I'm not a python developer, mostly a security engineer 😊 but I'm able to lend a hand if you get stuck on the security aspects when the python part works .

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or request

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions