docker-bastion/Dockerfile

83 lines
3.7 KiB
Docker

# ===========================================================================
# docker-bastion — Minimal SSH bastion for ForceCommand routing
#
# Public sshd → key-only auth → runs ONE preconfigured command (set via
# $FORCE_COMMAND). The bastion user's login shell is /sbin/nologin, so
# there is no fallback shell even if ForceCommand somehow fails to fire.
#
# Build args:
# ALPINE_VERSION — Alpine base version (default: 3.21)
# ===========================================================================
ARG ALPINE_VERSION=3.21
FROM alpine:${ALPINE_VERSION}
LABEL maintainer="docker-bastion"
LABEL description="Minimal SSH bastion: public ssh → ForceCommand → docker exec (or anything else)"
ENV TZ=UTC
RUN ln -snf /usr/share/zoneinfo/$TZ /etc/localtime && echo $TZ > /etc/timezone
# ---------------------------------------------------------------------------
# Packages
# openssh-server / openssh-keygen — the daemon + ssh-keygen for host keys
# docker-cli / docker-cli-compose — so FORCE_COMMAND can target containers
# tini — proper PID 1 / signal handling
# bash — startup script + interactive sessions
# ---------------------------------------------------------------------------
RUN apk add --no-cache \
openssh-server \
openssh-keygen \
docker-cli \
docker-cli-compose \
bash \
tini \
ca-certificates \
tzdata
# ---------------------------------------------------------------------------
# Bastion user — UID/GID 1000, /sbin/nologin shell (ForceCommand is the only path)
# ---------------------------------------------------------------------------
ARG SSH_UID=1000
ARG SSH_GID=1000
RUN addgroup -g ${SSH_GID} agent && \
adduser -D -u ${SSH_UID} -G agent -s /sbin/nologin agent && \
mkdir -p /home/agent/.ssh && \
chown -R agent:agent /home/agent/.ssh && \
chmod 700 /home/agent/.ssh
# ---------------------------------------------------------------------------
# sshd config + entrypoint
# /etc/ssh/keys/ — host keys (generated on first boot; mount as a
# volume to persist them across rebuilds)
# /etc/bastion/ — runtime-generated ForceCommand wrapper
# ---------------------------------------------------------------------------
COPY config/sshd_config /etc/ssh/sshd_config
COPY scripts/start-container /usr/local/bin/start-container
RUN chmod 0755 /usr/local/bin/start-container && \
mkdir -p /etc/bastion /etc/ssh/keys /var/empty && \
chmod 700 /etc/ssh/keys && \
chmod 711 /var/empty
# ---------------------------------------------------------------------------
# Environment
# FORCE_COMMAND — REQUIRED. The single command run on every login.
# Shell metacharacters are supported.
# Examples:
# docker exec -it app bash
# docker compose -f /workspace/compose.yml exec app bash
# cd /workspace && ./deploy.sh
# AUTHORIZED_KEYS_HOST — file path; merged into authorized_keys if present
# AUTHORIZED_KEYS_REPO — file path; merged into authorized_keys if present
# (mount either, both, or neither — at least one
# must exist or the container refuses to start)
# SSH_PORT — sshd listen port inside the container (default 22)
# ---------------------------------------------------------------------------
ENV FORCE_COMMAND=""
ENV AUTHORIZED_KEYS_HOST=/etc/bastion/authorized_keys.host
ENV AUTHORIZED_KEYS_REPO=/etc/bastion/authorized_keys.repo
ENV SSH_PORT=22
EXPOSE 22
ENTRYPOINT ["/sbin/tini", "--", "/usr/local/bin/start-container"]