【问题标题】:Jenkins SSH Host/User Certificates workflowJenkins SSH 主机/用户证书工作流程
【发布时间】:2023-03-06 16:41:01
【问题描述】:

如何在每次 Ansible 阶段运行时设置 SSH 证书签名过程? 我有使用 Terraform 创建 VM 然后运行 ​​Ansible 的管道。在 cloud-init 阶段创建和签名主机密钥,并配置公共用户 CA 密钥。 客户端/主机 CA 权限在 HashiCorp Vault 中配置。 因此,此时,无论配置了哪些 VM,我都可以通过 SSH 连接到每个 VM,因为我在 /etc/ssh/ssh_known_hosts 中全局设置了 @cert-authority *.example.com ecdsa-sha2-nistp521 AAAAB3NzaC1yc2EAAA...。我需要做的就是创建新密钥并对其进行签名,因为我的 TTL 很短。 但这在 Jenkins 中不起作用。 Jenkins 将所有 SSH 密钥存储在 /var/lib/jenkins/.ssh 下,默认情况下没有任何内容。 如果只是出于测试原因,我确实将我的个人用户密钥和证书 + ssh 配置文件复制到/var/lib/jenkins/.ssh,那么 Jenkins 可以愉快地运行 Ansible。 但是,每次我在我的 infra repo 中进行一些提交时,我都无法生成、签名和复制 jenkins 密钥。创建长寿证书也不好闻。

SSH 证书签名和轮换的惯用工作流程是什么?

【问题讨论】:

  • 这个可能有更多机会在 superuser.com 上得到回答

标签: jenkins ssh ansible


【解决方案1】:

我想到了其他解决方案。

创建 bash 脚本/etc/vault/sign-jenkins-cert.sh

#cat <<EOT >> /etc/vault/sign-jenkins-cert.sh
#!/bin/bash

set -eu -o pipefail

VAULT_ADDR='https://vault.example.com'
SSH_PUB_KEY_PATH='/var/lib/jenkins/.ssh/id_ecdsa.pub'
SSH_CERT_PATH='/var/lib/jenkins/.ssh/id_ecdsa-cert.pub'
ROLE_ID='<jenkins-role-id>'  # At cloud-init/kickstart stage this should be baked in
SECRET_ID='<jenkins-secret-id>'  # At cloud-init/kickstart stage this should be baked in

main () {
  local VAULT_TOKEN=$(vault_signin "${ROLE_ID}" "${SECRET_ID}")
  local SSH_PUB_KEY=$(cat "${SSH_PUB_KEY_PATH}")

  sign_ssh_cert "${VAULT_TOKEN}" "${SSH_PUB_KEY}" "${SSH_CERT_PATH}"

  chmod 0640 "${SSH_CERT_PATH}"
}

vault_signin () {
  local ROLE_ID=$1
  local SECRET_ID=$2

  local RES=$(curl -s --request POST \
    --data '{"role_id": "'"${ROLE_ID}"'", "secret_id": "'"${SECRET_ID}"'"}' \
    ${VAULT_ADDR}/v1/auth/approle/login | jq -r .auth.client_token)

  local RT=$?
  if [ "$RT" == "0" ]; then
    echo $RES
  else
    echo "Login with role $ROLE_ID failed. RT:$RT $RES"
    echo ""
  fi
}

sign_ssh_cert () {
  local VAULT_TOKEN="$1";
  local PUB_KEY="$2";
  local CERT_PATH="$3";

  curl -s \
    --header "X-Vault-Token: ${VAULT_TOKEN}" \
    --request POST \
    --data '{"public_key": "'"${PUB_KEY}"'", "cert_type": "user"}' \
    ${VAULT_ADDR}/v1/ssh-client-signer/sign/clientrole | jq -r .data.signed_key > "${CERT_PATH}"
}

main "$@"; exit
#EOT

然后将权限设置为0644 和root 所有权。

sudo chown root. /etc/vault/sign-jenkins-cert.sh && sudo chmod 0644 /etc/vault/sign-jenkins-cert.sh

然后创建 Systemd 单元/etc/systemd/system/sign-jenkins-certificate.service

#cat <<EOT >> /etc/systemd/system/sign-jenkins-certificate.service
[Unit]
Description=Sign a new host cert on boot, then daily
[Service]
ExecStart=/bin/sh /etc/vault/sign-jenkins-cert.sh
Restart=on-failure
RestartSec=20
Type=forking
#EOT

还将权限设置为0644 和root 所有权。

然后创建定时器单元/etc/systemd/system/sign-jenkins-certificate.timer

#cat <<EOT >> /etc/systemd/system/sign-jenkins-certificate.timer
[Unit]
Description=Sign a new host cert on boot, then daily
[Timer]
OnCalendar=daily
Persistent=true
Unit=sign-jenkins-certificate.service
[Install]
WantedBy=timers.target
#EOT

https://www.freedesktop.org/software/systemd/man/systemd.time.html#Calendar%20Events

验证

systemd-analyze verify /etc/systemd/system/sign-jenkins-certificate.timer

启用和启动计时器

systemctl enable sign-jenkins-certificate.timer && \
systemctl start sign-jenkins-certificate.timer && \
systemctl status sign-jenkins-certificate.timer

对于那些使用强化代理的人,请确保您的代理不会阻止 Vault ACL 中的 curl User-Agent。

此解决方案使用 Vault AppRole 进行身份验证,使用 Systemd 运行签名服务,无需 Jenkins 主机上的 Vault 代理。

这可能是有效的解决方案。但也许有更好的东西?

【讨论】:

  • 要注意的是限制 Jenkins 连接网络中任何主机的能力。
【解决方案2】:

我将自己列出“坏”的解决方案。不好,因为密钥轮换是手动的,而且 TTL 很可能会很长,因为我们很懒。

您需要在 Vault 中创建一个新的“jenkins”SSH 角色,其 TTL 类似于 52w

然后,理想情况下,您需要在 Jenkins 主机本身上创建新密钥。

ssh-keygen -t ecdsa -b 521 -f /var/lib/jenkins/.ssh/id_ecdsa -C "jenkins@jenkins-01"

然后由 Vault 演唱

vault write -field=signed_key ssh-client-signer/sign/jenkinsrole public_key=/var/lib/jenkins/.ssh/id_ecdsa.pub > /var/lib/jenkins/.ssh/id_ecdsa-cert.pub

这意味着 Vault 应该在系统上。 ...这是一个两难的选择——你是想通过网络复制 ssh 密钥(包括私有密钥),这有点味道,还是你想在 Jenkins 机器上安装 Vault。这取决于你。

拥有id_ecdsa-cert.pub 证书后,您可以使用ssh-keygen -Lf /var/lib/jenkins/.ssh/id_ecdsa-cert.pub 对其进行检查,查看有效期、委托人等是否正确。

因为很可能在基础架构中存在偏差,所以将 SSH 配置静态存储在 /var/lib/jenkins/.ssh/config 中会很痛苦。

相反,您可以创建 ansible-ssh.cfg 文件,其内容如下

Host bastion
  HostName bastion.example.com
  IdentitiesOnly yes
  IdentityFile ~/.ssh/id_ecdsa
  CertificateFile ~/.ssh/id_ecdsa-cert.pub
  Port 22
  Protocol 2
  User fedora
  LogLevel INFO

此文件可以(应该)与其他 Ansible 代码一起放置。

然后,在 ansible.cfg 文件中包含 SSH 节

[ssh_connection]
ssh_args = -F ./ansible-ssh.cfg

这样 Ansible 将始终获取最新的 SSH 配置。

如果您使用 Terraform,则可以动态更新 SSH 配置文件以包含新主机以及不包含的内容。

此解决方案与 Vault 无关。使用ssh-keygen 本身就可以轻松完成。

【讨论】:

    【解决方案3】:

    我做了类似的事情。我系统的核心是一个 python 脚本,它获取用户的常规凭据(用户名/密码),使用这些凭据向 Vault 进行身份验证,然后上传用户的公钥(id_rsa.pub)并保存返回的证书(id_rsa-cert.pub)。酒馆)。该脚本的主要用途之一是让人们可以访问构建机器以进行故障排除。我们将该过程与具有管理员权限(windows)或无密码 sudo(linux)的通用帐户配对。在 linux 上,我们锁定密码,在 Windows 上,我们有一个后台进程将密码旋转为随机密码。

    为了从 Jenkins 触发 Ansible,我在工作目录内的 .ssh 文件夹中创建了一个新的 ssh 密钥对。然后我执行脚本(这次我使用了一个 approle role-id/secret-id 而不是用户名/密码)。在 vars 文件中,我将 ansible_ssh_private_key_file 设置为指向新生成的私钥,该私钥赋予 Ansible 访问目标机器上备用 root 帐户的权限。

    我有 python 脚本设置,可以通过 pypi、yum、apt 和 Chocolatey 进行部署,以便于安装。

    【讨论】:

    • 非常感谢您分享一些额外的想法。就像您暗示的那样,通过我的方法,我需要小心 Jenkins 访问范围。目前它可以访问它想要的任何主机,这对我来说是个大问题。我目前的任务有点不同,但我很快就会重新审视这个问题。你给了我关于从工作目录进行范围访问的提示。
    猜你喜欢
    • 1970-01-01
    • 2018-12-04
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2017-09-10
    • 2018-10-07
    • 1970-01-01
    相关资源
    最近更新 更多