Files
2026-09-30 20:30:56 +03:00

369 lines
16 KiB
Docker

FROM python:3.12-slim-bookworm@sha256:782412e85d0f0984994c290652577d4018aff08145c85b262bb63dc0c7522254 AS python-base
ENV PATH=/usr/local/bin:/usr/bin:/bin:/usr/lib/postgresql/16/bin \
HOME=/data/home \
LANG=C.UTF-8 \
LC_ALL=C.UTF-8 \
PYTHONDONTWRITEBYTECODE=1
RUN /usr/local/bin/python3 -I -S -B -c "import sys; assert sys.version_info[:3] == (3, 12, 14), sys.version"
FROM python-base AS lock-generator
COPY docker/build-dependencies/requirements.lock /tmp/compiler.lock
RUN python3 -m pip --isolated install --index-url=https://pypi.org/simple \
--require-hashes --only-binary=:all: --no-compile --no-cache-dir \
-r /tmp/compiler.lock \
&& rm /tmp/compiler.lock
WORKDIR /src
COPY app/requirements.txt app/requirements-keycheckers.txt ./app/
COPY docker/requirements.in docker/requirements.lock docker/requirements-test.in docker/requirements-test.lock docker/requirements-worker.in docker/requirements-worker.lock ./docker/
COPY docker/build-dependencies/requirements.in docker/build-dependencies/requirements.lock ./docker/build-dependencies/
ENV CUSTOM_COMPILE_COMMAND="See docker/build-dependencies/README.md for the pinned Python 3.12.14 pip-tools generation command."
CMD ["python3", "-m", "piptools", "compile", "--generate-hashes", "--allow-unsafe", "--resolver=backtracking", "--strip-extras", "--no-emit-index-url", "--no-emit-trusted-host", "--index-url=https://pypi.org/simple", "--pip-args=--only-binary=:all:", "--output-file=docker/requirements.lock", "docker/requirements.in"]
FROM python-base AS trufflehog-download
ARG TARGETARCH
RUN python3 -I -S -B - "$TARGETARCH" <<'PY'
import hashlib
import os
import shutil
import sys
import tarfile
import urllib.request
checksums = {
"amd64": "dc24007c2f233bd61c05beabeb44aa27ea9b43288166279209abe0458c5ce76b",
"arm64": "7e65e771d2a247964056aa5edba0f8ae3945895e5dce867fe0ffbc7b0128239a",
}
architecture = sys.argv[1]
if architecture not in checksums:
raise SystemExit("TruffleHog is pinned only for linux/amd64 and linux/arm64")
name = f"trufflehog_3.97.4_linux_{architecture}.tar.gz"
url = "https://github.com/trufflesecurity/trufflehog/releases/download/v3.97.4/" + name
digest = hashlib.sha256()
size = 0
with urllib.request.urlopen(url, timeout=60) as response, open("/tmp/trufflehog.tar.gz", "wb") as output:
while chunk := response.read(1024 * 1024):
digest.update(chunk)
size += len(chunk)
output.write(chunk)
if digest.hexdigest() != checksums[architecture]:
raise SystemExit("TruffleHog archive SHA-256 mismatch")
if architecture == "amd64" and size != 34970205:
raise SystemExit("TruffleHog amd64 archive length mismatch")
with tarfile.open("/tmp/trufflehog.tar.gz", "r:gz") as archive:
member = archive.getmember("trufflehog")
if not member.isfile():
raise SystemExit("TruffleHog archive executable must be a regular file")
with archive.extractfile(member) as source, open("/trufflehog", "wb") as output:
shutil.copyfileobj(source, output)
os.chmod("/trufflehog", 0o755)
os.unlink("/tmp/trufflehog.tar.gz")
print(f"Verified {name}: {size} bytes, sha256:{digest.hexdigest()}")
PY
FROM python-base AS worker-dependencies
COPY docker/requirements-worker.lock /tmp/requirements-worker.lock
RUN python3 -m pip --isolated install --index-url=https://pypi.org/simple \
--require-hashes --only-binary=:all: --no-compile --no-cache-dir \
--target /worker-dependencies -r /tmp/requirements-worker.lock \
&& PYTHONPATH=/worker-dependencies python3 -I -S -B - <<'PY'
import sys
sys.path.insert(0, "/worker-dependencies")
import requests
import yaml
import zstandard
PY
RUN rm /tmp/requirements-worker.lock
FROM python-base AS worker-native-dependencies
RUN <<'SH'
set -eu
rm -f /etc/apt/sources.list /etc/apt/sources.list.d/debian.sources
printf '%s\n' \
'Types: deb' \
'URIs: https://snapshot.debian.org/archive/debian/20260914T000000Z/' \
'Suites: bookworm bookworm-updates' \
'Components: main' \
'Signed-By: /usr/share/keyrings/debian-archive-keyring.gpg' \
'Check-Valid-Until: no' \
'' \
'Types: deb' \
'URIs: https://snapshot.debian.org/archive/debian-security/20260914T000000Z/' \
'Suites: bookworm-security' \
'Components: main' \
'Signed-By: /usr/share/keyrings/debian-archive-keyring.gpg' \
'Check-Valid-Until: no' \
> /etc/apt/sources.list.d/debian.sources
export DEBIAN_FRONTEND=noninteractive
apt-get -o Acquire::Retries=3 -o Acquire::https::Timeout=30 -o APT::Update::Error-Mode=any update
apt-get install -y --no-install-recommends \
ca-certificates=20250419~deb12u1 \
git=1:2.39.5-0+deb12u3 \
tini=0.19.0-1+b3
install -d -o 10001 -g 10001 -m 0700 /data /data/home
/usr/sbin/groupadd --gid 10001 truf
/usr/sbin/useradd --uid 10001 --gid 10001 --no-create-home --home-dir /data/home --shell /usr/sbin/nologin truf
install -d -o 0 -g 0 -m 0755 /worker-git/bin /worker-git/libexec /worker-git/share
cp -aL /usr/bin/git /worker-git/bin/git
cp -aL /usr/lib/git-core /worker-git/libexec/git-core
cp -aL /usr/share/git-core /worker-git/share/git-core
find /worker-git -type d -exec chmod 0755 {} +
find /worker-git -type f -exec chmod go-w {} +
test -x /worker-git/bin/git
test -x /worker-git/libexec/git-core/git-remote-https
rm -rf /var/lib/apt/lists/* /var/cache/apt/archives/* /var/log/apt/*
SH
COPY --from=trufflehog-download --chown=0:0 --chmod=0755 /trufflehog /usr/local/bin/trufflehog
FROM worker-native-dependencies AS worker-package-build
ARG TARGETARCH
COPY --from=worker-dependencies --chown=0:0 /worker-dependencies /build/app/dependencies
COPY app/ /build/app/
COPY docs/remote-worker-quickstart-ru.md /build/README_RU.md
COPY docs/remote-worker-cheatsheet-windows-ru.md /build/
COPY docs/remote-worker-cheatsheet-linux-ru.md /build/
COPY docs/remote-worker-cheatsheet-docker-ru.md /build/
COPY docker/worker-package-pins.json /build/worker-package-pins.json
RUN case "$TARGETARCH" in \
amd64) platform_tag=linux-x86_64 ;; \
arm64) platform_tag=linux-aarch64 ;; \
*) echo "unsupported worker architecture" >&2; exit 1 ;; \
esac \
&& python3 -u -I -S -B /build/app/worker_package_builder.py assemble \
--output /opt/truf-worker \
--source-app /build/app \
--dependencies /build/app/dependencies \
--detector-policy /build/app/trufflehog-custom-detectors.yaml \
--trufflehog /usr/local/bin/trufflehog \
--git-root /worker-git \
--git-executable bin/git \
--platform-tag "$platform_tag" \
--operator-readme /build/README_RU.md \
--operator-cheatsheet /build/remote-worker-cheatsheet-windows-ru.md \
--operator-cheatsheet /build/remote-worker-cheatsheet-linux-ru.md \
--operator-cheatsheet /build/remote-worker-cheatsheet-docker-ru.md \
--build-inputs /build/worker-package-pins.json
FROM worker-native-dependencies AS worker
ENV GIT_EXEC_PATH=/opt/truf-worker/runtime/git/libexec/git-core \
GIT_TEMPLATE_DIR=/opt/truf-worker/runtime/git/share/git-core/templates
COPY --from=worker-package-build --chown=0:0 /opt/truf-worker /opt/truf-worker
RUN chown -R 10001:10001 /opt/truf-worker/app \
&& find /opt/truf-worker/app -type d -exec chmod 0700 {} + \
&& find /opt/truf-worker/app -type f -exec chmod 0600 {} + \
&& chown 10001:10001 /opt/truf-worker/worker-package.json \
&& chmod 0600 /opt/truf-worker/worker-package.json \
&& find /opt/truf-worker/bin /opt/truf-worker/runtime -type d -exec chmod 0755 {} + \
&& find /opt/truf-worker/bin /opt/truf-worker/runtime -type f -exec chmod go-w {} + \
&& test ! -e /opt/truf-worker/app/keycheck_runner.py \
&& test ! -d /opt/truf-worker/app/keycheckers \
&& test ! -e /usr/lib/postgresql \
&& test ! -e /usr/bin/psql
USER 10001:10001
RUN /opt/truf-worker/bin/trufflehog --version >/dev/null \
&& /opt/truf-worker/runtime/git/bin/git --version >/dev/null \
&& /usr/local/bin/python3 -I -S -B - <<'PY'
import sys
sys.path[:0] = ['/opt/truf-worker/app', '/opt/truf-worker/app/dependencies']
from importlib.util import find_spec
from worker_cli import parse_args
from worker_package import verify_worker_package
package = verify_worker_package('/opt/truf-worker/worker-package.json')
assert package['manifest']['schema'] == 3
assert package['manifest']['protocol_version'] == 2
assert {
(item['source'], item['platform'], item['planning_kind'])
for item in package['manifest']['capabilities']
} == {
('gitlab', 'gitlab', 'exact_git_v1'),
('dockerhub', 'docker', 'docker_direct_v1'),
('huggingface', 'huggingface', 'huggingface_space_v1'),
}
assert set(package['runtime_trees']) == {'git'}
assert parse_args(['run', '--server', 'https://worker.example', '--token', 'x' * 32]).command == 'run'
assert all(find_spec(name) is None for name in ('httpx', 'psycopg', 'starlette', 'streamlit'))
PY
WORKDIR /data
ENTRYPOINT ["/usr/bin/tini", "--", "/usr/local/bin/python3", "-u", "-I", "-S", "-B", "/opt/truf-worker/app/remote_worker_bootstrap.py", "--"]
CMD ["run"]
FROM python-base AS native-dependencies
ADD --checksum=sha256:0144068502a1eddd2a0280ede10ef607d1ec592ce819940991203941564e8e76 https://www.postgresql.org/media/keys/ACCC4CF8.asc /usr/share/keyrings/postgresql.asc
RUN <<'SH'
set -eu
chmod 0644 /usr/share/keyrings/postgresql.asc
rm -f /etc/apt/sources.list /etc/apt/sources.list.d/debian.sources
printf '%s\n' \
'Types: deb' \
'URIs: https://snapshot.debian.org/archive/debian/20260914T000000Z/' \
'Suites: bookworm bookworm-updates' \
'Components: main' \
'Signed-By: /usr/share/keyrings/debian-archive-keyring.gpg' \
'Check-Valid-Until: no' \
'' \
'Types: deb' \
'URIs: https://snapshot.debian.org/archive/debian-security/20260914T000000Z/' \
'Suites: bookworm-security' \
'Components: main' \
'Signed-By: /usr/share/keyrings/debian-archive-keyring.gpg' \
'Check-Valid-Until: no' \
> /etc/apt/sources.list.d/debian.sources
printf '%s\n' \
'deb [signed-by=/usr/share/keyrings/postgresql.asc] https://apt-archive.postgresql.org/pub/repos/apt bookworm-pgdg-archive main' \
> /etc/apt/sources.list.d/postgresql.list
# Only these exact PGDG packages may supplement the immutable Debian snapshot.
printf '%s\n' \
'Package: postgresql-16 postgresql-client-16 libpq5' \
'Pin: version 16.15-1.pgdg12+2' \
'Pin-Priority: 1001' \
'' \
'Package: postgresql-common postgresql-client-common' \
'Pin: version 293.pgdg12+1' \
'Pin-Priority: 1001' \
'' \
'Package: *' \
'Pin: origin apt-archive.postgresql.org' \
'Pin-Priority: -1' \
> /etc/apt/preferences.d/postgresql
printf '#!/bin/sh\nexit 101\n' > /usr/sbin/policy-rc.d
chmod 0755 /usr/sbin/policy-rc.d
export DEBIAN_FRONTEND=noninteractive
apt-get -o Acquire::Retries=3 -o Acquire::https::Timeout=30 -o APT::Update::Error-Mode=any update
apt-get install -y --no-install-recommends \
postgresql-common=293.pgdg12+1 \
postgresql-client-common=293.pgdg12+1
# Set this after common is installed but before installing any server package.
printf '\ncreate_main_cluster = false\n' >> /etc/postgresql-common/createcluster.conf
apt-get install -y --no-install-recommends \
ca-certificates=20250419~deb12u1 \
git=1:2.39.5-0+deb12u3 \
tini=0.19.0-1+b3 \
postgresql-16=16.15-1.pgdg12+2 \
postgresql-client-16=16.15-1.pgdg12+2 \
libpq5=16.15-1.pgdg12+2
test ! -d /var/lib/postgresql/16/main
rm -f /etc/ssl/private/ssl-cert-snakeoil.key /etc/ssl/certs/ssl-cert-snakeoil.pem
rm -rf /var/lib/apt/lists/* /var/cache/apt/archives/* /var/log/apt/*
/usr/sbin/groupadd --gid 10001 truf
/usr/sbin/useradd --uid 10001 --gid 10001 --no-create-home --home-dir /data/home --shell /usr/sbin/nologin truf
install -d -o 10001 -g 10001 -m 0700 /data /data/home
SH
RUN python3 -I -S -B - <<'PY'
import os
from pathlib import Path
# Keep the package's complete Git helper tree; regular hard links preserve argv[0].
for directory in (Path("/usr/local/bin"), Path("/usr/lib/git-core")):
for path in directory.iterdir():
if path.is_symlink() and os.access(path, os.X_OK):
target = path.resolve(strict=True)
if not target.is_file() or target.stat().st_uid != 0:
raise SystemExit(f"Untrusted executable target: {path}")
path.unlink()
os.link(target, path)
# Prefer native PG16 clients over the distribution's symlinked version wrappers.
for target in Path("/usr/lib/postgresql/16/bin").iterdir():
path = Path("/usr/bin") / target.name
if path.is_symlink():
path.unlink()
os.link(target, path)
for name in ("/usr/local/bin/python3", "/usr/bin/git", "/usr/lib/git-core/git-remote-https", "/usr/bin/tini"):
path = Path(name)
details = path.lstat()
if path.is_symlink() or not path.is_file() or details.st_uid != 0 or details.st_mode & 0o022:
raise SystemExit(f"Untrusted native executable: {path}")
PY
FROM native-dependencies AS dependencies
COPY docker/requirements.lock /tmp/requirements.lock
RUN python3 -m pip --isolated install --index-url=https://pypi.org/simple \
--require-hashes --only-binary=:all: --no-compile --no-cache-dir \
-r /tmp/requirements.lock \
&& python3 -m pip --isolated check \
&& rm /tmp/requirements.lock
USER 10001:10001
# Both public targets inherit these exact runtime contents; the default stays runtime.
FROM dependencies AS runtime-base
USER 0:0
RUN install -d -o 10001 -g 10001 -m 0700 /opt/truf /opt/truf/app /opt/truf/tests
USER 10001:10001
COPY --chown=10001:10001 app/ /opt/truf/app/
RUN python3 -I -S -B - <<'PY'
import os
import stat
for directory, directories, files in os.walk("/opt/truf/app", followlinks=False):
for path in [directory, *(os.path.join(directory, name) for name in directories + files)]:
details = os.lstat(path)
if not (stat.S_ISDIR(details.st_mode) or stat.S_ISREG(details.st_mode)):
raise SystemExit(f"Application links/special files are forbidden: {path}")
if os.path.basename(path) == "__pycache__" or path.endswith((".pyc", ".pyo")):
raise SystemExit(f"Application bytecode is forbidden: {path}")
if (details.st_uid, details.st_gid) != (10001, 10001):
raise SystemExit(f"Application ownership mismatch: {path}")
os.chmod(path, 0o700 if stat.S_ISDIR(details.st_mode) else 0o600)
PY
WORKDIR /opt/truf/app
ENTRYPOINT ["/usr/bin/tini", "--", "/usr/local/bin/python3", "-u", "-I", "-S", "-B", "/opt/truf/app/container_runtime.py"]
CMD ["run"]
FROM runtime-base AS test
USER 0:0
COPY --from=trufflehog-download --chown=0:0 --chmod=0755 /trufflehog /usr/local/bin/trufflehog
COPY docker/requirements-test.lock /tmp/requirements-test.lock
RUN python3 -m pip --isolated install --index-url=https://pypi.org/simple \
--require-hashes --only-binary=:all: --no-compile --no-cache-dir \
-r /tmp/requirements-test.lock \
&& python3 -m pip --isolated check \
&& rm /tmp/requirements-test.lock
USER 10001:10001
COPY --chown=10001:10001 tests/ /opt/truf/tests/
COPY --chown=10001:10001 --chmod=0600 .dockerignore /opt/truf/.dockerignore
COPY --chown=10001:10001 --chmod=0600 Dockerfile /opt/truf/Dockerfile
COPY --chown=10001:10001 --chmod=0600 start_runtime.ps1 start_core_runtime.ps1 stop_runtime.ps1 /opt/truf/
COPY --chown=10001:10001 --chmod=0600 compose.yaml compose.edge.yaml /opt/truf/
COPY --chown=10001:10001 deploy/ /opt/truf/deploy/
COPY --chown=10001:10001 docker/ /opt/truf/docker/
RUN python3 -I -S -B - <<'PY'
import os
from pathlib import Path
import stat
edge_e2e = {
path.name for path in Path('/opt/truf/tests').glob('edge_e2e_*.py')
}
if edge_e2e != {'edge_e2e_backend.py', 'edge_e2e_client.py'}:
raise SystemExit(f'Unexpected edge E2E test-stage inputs: {sorted(edge_e2e)}')
for directory, directories, files in os.walk("/opt/truf/tests", followlinks=False):
for path in [directory, *(os.path.join(directory, name) for name in directories + files)]:
details = os.lstat(path)
if not (stat.S_ISDIR(details.st_mode) or stat.S_ISREG(details.st_mode)):
raise SystemExit(f"Test links/special files are forbidden: {path}")
if os.path.basename(path) == "__pycache__" or path.endswith((".pyc", ".pyo")):
raise SystemExit(f"Test bytecode is forbidden: {path}")
if (details.st_uid, details.st_gid) != (10001, 10001):
raise SystemExit(f"Test ownership mismatch: {path}")
os.chmod(path, 0o700 if stat.S_ISDIR(details.st_mode) else 0o600)
PY
WORKDIR /opt/truf
ENTRYPOINT ["/usr/bin/tini", "--", "/usr/local/bin/python3", "-u", "-I", "-S", "-B", "/opt/truf/tests/container_unit.py"]
CMD []
FROM runtime-base AS runtime