Docker容器中的GitLab与宿主机共享 SSH 22端口

在利用 Docker 部署 GitLab 时,最棘手的问题之一往往是 SSH 服务的配置

通常有两种解决方案,但它们都有明显的缺陷:

  1. 修改 GitLab 容器的 SSH 端口(例如映射到 2222):这会导致克隆/推送地址变成 ssh://git@hostname:2222/group/project.git,不仅地址不美观,而且对习惯了标准 SSH 地址的用户不够友好。
  2. 将宿主机 SSH 端口改为其他端口,把宿主机 22 端口独占给 GitLab 容器:这虽然让 GitLab SSH 地址保持了标准格式,却给系统管理员远程管理宿主机带来了麻烦(需要指定非默认端口)。

本文将介绍一种优雅且安全的架构方案:共享宿主机 SSH 服务并直通 Docker 容器内的 GitLab
这意味着,你可以保持宿主机 SSH 服务正常运行在 22 端口,同时对于 git 用户的连接,系统会自动动态重定向到 Docker 容器内,无需占用额外的宿主机端口,用户拉取和推送代码的地址依然是标准的 git@hostname:group/project.git


1. 核心架构与工作原理

本方案的核心思想是:“宿主机认证并路由,容器内部响应与执行”

当用户通过 SSH 访问宿主机时,整个交互流程如下:

sequenceDiagram actor Client as 客户端 (Git CLI) participant HostSSH as 宿主机 sshd participant AuthCmd as 授权检查脚本<br/>(authorized-keys-check) participant GitLabContainer as GitLab 容器 participant ExecWrapper as 容器执行脚本<br/>(shell-docker-exec) Client->>HostSSH: 1. ssh git@host 发起连接 HostSSH->>AuthCmd: 2. 调用 AuthorizedKeysCommand 查询公钥 AuthCmd->>GitLabContainer: 3. docker exec 运行容器内 authorized-keys-check GitLabContainer-->>AuthCmd: 返回该公钥的限权指令 (command="/opt/gitlab/.../gitlab-shell key-1") AuthCmd-->>HostSSH: 返回公钥与限权指令 HostSSH->>Client: 4. 公钥认证成功,准备执行限权指令 HostSSH->>HostSSH: 5. 触发宿主机上的包装脚本 gitlab-shell HostSSH->>ExecWrapper: 6. sudo 提权运行 gitlab-shell-docker-exec (保留环境变量) ExecWrapper->>GitLabContainer: 7. docker exec 运行容器内的 gitlab-shell GitLabContainer->>Client: 8. 建立 Git 传输通道,开始克隆/推送数据

关键步骤解析:

  1. 动态公钥查询:宿主机并不在本地维护 git 用户的 authorized_keys 文件。而是通过 SSH 的 AuthorizedKeysCommand 选项,在用户尝试登录时,实时调用脚本去 GitLab 容器内查询用户的公钥。
  2. 强制命令执行(Force Command):GitLab 容器返回的公钥包含限制前缀 command="/opt/gitlab/embedded/service/gitlab-shell/bin/gitlab-shell key-XXX"。当客户端匹配该公钥时,宿主机的 SSH 只允许执行此命令,从而限制了用户的 shell 行为。
  3. 指令无缝桥接:宿主机上部署了一个同名包装脚本,它接收到 GitLab 的 key ID 后,通过带有环境变量保留的 sudo 运行 docker exec 进入容器,将执行流无缝移交给容器内的实际 gitlab-shell 引擎。

2. 核心脚本与配置文件详解(路径、权限与内容)

为了实现无缝路由,我们需要在宿主机上部署 3 个脚本、1 个 sudoer 配置以及 /etc/ssh/sshd_config 配置。请在对应目标路径下创建以下文件并配置相应权限。

1) gitlab-authorized-keys-check — 宿主机公钥查询桥接器

  • 目标路径/usr/local/sbin/gitlab-authorized-keys-check
  • 权限配置命令
    chmod 755 /usr/local/sbin/gitlab-authorized-keys-check
    chown root:root /usr/local/sbin/gitlab-authorized-keys-check
    
  • 作用:当有人尝试连接时,宿主机的 SSHD 服务会以 root 权限执行该脚本。脚本通过调用 docker exec 将查询请求转发给容器内的 GitLab 自带检查工具。

文件内容

#!/bin/bash
set -euo pipefail

CONTAINER="gitlab"

exec /usr/bin/docker exec -i \
    --user git \
    -e HOME=/var/opt/gitlab \
    -e USER=git \
    -e LOGNAME=git \
    "$CONTAINER" \
    /opt/gitlab/embedded/service/gitlab-shell/bin/gitlab-shell-authorized-keys-check "$@"

2) gitlab-shell — 宿主机 Shell 包装器

  • 目标路径/opt/gitlab/embedded/service/gitlab-shell/bin/gitlab-shell
  • 权限配置命令
    mkdir -p /opt/gitlab/embedded/service/gitlab-shell/bin/
    chmod 755 /opt/gitlab/embedded/service/gitlab-shell/bin/gitlab-shell
    chown root:root /opt/gitlab/embedded/service/gitlab-shell/bin/gitlab-shell
    
  • 作用:由于 GitLab 返回的公钥指令包含了容器内的执行路径 /opt/gitlab/embedded/service/gitlab-shell/bin/gitlab-shell,我们必须在宿主机上伪造一个同名代理脚本,负责将执行请求通过 sudo 交接给真正的直通脚本。

文件内容

#!/bin/bash
exec /usr/bin/sudo /usr/local/sbin/gitlab-shell-docker-exec "$@"

3) gitlab-shell-docker-exec — 安全过滤与容器直通执行器

  • 目标路径/usr/local/sbin/gitlab-shell-docker-exec
  • 权限配置命令
    chmod 755 /usr/local/sbin/gitlab-shell-docker-exec
    chown root:root /usr/local/sbin/gitlab-shell-docker-exec
    
  • 作用:接收来自包装器的 key-XXX 参数,对参数格式进行严格校验,防止任意命令注入宿主机,校验通过后调用 docker exec 携带环境变量直通运行容器内真正的 gitlab-shell

文件内容

#!/bin/bash
set -euo pipefail

CONTAINER="gitlab"

if [ "$#" -lt 1 ]; then
    echo "Missing GitLab key id" >&2
    exit 1
fi

for arg in "$@"; do
    case "$arg" in
        key-*|username-*)
            ;;
        *)
            echo "Invalid GitLab shell argument: $arg" >&2
            exit 1
            ;;
    esac
done

exec /usr/bin/docker exec -i -u git \
    -e SSH_ORIGINAL_COMMAND="${SSH_ORIGINAL_COMMAND:-}" \
    -e SSH_CONNECTION="${SSH_CONNECTION:-}" \
    -e SSH_CLIENT="${SSH_CLIENT:-}" \
    -e GIT_PROTOCOL="${GIT_PROTOCOL:-}" \
    -e HOME=/var/opt/gitlab \
    "$CONTAINER" \
    /opt/gitlab/embedded/service/gitlab-shell/bin/gitlab-shell "$@"

4) gitlab-shell-docker — Sudo 提权与环境变量保留配置

  • 目标路径/etc/sudoers.d/gitlab-shell-docker
  • 权限配置命令
    chmod 440 /etc/sudoers.d/gitlab-shell-docker
    chown root:root /etc/sudoers.d/gitlab-shell-docker
    
  • 作用:允许宿主机 git 用户免密以 root 身份运行直通脚本,并在 sudo 切换时强制保留 SSH_ORIGINAL_COMMANDSSH_CONNECTIONSSH_CLIENT 以及 GIT_PROTOCOL 环境变量。

文件内容

Defaults:git !requiretty
Defaults:git env_keep += "SSH_ORIGINAL_COMMAND SSH_CONNECTION SSH_CLIENT GIT_PROTOCOL"

git ALL=(root) NOPASSWD: /usr/local/sbin/gitlab-shell-docker-exec *

5) sshd_config 追加配置 — SSHD 服务过滤与路由配置

  • 目标路径/etc/ssh/sshd_config(追加于文件尾部)
  • 权限配置命令:系统默认(通常为 chmod 600, chown root:root
  • 作用:针对 git 用户进行匹配限制,不读取宿主机本地授权公钥文件,完全使用 gitlab-authorized-keys-check 脚本获取临时公钥,且彻底禁用 TTY 及端口转发。

追加配置

AcceptEnv GIT_PROTOCOL
Match User git
    AuthorizedKeysFile none
    AuthorizedKeysCommand /usr/local/sbin/gitlab-authorized-keys-check git %u %k
    AuthorizedKeysCommandUser root
    PasswordAuthentication no
    PubkeyAuthentication yes
    PermitTTY no
    AllowTcpForwarding no
    X11Forwarding no
    AllowAgentForwarding no

3. 安装与配置步骤

通过以下精简步骤即可完成全部配置:

第一步:在宿主机创建 git 用户

该用户专用于处理宿主机接收到的 SSH 代码拉取与推送连接。

useradd git

第二步:部署脚本与配置文件

参考本文第二章节的路径、所有权及权限配置命令,在宿主机对应位置直接创建以下 4 个文件并写入对应源码,完成权限配置:

  1. 宿主机公钥查询桥接器:/usr/local/sbin/gitlab-authorized-keys-check
  2. 宿主机 Shell 包装器:/opt/gitlab/embedded/service/gitlab-shell/bin/gitlab-shell
  3. 安全过滤与容器直通执行器:/usr/local/sbin/gitlab-shell-docker-exec
  4. Sudo 提权配置文件:/etc/sudoers.d/gitlab-shell-docker

第三步:配置宿主机 SSH 并重启服务

  1. 打开宿主机的 /etc/ssh/sshd_config 文件,将第二章第 5 小节的 Match User git 配置块追加到文件末尾。
  2. 重启宿主机 SSH 服务使配置生效:
systemctl restart sshd

4. 环境验证(部署测试容器)

为了验证该环境,我们需要在 Docker 中启动 GitLab 容器。此处提供一个测试容器的启动脚本。

  • 目标路径/opt/gitlab/run-gitlab.sh(或者 /var/gitlab/run-gitlab.sh
  • 权限配置命令
    mkdir -p /opt/gitlab   # (或 mkdir -p /var/gitlab)
    chmod 755 /opt/gitlab/run-gitlab.sh
    chown root:root /opt/gitlab/run-gitlab.sh
    
  • 特别注意:脚本中的 GITLAB_HOST=192.168.2.220 仅为示例,在实际配置中,请务必修改为您自己宿主机的实际 IP 地址,否则外部访问会无法正确寻址。

测试环境部署脚本

#!/bin/bash
script_dir=$(cd $(dirname $0);pwd)

# 定义宿主机上保存 GitLab 数据的根目录(默认为脚本所在目录)
GITLAB_HOME=$script_dir
# 定义部署的 GitLab 版本
GITLAB_VERSION=19.1.0
# 特别注意:请将此处的 IP 修改为您自己宿主机的实际 IP 地址
GITLAB_HOST=192.168.2.220

mkdir -p $GITLAB_HOME
docker run --detach \
  --hostname gitlab.example.com \
  --env GITLAB_OMNIBUS_CONFIG="external_url 'http://$GITLAB_HOST:8929'" \
  --publish 8929:8929 \
  --name gitlab \
  --restart always \
  --volume $GITLAB_HOME/config:/etc/gitlab \
  --volume $GITLAB_HOME/logs:/var/log/gitlab \
  --volume $GITLAB_HOME/data:/var/opt/gitlab \
  --shm-size 256m \
  gitlab/gitlab-ce:${GITLAB_VERSION}-ce.0

运行该脚本后,GitLab CE 将开始在后台运行。容器正常启动且初始化完毕后,您即可在 GitLab 网页端添加个人公钥,并在客户端通过传统的 git clone git@<您的宿主机IP>:<group>/<project>.git 命令进行测试。


5. 方案的优势与总结

  1. 零端口污染:无需在宿主机上映射额外的 SSH 端口(如 2222),也无需修改宿主机自身的 SSH 服务端口。
  2. 标准 URL:用户克隆或推送代码时,使用最符合直觉的 git@<host-ip>:<group>/<project>.git 地址。
  3. 极佳的安全隔离
    • 宿主机 git 用户被限制为不能申请 TTY、不能进行端口转发。
    • gitlab-shell-docker-exec 进行了严格的参数前缀校验,杜绝了参数注入风险。
    • 所有核心 Git 操作和文件读写完全在 Docker 容器内部进行,即使发生安全漏洞,也无法轻易穿透到宿主机环境。

通过该方案,您可以完美调和“Docker 容器隔离性”与“宿主机端口一致性”的矛盾,为团队提供优雅、高效的私有化 Git 托管服务。

posted @ 2026-06-24 17:00  两仪清风  阅读(11)  评论(0)    收藏  举报