91 lines
4.1 KiB
Docker
91 lines
4.1 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 \
|
|
busybox-extras \
|
|
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
|
|
# HTTP_TOKEN — optional. When set, enables an HTTP listener on
|
|
# $HTTP_PORT serving /cgi-bin/run. Clients must
|
|
# send `Authorization: Bearer <HTTP_TOKEN>`.
|
|
# Leave unset for SSH-only mode.
|
|
# HTTP_PORT — HTTP listen port (default 8080).
|
|
# SSH_PORT — sshd listen port inside the container (default 22).
|
|
# 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)
|
|
# ---------------------------------------------------------------------------
|
|
ENV FORCE_COMMAND=""
|
|
ENV HTTP_TOKEN=""
|
|
ENV HTTP_PORT=8080
|
|
ENV SSH_PORT=22
|
|
ENV AUTHORIZED_KEYS_HOST=/etc/bastion/authorized_keys.host
|
|
ENV AUTHORIZED_KEYS_REPO=/etc/bastion/authorized_keys.repo
|
|
|
|
EXPOSE 22 8080
|
|
|
|
ENTRYPOINT ["/sbin/tini", "--", "/usr/local/bin/start-container"]
|