强曰为道
与天地相似,故不违。知周乎万物,而道济天下,故不过。旁行而不流,乐天知命,故不忧.
文档目录

Dropbear SSH 完全指南 / 09 - Docker 与容器化

第九章:Docker 与容器化

9.1 为什么在容器中使用 SSH

在容器化环境中使用 SSH 服务的需求通常源于以下场景:

场景说明推荐方案
CI/CD 构建环境Jenkins 等工具需要 SSH 访问构建节点Dropbear
遗留系统迁移老系统依赖 SSH 执行维护脚本Dropbear
嵌入式仿真模拟嵌入式设备的 SSH 管理接口Dropbear
远程调试容器内部调试和文件传输Dropbear 或 SSH
多租户访问控制不同团队通过 SSH 访问共享容器OpenSSH
Git 操作Git over SSHOpenSSH 或 Git HTTP

注意: 在容器中运行 SSH 服务并不符合容器化最佳实践(每个容器只运行一个服务)。但在某些实际场景中,这是必要的折衷方案。

Dropbear vs OpenSSH 在容器中的对比

对比项DropbearOpenSSH
镜像大小~1-5 MB~20-50 MB
内存占用~1-2 MB~5-15 MB
启动速度极快
依赖项极少zlib, openssl, pam…
功能完整度基础完整
安全更新随镜像更新随系统更新

9.2 基础 Docker 镜像

Alpine Linux 方案(推荐)

# Dockerfile.dropbear - 基于 Alpine 的 Dropbear 镜像
FROM alpine:3.19

# 安装 Dropbear
RUN apk add --no-cache dropbear \
    && mkdir -p /etc/dropbear /var/run/dropbear

# 生成主机密钥(构建时)
RUN dropbearkey -t ed25519 -f /etc/dropbear/dropbear_ed25519_host_key \
    && dropbearkey -t rsa -f /etc/dropbear/dropbear_rsa_host_key

# 设置 root 密码(或使用 authorized_keys)
RUN echo 'root:changeme' | chpasswd

# 暴露 SSH 端口
EXPOSE 22

# 启动 Dropbear
CMD ["dropbear", "-F", "-E", "-R", "-w", "-s", "-p", "22"]
# 构建镜像
docker build -t dropbear-alpine -f Dockerfile.dropbear .

# 运行容器
docker run -d \
    --name dropbear-test \
    -p 2222:22 \
    dropbear-alpine

# 连接测试
dbclient -p 2222 root@localhost

Debian Slim 方案

# Dockerfile.dropbear-debian - 基于 Debian 的 Dropbear 镜像
FROM debian:bookworm-slim

RUN apt-get update \
    && apt-get install -y --no-install-recommends \
       dropbear-bin \
    && rm -rf /var/lib/apt/lists/*

RUN mkdir -p /etc/dropbear /var/run/dropbear \
    && dropbearkey -t ed25519 -f /etc/dropbear/dropbear_ed25519_host_key \
    && dropbearkey -t rsa -f /etc/dropbear/dropbear_rsa_host_key

RUN echo 'root:changeme' | chpasswd

EXPOSE 22
CMD ["/usr/sbin/dropbear", "-F", "-E", "-R", "-w", "-s", "-p", "22"]

极简多阶段构建

# Dockerfile.dropbear-multi - 多阶段构建最小镜像
# 阶段一:编译 Dropbear
FROM alpine:3.19 AS builder

RUN apk add --no-cache build-base zlib-dev linux-headers

ARG DROPBEAR_VERSION=2024.86

RUN wget -q https://matt.ucc.asn.au/dropbear/releases/dropbear-${DROPBEAR_VERSION}.tar.bz2 \
    && tar xjf dropbear-${DROPBEAR_VERSION}.tar.bz2 \
    && cd dropbear-${DROPBEAR_VERSION} \
    && echo '#define DROPBEAR_X11FWD 0' > localoptions.h \
    && echo '#define DROPBEAR_CLI_LOCALTCPFWD 0' >> localoptions.h \
    && echo '#define DROPBEAR_CLI_REMOTETCPFWD 0' >> localoptions.h \
    && echo '#define DROPBEAR_AGENTFWD 0' >> localoptions.h \
    && echo '#define DROPBEAR_SFTP_SERVER 0' >> localoptions.h \
    && CFLAGS="-Os -ffunction-sections -fdata-sections" \
       LDFLAGS="-Wl,--gc-sections" \
       ./configure \
          --prefix=/usr \
          --disable-zlib \
          --disable-pam \
          --enable-bundled-libtom \
          --disable-syslog \
          --enable-static \
    && make -j$(nproc) MULTI=1 \
    && strip dropbearmulti

# 阶段二:最终镜像
FROM scratch

COPY --from=builder /dropbear-*/dropbearmulti /usr/bin/dropbearmulti

# 创建符号链接
RUN ["/usr/bin/dropbearmulti", "dropbearmulti"]
# 注意:scratch 镜像中需要手动创建链接
# 实际使用时推荐从 builder 复制多个符号链接

# 最终镜像仅包含 Dropbear 二进制

9.3 安全 Docker 配置

使用公钥认证

# Dockerfile.dropbear-pubkey - 公钥认证专用
FROM alpine:3.19

RUN apk add --no-cache dropbear \
    && mkdir -p /etc/dropbear /root/.ssh /var/run/dropbear \
    && chmod 700 /root/.ssh

RUN dropbearkey -t ed25519 -f /etc/dropbear/dropbear_ed25519_host_key

# authorized_keys 将在运行时挂载
EXPOSE 22

CMD ["dropbear", "-F", "-E", "-R", "-s", "-w", "-p", "22"]
# 运行时挂载公钥
docker run -d \
    --name dropbear-secure \
    -p 2222:22 \
    -v /path/to/authorized_keys:/root/.ssh/authorized_keys:ro \
    -v /path/to/host-keys:/etc/dropbear:ro \
    dropbear-pubkey

docker-compose 配置

# docker-compose.yml
version: '3.8'

services:
  dropbear:
    build:
      context: .
      dockerfile: Dockerfile.dropbear-pubkey
    container_name: dropbear-ssh
    ports:
      - "2222:22"
    volumes:
      # 挂载公钥(只读)
      - ./ssh/authorized_keys:/root/.ssh/authorized_keys:ro
      # 持久化主机密钥
      - dropbear-host-keys:/etc/dropbear
    environment:
      - DROPBEAR_PORT=22
    restart: unless-stopped
    # 安全限制
    read_only: false  # Dropbear 需要写某些文件
    tmpfs:
      - /var/run
      - /tmp
    cap_drop:
      - ALL
    cap_add:
      - NET_BIND_SERVICE  # 绑定 <1024 端口需要
      - SETUID
      - SETGID
    security_opt:
      - no-new-privileges:true

volumes:
  dropbear-host-keys:

安全加固选项

# 安全加固的 Dropbear 容器
FROM alpine:3.19

RUN apk add --no-cache dropbear \
    && mkdir -p /etc/dropbear /var/run/dropbear /home/admin/.ssh \
    && chmod 700 /home/admin/.ssh \
    && adduser -D -s /bin/sh admin \
    && chown admin:admin /home/admin/.ssh

RUN dropbearkey -t ed25519 -f /etc/dropbear/dropbear_ed25519_host_key

# 非 root 用户运行
USER admin
EXPOSE 2022

# 使用非特权端口,禁止 root 登录
CMD ["dropbear", "-F", "-E", "-R", "-s", "-p", "2022", "-U", "admin", "-G", "admin"]

9.4 嵌入式容器场景

OpenWrt Docker 镜像

# Dockerfile.openwrt-dropbear - 模拟 OpenWrt Dropbear 环境
FROM alpine:3.19

# 模拟 OpenWrt 目录结构
RUN apk add --no-cache dropbear \
    && mkdir -p /etc/dropbear /etc/config /etc/init.d \
    /var/run/dropbear

# OpenWrt 风格配置文件
RUN echo 'config dropbear' > /etc/config/dropbear \
    && echo '    option PasswordAuth "off"' >> /etc/config/dropbear \
    && echo '    option Port "22"' >> /etc/config/dropbear \
    && echo '    option Interface "lan"' >> /etc/config/dropbear

# 生成密钥
RUN dropbearkey -t ed25519 -f /etc/dropbear/dropbear_ed25519_host_key

EXPOSE 22
CMD ["dropbear", "-F", "-E", "-R", "-s", "-p", "22"]

Buildroot 容器构建

# Dockerfile.buildroot-dropbear - 使用 Buildroot 构建最小化系统
FROM ubuntu:22.04 AS buildroot

RUN apt-get update && apt-get install -y \
    build-essential git wget cpio python unzip \
    bc rsync file libssl-dev \
    && rm -rf /var/lib/apt/lists/*

# 获取 Buildroot
ARG BUILDROOT_VERSION=2024.02
RUN git clone --depth 1 --branch ${BUILDROOT_VERSION} \
    https://github.com/buildroot/buildroot.git /buildroot

WORKDIR /buildroot

# 使用最小化配置 + Dropbear
RUN make qemu_aarch64_virt_defconfig \
    && sed -i 's/BR2_PACKAGE_OPENSSH=y/# BR2_PACKAGE_OPENSSH is not set/' .config \
    && echo 'BR2_PACKAGE_DROPBEAR=y' >> .config \
    && echo 'BR2_PACKAGE_DROPBEAR_CLIENT=y' >> .config \
    && make olddefconfig \
    && make -j$(nproc)

# 产物在 output/images/ 下

9.5 远程管理容器

通过 SSH 管理 Docker 容器

#!/bin/sh
# manage-container-via-ssh.sh - 通过 SSH 管理容器中的服务

CONTAINER_NAME="myapp"
SSH_PORT=2222
SSH_KEY="~/.ssh/id_ed25519"

# 在容器中执行命令
ssh_exec() {
    dbclient -i "$SSH_KEY" -p "$SSH_PORT" \
        -o "StrictHostKeyChecking=no" \
        root@localhost "$@"
}

# 部署配置文件
deploy_config() {
    local config_file="$1"
    local remote_path="$2"
    dbclient -i "$SSH_KEY" -p "$SSH_PORT" \
        "$config_file" "root@localhost:$remote_path"
}

# 日志查看
view_logs() {
    ssh_exec "tail -f /var/log/app.log"
}

# 服务重启
restart_service() {
    ssh_exec "kill -HUP \$(cat /var/run/app.pid)"
}

case "$1" in
    exec)    shift; ssh_exec "$@" ;;
    deploy)  deploy_config "$2" "$3" ;;
    logs)    view_logs ;;
    restart) restart_service ;;
    *)       echo "用法: $0 {exec|deploy|logs|restart} [args...]" ;;
esac

Docker + Dropbear + 端口转发

# 启动带端口转发的 Dropbear 容器
docker run -d \
    --name dropbear-gateway \
    -p 2222:22 \
    -p 8080:80 \
    -p 3306:3306 \
    dropbear-alpine

# 通过 SSH 隧道访问容器内部服务
dbclient -L 8080:localhost:80 -L 3306:localhost:3306 \
    -p 2222 root@localhost

# 或直接通过 Docker 端口映射
curl http://localhost:8080
mysql -h 127.0.0.1 -P 3306

9.6 容器安全最佳实践

安全检查清单

容器 SSH 安全检查:
┌──────────────────────────────────────────────────────┐
│ ✅ 使用公钥认证,禁用密码认证 (-s)                     │
│ ✅ 禁止 root 登录 (-w)                                │
│ ✅ 使用非 root 用户运行                               │
│ ✅ 限制端口映射范围                                   │
│ ✅ 使用 read-only 文件系统(尽可能)                   │
│ ✅ 删除不必要的 capabilities                          │
│ ✅ 设置 no-new-privileges                             │
│ ✅ 使用 secrets 管理敏感数据                          │
│ ✅ 定期更新镜像                                       │
│ ✅ 使用安全扫描工具检查镜像                            │
│ ❌ 不要在生产环境使用密码认证                          │
│ ❌ 不要暴露 SSH 端口到公网(除非必要)                  │
│ ❌ 不要在镜像中硬编码密钥或密码                        │
└──────────────────────────────────────────────────────┘

使用 Docker Secrets

# docker-compose.yml - 使用 Docker Secrets
version: '3.8'

services:
  dropbear:
    image: dropbear-alpine
    ports:
      - "2222:22"
    secrets:
      - ssh_authorized_keys
      - host_key_ed25519
      - host_key_rsa
    volumes:
      - dropbear-keys:/etc/dropbear
    command: >
      sh -c "
        cp /run/secrets/host_key_ed25519 /etc/dropbear/dropbear_ed25519_host_key &&
        cp /run/secrets/host_key_rsa /etc/dropbear/dropbear_rsa_host_key &&
        cp /run/secrets/ssh_authorized_keys /root/.ssh/authorized_keys &&
        chmod 600 /etc/dropbear/* /root/.ssh/authorized_keys &&
        dropbear -F -E -R -s -p 22
      "

secrets:
  ssh_authorized_keys:
    file: ./secrets/authorized_keys
  host_key_ed25519:
    file: ./secrets/dropbear_ed25519_host_key
  host_key_rsa:
    file: ./secrets/dropbear_rsa_host_key

volumes:
  dropbear-keys:

网络隔离

# docker-compose.yml - 网络隔离
version: '3.8'

services:
  dropbear:
    image: dropbear-alpine
    ports:
      - "127.0.0.1:2222:22"  # 仅绑定到 localhost
    networks:
      - internal
      - management

  app:
    image: myapp:latest
    networks:
      - internal
    # 不暴露端口到宿主机

networks:
  internal:
    internal: true  # 内部网络,无法访问外网
  management:
    # 管理网络,可访问外网

9.7 CI/CD 集成

Jenkins Agent SSH 容器

# Dockerfile.jenkins-dropbear - Jenkins SSH Agent
FROM alpine:3.19

RUN apk add --no-cache \
       dropbear \
       git \
       openssh-client \
       bash \
    && mkdir -p /etc/dropbear /home/jenkins/.ssh /var/run/dropbear

# 创建 Jenkins 用户
RUN adduser -D -s /bin/bash jenkins

# 生成主机密钥
RUN dropbearkey -t ed25519 -f /etc/dropbear/dropbear_ed25519_host_key

# Jenkins 公钥将在运行时注入
RUN chown -R jenkins:jenkins /home/jenkins

EXPOSE 22

CMD dropbear -F -E -R -p 22 -w -U jenkins -G jenkins
# Jenkins 配置 SSH Agent
# Manage Jenkins → Manage Credentials → SSH Username with private key
# Username: jenkins
# Private Key: <Jenkins 用户的私钥>
# Host: dropbear-agent:22

自动化测试中的 SSH

# test-with-ssh.sh - 使用 Dropbear 容器进行 SSH 测试

#!/bin/bash
set -e

CONTAINER_NAME="test-ssh-$(date +%s)"
SSH_PORT=$(shuf -i 20000-30000 -n 1)

# 启动 Dropbear 容器
docker run -d \
    --name "$CONTAINER_NAME" \
    -p "$SSH_PORT:22" \
    -v "$PWD/test-data:/data:ro" \
    dropbear-alpine

# 等待 SSH 就绪
echo "等待 SSH 服务就绪..."
for i in $(seq 1 30); do
    if dbclient -p "$SSH_PORT" -o "StrictHostKeyChecking=no" \
       -o "UserKnownHostsFile=/dev/null" \
       root@localhost "echo ready" 2>/dev/null; then
        echo "SSH 已就绪"
        break
    fi
    sleep 1
done

# 执行测试
echo "执行测试..."
dbclient -p "$SSH_PORT" root@localhost "ls /data"
dbclient -p "$SSH_PORT" root@localhost "cat /data/test.txt"

# 清理
docker rm -f "$CONTAINER_NAME"
echo "测试完成"

9.8 业务场景:IoT 设备仿真

# Dockerfile.iot-simulator - IoT 设备仿真器
FROM alpine:3.19

RUN apk add --no-cache \
       dropbear \
       busybox-extras \
       curl \
       jq \
    && mkdir -p /etc/dropbear /var/run/dropbear \
    /opt/iot /var/log/iot

# 生成设备密钥
RUN dropbearkey -t ed25519 -f /etc/dropbear/dropbear_ed25519_host_key

# 模拟设备信息
RUN echo '#!/bin/sh' > /usr/local/bin/device-info \
    && echo 'echo "=== IoT 设备信息 ==="' >> /usr/local/bin/device-info \
    && echo 'echo "型号: IoT-Sensor-2000"' >> /usr/local/bin/device-info \
    && echo 'echo "固件: v2.1.0"' >> /usr/local/bin/device-info \
    && echo 'echo "序列号: $(hostname)"' >> /usr/local/bin/device-info \
    && echo 'echo "运行时间: $(uptime -p)"' >> /usr/local/bin/device-info \
    && echo 'echo "内存: $(free -h | awk "/Mem/{print \\$3\\\"/\\\"\\$2}")"' >> /usr/local/bin/device-info \
    && chmod +x /usr/local/bin/device-info

# 模拟传感器数据
RUN echo '#!/bin/sh' > /usr/local/bin/read-sensor \
    && echo 'echo "温度: $(awk "BEGIN{srand(); printf \"%.1f\", 20+rand()*15}")°C"' >> /usr/local/bin/read-sensor \
    && echo 'echo "湿度: $(awk "BEGIN{srand(); printf \"%.0f\", 40+rand()*40}")%"' >> /usr/local/bin/read-sensor \
    && echo 'echo "压力: $(awk "BEGIN{srand(); printf \"%.0f\", 1000+rand()*30}")hPa"' >> /usr/local/bin/read-sensor \
    && chmod +x /usr/local/bin/read-sensor

EXPOSE 22

CMD ["dropbear", "-F", "-E", "-R", "-s", "-p", "22", "-m"]
# 运行 IoT 仿真器集群
for i in $(seq 1 5); do
    docker run -d \
        --name "iot-sensor-$i" \
        -p "220$i:22" \
        iot-simulator
done

# 从仿真器读取数据
for i in $(seq 1 5); do
    echo "=== Sensor $i ==="
    dbclient -p "220$i" root@localhost "read-sensor"
done

9.9 容器健康检查

# 健康检查
HEALTHCHECK --interval=30s --timeout=10s --retries=3 \
    CMD dbclient -o "StrictHostKeyChecking=no" \
        -o "UserKnownHostsFile=/dev/null" \
        -p 22 root@localhost "echo ok" || exit 1
# docker-compose.yml 健康检查
services:
  dropbear:
    image: dropbear-alpine
    healthcheck:
      test: ["CMD", "dbclient", "-o", "StrictHostKeyChecking=no",
             "-o", "UserKnownHostsFile=/dev/null",
             "-p", "22", "root@localhost", "echo", "ok"]
      interval: 30s
      timeout: 10s
      retries: 3
      start_period: 10s

9.10 本章小结

要点说明
镜像选择Alpine Linux 最小,Debian Slim 兼容
安全配置公钥认证 + 非 root + 最小权限
网络隔离绑定 localhost + 内部网络
密钥管理挂载 secrets,不硬编码在镜像
CI/CD适用于 Jenkins Agent、自动化测试
IoT 仿真Dropbear 非常适合模拟嵌入式设备 SSH 接口

扩展阅读


上一章:dropbearkey 工具 | 下一章:最佳实践