前言

Arch Linux 和 Rust 这两个开源组织的基础设施代码是开源的,那么,在实现基础设施代码开源的时候,如何保证私密信息安全呢?

实现在这里:

Arch Linux 使用 ansible-vault 和 gnupg 来管理私密信息,包括 Terraform 的 ak/sk/token。

Rust Team 使用 aws sts 实现权限管理。使用 aws ssm 来管理 Ansible 和 Terraform 中的私密信息。 Terraform 的 state 保存在 S3 上。

Arch Linux 团队的实践

关于 Ansible 的部分

Arch Linux 是通过 ansible.cfg 中的: vault_identity_list 来实现。

vault_identity_list 是一个逗号分隔的数组,可以传入多份包含密码的文件, 也可以传入多份可执行文件(该文件需要具备可执行权限),会是这样的形式:

default@misc/vault-keyring-client.sh,super@misc/vault-keyring-client.sh

实际含义是,依次用如下的形式调用可执行文件:

vault-keyring-client.sh --vault-id default
vault-keyring-client.sh --vault-id super

只要可执行的脚本将密钥打印到标准输出即可。因此,我们就可以:

  • 将密钥通过 gnupg 加密到一份文件内,存储到 gitlab 上,通过 bash 封装 gnupg 进行解密
  • 将密钥存放在 1password,通过 bash 脚本封装 1password 命令行解密

Arch Linux 团队用的就是第一种方案,见:

vault-keyring-client.sh

#!/bin/sh

readonly vault_password_file_encrypted="$(dirname $0)/vault-$2-password.gpg"

# flock used to work around "gpg: decryption failed: No secret key" in tf-stage2
# would otherwise need 'auto-expand-secmem' (https://dev.gnupg.org/T3530#106174)
flock "$vault_password_file_encrypted" \
  gpg --batch --decrypt --quiet "$vault_password_file_encrypted"

vault_identity_list 可以传多份可执行文件,意味着有多个密钥,同时也意味着仓库内, 会有不同的密钥加密的密文。在通过:

ansible-vault view vault_filename.yml

的时候,会自动尝试所有的密钥。

在创建密文的时候,会询问将使用哪个 vault-id 对应的密钥来加密密文:

ansible-vault create --encrypt-vault-id <vault-id> vault_filename.yml

gnupg 加密的文件,是支持多人解密的。Arch Linux 项目将不同的 ansible-vault 密钥保存在不同的文件内并用 gnupg 加密成 gpg 密文。见: misc 中的如下文件,里面存放的就是 Ansible Vault 的密钥:

通过 gnupg 来控制谁可以解密 gnupg 密文,拿到 ansible-vault 密钥从而实现权限控制。 即:

  • 通过将私密信息使用不同的 Ansible Vault 密钥加密的方式从而给私密信息分级。
  • 通过 gnupg 控制谁可能解密哪个 gpg 密文,从而实现人的权限的管理。

他们的开发者的 gnupg 公钥一般会传到 gnupg keyserver,公钥 ID 信息如下: root_access

archlinux 项目还用到了 Terraform 来管理 IAM(Identity and Access Management Solution), 管理基础设施(比如云服务器,dns 解析记录)。他们的 IAM 解决方案是 keyclock。基础设施用的是 hcloud。

IaC(基础设施代码化) 一般需要提供 ak/sk。这块使用了一份 python 脚本,如下: get_key.py

通过这份脚本可以从任意的 Ansible Vault 内获取到特定的 key 的 vaule,然后以 json 或者其他的格式发到标准输出。

关于 Terraform 的部分

backend 使用了 postgresql,初次使用这个仓库会执行如下的操作:

terraform init -backend-config="conn_str=postgres://terraform:$(../misc/get_key.py ../group_vars/all/vault_terraform.yml vault_terraform_db_password)@state.archlinux.org?sslmode=verify-full"

将 pg 的用户密码等信息保存到本地的 terraform.tfstate 文件(这个文件也在 .gitignore 内)。因为还是用 get_key.py 去获取的私密信息。因此,实际上也还是使用的 ansible-vault 的方案。

用户有了 backend 的访问权限后,在 Terraform 代码中使用了 external provider: external

这个 provider 可以调用外部可执行文件,将执行结果保存到 backend 内,比如:

data "external" "vault_hetzner" {
  program = [
    "${path.module}/../misc/get_key.py", "${path.module}/../misc/vaults/vault_hetzner.yml",
    "hetzner_cloud_api_key",
    "hetzner_dns_api_key",
    "--format", "json"
  ]
}

# 省略部分不相关的内容

provider "hcloud" {
  token = data.external.vault_hetzner.result.hetzner_cloud_api_key
}

provider "hetznerdns" {
  apitoken = data.external.vault_hetzner.result.hetzner_dns_api_key
}

可以看到,依旧是调用 get_key.py 这个脚本,依旧是 ansible-vault 方案。

以上就是 archlinux 团队管理基础设施私密信息的解决方案。

Rust 团队的实践

关于 Terraform 的部分

Rust Team 主要是通过 aws 的 cli,通过执行:

eval $(~/PATH/TO/SIMPLEINFRA/aws-creds.py)

访问 sts 服务,通过 2fa 认证后本地的 shell 就具备临时的权限了。

因为 backend 使用的是 S3,所以只要获取了临时权限,且相关的策略存在即可控制这些基础设施。

关于 Ansible 的部分

Ansible 部分使用了 Ansible Lookup Module: aws_ssm

aws_ssm 是一个可以跟 aws systems manager 服务交互的模块,该服务提供了 ssm 文档功能。ansible 的私密信息就是以 ssm 文档的形式存放在 aws 上。

ssm_all: "{{ lookup('aws_ssm', '/prod/ansible/all/', region='us-west-1', shortnames=true, bypath=true, recursive=true) }}"

vars_papertrail_url: "{{ ssm_all['papertrail-url'] }}"

然后通过 jinja 的语法,按照 key value 的形式按需索引对应的变量。

以上就是 Rust Team 管理基础设施私密信息的解决方案。