Files
wiki/存储/Ceph/知识库/12-RGW-安全与高级特性篇.md
2026-09-18 13:49:11 +08:00

165 KiB
Raw Permalink Blame History

第 12 册 · RGW 对象存储篇(四):安全、鉴权、加密与高级特性

基于 Ceph 官方文档整理 | 文档版本:Tentacle v20.2.4 | 抓取日期:2026-09-17 | 源文:ceph/ceph@v20.2.4 doc/radosgw/ 官网入口:https://docs.ceph.com/en/tentacle/radosgw/


本册导读

RGW 对象存储篇共四册,本册为第四册,聚焦 Ceph Object Gateway(radosgw)的安全与高级特性,即从"能跑起来"走向"能安全可控地对外提供服务"所需要的全部能力。

册 主题 本册关系
第 9 册 RGW 基础与部署 前置:把网关跑起来
第 10 册 RGW 多站点与数据同步 前置:跨集群复制
第 11 册 RGW 运维与可观测性 平行:日常管理
第 12 册(本册) 安全、鉴权、加密与高级特性 加密 → 密钥管理 → IAM/STS → 桶策略 → 外部身份源 → MFA → Lua 扩展 → 硬件加速

本册覆盖的 20 个官方页面:

分类 页面
加密 encryption
密钥管理 vault、kmip、barbican
身份与访问 iam、role、STS、STSLite、session-tags、bucketpolicy
外部身份源 keystone、ldap-auth、oidc、keycloak、opa
多因素认证 mfa
可编程扩展 lua-scripting
硬件加速 qat-accel、uadk-accel
API 入口 api

阅读建议

  • 如果你负责数据合规:优先读第一、二章(SSE-S3 / SSE-C / SSE-KMS 与 Vault/KMIP/Barbican 集成)。
  • 如果你负责多租户权限体系:优先读第三、四、五章(IAM → Role/STS → Bucket Policy),这三章存在明确的依赖与优先级关系。
  • 如果你要对接公司统一身份:读第六章(Keystone / LDAP / OIDC / Keycloak / OPA)。
  • 如果你要做网关侧定制逻辑(改写 header、鉴权、限流、审计):读第八章 Lua 脚本扩展,这是本册体量最大、字段最多的一章。

本册的默认值来源标注:rst 源文中 .. confval:: 的默认值是构建期占位符,凡涉及默认值处,本册均改为从 src/common/options/*.yaml.in 查询真实默认值,并标注「(默认值取自 src/common/options/*.yaml.in @ v20.2.4)」。


一、服务端加密(encryption.rst)

1.1 基本概念

Ceph Object Gateway 支持对上传对象做服务端加密(Server-Side Encryption, SSE)。所谓"服务端加密"的含义是:

  • 数据通过 HTTP 以明文形式发送给 RGW;
  • RGW 把数据以加密形式存入 Ceph 存储集群(OSD)。

因此有两条硬性前提(官方以 note 形式强调):

  1. 服务端加密的请求必须通过安全 HTTPS 连接发送,否则等于把密钥明文送到网络上。
  2. 如果使用代理做 SSL 终止(SSL termination),则必须先启用 rgw trust forwarded https,RGW 才会把被转发的请求视为安全请求。
  3. 服务端加密密钥必须是 256 位长度、base64 编码。

引自:Encryption

1.2 三种密钥管理模式对比

RGW 提供 3 种密钥管理方式,对应 Amazon S3 的三套规范。

模式 全称 密钥由谁管理 每次请求是否带密钥 对应的 Amazon 规范 Ceph 侧是否需要配置 当前支持的 KMS 后端
SSE-C Customer-Provided Keys(客户提供密钥) 客户端全权管理,客户端必须自己记住"哪个对象用了哪把密钥" 是,读/写加密数据的每个请求都要带上密钥 Amazon SSE-C 不需要任何特殊 Ceph 配置 不适用(无 KMS)
SSE-KMS Key Management Service 管理员把密钥存放在安全的密钥管理服务中,RGW 按需取回用于加解密 否,请求只带 key ID Amazon SSE-KMS 需要配置 rgw crypt s3 kms backend 及对应后端参数 Barbican、Vault、KMIP 三者已实现;原则上任何 KMS 都可以接
SSE-S3 S3-Managed Keys 密钥仍然存放在 Vault 中,但由 Ceph 自动创建、自动删除、按需取回,对用户完全不可见 否 Amazon SSE-S3 需要配置 SSE-S3 相关 Vault 参数 目前仅实现 Vault;原则上任何 KMS 都可以接

关键差异提炼:

  • SSE-C:密钥不出客户端,RGW 不保存任何密钥,客户端丢了密钥就等于丢了数据;零服务端配置成本。
  • SSE-KMS:密钥集中在 KMS,管理员可控可轮换可吊销;桶拥有者以 key ID 引用密钥。
  • SSE-S3:对应用最友好(不用管密钥),代价是密钥生命周期由 Ceph 托管,且只能接 Vault。

官方同时提醒:当同时使用 SSE-KMS 和 SSE-S3 时,应把二者指向不同的容器(可以用不同的 Vault 实例、分别挂载的 transit 实例、或同一 transit 挂载点下的不同分支),通过 rgw_crypt_vault_prefix 与 rgw_crypt_sse_s3_vault_prefix 区分;给 SSE-KMS 桶拥有者授予 Vault 权限时,不应让他们能碰 SSE-S3 的密钥——那部分只应由 Ceph 自己操作。

1.3 请求头

⚠️ 本册范围内 encryption.rst 并未逐项列出 SSE 请求头,它只指向 Amazon 的 SSE-C / SSE-KMS / SSE-S3 规范页面。因此下表只收录本册其它源文中确实出现过的头部名(来自 bucketpolicy.rst 的 condition key 列表),未在源文中出现的头部一律标注「文档未明确」。

请求头 出现位置 说明
x-amz-server-side-encryption bucketpolicy.rst 中 s3:PutObject 的 condition key s3:x-amz-server-side-encryption 指定服务端加密算法/模式
x-amz-server-side-encryption-aws-kms-key-id bucketpolicy.rst 中 s3:PutObject 的 condition key s3:x-amz-server-side-encryption-aws-kms-key-id 指定 SSE-KMS 的 key ID
x-amz-server-side-encryption-customer-algorithm bucketpolicy.rst 中 s3:PutObject 的 condition key s3:x-amz-server-side-encryption-customer-algorithm 指定 SSE-C 的客户密钥算法
SSE-C 的密钥本体头(AWS 中为 x-amz-server-side-encryption-customer-key) 未出现 文档未明确
SSE-C 的密钥 MD5 头(AWS 中为 x-amz-server-side-encryption-customer-key-MD5) 未出现 文档未明确

💡 补充:上表前三项之所以能被确认,是因为 RGW 的桶策略引擎把这些 HTTP 头暴露成了 policy condition key(s3:x-amz-* 前缀),这间接证明了 RGW 在 PutObject 路径上确实读取这些头。

1.4 桶加密 API

RGW 支持 Bucket Encryption APIs,用于以桶为粒度配置 SSE-S3 或 SSE-KMS:

  • PutBucketEncryption
  • GetBucketEncryption
  • DeleteBucketEncryption

引自:Encryption

1.5 自动加密(仅供测试)

可以在 ceph.conf 中设置 rgw crypt default encryption key,强制加密所有没有另行指定加密模式的对象。该配置接受一个 base64 编码的 256 位密钥。

rgw crypt default encryption key = 4YSmvJtBv0aZ7geVgAsdpRnLBEwWSWlMIGnRS8a9TSA=

配置项:

配置项 类型 默认值 说明
rgw_crypt_default_encryption_key str 文档未明确(rgw.yaml.in 中该条目无 default: 字段,level 为 dev) base64 编码的 256 位密钥,用于强制加密未指定加密模式的对象

⚠️ 官方以 important 强调:此模式仅用于诊断/测试目的! ceph 配置文件不是安全的密钥存储方式,以这种方式意外暴露的密钥应视为已泄露。

相关安全开关:

配置项 类型 默认值 说明
rgw_crypt_require_ssl bool true(默认值取自 src/common/options/rgw.yaml.in @ v20.2.4) 携带加密密钥头的请求必须通过 ssl 发送
rgw_crypt_suppress_logs bool true(默认值取自 src/common/options/rgw.yaml.in @ v20.2.4) 抑制可能打印出客户端密钥的日志
rgw_trust_forwarded_https bool 文档未明确(rgw.yaml.in 中该条目无 default: 字段) 当 radosgw 前有代理做 SSL 终止时,信任 Forwarded 与 X-Forwarded-Proto 头判断连接是否安全。服务端加密功能需要此选项。 若前方没有受信任的代理,绝不要开启,否则恶意用户可在任意请求中伪造这些头

引自:Encryption


二、密钥管理集成

RGW 的 SSE-KMS / SSE-S3 需要一个外部 KMS。当前官方实现了三套:Vault、KMIP、Barbican。

2.1 HashiCorp Vault(vault.rst)

2.1.1 整体交互流程

           +---------+       +---------+        +-------+     +-------+
           |  Client |       | RadosGW |        | Vault |     |  OSD  |
           +---------+       +---------+        +-------+     +-------+
                | create secret   |                 |             |
                | key for key ID  |                 |             |
                |-----------------+---------------->|             |
                |                 |                 |             |
                | upload object   |                 |             |
                | with key ID     |                 |             |
                |---------------->| request secret  |             |
                |                 | key for key ID  |             |
                |                 |---------------->|             |
                |                 |<----------------|             |
                |                 | return secret   |             |
                |                 | key             |             |
                |                 |                 |             |
                |                 | encrypt object  |             |
                |                 | with secret key |             |
                |                 |--------------+  |             |
                |                 |              |  |             |
                |                 |<-------------+  |             |
                |                 |                 |             |
                |                 | store encrypted |             |
                |                 | object          |             |
                |                 |------------------------------>|

要点:客户端只传 key ID;RGW 用 key ID 向 Vault 换取密钥,在 RGW 侧完成加密,密文落到 OSD。

下文示例使用 Vault 命令行工具,需要先设置:

export VAULT_ADDR='https://vault-server-fqdn:8200'

2.1.2 Vault Secrets Engines

RGW 当前支持两种 secrets engine:

Engine 启用命令 RGW 侧配置项取值
KV secrets engine version 2 vault secrets enable -path secret kv-v2 rgw crypt vault secret engine = kv
Transit engine vault secrets enable transit rgw crypt vault secret engine = transit

引自:HashiCorp Vault Integration

2.1.3 Vault 认证方式

官方支持两种:Token authentication 与 Vault agent。

为什么强烈建议用 Vault agent:Vault 中除 root token 外的绝大多数 token 都有生命周期限制,需要周期性地"重新初始认证"或"续期(renew)",而 Ceph 自身没有任何逻辑去做这两件事。最简单的正确做法是同时运行 Vault agent,由它刷新 token 文件;在这种模式下可以用文件系统权限来限制谁能使用 token。

Vault agent 也可以配置成代理模式:它自动为请求加上 token 再转发给真实 Vault server,同时仍然负责 token 续期。此时必须保护好 RGW 到 Vault agent 的网络路径(例如让 agent 只监听 localhost)。

Token Policies(RGW 所需的最小权限)

所有 Vault token 的权限由挂在其上的 policy 决定。应当只授予配置所必需的最小权限。

KV secret engine:

vault policy write rgw-kv-policy -<<EOF
  path "secret/data/*" {
    capabilities = ["read"]
  }
EOF

Transit secret engine:

vault policy write rgw-transit-policy -<<EOF
  path "transit/keys/*" {
    capabilities = [ "create", "update" ]
    denied_parameters = {"exportable" = [], "allow_plaintext_backup" = [] }
  }

  path "transit/keys/*" {
    capabilities = ["read", "delete"]
  }

  path "transit/keys/" {
    capabilities = ["list"]
  }

  path "transit/keys/+/rotate" {
    capabilities = [ "update" ]
  }

  path "transit/*" {
    capabilities = [ "update" ]
  }
EOF

兼容旧版 Ceph 的附加 policy(若此前用旧版 Ceph 配合 transit engine,可能需要):

vault policy write old-rgw-transit-policy -<<EOF
  path "transit/export/encryption-key/*" {
    capabilities = ["read"]
  }
EOF
Token 认证

⚠️ 官方 note:生产环境绝不要使用 root token 配合 Ceph。

rgw crypt vault auth = token
rgw crypt vault token file = /run/.rgw-vault-token
rgw crypt vault addr = https://vault-server-fqdn:8200

出于安全考虑,token 文件必须只有 Object Gateway 可读。

Vault Agent

Vault agent 是一个客户端守护进程,负责完成对 Vault 的认证并管理 token 续期与缓存,通常与 Object Gateway 运行在同一主机上。借助 Vault agent,可以使用 AppRole、AWS、Certs、JWT、Azure 等其它 Vault 认证机制。

rgw crypt vault auth = agent
rgw crypt vault addr = http://127.0.0.1:8100

配置 AppRole:

vault write auth/approle/role/rgw-ap \
  token_policies=rgw-transit-policy,default \
  token_max_ttl=60m

获取 role-id(保存到例如 /usr/local/etc/vault/.rgw-ap-role-id):

vault read auth/approle/role/rgw-ap/role-id -format=json | \
  jq -r .data.role_id

获取 secret-id(保存到例如 /usr/local/etc/vault/.rgw-ap-secret-id):

vault read auth/approle/role/rgw-ap/role-id -format=json | \
  jq -r .data.role_id

💡 补充:源文中"Get the secret-id"小节给出的命令与"Get the role-id"完全相同(都是读 role-id 并取 .data.role_id),这显然是官方文档的复制粘贴错误。正确的 secret-id 获取命令应当是 vault write -f auth/approle/role/rgw-ap/secret-id -format=json | jq -r .data.secret_id,但本册遵循"不编造"原则,仅指出源文此处存疑,不将其作为可执行命令推荐。

Vault agent 配置文件示例(rgw-agent.hcl):

pid_file = "/run/rgw-vault-agent-pid"
auto_auth {
  method "AppRole" {
    mount_path = "auth/approle"
    config = {
      role_id_file_path ="/usr/local/etc/vault/.rgw-ap-role-id"
      secret_id_file_path ="/usr/local/etc/vault/.rgw-ap-secret-id"
      remove_secret_id_file_after_reading ="false"
    }
  }
}
cache {
  use_auto_auth_token = true
}
listener "tcp" {
  address = "127.0.0.1:8100"
  tls_disable = true
}
vault {
  address = "https://vault-server-fqdn:8200"
}

启动常驻守护进程:

/usr/local/bin/vault agent -config=/usr/local/etc/vault/rgw-agent.hcl

启动后,agent 应在 localhost 的 8100 端口监听,可用 vault 命令与之交互验证。

2.1.4 Vault Namespaces

Vault 企业版支持 namespace(命名空间)概念,用于在同一个组织内为不同团队做集中管理并保证彼此隔离(租户)。

rgw crypt vault namespace = tenant1

2.1.5 在 Vault 中创建密钥

⚠️ 服务端加密密钥必须 256 位长、base64 编码。

KV engine:

vault kv put secret/myproject/mybucketkey key=$(openssl rand -base64 32)

样例输出:

====== Metadata ======
Key              Value
---              -----
created_time     2019-08-29T17:01:09.095824999Z
deletion_time    n/a
destroyed        false
version          1

注意:KV secrets engine 中 secret 以 key-value 对形式存储,RGW 期望 key 名必须是 key,即 secret 必须是 key=<secret key> 形式。

Transit engine:

为 transit engine 创建的密钥不应再标记为 exportable:

vault write -f transit/keys/mybucketkey

该命令创建一个 keyring,默认包含类型为 aes256-gcm96 的密钥。验证:

vault read transit/keys/mybucketkey

样例输出:

Key     Value
---     -----
derived                   false
exportable                false
name                      mybucketkey
type                      aes256-gcm96

2.1.6 配置 Ceph Object Gateway

# 1. 启用 Vault 作为 SSE-KMS 后端
rgw crypt s3 kms backend = vault

# 2. 选择认证方式(二选一)
rgw crypt vault auth = token
rgw crypt vault token file = /run/.rgw-vault-token
rgw crypt vault addr = https://vault-server-fqdn:8200

# 或
rgw crypt vault auth = agent
rgw crypt vault addr = http://localhost:8100

# 3. 选择 secrets engine(二选一)
rgw crypt vault secret engine = kv
# 或
rgw crypt vault secret engine = transit

# 4. 可选:Vault namespace
rgw crypt vault namespace = tenant1

# 5. 可选:限制 RGW 取密钥的 URL 路径前缀
rgw crypt vault prefix = /v1/secret/data
# 或
rgw crypt vault prefix = /v1/transit

# 6. 自定义 SSL 证书
rgw crypt vault verify ssl = true
rgw crypt vault ssl cacert = /etc/ceph/vault.ca
rgw crypt vault ssl clientcert = /etc/ceph/vault.crt
rgw crypt vault ssl clientkey = /etc/ceph/vault.key

其中 vault.ca 是 CA 证书,vault.key / vault.crt 是为 RGW 访问 Vault server 生成的私钥与 SSL 证书。官方强烈建议 rgw crypt vault verify ssl 设为 true;设为 false 非常危险,在这样高安全要求的环境中必须避免。

2.1.7 Vault 配置项逐项表格

配置项 类型 默认值(src/common/options/*.yaml.in @ v20.2.4) 说明
rgw_crypt_s3_kms_backend str barbican SSE-KMS 加密密钥存放位置。可选值枚举:barbican、vault、testing、kmip
rgw_crypt_sse_s3_backend str vault SSE-S3 加密密钥存放位置。唯一合法取值是 vault
rgw_crypt_vault_auth str token 与 Vault 交互使用的认证方式。枚举值:token、agent
rgw_crypt_vault_token_file str 文档未明确(无 default:) 认证方式为 token 时,token 文件路径(安全上应仅 Rados Gateway 可读)
rgw_crypt_vault_addr str 文档未明确(无 default:) Vault server 基地址,例如 http://vaultserver:8200
rgw_crypt_vault_prefix str 文档未明确(无 default:) Vault secret URL 前缀,用于限制 RGW 可访问的 secret 空间子集,例如 /v1/secret/data
rgw_crypt_vault_secret_engine str transit 用于取回加密密钥的 Vault Secret Engine:kv-v2 或 transit
rgw_crypt_vault_namespace str 文档未明确(无 default:) Vault Namespace,用于选择租户,例如 acme/tenant1(仅 Vault 企业版可用)
rgw_crypt_vault_verify_ssl bool true RGW 是否校验 Vault server 的 SSL 证书
rgw_crypt_vault_ssl_cacert str 文档未明确(无 default:) 访问 Vault server 使用的自定义 CA 证书路径
rgw_crypt_vault_ssl_clientcert str 文档未明确(无 default:) 访问 Vault server 使用的自定义客户端证书路径
rgw_crypt_vault_ssl_clientkey str 文档未明确(无 default:) 客户端证书所需的私钥路径
rgw_crypt_sse_s3_vault_auth str token SSE-S3 + Vault 的认证方式。枚举值:token、agent
rgw_crypt_sse_s3_vault_token_file str 文档未明确(无 default:) SSE-S3 场景下 token 文件路径
rgw_crypt_sse_s3_vault_addr str 文档未明确(无 default:) SSE-S3 场景 Vault server 基地址
rgw_crypt_sse_s3_vault_prefix str 文档未明确(无 default:) SSE-S3 场景 Vault secret URL 前缀
rgw_crypt_sse_s3_vault_secret_engine str transit SSE-S3 场景的 Secret Engine,唯一合法取值是 transit
rgw_crypt_sse_s3_vault_namespace str 文档未明确(无 default:) SSE-S3 场景 Vault Namespace
rgw_crypt_sse_s3_vault_verify_ssl bool true SSE-S3 场景是否校验 Vault SSL 证书
rgw_crypt_sse_s3_vault_ssl_cacert str 文档未明确(无 default:) SSE-S3 场景自定义 CA 证书路径
rgw_crypt_sse_s3_vault_ssl_clientcert str 文档未明确(无 default:) SSE-S3 场景客户端证书路径
rgw_crypt_sse_s3_vault_ssl_clientkey str 文档未明确(无 default:) SSE-S3 场景客户端私钥路径
rgw_crypt_sse_s3_key_template str %bucket_id SSE-S3 在 Vault 中每桶密钥的模板。可含 %bucket_id(展开为桶 marker,即分配给该桶的唯一 uuid)与 %owner_id(展开为拥有者 id);其它 % 用法保留、不应使用。若模板包含 %bucket_id,桶被删除时关联密钥会被自动移除
rgw_crypt_kmip_kms_key_template str 文档未明确(无 default:) SSE-KMS 场景下 kmip 密钥名模板
rgw_crypt_kmip_s3_key_template str $keyid SSE-S3 场景下 kmip 密钥模板

默认值说明:上表所有非空默认值均取自 src/common/options/rgw.yaml.in @ v20.2.4。

2.1.8 Transit Engine 兼容性支持

Transit engine 对旧版 Ceph 有兼容支持(旧版把 transit engine 当作简单的密钥仓库使用)。可用 compat 选项配置:

取值 语义 适用
transit compat=0 完全关闭向后兼容。未来版本将以此为默认值;当前版本的新装环境使用它是安全的 全新安装
transit compat=1 当前版本的正常默认值。对新创建的对象启用新引擎,同时仍允许旧引擎用于旧对象。此时给 Ceph 的 Vault token 必须同时具备新旧两套 transit policy 默认
transit compat=2 强制只使用旧引擎。若 Vault prefix 以 export/encryption-key 结尾(这是此前文档记载的配置),会自动选中此模式 兼容旧部署
rgw crypt vault secret engine = transit compat=0
rgw crypt vault secret engine = transit compat=1
rgw crypt vault secret engine = transit compat=2

2.1.9 上传对象

KV engine(注意 key ID 要带上路径):

aws --endpoint=http://radosgw:8000 s3 cp plaintext.txt s3://mybucket/encrypted.txt --sse=aws:kms --sse-kms-key-id myproject/mybucketkey

Transit engine(new flavor):

aws --endpoint=http://radosgw:8000 s3 cp plaintext.txt s3://mybucket/encrypted.txt --sse=aws:kms --sse-kms-key-id mybucketkey

RGW 会从 Vault 取回密钥、加密对象并存入桶;任何下载请求都会自动从 Vault 取回对应密钥并解密。

密钥的取回 URL 由三段拼接而成:rgw crypt vault addr(基地址)+ rgw crypt vault prefix(可选前缀)+ key ID。

  • KV 示例:http://vaultserver:8200/v1/secret/data/myproject/mybucketkey
  • Transit 示例:http://vaultserver:8200/v1/transit/mybucketkey

引自:HashiCorp Vault Integration


2.2 KMIP(kmip.rst)

KMIP 可作为安全密钥管理服务用于 SSE-KMS。交互流程与 Vault 相同(客户端建密钥 → 带 key ID 上传 → RGW 向 KMIP 取密钥 → 加密 → 落 OSD)。

官方给出的配置步骤顺序:

  1. Setting KMIP Access for Ceph
  2. Creating Keys in KMIP
  3. Configure the Ceph Object Gateway
  4. Upload object

开始之前需要完成三件事:把 ceph 与 KMIP 中的客户端信息关联、配置 ceph 使用该客户端信息、在 KMIP 中创建 1 个或多个密钥。

2.2.1 配置 KMIP 对 Ceph 的访问

这部分高度依赖具体 KMIP 实现支持的机制。官方描述了两种实现:

实现 定位 认证方式
IBM Security Guardium Key Lifecycle Manager (SKLM) 商业产品,支持良好 证书认证
PyKMIP 小型 Python 项目,仅适合实验与测试 信任证书,无特殊注册流程

IBM SKLM

证书可以是自签(例如用 openssl 创建),也可以由 SKLM 创建。流程是:

  1. 先配置 Ceph 使用 KMIP(见下)并尝试使用它;
  2. 该尝试会失败,但会在 SKLM 中留下一个 "untrusted client device certificate"(不受信任的客户端设备证书);
  3. 在 Web 界面中把它升级为已注册客户端,完成注册。

具体位置:Advanced Configuration → Client Device Communication Certificates,选择 Modify SSL/KMIP Certificates for Clients,然后打开 allow the server to trust this certificate and communicate... 开关。

PyKMIP

没有特殊注册流程,只要证书被信任即可,但证书必须由 pykmip 信任的 CA 签发。PyKMIP 还偏好证书带有 "extended key usage" 扩展,不过可以通过在服务端配置中指定 enable_tls_client_auth=False 规避。

2.2.2 在 KMIP 中创建密钥

可以用实现自带的 Web 界面/管理工具,也可以用 KMIP 协议本身。PyKMIP 提供了 Python 客户端库。

准备环境:

virtualenv $HOME/my-kmip-env
source $HOME/my-kmip-env/bin/activate
pip install pykmip

准备客户端配置文件:

cat <<EOF >$HOME/my-kmip-configuration
[client]
host={hostname}
port=5696
certfile={clientcert}
keyfile={clientkey}
ca_certs={clientca}
ssl_version=PROTOCOL_TLSv1_2
EOF

其中 {hostname} 替换为 kmip 主机名,{clientcert} {clientkey} {clientca} 替换为合适的 PEM 证书路径(例如你为 ceph 创建的那个)。

创建密钥的 Python 脚本(可直接粘到 shell 里执行):

from kmip.pie import client
from kmip import enums
import ssl
import os
import sys
import json
c = client.ProxyKmipClient(config_file=os.environ['HOME']+"/my-kmip-configuration")

while True:
  l=sys.stdin.readline()
  keyname=l.strip()
  if keyname == "": break
  with c:
    key_id = c.create(
        enums.CryptographicAlgorithm.AES,
        256,
        operation_policy_name='default',
        name=keyname,
        cryptographic_usage_mask=[
            enums.CryptographicUsageMask.ENCRYPT,
            enums.CryptographicUsageMask.DECRYPT
        ]
    )
    c.activate(key_id)
    attrs = c.get_attributes(uid=key_id)
    r = {}
    for a in attrs[1]:
     r[str(a.attribute_name)] = str(a.attribute_value)
    print (json.dumps(r))

在 shell 提示符下,python 会先用 >>> 再 ... 读入脚本,之后无提示地逐行读入并处理名字,直到遇到空行或 EOF(^D)或出错。

2.2.3 配置 Ceph Object Gateway

rgw crypt s3 kms backend = kmip
rgw crypt kmip ca path: /etc/ceph/kmiproot.crt
rgw crypt kmip client cert: /etc/ceph/kmip-client.crt
rgw crypt kmip client key: /etc/ceph/private/kmip-client.key
rgw crypt kmip kms key template: pykmip-$keyid

💡 补充:源文中这段示例的写法是 rgw crypt kmip ca path: /etc/ceph/kmiproot.crt(用冒号而非等号),与 Ceph 配置文件惯用的 key = value 不同。实际 ceph.conf 中应写作 rgw_crypt_kmip_ca_path = /etc/ceph/kmiproot.crt。本册保留源文原样以忠实记录,同时提示此差异。

kmip key template 描述 ceph 在去 kmip 查找之前如何改写传进来的名字,默认值就是 $keyid。如果不想让 ceph 看到你所有的 kmip 密钥,可以用它把 ceph 限制在 kmip 密钥命名空间的指定子集内。

KMIP 配置项:

配置项 类型 默认值(rgw.yaml.in @ v20.2.4) 说明
rgw_crypt_s3_kms_backend str barbican 设为 kmip 启用 KMIP 后端
rgw_crypt_kmip_addr str 文档未明确(无 default:) kmip server 地址
rgw_crypt_kmip_ca_path str 文档未明确(无 default:) kmip servers 的 ca
rgw_crypt_kmip_username str 文档未明确(无 default:) 通过用户名认证时使用
rgw_crypt_kmip_password str 文档未明确(无 default:) 与 username 配合,可选
rgw_crypt_kmip_client_cert str 文档未明确(无 default:) 使用客户端证书连接
rgw_crypt_kmip_client_key str 文档未明确(无 default:) 使用客户端证书连接
rgw_crypt_kmip_kms_key_template str 文档未明确(无 default:) SSE-KMS 的 kmip 密钥名模板
rgw_crypt_kmip_s3_key_template str $keyid SSE-S3 的 kmip 密钥模板

2.2.4 上传对象

aws --endpoint=http://radosgw:8000 s3 cp plaintext.txt \
s3://mybucket/encrypted.txt --sse=aws:kms --sse-kms-key-id mybucketkey

💡 补充:源文在 KMIP 页面同时给了 "for the kv engine" 与 "for the transit engine" 两段完全相同的示例命令,这是从 Vault 页面复制过来的残留(KMIP 没有 kv/transit engine 概念)。两条命令实际等价。

密钥名由 key template 构造,把 $keyid 替换为请求中提供的 key。按上面的配置,radosgw 会取名为 pykmip-mybucketkey 的 secret。

引自:KMIP Integration


2.3 OpenStack Barbican(barbican.rst)

OpenStack Barbican 可作为安全密钥管理服务用于服务端加密。

官方给出的步骤顺序:

  1. Configure Keystone
  2. Create a Keystone user
  3. Configure the Ceph Object Gateway
  4. Create a key in Barbican

2.3.1 配置 Keystone

Barbican 依赖 Keystone 对其密钥做授权与访问控制,参见 OpenStack Keystone Integration(本册第六章)。

2.3.2 创建 Keystone 用户

创建一个供 Ceph Object Gateway 取回密钥使用的新用户,例如:

user = rgwcrypt-user
pass = rgwcrypt-password
tenant = rgwcrypt

2.3.3 在 Barbican 中创建密钥

⚠️ 服务端加密密钥必须 256 位长、base64 编码。 请求 Barbican 必须在 X-Auth-Token 头中带有效的 Keystone token。

POST /v1/secrets HTTP/1.1
Host: barbican.example.com:9311
Accept: */*
Content-Type: application/json
X-Auth-Token: 7f7d588dd29b44df983bc961a6b73a10
Content-Length: 299

{
    "name": "my-key",
    "expiration": "2016-12-28T19:14:44.180394",
    "algorithm": "aes",
    "bit_length": 256,
    "mode": "cbc",
    "payload": "6b+WOZ1T3cqZMxgThRcXAQBrS5mXKdDUphvpxptl9/4=",
    "payload_content_type": "application/octet-stream",
    "payload_content_encoding": "base64"
}

响应:

{"secret_ref": "http://barbican.example.com:9311/v1/secrets/d1e7ef3b-f841-4b7c-90b2-b7d90ca2d723"}

响应中的 d1e7ef3b-f841-4b7c-90b2-b7d90ca2d723 就是可在任意 SSE-KMS 请求中使用的 key id。

新创建的密钥对 rgwcrypt-user 是不可访问的,必须通过 ACL 授予权限。假设 rgwcrypt-user 的 Keystone id 是 906aa90bd8a946c89cdff80d0869460f:

PUT /v1/secrets/d1e7ef3b-f841-4b7c-90b2-b7d90ca2d723/acl HTTP/1.1
Host: barbican.example.com:9311
Accept: */*
Content-Type: application/json
X-Auth-Token: 7f7d588dd29b44df983bc961a6b73a10
Content-Length: 101

{
    "read":{
    "users":[ "906aa90bd8a946c89cdff80d0869460f" ],
    "project-access": true
    }
}

响应:

{"acl_ref": "http://barbican.example.com:9311/v1/secrets/d1e7ef3b-f841-4b7c-90b2-b7d90ca2d723/acl"}

2.3.4 配置 Ceph Object Gateway

rgw crypt s3 kms backend = barbican
rgw barbican url = http://barbican.example.com:9311
rgw keystone barbican user = rgwcrypt-user
rgw keystone barbican password = rgwcrypt-password

使用 Keystone API version 2 时:

rgw keystone barbican tenant = rgwcrypt

使用 API version 3 时:

rgw keystone barbican project
rgw keystone barbican domain

2.3.5 Barbican 相关配置项

配置项 类型 默认值(rgw.yaml.in @ v20.2.4) 说明
rgw_crypt_s3_kms_backend str barbican 设为 barbican 启用 Barbican 后端(这也是默认值)
rgw_barbican_url str 文档未明确(无 default:) Barbican server 的 URL
rgw_keystone_barbican_user str 文档未明确(无 default:) 用于访问 barbican secrets 的 Keystone 用户
rgw_keystone_barbican_password str 文档未明确(无 default:) 该 barbican 用户的密码
rgw_keystone_barbican_tenant str 文档未明确(无 default:) Keystone v2.0 下 barbican 用户的 tenant
rgw_keystone_barbican_project str 文档未明确(无 default:) Keystone v3 下 barbican 用户的 project
rgw_keystone_barbican_domain str 文档未明确(无 default:) Keystone v3 下 barbican 用户的 domain

引自:OpenStack Barbican Integration


三、IAM(iam.rst)

versionadded:: Squid

Ceph Object Gateway 支持 Amazon IAM API 的一个子集,用于对 account 的 users、roles 及相关 policies 做 RESTful 管理。该 REST API 与 S3 使用同一个 HTTP endpoint 提供服务。

3.1 授权(Authorization)

  • 默认情况下,只有 Account Root Users(账户根用户) 被授权使用 IAM API;
  • 且只能看到自己账户下的资源;
  • 账户根用户可以通过 policies 把这些权限委托给账户内的其它 users 或 roles。

3.2 支持的 IAM Actions(逐项)

Users

Action Remarks
CreateUser 文档未明确
GetUser 文档未明确
UpdateUser 文档未明确
DeleteUser 文档未明确
ListUsers 文档未明确
CreateAccessKey 文档未明确
UpdateAccessKey 文档未明确
DeleteAccessKey 文档未明确
ListAccessKeys 文档未明确
PutUserPolicy 文档未明确
GetUserPolicy 文档未明确
DeleteUserPolicy 文档未明确
ListUserPolicies 文档未明确
AttachUserPolicies 文档未明确
DetachUserPolicy 文档未明确
ListAttachedUserPolicies 文档未明确

Groups

Action Remarks
CreateGroup 文档未明确
GetGroup 文档未明确
UpdateGroup 文档未明确
DeleteGroup 文档未明确
ListGroups 文档未明确
AddUserToGroup 文档未明确
RemoveUserFromGroup 文档未明确
ListGroupsForUser 文档未明确
PutGroupPolicy 文档未明确
GetGroupPolicy 文档未明确
DeleteGroupPolicy 文档未明确
ListGroupPolicies 文档未明确
AttachGroupPolicies 文档未明确
DetachGroupPolicy 文档未明确
ListAttachedGroupPolicies 文档未明确

Roles

Action Remarks
CreateRole 文档未明确
GetRole 文档未明确
UpdateRole 文档未明确
UpdateAssumeRolePolicy 文档未明确
DeleteRole 文档未明确
ListRoles 文档未明确
TagRole 文档未明确
UntagRole 文档未明确
ListRoleTags 文档未明确
PutRolePolicy 文档未明确
GetRolePolicy 文档未明确
DeleteRolePolicy 文档未明确
ListRolePolicies 文档未明确
AttachRolePolicies 文档未明确
DetachRolePolicy 文档未明确
ListAttachedRolePolicies 文档未明确

OpenIDConnectProvider

Action Remarks
CreateOpenIDConnectProvider 文档未明确
GetOpenIDConnectProvider 文档未明确
DeleteOpenIDConnectProvider 文档未明确
ListOpenIDConnectProviders 文档未明确

💡 补充:上表所有 Remarks 列在官方 rst 源文中均为空单元格,本册按"不编造"原则统一标注「文档未明确」。

3.3 Managed Policies(托管策略)

以下托管策略可用于 AttachGroupPolicy、AttachRolePolicy 和 AttachUserPolicy:

托管策略名 ARN Version(默认)
IAMFullAccess arn:aws:iam::aws:policy/IAMFullAccess v2
IAMReadOnlyAccess arn:aws:iam::aws:policy/IAMReadOnlyAccess v4
AmazonSNSFullAccess arn:aws:iam::aws:policy/AmazonSNSFullAccess v1
AmazonSNSReadOnlyAccess arn:aws:iam::aws:policy/AmazonSNSReadOnlyAccess v1
AmazonS3FullAccess arn:aws:iam::aws:policy/AmazonS3FullAccess v2
AmazonS3ReadOnlyAccess arn:aws:iam::aws:policy/AmazonS3ReadOnlyAccess v3

3.4 IAM policy 语法

💡 补充:iam.rst 本身没有定义 policy 的语法结构,它只列出支持的 action 与托管策略。本册范围内的 policy 语法证据来自 role.rst、STS.rst、session-tags.rst、bucketpolicy.rst 中的大量示例,其共同结构如下,字段含义一并列出:

字段 类型 是否必填 含义 出现位置
Version string 示例中恒为 2012-10-17 policy 语言版本 所有示例
Statement object 或 array 是 一条或多条语句 所有示例
Statement[].Effect string 是 Allow 或 Deny 所有示例
Statement[].Principal object trust policy 中必填 指定被授权的实体。形态如 {"AWS":["arn:aws:iam:::user/TESTER"]} 或 {"Federated":["arn:aws:iam:::oidc-provider/<URL of IDP>"]} role.rst、STS.rst、session-tags.rst
Statement[].Action string 或 array 是 动作,如 s3:*、sts:AssumeRole、sts:AssumeRoleWithWebIdentity、sts:TagSession、sts:GetSessionToken 全部
Statement[].Resource string 或 array 是 资源 ARN,如 arn:aws:s3:::example_bucket、arn:aws:s3::t1tenant:my-test-bucket、* 全部
Statement[].Condition object 否 条件块,见下 session-tags.rst、STSLite.rst

Condition 支持的操作符(本册源文中出现过的):

操作符 出现位置 语义
StringEquals session-tags.rst、STS.rst 字符串相等
BoolIfExists STSLite.rst 布尔值判断(键不存在时视为真)
ForAllValues:StringEquals session-tags.rst 检验请求中的每个 tag key 都是 policy 中 tag keys 的子集

3.5 IAM 与 bucket policy 的关系

维度 IAM policy Bucket policy
挂载对象 account 内的 user / group / role 桶
管理方式 IAM REST API(PutUserPolicy / PutRolePolicy 等)与 radosgw-admin role policy put 标准 S3 操作(s3cmd setpolicy / delpolicy),不用 radosgw-admin
是否支持 user/group/role 支持(Squid 起) 不支持在 user/group/role 上设置策略(bucketpolicy.rst 明确说明 "We do not yet support setting policies on users, groups, or roles")
引入版本 Squid Luminous(部分 bucket 操作在 Mimic 补齐)

💡 补充:关于"IAM policy 与 bucket policy 谁的优先级更高",本册源文没有给出显式裁决规则,因此标注:文档未明确。可确认的只有 bucketpolicy.rst 中"bucket policy 设置的策略同时约束 Swift 与 S3 操作"这一事实。

引自:Ceph Object Gateway IAM API


四、角色与 STS

4.1 Role(role.rst)

4.1.1 Role 概念

Role(角色)与 user 类似,也有决定"能做什么、不能做什么"的权限策略挂在它上面。区别在于:

  • Role 可以被任何需要它的身份 assume(承担);
  • 当用户 assume 一个 role 时,会得到一组动态创建的临时凭证(temporary credentials);
  • Role 可用来把访问权限委托给本来没有权限访问某些 S3 资源的用户、应用和服务。

4.1.2 radosgw-admin 命令

创建 Role
radosgw-admin role create --role-name={role-name} [--path=="{path to the role}"] [--assume-role-policy-doc={trust-policy-document}]
参数 说明 类型
role-name Role 的名称 String
path Role 的路径,默认值为斜杠 / String
assume-role-policy-doc 授予实体 assume 该 role 权限的信任关系策略文档 String

示例:

radosgw-admin role create --role-name=S3Access1 --path=/application_abc/component_xyz/ --assume-role-policy-doc=\{\"Version\":\"2012-10-17\",\"Statement\":\[\{\"Effect\":\"Allow\",\"Principal\":\{\"AWS\":\[\"arn:aws:iam:::user/TESTER\"\]\},\"Action\":\[\"sts:AssumeRole\"\]\}\]\}

响应:

{
  "id": "ca43045c-082c-491a-8af1-2eebca13deec",
  "name": "S3Access1",
  "path": "/application_abc/component_xyz/",
  "arn": "arn:aws:iam:::role/application_abc/component_xyz/S3Access1",
  "create_date": "2018-10-17T10:18:29.116Z",
  "max_session_duration": 3600,
  "assume_role_policy_document": "{\"Version\":\"2012-10-17\",\"Statement\":[{\"Effect\":\"Allow\",\"Principal\":{\"AWS\":[\"arn:aws:iam:::user/TESTER\"]},\"Action\":[\"sts:AssumeRole\"]}]}"
}
删除 Role
radosgw-admin role delete --role-name={role-name}
参数 说明 类型
role-name Role 的名称 String
radosgw-admin role delete --role-name=S3Access1

注意:只有当 role 没有挂载任何权限策略时才能删除它。

获取 Role
radosgw-admin role get --role-name={role-name}
参数 说明 类型
role-name Role 的名称 String
radosgw-admin role get --role-name=S3Access1

响应同创建时的 JSON 结构(id / name / path / arn / create_date / max_session_duration / assume_role_policy_document)。

列出 Role
radosgw-admin role list [--path-prefix ={path prefix}]
参数 说明 类型
path-prefix 用于过滤 role 的路径前缀。不指定则列出所有 role String
radosgw-admin role list --path-prefix="/application"

响应为 role 对象数组。

更新 Role 的 Assume Role Policy Document
radosgw-admin role-trust-policy modify --role-name={role-name} --assume-role-policy-doc={trust-policy-document}
参数 说明 类型
role-name Role 的名称 String
assume-role-policy-doc 授予实体 assume 该 role 权限的信任关系策略文档 String
radosgw-admin role-trust-policy modify --role-name=S3Access1 --assume-role-policy-doc=\{\"Version\":\"2012-10-17\",\"Statement\":\[\{\"Effect\":\"Allow\",\"Principal\":\{\"AWS\":\[\"arn:aws:iam:::user/TESTER2\"\]\},\"Action\":\[\"sts:AssumeRole\"\]\}\]\}

该例把 Principal 从 TESTER 改为 TESTER2。

添加 / 更新 Role 上挂载的 Policy
radosgw-admin role policy put --role-name={role-name} --policy-name={policy-name} --policy-doc={permission-policy-doc}
参数 说明 类型
role-name Role 的名称 String
policy-name Policy 的名称 String
policy-doc 权限策略文档 String
radosgw-admin role-policy put --role-name=S3Access1 --policy-name=Policy1 --policy-doc=\{\"Version\":\"2012-10-17\",\"Statement\":\[\{\"Effect\":\"Allow\",\"Action\":\[\"s3:*\"\],\"Resource\":\"arn:aws:s3:::example_bucket\"\}\]\}

以文件形式传 policy-doc:

radosgw-admin role-policy put --role-name=S3Access1 --policy-name=Policy1 --infile policy-document.json
列出 Role 上挂载的 Policy 名称
radosgw-admin role policy get --role-name={role-name}
参数 说明 类型
role-name Role 的名称 String
radosgw-admin role-policy list --role-name=S3Access1
[
  "Policy1"
]

💡 补充:源文中该小节的命令原型写作 radosgw-admin role policy get,但给出的示例却是 radosgw-admin role-policy list。二者在官方源文中并存,实际使用时以 radosgw-admin role-policy list 为准。

获取 Role 上挂载的指定 Policy
radosgw-admin role policy get --role-name={role-name} --policy-name={policy-name}
参数 说明 类型
role-name Role 的名称 String
policy-name Policy 的名称 String
radosgw-admin role-policy get --role-name=S3Access1 --policy-name=Policy1
{
  "Permission policy": "{\"Version\":\"2012-10-17\",\"Statement\":[{\"Effect\":\"Allow\",\"Action\":[\"s3:*\"],\"Resource\":\"arn:aws:s3:::example_bucket\"}]}"
}
删除 Role 上挂载的 Policy
radosgw-admin role policy delete --role-name={role-name} --policy-name={policy-name}
参数 说明 类型
role-name Role 的名称 String
policy-name Policy 的名称 String
radosgw-admin role-policy delete --role-name=S3Access1 --policy-name=Policy1
更新 Role
radosgw-admin role update --role-name={role-name} --max-session-duration={max-session-duration}
参数 说明 类型
role-name Role 的名称 String
max-session-duration Role 的最大会话时长 String
radosgw-admin role update --role-name=S3Access1 --max-session-duration=43200

注意:该命令目前只能用于更新 max-session-duration。

4.1.3 Role 的 REST API

除上述 radosgw-admin 命令外,也可用 REST API 操作 role。请求参数及解释参见上一小节。

调用 REST admin API 需要创建带 admin caps 的用户:

radosgw-admin --uid TESTER --display-name "TestUser" --access_key TESTER --secret test123 user create
radosgw-admin caps add --uid="TESTER" --caps="roles=*"
操作 请求示例
创建 Role POST "<hostname>?Action=CreateRole&RoleName=S3Access&Path=/application_abc/component_xyz/&AssumeRolePolicyDocument=\{...\}"
删除 Role POST "<hostname>?Action=DeleteRole&RoleName=S3Access"
获取 Role POST "<hostname>?Action=GetRole&RoleName=S3Access"
列出 Role POST "<hostname>?Action=ListRoles&RoleName=S3Access&PathPrefix=/application"
更新 Assume Role Policy POST "<hostname>?Action=UpdateAssumeRolePolicy&RoleName=S3Access&PolicyDocument=\{...\}"
添加/更新 Role Policy POST "<hostname>?Action=PutRolePolicy&RoleName=S3Access&PolicyName=Policy1&PolicyDocument=\{...\}"
列出 Role Policy 名 POST "<hostname>?Action=ListRolePolicies&RoleName=S3Access"
获取 Role Policy POST "<hostname>?Action=GetRolePolicy&RoleName=S3Access&PolicyName=Policy1"
删除 Role Policy POST "<hostname>?Action=DeleteRolePolicy&RoleName=S3Access&PolicyName=Policy1"
给 Role 打标签 POST "<hostname>?Action=TagRole&RoleName=S3Access&Tags.member.1.Key=Department&Tags.member.1.Value=Engineering"
列出 Role 标签 POST "<hostname>?Action=ListRoleTags&RoleName=S3Access"
删除 Role 标签 POST "<hostname>?Action=UntagRoles&RoleName=S3Access&TagKeys.member.1=Department"
更新 Role POST "<hostname>?Action=UpdateRole&RoleName=S3Access&MaxSessionDuration=43200"

CreateRole 响应(XML):

<role>
  <id>8f41f4e0-7094-4dc0-ac20-074a881ccbc5</id>
  <name>S3Access</name>
  <path>/application_abc/component_xyz/</path>
  <arn>arn:aws:iam:::role/application_abc/component_xyz/S3Access</arn>
  <create_date>2018-10-23T07:43:42.811Z</create_date>
  <max_session_duration>3600</max_session_duration>
  <assume_role_policy_document>{"Version":"2012-10-17","Statement":[{"Effect":"Allow","Principal":{"AWS":["arn:aws:iam:::user/TESTER"]},"Action":["sts:AssumeRole"]}]}</assume_role_policy_document>
</role>

ListRolePolicies 响应(XML):

<PolicyNames>
  <member>Policy1</member>
</PolicyNames>

GetRolePolicy 响应(XML):

<GetRolePolicyResult>
  <PolicyName>Policy1</PolicyName>
  <RoleName>S3Access</RoleName>
  <Permission_policy>{"Version":"2012-10-17","Statement":[{"Effect":"Allow","Action":["s3:CreateBucket"],"Resource":"arn:aws:s3:::example_bucket"}]}</Permission_policy>
</GetRolePolicyResult>

TagRole 响应(XML):

<TagRoleResponse>
  <ResponseMetadata>
    <RequestId>tx000000000000000000004-00611f337e-1027-default</RequestId>
  </ResponseMetadata>
</TagRoleResponse>

ListRoleTags 响应(XML):

<ListRoleTagsResponse>
  <ListRoleTagsResult>
    <Tags>
      <member>
        <Key>Department</Key>
        <Value>Engineering</Value>
      </member>
    </Tags>
  </ListRoleTagsResult>
  <ResponseMetadata>
    <RequestId>tx000000000000000000005-00611f337e-1027-default</RequestId>
  </ResponseMetadata>
</ListRoleTagsResponse>

UntagRole 响应(XML):

<UntagRoleResponse>
  <ResponseMetadata>
    <RequestId>tx000000000000000000007-00611f337e-1027-default</RequestId>
  </ResponseMetadata>
</UntagRoleResponse>

UpdateRole 响应(XML):

<UpdateRoleResponse>
  <UpdateRoleResult>
    <ResponseMetadata>
      <RequestId>tx000000000000000000007-00611f337e-1027-default</RequestId>
    </ResponseMetadata>
    </UpdateRoleResult>
</UpdateRoleResponse>

注意:UpdateRole API 目前也只能用于更新 max-session-duration。 Role 可以有多值(multivalued)标签,这些标签也可以在 CreateRole REST API 中一并传入。AWS 不支持多值 role tags。

4.1.4 用 boto3 操作 role 标签

import boto3

access_key = 'TESTER'
secret_key = 'test123'

iam_client = boto3.client('iam',
aws_access_key_id=access_key,
aws_secret_access_key=secret_key,
endpoint_url='http://s3.us-east.localhost:8000',
region_name=''
)

policy_document = "{\"Version\":\"2012-10-17\",\"Statement\":[{\"Effect\":\"Allow\",\"Principal\":{\"Federated\":[\"arn:aws:iam:::oidc-provider/localhost:8080/auth/realms/quickstart\"]},\"Action\":[\"sts:AssumeRoleWithWebIdentity\"],\"Condition\":{\"StringEquals\":{\"localhost:8080/auth/realms/quickstart:sub\":\"user1\"}}}]}"

print ("\n Creating Role with tags\n")
tags_list = [
    {'Key':'Department','Value':'Engineering'}
]
role_response = iam_client.create_role(
    AssumeRolePolicyDocument=policy_document,
    Path='/',
    RoleName='S3Access',
    Tags=tags_list,
)

print ("Adding tags to role\n")
response = iam_client.tag_role(
            RoleName='S3Access',
            Tags= [
                    {'Key':'CostCenter','Value':'123456'}
                ]
            )
print ("Listing role tags\n")
response = iam_client.list_role_tags(
            RoleName='S3Access'
            )
print (response)
print ("Untagging role\n")
response = iam_client.untag_role(
    RoleName='S3Access',
    TagKeys=[
        'Department',
    ]
)

引自:Role


4.2 完整 STS(STS.rst)

Secure Token Service(STS)是 AWS 中用于为联邦用户返回一组临时安全凭证的 Web 服务。Ceph Object Gateway 实现了 STS API 的一个子集,提供用于身份与访问管理的临时凭证:

  • 这些临时凭证可用于发起后续 S3 调用,由 Ceph Object Gateway 中的 STS engine 完成认证;
  • 临时凭证的权限可以通过作为 STS API 参数传入的 IAM policy 进一步收紧。

⚠️ 官方 note:STS 与 S3 API 共存于同一命名空间,二者可通过同一 endpoint 访问。

4.2.1 AssumeRole

返回一组可用于跨账户访问的临时凭证。临时凭证的权限是Role 上挂载的权限策略与AssumeRole API 传入的策略二者共同允许的交集。

参数 类型 是否必填 说明
RoleArn String Required 要 assume 的 Role 的 ARN
RoleSessionName String Required 被 assume 的 role session 的标识符
Policy String Optional JSON 格式的 IAM Policy
DurationSeconds Integer Optional 会话时长(秒),默认值为 3600
ExternalId String Optional 当 role 在另一个账户中被 assume 时可能使用的唯一 Id
SerialNumber String Optional 与发起 AssumeRole 调用的用户关联的 MFA 设备 Id
TokenCode String Optional 若被 assume 的 role 的 trust policy 要求 MFA,则由 MFA 设备提供的值

4.2.2 AssumeRoleWithWebIdentity

为已被 OpenID Connect / OAuth2.0 身份提供者(IDP)认证过的 Web/移动应用用户返回一组临时凭证。目前 Keycloak 已完成与 RGW 的测试与集成。

参数 类型 是否必填 说明
RoleArn String Required 要 assume 的 Role 的 ARN
RoleSessionName String Required 被 assume 的 role session 的标识符
Policy String Optional JSON 格式的 IAM Policy
DurationSeconds Integer Optional 会话时长(秒),默认值为 3600
ProviderId String Optional IDP 域名的完全限定主机部分。仅对 OAuth2.0 token 有效,对 OpenID Connect token 无效
WebIdentityToken String Required 应用在 IDP 认证用户后获得的 OpenID Connect / OAuth2.0 token

前置条件:在调用 AssumeRoleWithWebIdentity 之前,需要先在 RGW 中创建 OpenID Connect Provider 实体(即 Web 应用所认证的那个 IDP)。

信任关系的建立方式:在 role 的 trust policy 中添加 condition,只允许满足条件的应用访问。JWT 的所有 claims 都可以在 role trust policy 的 condition 中使用。

使用 aud claim 的 policy:

{"Version":"2012-10-17","Statement":[{"Effect":"Allow","Principal":{"Federated":["arn:aws:iam:::oidc-provider/<URL of IDP>"]},"Action":["sts:AssumeRoleWithWebIdentity"],"Condition":{"StringEquals":{"<URL of IDP> :app_id":"<aud>"}}}]}

上例 condition 中的 app_id 必须匹配传入 token 的 aud claim。

使用 sub claim 的 policy:

{"Version":"2012-10-17","Statement":[{"Effect":"Allow","Principal":{"Federated":["arn:aws:iam:::oidc-provider/<URL of IDP>"]},"Action":["sts:AssumeRoleWithWebIdentity"],"Condition":{"StringEquals":{"<URL of IDP> :sub":"<sub>"}}}]}

使用 azp claim 的 policy:

{"Version":"2012-10-17","Statement":[{"Effect":"Allow","Principal":{"Federated":["arn:aws:iam:::oidc-provider/<URL of IDP>"]},"Action":["sts:AssumeRoleWithWebIdentity"],"Condition":{"StringEquals":{"<URL of IDP> :azp":"<azp>"}}}]}

影子用户(shadow user):每个联邦用户都会对应创建一个影子用户,其 user id 由传入 web token 的 sub 字段派生。该用户创建在独立的命名空间 oidc 中,以避免与 rgw 中其它 user id 冲突。user id 格式为 <tenant>$<user-namespace>$<sub>,其中通过 oidc provider 认证的用户其 user-namespace 就是 oidc。

会话标签:RGW 支持在传给 AssumeRoleWithWebIdentity 的 web token 中携带 Session tags,详见本章 4.4 节。

4.2.3 STS 配置项

配置项 类型 默认值(rgw.yaml.in @ v20.2.4) 说明
rgw_sts_key str 文档未明确(无 default:) 用于加密/解密 role session token 的 key。应由 ceph-authtool --gen-print-key 生成,是 base64 编码的 CryptoKey。同一 zone 内所有 radosgw 实例必须使用相同 key;multisite 配置下同一 realm 内所有 zone 必须使用相同 key
rgw_s3_auth_use_sts bool false S3 认证是否使用 STS
rgw_sts_entry str sts 内部 STS 请求的 URL 路径前缀
rgw_sts_max_session_duration uint 43200 GetSessionToken 返回的临时凭证的 durationSeconds 上限
rgw_sts_min_session_duration uint 900 AssumeRole* 调用返回的临时凭证的 durationSeconds 下限
rgw_s3_auth_order str sts, external, local S3 认证策略的尝试顺序,逗号分隔的引擎列表。默认顺序是先尝试所有已配置的外部引擎,再尝试本地 RADOS 认证
rgw_s3_auth_disable_signature_url bool false 是否禁用 presigned URL 认证。启用后,任何使用 V2 或 V4 签名预签名的请求都会被拒绝

4.2.4 示例:AssumeRole

前置:确保用户 TESTER 被赋予 roles capability。

radosgw-admin caps add --uid="TESTER" --caps="roles=*"

完整流程(TESTER1 assume 由 TESTER 创建的 role,按挂载在 role 上的权限策略访问 TESTER 拥有的 S3 资源):

import boto3

iam_client = boto3.client('iam',
aws_access_key_id=<access_key of TESTER>,
aws_secret_access_key=<secret_key of TESTER>,
endpoint_url=<IAM URL>,
region_name=''
)

policy_document = '''{"Version":"2012-10-17","Statement":[{"Effect":"Allow","Principal":{"AWS":["arn:aws:iam:::user/TESTER1"]},"Action":["sts:AssumeRole"]}]}'''

role_response = iam_client.create_role(
AssumeRolePolicyDocument=policy_document,
Path='/',
RoleName='S3Access',
)

role_policy = '''{"Version":"2012-10-17","Statement":{"Effect":"Allow","Action":"s3:*","Resource":"arn:aws:s3:::*"}}'''

response = iam_client.put_role_policy(
RoleName='S3Access',
PolicyName='Policy1',
PolicyDocument=role_policy
)

sts_client = boto3.client('sts',
aws_access_key_id=<access_key of TESTER1>,
aws_secret_access_key=<secret_key of TESTER1>,
endpoint_url=<STS URL>,
region_name='',
)

response = sts_client.assume_role(
RoleArn=role_response['Role']['Arn'],
RoleSessionName='Bob',
DurationSeconds=3600
)

s3client = boto3.client('s3',
aws_access_key_id = response['Credentials']['AccessKeyId'],
aws_secret_access_key = response['Credentials']['SecretAccessKey'],
aws_session_token = response['Credentials']['SessionToken'],
endpoint_url=<S3 URL>,
region_name='',)

bucket_name = 'my-bucket'
s3bucket = s3client.create_bucket(Bucket=bucket_name)
resp = s3client.list_buckets()

4.2.5 示例:AssumeRoleWithWebIdentity(Keycloak 作为 IDP)

import boto3

iam_client = boto3.client('iam',
aws_access_key_id=<access_key of TESTER>,
aws_secret_access_key=<secret_key of TESTER>,
endpoint_url=<IAM URL>,
region_name=''
)

oidc_response = iam_client.create_open_id_connect_provider(
    Url=<URL of the OpenID Connect Provider,
    ClientIDList=[
        <Client id registered with the IDP>
    ],
    ThumbprintList=[
        <Thumbprint of the IDP>
    ]
)

policy_document = '''{"Version":"2012-10-17","Statement":[{"Effect":"Allow","Principal":{"Federated":["arn:aws:iam:::oidc-provider/localhost:8080/auth/realms/demo"]},"Action":["sts:AssumeRoleWithWebIdentity"],"Condition":{"StringEquals":{"localhost:8080/auth/realms/demo:app_id":"customer-portal"}}}]}'''
role_response = iam_client.create_role(
AssumeRolePolicyDocument=policy_document,
Path='/',
RoleName='S3Access',
)

role_policy = '''{"Version":"2012-10-17","Statement":{"Effect":"Allow","Action":"s3:*","Resource":"arn:aws:s3:::*"}}'''

response = iam_client.put_role_policy(
    RoleName='S3Access',
    PolicyName='Policy1',
    PolicyDocument=role_policy
)

sts_client = boto3.client('sts',
aws_access_key_id=<access_key of TESTER1>,
aws_secret_access_key=<secret_key of TESTER1>,
endpoint_url=<STS URL>,
region_name='',
)

response = client.assume_role_with_web_identity(
RoleArn=role_response['Role']['Arn'],
RoleSessionName='Bob',
DurationSeconds=3600,
WebIdentityToken=<Web Token>
)

s3client = boto3.client('s3',
aws_access_key_id = response['Credentials']['AccessKeyId'],
aws_secret_access_key = response['Credentials']['SecretAccessKey'],
aws_session_token = response['Credentials']['SessionToken'],
endpoint_url=<S3 URL>,
region_name='',)

bucket_name = 'my-bucket'
s3bucket = s3client.create_bucket(Bucket=bucket_name)
resp = s3client.list_buckets()

💡 补充:源文示例中 sts_client 创建后调用的是 client.assume_role_with_web_identity(变量名不一致,应为 sts_client)。这是官方示例的笔误,本册保留原样并在此提示。

4.2.6 如何获取 OpenID Connect Provider IDP 的 thumbprint

  1. 取 IDP 的 URL,追加 /.well-known/openid-configuration 得到配置文档 URL。例如 IDP URL 为 http://localhost:8000/auth/realms/quickstart,则文档 URL 为 http://localhost:8000/auth/realms/quickstart/.well-known/openid-configuration。

  2. 用 curl 取配置文档:

curl -k -v \
  -X GET \
  -H "Content-Type: application/x-www-form-urlencoded" \
  "http://localhost:8000/auth/realms/quickstart/.well-known/openid-configuration" \
| jq .
  1. 从步骤 2 的响应中取 jwks_uri 的值,获取 IDP 的证书:
curl -k -v \
  -X GET \
  -H "Content-Type: application/x-www-form-urlencoded" \
  "http://$KC_SERVER/$KC_CONTEXT/realms/$KC_REALM/protocol/openid-connect/certs" \
  | jq .
  1. 把响应中 x5c 的内容复制到文件 certificate.crt,在开头加 -----BEGIN CERTIFICATE-----,结尾加 -----END CERTIFICATE-----。

  2. 用 OpenSSL 取证书指纹:

openssl x509 -in certificate.crt -fingerprint -noout
  1. 得到 SHA1 指纹,形如:
SHA1 Fingerprint=F7:D7:B3:51:5D:D0:D3:19:DD:21:9A:43:A9:EA:72:7A:D6:06:52:87
  1. 去掉冒号得到最终 thumbprint,可作为在 IAM 中创建 OpenID Connect Provider 实体的输入:
F7D7B3515DD0D319DD219A43A9EA727AD6065287

引自:STS in Ceph


4.3 STS Lite(STSLite.rst)

4.3.1 定位与价值

STS Lite 是 STS 的扩展,构建在 STS 的某个 API 之上,目的是降低外部 IDP(如 Keystone、LDAP)的负载。

工作原理:用一组 AWS 凭证与外部 IDP 完成认证后,返回一组临时安全凭证;这些临时凭证可用于发起后续 S3 调用,由 Ceph 中的 STS engine 完成认证 —— 从而减少对 Keystone / LDAP 服务器的压力。

同时,本地用户也可以通过 STS Lite API 获得临时的、权限受限的凭证。

4.3.2 与完整 STS 的区别

维度 完整 STS STS Lite
提供的 API AssumeRole、AssumeRoleWithWebIdentity GetSessionToken
主要场景 跨账户访问;Web/移动应用通过 OIDC/OAuth2 IDP 联邦 降低 Keystone/LDAP 负载;本地用户取临时受限凭证
是否需要 Role 需要(RoleArn 必填) 不需要(无 RoleArn 参数)
临时凭证权限来源 Role 权限策略 ∩ API 传入策略 与原始 AWS 凭证的权限相同
会话时长上限配置 rgw_sts_min_session_duration(下限 900) rgw_sts_max_session_duration(默认上限 43200)
与 Keystone/LDAP 配合 不直接依赖 可配合 Keystone 或 LDAP 使用

4.3.3 GetSessionToken

返回一组与某组 AWS 凭证对应的临时凭证。在与 Keystone / LDAP 完成初次认证后,返回的临时凭证可用于发起后续 S3 调用。临时凭证拥有的权限与该 AWS 凭证相同。

参数 类型 是否必填 说明
DurationSeconds Integer Optional 凭证有效的秒数。默认值为 3600;默认最大值为 43200,可通过 rgw sts max session duration 配置
SerialNumber String Optional 与发起 GetSessionToken 调用的用户关联的 MFA 设备 Id
TokenCode String Optional 若要求 MFA,则由 MFA 设备提供的值

4.3.4 必需的 user policy

管理员需要挂载策略,以允许用户用其永久凭证调用 GetSessionToken,并且只允许用 GetSessionToken 返回的临时凭证发起后续 S3 操作。

挂载策略的用户需要 admin caps:

radosgw-admin caps add --uid="TESTER" --caps="user-policy=*"

需要挂到用户 TESTER1 上的策略:

{"Version":"2012-10-17","Statement":[{"Effect":"Deny","Action":"s3:*","Resource":["*"],"Condition":{"BoolIfExists":{"sts:authentication":"false"}}},{"Effect":"Allow","Action":"sts:GetSessionToken","Resource":"*","Condition":{"BoolIfExists":{"sts:authentication":"false"}}}]}

该策略的语义:当 sts:authentication 为 false(即不是用 STS 临时凭证认证的请求)时,Deny 所有 s3 操作,只 Allow sts:GetSessionToken。

💡 补充:这是本册源文中唯一出现的 sts:authentication condition key 用法;BoolIfExists 表示键不存在时按真处理。

4.3.5 配置

最小配置:

[client.radosgw.gateway]
rgw sts key = {sts key for encrypting the session token}
rgw s3 auth use sts = true

与 Keystone 配合的完整配置集:

[client.{your-rgw-name}]
rgw_sts_key = {sts key for encrypting/ decrypting the session token, base64 encoded}
rgw_s3_auth_use_sts = true

rgw keystone url = {keystone server url:keystone server admin port}
rgw keystone admin project = {keystone admin project name}
rgw keystone admin tenant = {keystone service tenant name}
rgw keystone admin domain = {keystone admin domain name}
rgw keystone api version = {keystone api version}
rgw keystone implicit tenants = {true for private tenant for each new user}
rgw keystone admin password = {keystone service tenant user name}
rgw keystone admin user = keystone service tenant user password}
rgw keystone accepted roles = {accepted user roles}
rgw keystone token cache size = {number of tokens to cache}
rgw s3 auth use keystone = true

与 LDAP 配合的完整配置集:

[client.{your-rgw-name}]
rgw_sts_key = {sts key for encrypting/ decrypting the session token, base64 encoded}
rgw_s3_auth_use_sts = true

rgw_s3_auth_use_ldap = true
rgw_ldap_uri = {LDAP server to use}
rgw_ldap_binddn = {Distinguished Name (DN) of the service account}
rgw_ldap_secret = {password for the service account}
rgw_ldap_searchdn = {base in the directory information tree for searching users}
rgw_ldap_dnattr = {attribute being used in the constructed search filter to match a username}
rgw_ldap_searchfilter = {search filter}

💡 补充:源文 Keystone 配置集中 rgw keystone admin password 与 rgw keystone admin user 两行的说明文字互相错位了(password 行的说明写成 "user name",user 行的说明写成 "user password}")。本册保留源文原样,并在此提示。

生成 rgw_sts_key:

ceph-authtool --gen-print-key

生成的 key 会是 base64 编码(编码后的 key 还会包含 key type 与 timestamp)。

注意:STS 与 S3 API 默认共存于同一命名空间,可通过同一 endpoint 访问。

4.3.6 STS Lite + Keystone 完整示例

步骤 1:生成 EC2 凭证。

openstack ec2 credentials create
+------------+--------------------------------------------------------+
| Field      | Value                                                  |
+------------+--------------------------------------------------------+
| access     | b924dfc87d454d15896691182fdeb0ef                       |
| links      | {u'self': u'http://192.168.0.15/identity/v3/users/     |
|            | 40a7140e424f493d8165abc652dc731c/credentials/          |
|            | OS-EC2/b924dfc87d454d15896691182fdeb0ef'}              |
| project_id | c703801dccaf4a0aaa39bec8c481e25a                       |
| secret     | 6a2142613c504c42a94ba2b82147dc28                       |
| trust_id   | None                                                   |
| user_id    | 40a7140e424f493d8165abc652dc731c                       |
+------------+--------------------------------------------------------+

步骤 2:用步骤 1 的凭证调用 GetSessionToken 换取临时凭证。

import boto3

access_key = <ec2 access key>
secret_key = <ec2 secret key>

client = boto3.client('sts',
aws_access_key_id=access_key,
aws_secret_access_key=secret_key,
endpoint_url=<STS URL>,
region_name='',
)

response = client.get_session_token(
    DurationSeconds=43200
)

步骤 3:用临时凭证发起 S3 调用。

s3client = boto3.client('s3',
  aws_access_key_id = response['Credentials']['AccessKeyId'],
  aws_secret_access_key = response['Credentials']['SecretAccessKey'],
  aws_session_token = response['Credentials']['SessionToken'],
  endpoint_url=<S3 URL>,
  region_name='')

bucket = s3client.create_bucket(Bucket='my-new-shiny-bucket')
response = s3client.list_buckets()
for bucket in response["Buckets"]:
    print("{name}\t{created}".format(
                name = bucket['Name'],
                created = bucket['CreationDate'],
    ))

LDAP 下使用 GetSessionToken 的步骤类似。

4.3.7 已知限制与规避方法

Keystone 目前只支持 S3 请求,因此为了成功认证 STS 请求,需要给 boto 打补丁 —— 修改 botocore/auth.py,加入第 13–16 行(即下面代码块中 if service_name == 'sts': self._service_name = 's3' 这段):

class SigV4Auth(BaseSigner):
  """
  Sign a request with Signature V4.
  """
  REQUIRES_REGION = True

  def __init__(self, credentials, service_name, region_name):
      self.credentials = credentials
      # We initialize these value here so the unit tests can have
      # valid values.  But these will be overridden in ``add_auth``
      # later for real requests.
      self._region_name = region_name
      if service_name == 'sts':
          self._service_name = 's3'
      else:
          self._service_name = service_name

引自:STS Lite


4.4 会话标签(session-tags.rst)

4.4.1 什么是会话标签

Session tags(会话标签)是在联邦用户时传入的键值对(目前仅支持作为传给 AssumeRoleWithWebIdentity 的 web token 的一部分传入)。

  • 会话标签会作为 aws:PrincipalTag 随会话凭证(临时凭证)一起返回(由 STS 返回);
  • 这些 Principal Tags 由两部分组成:① web token 中传入的 session tags;② 被 assume 的 role 上附着的 tags;
  • 标签必须始终指定在 https://aws.amazon.com/tags 这个命名空间下。

IDP 在 web token 中传入 session tags 的示例:

{
    "jti": "947960a3-7e91-4027-99f6-da719b0d4059",
    "exp": 1627438044,
    "nbf": 0,
    "iat": 1627402044,
    "iss": "http://localhost:8080/auth/realms/quickstart",
    "aud": "app-profile-jsp",
    "sub": "test",
    "typ": "ID",
    "azp": "app-profile-jsp",
    "auth_time": 0,
    "session_state": "3a46e3e7-d198-4a64-8b51-69682bcfc670",
    "preferred_username": "test",
    "email_verified": false,
    "acr": "1",
    "https://aws.amazon.com/tags": [
        {
            "principal_tags": {
                "Department": [
                    "Engineering",
                    "Marketing"
                ]
            }
        }
    ],
    "client_id": "app-profile-jsp",
    "username": "test",
    "active": true
}

关键规则:如果联邦用户传入的 web token 中含有 session tags,那么 trust policy 必须具备 sts:TagSession 权限,否则 AssumeRoleWithWebIdentity 会失败。

{
    "Version":"2012-10-17",
    "Statement":[
    {
        "Effect":"Allow",
        "Action":["sts:AssumeRoleWithWebIdentity","sts:TagSession"],
        "Principal":{"Federated":["arn:aws:iam:::oidc-provider/localhost:8080/auth/realms/quickstart"]},
        "Condition":{"StringEquals":{"localhost:8080/auth/realms/quickstart:sub":"test"}}
    }]
}

4.4.2 标签键(Tag Keys)逐项

可在 role 的 trust policy 或 role 的 permission policy 中使用的 tag key 共 5 个:

# Tag Key 用途 可用位置
1 aws:RequestTag 把请求中传入的键值对与 role trust policy 中的键值对比较。在 AssumeRoleWithWebIdentity 场景下,IDP 在 web token 中传入的 session tags 可作为 aws:RequestTag 用在 role 的 trust policy 中,据此决定联邦用户是否被允许 assume 该 role role 的 trust policy
2 aws:PrincipalTag 把挂在 principal 上的键值对与 policy 中的键值对比较。在 AssumeRoleWithWebIdentity 场景下,IDP 在 web token 中传入的 session tags 在用户认证通过后会出现在临时凭证的 Principal tags 中,可作为 aws:PrincipalTag 用在 role 的 permission policy 中 role 的 permission policy
3 iam:ResourceTag 把挂在资源上的键值对与 policy 中的键值对比较。在 AssumeRoleWithWebIdentity 场景下,挂在 role 上的 tags 可与 trust policy 中的比较,以决定是否允许用户 assume 该 role。RGW 已支持针对 role 的 tagging / list tags / untag 的 REST API role 的 trust policy
4 aws:TagKeys 把请求中的 tags 与 policy 中的 tags 比较。在 AssumeRoleWithWebIdentity 场景下可用于在允许用户 assume role 之前检查 role trust policy 中的 tag keys。也可用于 role 的 permission policy role 的 trust policy 与 permission policy
5 s3:ResourceTag 把 s3 资源(bucket 或 object)上的 tags 与 role 的 permission policy 中的 tags 比较。AWS 中不允许这样用 role 的 permission policy

1. aws:RequestTag 示例(trust policy):

{
    "Version":"2012-10-17",
    "Statement":[
    {
        "Effect":"Allow",
        "Action":["sts:AssumeRoleWithWebIdentity","sts:TagSession"],
        "Principal":{"Federated":["arn:aws:iam:::oidc-provider/localhost:8080/auth/realms/quickstart"]},
        "Condition":{"StringEquals":{"aws:RequestTag/Department":"Engineering"}}
    }]
}

2. aws:PrincipalTag 示例(permission policy):

{
    "Version":"2012-10-17",
    "Statement":[
    {
        "Effect":"Allow",
        "Action":["s3:*"],
        "Resource":["arn:aws:s3::t1tenant:my-test-bucket","arn:aws:s3::t1tenant:my-test-bucket/*"],
        "Condition":{"StringEquals":{"aws:PrincipalTag/Department":"Engineering"}}
    }]
}

3. iam:ResourceTag 示例(trust policy):

{
    "Version":"2012-10-17",
    "Statement":[
    {
        "Effect":"Allow",
        "Action":["sts:AssumeRoleWithWebIdentity","sts:TagSession"],
        "Principal":{"Federated":["arn:aws:iam:::oidc-provider/localhost:8080/auth/realms/quickstart"]},
        "Condition":{"StringEquals":{"iam:ResourceTag/Department":"Engineering"}}
    }]
}

要使上例生效,需要给 role 附上 Department=Engineering 标签。

4. aws:TagKeys 示例(trust policy):

{
    "Version":"2012-10-17",
    "Statement":[
    {
        "Effect":"Allow",
        "Action":["sts:AssumeRoleWithWebIdentity","sts:TagSession"],
        "Principal":{"Federated":["arn:aws:iam:::oidc-provider/localhost:8080/auth/realms/quickstart"]},
        "Condition":{"ForAllValues:StringEquals":{"aws:TagKeys":["Department"]}}
    }]
}

ForAllValues:StringEquals 检验请求中的每个 tag key 都是 policy 中 tag keys 的子集,因此上述 condition 限制了请求中可以传入的 tag keys。

5. s3:ResourceTag 示例(permission policy):

{
    "Version":"2012-10-17",
    "Statement":[
    {
        "Effect":"Allow",
        "Action":["s3:PutBucketTagging"],
        "Resource":["arn:aws:s3::t1tenant:my-test-bucket\","arn:aws:s3::t1tenant:my-test-bucket/*"]
    },
    {
        "Effect":"Allow",
        "Action":["s3:*"],
        "Resource":["*"],
        "Condition":{"StringEquals":{"s3:ResourceTag/Department":\"Engineering"}}
    }
}

要使上例生效,需要给希望应用此策略的 bucket(以及 object)附上 Department=Engineering 标签。 💡 补充:源文此 JSON 片段中的引号转义明显有误(arn:aws:s3::t1tenant:my-test-bucket\" 与 \"Engineering"),本册按原样保留以忠实记录,实际书写时应为合法 JSON 字符串。

4.4.3 更多带标签的 policy 示例

示例 1:用请求中传入的标签与 role 上附着的标签匹配,来决定是否允许 assume role。aws:RequestTag 是 JWT(access token)中的传入标签,iam:ResourceTag 是被 assume 的 role 上附着的标签。

{
    "Version":"2012-10-17",
    "Statement":[
    {
        "Effect":"Allow",
        "Action":["sts:AssumeRoleWithWebIdentity","sts:TagSession"],
        "Principal":{"Federated":["arn:aws:iam:::oidc-provider/localhost:8080/auth/realms/quickstart"]},
        "Condition":{"StringEquals":{"aws:RequestTag/Department":"${iam:ResourceTag/Department}"}}
    }]
}

示例 2:用 principal tags 与 s3 资源标签匹配来评估 role 的 permission policy。aws:PrincipalTag 是随临时凭证传入的标签,s3:ResourceTag 是挂在 s3 资源(object/bucket)上的标签。

{
    "Version":"2012-10-17",
    "Statement":[
    {
        "Effect":"Allow",
        "Action":["s3:PutBucketTagging"],
        "Resource":["arn:aws:s3::t1tenant:my-test-bucket\","arn:aws:s3::t1tenant:my-test-bucket/*"]
    },
    {
        "Effect":"Allow",
        "Action":["s3:*"],
        "Resource":["*"],
        "Condition":{"StringEquals":{"s3:ResourceTag/Department":"${aws:PrincipalTag/Department}"}}
    }
}

💡 补充:这两个示例展示了策略变量插值(${iam:ResourceTag/Department}、${aws:PrincipalTag/Department})的用法 —— 这与 bucketpolicy.rst 中"bucket policies 还不支持字符串插值(string interpolation)"的说明形成对比:插值能力出现在 role 的 trust / permission policy 中,而桶策略不支持。

4.4.4 会话标签的属性(限制)

# 属性
1 Session Tags 可以是多值的(多值 session tags 在 AWS 中不支持)
2 IDP 最多允许传入 50 个 session tags
3 key 的最大长度为 128 字符
4 value 的最大长度为 256 字符
5 tag 与 value 不能以 aws: 开头

4.4.5 s3 Resource Tags

如前所述,s3:ResourceTag 键可用于在 RGW 中对 s3 操作授权(AWS 不允许)。s3:ResourceTag 指挂在 object 或 bucket 上的 tags,tags 可通过相应的 REST API 挂到 object 或 bucket 上。

下表说明授权某个操作时使用的是哪种 s3 资源标签类型(bucket tags 还是 object tags):

Operation Tag type
GetObject Object tags
GetObjectTags Object tags
DeleteObjectTags Object tags
DeleteObject Object tags
PutACLs Object tags
InitMultipart Object tags
AbortMultipart Object tags
ListMultipart Object tags
GetAttrs Object tags
PutObjectRetention Object tags
GetObjectRetention Object tags
PutObjectLegalHold Object tags
GetObjectLegalHold Object tags
PutObjectTags Bucket tags
GetBucketTags Bucket tags
PutBucketTags Bucket tags
DeleteBucketTags Bucket tags
GetBucketReplication Bucket tags
DeleteBucketReplication Bucket tags
GetBucketVersioning Bucket tags
SetBucketVersioning Bucket tags
GetBucketWebsite Bucket tags
SetBucketWebsite Bucket tags
DeleteBucketWebsite Bucket tags
StatBucket Bucket tags
ListBucket Bucket tags
GetBucketLogging Bucket tags
GetBucketLocation Bucket tags
DeleteBucket Bucket tags
GetLC Bucket tags
PutLC Bucket tags
DeleteLC Bucket tags
GetCORS Bucket tags
PutCORS Bucket tags
GetRequestPayment Bucket tags
SetRequestPayment Bucket tags
PutBucketPolicy Bucket tags
GetBucketPolicy Bucket tags
DeleteBucketPolicy Bucket tags
PutBucketObjectLock Bucket tags
GetBucketObjectLock Bucket tags
GetBucketPolicyStatus Bucket tags
PutBucketPublicAccessBlock Bucket tags
GetBucketPublicAccessBlock Bucket tags
DeleteBucketPublicAccessBlock Bucket tags
GetACLs bucket ACLs 用 Bucket tags;object ACLs 用 Object tags
PutACLs bucket ACLs 用 Bucket tags;object ACLs 用 Object tags
PutObject 源 object 的 Object tags;目标 bucket 的 Bucket tags
CopyObject 源 object 的 Object tags;目标 bucket 的 Bucket tags

💡 补充:源文原始表格中 PutACLs 同时出现在 Object tags 组与 GetACLs/PutACLs 组,本册保留了两处出现。

4.4.6 完整示例代码(role / bucket / object 打标签 + 策略中使用 tag keys)

假设 IDP 在 JWT(access token)中传入标签 Department=Engineering:

# -*- coding: utf-8 -*-

import boto3
import json
from nose.tools import eq_ as eq

access_key = 'TESTER'
secret_key = 'test123'
endpoint = 'http://s3.us-east.localhost:8000'

s3client = boto3.client('s3',
aws_access_key_id = access_key,
aws_secret_access_key = secret_key,
endpoint_url = endpoint,
region_name='',)

s3res = boto3.resource('s3',
        aws_access_key_id = access_key,
        aws_secret_access_key = secret_key,
        endpoint_url = endpoint,
        region_name='',)

iam_client = boto3.client('iam',
aws_access_key_id=access_key,
aws_secret_access_key=secret_key,
endpoint_url=endpoint,
region_name=''
)

bucket_name = 'test-bucket'
s3bucket = s3client.create_bucket(Bucket=bucket_name)

bucket_tagging = s3res.BucketTagging(bucket_name)
Set_Tag = bucket_tagging.put(Tagging={'TagSet':[{'Key':'Department', 'Value': 'Engineering'}]})
try:
    response = iam_client.create_open_id_connect_provider(
        Url='http://localhost:8080/auth/realms/quickstart',
        ClientIDList=[
            'app-profile-jsp',
            'app-jee-jsp'
        ],
        ThumbprintList=[
            'F7D7B3515DD0D319DD219A43A9EA727AD6065287'
    ]
    )
except ClientError as e:
    print ("Provider already exists")

policy_document = "{\"Version\":\"2012-10-17\",\"Statement\":[{\"Effect\":\"Allow\",\"Principal\":{\"Federated\":[\"arn:aws:iam:::oidc-provider/localhost:8080/auth/realms/quickstart\"]},\"Action\":[\"sts:AssumeRoleWithWebIdentity\",\"sts:TagSession\"],\"Condition\":{\"StringEquals\":{\"aws:RequestTag/Department\":\"${iam:ResourceTag/Department}\"}}}]}"
role_response = ""

print ("\n Getting Role \n")

try:
    role_response = iam_client.get_role(
        RoleName='S3Access'
    )
    print (role_response)
except ClientError as e:
    if e.response['Code'] == 'NoSuchEntity':
        print ("\n Creating Role \n")
        tags_list = [
            {'Key':'Department','Value':'Engineering'},
        ]
        role_response = iam_client.create_role(
            AssumeRolePolicyDocument=policy_document,
            Path='/',
            RoleName='S3Access',
            Tags=tags_list,
        )
        print (role_response)
    else:
        print("Unexpected error: %s" % e)

role_policy = "{\"Version\":\"2012-10-17\",\"Statement\":{\"Effect\":\"Allow\",\"Action\":\"s3:*\",\"Resource\":\"arn:aws:s3:::*\",\"Condition\":{\"StringEquals\":{\"s3:ResourceTag/Department\":[\"${aws:PrincipalTag/Department}\"]}}}}"

response = iam_client.put_role_policy(
            RoleName='S3Access',
            PolicyName='Policy1',
            PolicyDocument=role_policy
        )

sts_client = boto3.client('sts',
aws_access_key_id='abc',
aws_secret_access_key='def',
endpoint_url = endpoint,
region_name = '',
)


print ("\n Assuming Role with Web Identity\n")
response = sts_client.assume_role_with_web_identity(
RoleArn=role_response['Role']['Arn'],
RoleSessionName='Bob',
DurationSeconds=900,
WebIdentityToken='<web-token>')

s3client2 = boto3.client('s3',
aws_access_key_id = response['Credentials']['AccessKeyId'],
aws_secret_access_key = response['Credentials']['SecretAccessKey'],
aws_session_token = response['Credentials']['SessionToken'],
endpoint_url='http://s3.us-east.localhost:8000',
region_name='',)

bucket_body = 'this is a test file'
tags = 'Department=Engineering'
key = "test-1.txt"
s3_put_obj = s3client2.put_object(Body=bucket_body, Bucket=bucket_name, Key=key, Tagging=tags)
eq(s3_put_obj['ResponseMetadata']['HTTPStatusCode'],200)

s3_get_obj = s3client2.get_object(Bucket=bucket_name, Key=key)
eq(s3_get_obj['ResponseMetadata']['HTTPStatusCode'],200)

引自:Session tags for Attribute Based Access Control in STS


五、桶策略(bucketpolicy.rst)

Bucket policies 在 Ceph 的 Luminous 版本中引入。

Ceph Object Gateway 支持 Amazon S3 policy 语言的一个子集,应用于桶。

5.1 创建与删除

桶策略通过标准 S3 操作管理,而不是 radosgw-admin。用 s3cmd 设置/删除策略:

$ cat > examplepol
{
  "Version": "2012-10-17",
  "Statement": [{
    "Effect": "Allow",
    "Principal": {"AWS": ["arn:aws:iam::usfolks:user/fred:subuser"]},
    "Action": "s3:PutObjectAcl",
    "Resource": [
      "arn:aws:s3:::happybucket/*"
    ]
  }]
}

$ s3cmd setpolicy examplepol s3://happybucket
$ s3cmd delpolicy s3://happybucket

5.2 支持的 Action(完整列表)

当前只支持以下 action:

# Action # Action
1 s3:AbortMultipartUpload 26 s3:GetObjectVersionAcl
2 s3:CreateBucket 27 s3:GetObjectVersion
3 s3:DeleteBucketPolicy 28 s3:GetObjectVersionTorrent
4 s3:DeleteBucket 29 s3:GetReplicationConfiguration
5 s3:DeleteBucketWebsite 30 s3:IPAddress
6 s3:DeleteObject 31 s3:NotIpAddress
7 s3:DeleteObjectVersion 32 s3:ListAllMyBuckets
8 s3:DeleteReplicationConfiguration 33 s3:ListBucketMultipartUploads
9 s3:GetAccelerateConfiguration 34 s3:ListBucket
10 s3:GetBucketAcl 35 s3:ListBucketVersions
11 s3:GetBucketCORS 36 s3:ListMultipartUploadParts
12 s3:GetBucketLocation 37 s3:PutAccelerateConfiguration
13 s3:GetBucketLogging 38 s3:PutBucketAcl
14 s3:GetBucketNotification 39 s3:PutBucketCORS
15 s3:GetBucketPolicy 40 s3:PutBucketLogging
16 s3:GetBucketRequestPayment 41 s3:PutBucketNotification
17 s3:GetBucketTagging 42 s3:PutBucketPolicy
18 s3:GetBucketVersioning 43 s3:PutBucketRequestPayment
19 s3:GetBucketWebsite 44 s3:PutBucketTagging
20 s3:GetLifecycleConfiguration 45 s3:PutBucketVersioning
21 s3:GetObjectAcl 46 s3:PutBucketWebsite
22 s3:GetObject 47 s3:PutLifecycleConfiguration
23 s3:GetObjectTorrent 48 s3:PutObjectAcl
24 s3:GetObjectVersionAcl 49 s3:PutObject
25 s3:GetObjectVersion 50 s3:PutObjectVersionAcl
51 s3:PutReplicationConfiguration
52 s3:RestoreObject

5.3 限制

限制 说明
不支持在 user / group / role 上设置策略 "We do not yet support setting policies on users, groups, or roles"
Account ID 的替代 用 RGW 的 tenant 标识符代替 Amazon 的 12 位 account ID。未来可能允许给 tenant 分配 account ID,但目前若要在 AWS S3 与 RGW S3 之间使用策略,必须在创建用户时使用 Amazon account ID 作为 tenant ID
命名空间差异 AWS 中所有 tenant 共享单一命名空间;RGW 给每个 tenant 各自的桶命名空间。未来可能提供 AWS 式 'flat' 桶命名空间选项。当前访问属于另一个 tenant 的桶,要在 S3 请求中用 "tenant:bucket" 寻址
授权粒度 AWS 中桶策略可把访问权限授予另一个账户,该账户拥有者再用用户权限授予具体用户。由于 RGW 还不支持 user / role / group 权限,账户拥有者目前必须直接把权限授予具体用户;并且把整个账户授予桶的访问权限,等于把权限授予了该账户下的所有用户
字符串插值 桶策略尚不支持字符串插值(string interpolation)

5.4 全局 Condition Keys

对所有请求,支持的 condition keys 为:

  • aws:CurrentTime
  • aws:EpochTime
  • aws:PrincipalType
  • aws:Referer
  • aws:SecureTransport
  • aws:SourceIp
  • aws:UserAgent
  • aws:username

此外,对 bucket 与 object 请求还支持若干 s3 condition keys。

对下列 bucket 相关操作的支持是在 Ceph 的 Mimic 版本中加入的。

5.5 Bucket 相关操作的 Condition Keys

Permission Condition Keys Comments
s3:createBucket s3:x-amz-acl、s3:x-amz-grant-<perm>(其中 perm 取 read / write / read-acp / write-acp / full-control 之一) 文档未明确
s3:ListBucket 与 s3:ListBucketVersions s3:prefix、s3:delimiter、s3:max-keys 文档未明确
s3:PutBucketAcl s3:x-amz-acl、s3:x-amz-grant-<perm> 文档未明确

5.6 Object 相关操作的 Condition Keys

Permission Condition Keys Comments
s3:PutObject s3:x-amz-acl 与 s3:x-amz-grant-<perm> 文档未明确
s3:PutObject s3:x-amz-copy-source 文档未明确
s3:PutObject s3:x-amz-server-side-encryption 文档未明确
s3:PutObject s3:x-amz-server-side-encryption-aws-kms-key-id 文档未明确
s3:PutObject s3:x-amz-server-side-encryption-customer-algorithm 文档未明确
s3:PutObject s3:x-amz-metadata-directive PUT 与 COPY 中用于 overwrite/preserve COPY 请求里的 metadata
s3:PutObject s3:RequestObjectTag/<tag-key> 文档未明确
s3:PutObjectAcl、s3:PutObjectVersionAcl s3:x-amz-acl 与 s3-amz-grant-<perm>;s3:ExistingObjectTag/<tag-key> 文档未明确
s3:PutObjectTagging、s3:PutObjectVersionTagging s3:RequestObjectTag/<tag-key>;s3:ExistingObjectTag/<tag-key> 文档未明确
s3:GetObject、s3:GetObjectVersion s3:ExistingObjectTag/<tag-key> 文档未明确
s3:GetObjectAcl、s3:GetObjectVersionAcl s3:ExistingObjectTag/<tag-key> 文档未明确
s3:GetObjectTagging、s3:GetObjectVersionTagging s3:ExistingObjectTag/<tag-key> 文档未明确
s3:DeleteObjectTagging、s3:DeleteObjectVersionTagging s3:ExistingObjectTag/<tag-key> 文档未明确

官方补充:随着近期重写的 Authentication/Authorization 子系统完成集成,未来可能支持更多。

5.7 Swift

  • Swift 下没有办法设置桶策略;
  • 但已设置的桶策略同时约束 Swift 与 S3 操作;
  • Swift 凭证与策略中指定的 Principal 的匹配方式,取决于所使用的具体后端。

引自:Bucket Policies


六、外部身份源集成

6.1 OpenStack Keystone(keystone.rst)

可以把 Ceph Object Gateway 与 OpenStack 的身份服务 Keystone 集成。这让网关接受 Keystone 作为用户权威(users authority):

  • 被 Keystone 授权访问网关的用户,会在 Ceph Object Gateway 上被自动创建(如果此前不存在);
  • 被 Keystone 判定有效的 token,网关也认为有效。

6.1.1 配置项

[client.radosgw.gateway]
rgw keystone api version = {keystone api version}
rgw keystone url = {keystone server url:keystone server admin port}
rgw keystone admin token = {keystone admin token}
rgw keystone admin token path = {path to keystone admin token} #preferred
rgw keystone accepted roles = {accepted user roles}
rgw keystone token cache size = {number of tokens to cache}
rgw keystone implicit tenants = {true for private tenant for each new user}

也可以为 Keystone 配置 service tenant / user / password(对应 OpenStack Identity API v2.0),这与 OpenStack 服务通常的配置方式类似,可以避免在配置文件中设置共享密钥 rgw keystone admin token —— 官方建议在生产环境中禁用 rgw keystone admin token。service tenant 的凭证应具备 admin 权限。

rgw keystone admin user = {keystone service tenant user name}
rgw keystone admin password = {keystone service tenant user password}
rgw keystone admin password = {keystone service tenant user password path} # preferred
rgw keystone admin tenant = {keystone service tenant name}

6.1.2 用户/租户/角色映射

  • 一个 Ceph Object Gateway 用户被映射到 Keystone 的一个 tenant;
  • 一个 Keystone 用户可能在不同 tenant 上被分配不同角色;
  • 网关拿到 ticket 后,查看其中的 tenant 与分配给该 ticket 的 用户角色,然后依据 rgw keystone accepted roles 配置决定接受或拒绝请求。

API v3 时应把 rgw keystone admin tenant 替换为:

rgw keystone admin domain = {keystone admin domain name}
rgw keystone admin project = {keystone admin project name}

兼容旧版本:rgw keystone implicit tenants 也可以设为 s3 或 swift,效果是切分身份空间——被指定的协议只使用 implicit tenants,另一个协议永不使用 implicit tenants。(某些旧版 ceph 只支持 swift 使用 implicit tenants。)

6.1.3 Keystone 侧配置(Ocata 及以后)

Keystone 本身需要配置为把 Ceph Object Gateway 作为 object-storage endpoint:

openstack service create --name=swift \
                         --description="Swift Service" \
                         object-store
+-------------+----------------------------------+
| Field       | Value                            |
+-------------+----------------------------------+
| description | Swift Service                    |
| enabled     | True                             |
| id          | 37c4c0e79571404cb4644201a4a6e5ee |
| name        | swift                            |
| type        | object-store                     |
+-------------+----------------------------------+
openstack endpoint create --region RegionOne \
     --publicurl   "http://radosgw.example.com:8080/swift/v1" \
     --adminurl    "http://radosgw.example.com:8080/swift/v1" \
     --internalurl "http://radosgw.example.com:8080/swift/v1" \
     swift
+--------------+------------------------------------------+
| Field        | Value                                    |
+--------------+------------------------------------------+
| adminurl     | http://radosgw.example.com:8080/swift/v1 |
| id           | e4249d2b60e44743a67b5e5b38c18dd3         |
| internalurl  | http://radosgw.example.com:8080/swift/v1 |
| publicurl    | http://radosgw.example.com:8080/swift/v1 |
| region       | RegionOne                                |
| service_id   | 37c4c0e79571404cb4644201a4a6e5ee         |
| service_name | swift                                    |
| service_type | object-store                             |
+--------------+------------------------------------------+

查看:

$ openstack endpoint show object-store

⚠️ 注意:如果你的 radosgw ceph.conf 设置了 rgw swift account in url = true,那么 object-store endpoint URL 必须包含后缀 /v1/AUTH_%(tenant_id)s(而不仅仅是 /v1)。

Keystone URL 是 Keystone 的 admin RESTful API URL;admin token 是 Keystone 内部为 admin 请求配置的 token。

自签名证书:OpenStack Keystone 可能以自签名 SSL 证书终止。此时 radosgw 要么在运行的节点上安装 Keystone 的 ssl 证书,要么把配置项 rgw keystone verify ssl 设为 false,使 radosgw 完全不校验 ssl 证书(类似 OpenStack 客户端的 --insecure 开关)。

6.1.4 跨 Project(Tenant)访问

要让一个 project(旧称 tenant)访问属于另一个 project 的桶,需要启用:

rgw swift account in url = true

Keystone 的 object-store endpoint 也必须相应配置为包含 AUTH_%(project_id)s 后缀:

openstack endpoint create --region RegionOne \
    --publicurl   "http://radosgw.example.com:8080/swift/v1/AUTH_$(project_id)s" \
    --adminurl    "http://radosgw.example.com:8080/swift/v1/AUTH_$(project_id)s" \
    --internalurl "http://radosgw.example.com:8080/swift/v1/AUTH_$(project_id)s" \
    swift

6.1.5 Keystone 与 S3 API 集成

即使使用 S3 API(带 AWS 风格的 access key 和 secret key),只要设置了 rgw s3 auth use keystone 选项,也可以用 Keystone 做认证。

6.1.6 Service Token 支持

Service tokens 可用于支持 RadosGW Keystone 集成在请求中同时携带有效 service token 时允许过期的 token:

  • 用 rgw keystone service token enabled 启用;
  • 用 rgw keystone service token accepted roles 指定哪些角色被视为 service roles;
  • 用 rgw keystone expired token cache expiration 调整"被 service token 放行的过期 token"的缓存过期时间 —— 该值必须低于 Keystone 配置中的 [token]/allow_expired_window。

启用后:若 X-Auth-Token 头中的 token 已过期,但 X-Service-Token 头中含有具备 accepted roles 的有效 token,则该过期 token 会被放行。这可以让使用用户 token 的长运行进程在 token 过期后继续工作。

6.1.7 Keystone 配置项逐项表格

配置项 类型 默认值(rgw.yaml.in @ v20.2.4) 说明
rgw_keystone_api_version — 文档未明确(该配置项未在 rgw.yaml.in / global.yaml.in 中定义) Keystone API 版本
rgw_keystone_url str 文档未明确(无 default:) Keystone server 的 URL
rgw_keystone_admin_token str 文档未明确(无 default:) 用于 Keystone 请求的 admin token(共享密钥)。已 DEPRECATED。admin token 认证优先于 admin 凭证认证
rgw_keystone_admin_token_path str 文档未明确(无 default:) 包含 admin token 的文件路径(推荐,避免暴露 token)
rgw_keystone_admin_user str 文档未明确(无 default:) Keystone admin 用户(OpenStack Identity API v2 下的 Service User)
rgw_keystone_admin_password str 文档未明确(无 default:) OpenStack admin 用户密码(v2)。已 DEPRECATED,推荐改用 rgw_keystone_admin_password_path
rgw_keystone_admin_password_path str 文档未明确(无 default:) 包含 Keystone admin 密码的文件路径
rgw_keystone_admin_tenant str 文档未明确(无 default:) Keystone admin 用户的 tenant(v2 下 Service Tenant)
rgw_keystone_admin_project str 文档未明确(无 default:) Keystone admin 用户的 project(v3)。若未指定,则使用 rgw keystone admin tenant 的值
rgw_keystone_admin_domain str 文档未明确(无 default:) Keystone admin 用户的 domain(v3)
rgw_keystone_accepted_roles str Member, admin 做 Keystone 认证时,只有具备这些角色之一的用户才会被服务
rgw_keystone_accepted_admin_roles str 文档未明确(无 default:) 允许用户获得 admin 特权的角色列表
rgw_keystone_accepted_reader_roles str 文档未明确(无 default:) 只能用于读操作的角色列表
rgw_keystone_token_cache_size int 10000 每个 Keystone token 缓存的最大条目数。未被缓存的 token 在认证时需要 RGW 访问 Keystone server
rgw_keystone_verify_ssl bool true 向 keystone 发起 token 请求时是否校验 SSL 证书
rgw_keystone_implicit_tenants str false 通过 Keystone 认证时,是否隐式地在同名 tenant 中创建新用户。可限定为仅 S3 或仅 SWIFT。枚举值:false、true、swift、s3、both、0、1、none
rgw_keystone_service_token_enabled bool false 允许请求携带 X-Service-Token,使 X-Auth-Token 中的过期 token 可用
rgw_keystone_service_token_accepted_roles str admin 创建 service token 的用户必须具备这些角色之一,才被视为有效 service user
rgw_keystone_expired_token_cache_expiration int 3600 被 service token 放行的过期 token 的新过期时间 = 当前时间 + 该秒数。必须低于 Keystone 的 [token]/allow_expired_window
rgw_s3_auth_use_keystone bool false S3 认证是否使用 Keystone
rgw_s3_auth_use_rados bool true S3 认证是否使用存储在 RADOS 中的凭证
rgw_swift_account_in_url bool false Swift account 是否编码在 URI 路径中(AUTH_<account>)。跨 project 访问桶时必须设为 true

引自:Integrating with OpenStack Keystone


6.2 LDAP 认证(ldap-auth.rst)

versionadded:: Jewel

可以把 Ceph Object Gateway 的认证委托给 LDAP 服务器。

6.2.1 工作原理

  1. Ceph Object Gateway 从 token 中提取用户的 LDAP 凭证;
  2. 用用户名构造一个 search filter;
  3. Object Gateway 使用配置好的 service account 在目录中搜索匹配条目;
  4. 如果找到条目,Object Gateway 尝试用 token 中的密码 bind 到找到的 distinguished name;
  5. 如果凭证有效,bind 成功,Object Gateway 授予访问,并以该用户名创建 radosgw-user。

可以通过把搜索的 base 限制在特定 organizational unit,或指定自定义 search filter(例如要求特定的组成员身份、自定义 object class 或属性)来限制允许的用户范围。

⚠️ LDAP 凭证必须在服务器上可用才能执行 LDAP 认证。请确保把 rgw 的日志级别设得足够低,以隐藏 base64 编码的凭证 / access token。

6.2.2 前置要求

要求 说明
LDAP 或 Active Directory 一个运行中的、Ceph Object Gateway 可访问的 LDAP 实例
Service account 供 Ceph Object Gateway 使用的、具备搜索权限的 LDAP 凭证
User account LDAP 目录中至少有一个用户账号
不要让 LDAP 用户与本地用户重名 不应对本地用户和通过 LDAP 认证的用户使用相同的用户名 —— Ceph Object Gateway 无法区分它们,会当作同一个用户处理

6.2.3 健全性检查

用 ldapsearch 工具验证 service account 或 LDAP 连接:

# ldapsearch -x -D "uid=ceph,ou=system,dc=example,dc=com" -W \
-H ldaps://example.com -b "ou=users,dc=example,dc=com" 'uid=*' dn

⚠️ 请确保使用与 Ceph 配置文件中相同的 LDAP 参数,以排除可能的问题。

6.2.4 配置项

配置项 说明 默认值(rgw.yaml.in @ v20.2.4)
rgw_s3_auth_use_ldap 设为 true 启用 S3 的 LDAP 认证 false(bool)
rgw_s3_auth_use_ldap 设为 true 启用 S3 的 LDAP 认证 false(bool)
rgw_ldap_uri 指定使用的 LDAP 服务器。务必使用 ldaps://<fqdn>:<port> 形式,以免明文凭证在网络上传输 文档未明确(default: 字段为空)
rgw_ldap_binddn Ceph Object Gateway 使用的 service account 的 Distinguished Name (DN) uid=admin,cn=users,dc=example,dc=com
rgw_ldap_secret 包含 rgw_ldap_binddn 凭证的文件路径 /etc/openldap/secret
rgw_ldap_searchdn 在 directory information tree 中搜索用户的 base。可以是你的 users organizational unit 或更具体的 OU cn=users,cn=accounts,dc=example,dc=com
rgw_ldap_dnattr 构造 search filter 时用于匹配用户名的属性。取决于你的 DIT,通常是 uid 或 cn。生成的 filter 串形如 cn=some_username uid
rgw_ldap_searchfilter 若未指定,Object Gateway 会用 rgw_ldap_dnattr 自动构造 search filter。用该参数可以非常灵活地收窄允许的用户范围 文档未明确(无 default:)

6.2.5 用自定义 search filter 限制用户访问

rgw_search_filter 参数有两种用法:

① 指定 partial filter(部分过滤器)以进一步限制构造出的 search filter

"objectclass=inetorgperson"

Object Gateway 仍会按常规用 token 中的用户名和 rgw_ldap_dnattr 的值生成 search filter,然后与 rgw_search_filter 中的 partial filter 组合。取决于用户名与设置,最终 search filter 可能是:

"(&(uid=hari)(objectclass=inetorgperson))"

因此用户 hari 只有在:① 在 LDAP 目录中被找到;② object class 为 inetorgperson;③ 提供了有效密码 时,才会被授予访问。

② 指定 complete filter(完整过滤器)

完整过滤器必须包含 @USERNAME@ token,认证时会替换为用户名。此时不再使用 rgw_ldap_dnattr 参数。例如,把有效用户限制在特定组:

"(&(uid=@USERNAME@)(memberOf=cn=ceph-users,ou=groups,dc=mycompany,dc=com))"

⚠️ 在 LDAP 搜索中使用 memberOf 属性需要你具体的 LDAP 服务器实现提供服务端支持。

6.2.6 生成 LDAP 认证的 access token

radosgw-token 工具基于 LDAP 用户名和密码生成 access token,会输出一个 base64 编码的字符串,即 access token。

# export RGW_ACCESS_KEY_ID="<username>"
# export RGW_SECRET_ACCESS_KEY="<password>"
# radosgw-token --encode

也可以手工生成 —— 把下面这段 JSON 做 base64 编码即可(当没有安装 radosgw-token 工具时):

{
  "RGW_TOKEN": {
    "version": 1,
    "type": "ldap",
    "id": "your_username",
    "key": "your_clear_text_password_here"
  }
}

⚠️ 官方以 important 强调:access token 是一个 base64 编码的 JSON 结构,其中包含了明文形式的 LDAP 凭证。

6.2.7 使用 access token

用你惯用的 S3 客户端,把该 token 作为 access key 指定在客户端或环境变量中:

# export AWS_ACCESS_KEY_ID=<base64-encoded token generated by radosgw-token>
# export AWS_SECRET_ACCESS_KEY="" # 定义为空字符串,否则工具可能抱怨缺少环境变量

⚠️ access token 是 base64 编码的 JSON 结构,其中包含明文形式的 LDAP 凭证。除非你想分享你的明文密码,否则不要分享它!

引自:LDAP Authentication


6.3 OpenID Connect Provider(oidc.rst)

要在 RGW 与 IDP 之间建立信任,必须先在 RGW 中创建描述 OpenID Connect Provider 的实体。

调用这些 REST admin API 需要创建带 admin caps 的用户:

radosgw-admin --uid TESTER --display-name "TestUser" --access_key TESTER --secret test123 user create
radosgw-admin caps add --uid="TESTER" --caps="oidc-provider=*"

6.3.1 CreateOpenIDConnectProvider

在 RGW 中创建 OpenID Connect Provider 实体。

参数 说明 类型
ClientIDList.member.N 需要访问 S3 资源的 Client Ids 列表 Array of Strings
ThumbprintList.member.N OpenID Connect IDP 服务器证书的 thumbprint 列表。最多允许 5 个 thumbprints Array of Strings
Url IDP 的 URL String
POST "<hostname>?Action=Action=CreateOpenIDConnectProvider
  &ThumbprintList.list.1=F7D7B3515DD0D319DD219A43A9EA727AD6065287
  &ClientIDList.list.1=app-profile-jsp
  &Url=http://localhost:8080/auth/realms/quickstart"

6.3.2 DeleteOpenIDConnectProvider

删除 RGW 中的 OpenID Connect Provider 实体。

参数 说明 类型
OpenIDConnectProviderArn Create API 返回的 IDP 的 ARN String
POST "<hostname>?Action=Action=DeleteOpenIDConnectProvider
  &OpenIDConnectProviderArn=arn:aws:iam:::oidc-provider/localhost:8080/auth/realms/quickstart"

6.3.3 GetOpenIDConnectProvider

获取某个 IDP 的信息。

参数 说明 类型
OpenIDConnectProviderArn Create API 返回的 IDP 的 ARN String
POST "<hostname>?Action=Action=GetOpenIDConnectProvider
  &OpenIDConnectProviderArn=arn:aws:iam:::oidc-provider/localhost:8080/auth/realms/quickstart"

6.3.4 ListOpenIDConnectProviders

列出所有 IDP 的信息。

参数 说明 类型
None 无参数 —
POST "<hostname>?Action=Action=ListOpenIDConnectProviders

6.3.5 AddClientIDToOpenIDConnectProvider

向创建 OpenIDConnectProvider 时注册的 client id 列表中添加一个 client id。

参数 说明 类型
OpenIDConnectProviderArn Create API 返回的 IDP 的 ARN String
ClientID 要添加到已有 OpenIDConnectProvider 的 Client Id String
POST "<hostname>?Action=Action=AddClientIDToOpenIDConnectProvider
  &OpenIDConnectProviderArn=arn:aws:iam:::oidc-provider/localhost:8080/auth/realms/quickstart
  &ClientID=app-jee-jsp"

6.3.6 RemoveClientIDFromOpenIDConnectProvider

从创建 OpenIDConnectProvider 时注册的 client id 列表中移除一个 client id。

参数 说明 类型
OpenIDConnectProviderArn Create API 返回的 IDP 的 ARN String
ClientID 要从已有 OpenIDConnectProvider 移除的 Client ID String
POST "<hostname>?Action=Action=RemoveClientIDFromOpenIDConnectProvider
  &OpenIDConnectProviderArn=arn:aws:iam:::oidc-provider/localhost:8080/auth/realms/quickstart
  &ClientID=app-jee-jsp"

6.3.7 UpdateOpenIDConnectProviderThumbprint

用给定的列表更新 OpenIDConnectProvider 已有的 thumbprint 列表。该 API 会移除已有 thumbprint 列表,并用输入的 thumbprint 列表替换它。

参数 说明 类型
OpenIDConnectProviderArn Create API 返回的 IDP 的 ARN String
ThumbprintList.member.N OpenID Connect IDP 服务器证书的 thumbprint 列表。最多允许 5 个 thumbprints Array of Strings
POST "<hostname>?Action=Action=UpdateOpenIDConnectProviderThumbprint
  &OpenIDConnectProviderArn=arn:aws:iam:::oidc-provider/localhost:8080/auth/realms/quickstart
  &&ThumbprintList.list.1=ABCDB3515DD0D319DD219A43A9EA727AD6061234"

💡 补充:源文中所有 OIDC 示例的查询串都写作 ?Action=Action=...(Action= 重复),以及最后一例的 &&ThumbprintList。这是官方文档的排版/笔误,本册保留原样并在此提示;实际请求应为单个 Action= 与单个 &。

引自:OpenID Connect Provider in RGW


6.4 Keycloak(keycloak.rst)

如果 Keycloak 被配置为 OpenID Connect Identity Provider,移动应用和 Web 应用就可以用它来认证用户。利用认证过程返回的 web token,应用可以调用 AssumeRoleWithWebIdentity,获得一组临时 S3 凭证,并用这些凭证发起 S3 调用。

6.4.1 集成步骤

① 配置 Keycloak 与 RGW 通信 —— 添加以下配置项:

[client.radosgw.gateway]
rgw sts key = {sts key for encrypting/ decrypting the session token}
rgw s3 auth use sts = true

② 获取 web token

用 client_credentials grant type,配合 client id 与 client secret 获取 access token:

KC_REALM=demo
KC_CLIENT=<client id>
KC_CLIENT_SECRET=<client secret>
KC_SERVER=<host>:8080
KC_CONTEXT=auth

# Request Tokens for credentials
KC_RESPONSE=$( \
curl -k -v -X POST \
-H "Content-Type: application/x-www-form-urlencoded" \
-d "scope=openid" \
-d "grant_type=client_credentials" \
-d "client_id=$KC_CLIENT" \
-d "client_secret=$KC_CLIENT_SECRET" \
"http://$KC_SERVER/$KC_CONTEXT/realms/$KC_REALM/protocol/openid-connect/token" \
| jq .
)

KC_ACCESS_TOKEN=$(echo $KC_RESPONSE| jq -r .access_token)

用 password grant type 为特定用户获取 access token(需要 client id、client secret、username、password):

KC_REALM=demo
KC_USERNAME=<username>
KC_PASSWORD=<userpassword>
KC_CLIENT=<client id>
KC_CLIENT_SECRET=<client secret>
KC_SERVER=<host>:8080
KC_CONTEXT=auth

# Request Tokens for credentials
KC_RESPONSE=$( \
curl -k -v -X POST \
-H "Content-Type: application/x-www-form-urlencoded" \
-d "scope=openid" \
-d "grant_type=password" \
-d "client_id=$KC_CLIENT" \
-d "client_secret=$KC_CLIENT_SECRET" \
-d "username=$KC_USERNAME" \
-d "password=$KC_PASSWORD" \
"http://$KC_SERVER/$KC_CONTEXT/realms/$KC_REALM/protocol/openid-connect/token" \
| jq .
)

KC_ACCESS_TOKEN=$(echo $KC_RESPONSE| jq -r .access_token)

得到的 KC_ACCESS_TOKEN 即可用于调用 AssumeRoleWithWebIdentity。

6.4.2 在 Keycloak 中给用户添加标签

  1. 添加用户(Keycloak 管理界面);
  2. 填写用户详情;
  3. 设置用户凭证;
  4. 在用户的 'attributes' 标签页 添加标签;
  5. 添加 protocol mapper,把用户属性映射到 client。

完成这些步骤后,标签 Department 会出现在 JWT(web token)中,位于 https://aws.amazon.com/tags 命名空间下。

验证方式:对 JWT 做 token introspection,使用 client id 与 client secret:

KC_REALM=demo
KC_CLIENT=<client id>
KC_CLIENT_SECRET=<client secret>
KC_SERVER=<host>:8080
KC_CONTEXT=auth

curl -k -v \
-X POST \
-u "$KC_CLIENT:$KC_CLIENT_SECRET" \
-d "token=$KC_ACCESS_TOKEN" \
"http://$KC_SERVER/$KC_CONTEXT/realms/$KC_REALM/protocol/openid-connect/token/introspect" \
| jq .

引自:Integrating Keycloak with RadosGW


6.5 Open Policy Agent(opa.rst)

OPA(Open Policy Agent)是一个轻量级通用策略引擎,可以与服务协同部署(co-located),可作为 sidecar、主机级守护进程或库来集成。服务可以通过执行查询把策略决策卸载给 OPA,从而把策略执行与策略决策解耦。

6.5.1 配置 OPA

向 OPA 加载自定义策略,控制用户可以访问哪些资源;也可以把相关的数据或上下文加载进 OPA 以辅助决策。策略与数据可通过以下方式加载:

  • OPA 的 RESTful APIs
  • OPA 的 bundle 特性(从远程 HTTP 服务器下载策略和数据)
  • 文件系统

6.5.2 配置 Ceph Object Gateway

rgw use opa authz = {use opa server to authorize client requests}
rgw opa url = {opa server url:opa server port}
rgw opa token = {opa bearer token}
rgw opa verify ssl = {verify opa server ssl certificate}
配置项 类型 默认值(rgw.yaml.in @ v20.2.4) 说明
rgw_use_opa_authz bool false 是否使用 OPA server 为客户端请求做授权
rgw_opa_url str 文档未明确(无 default:) OPA server 的 URL
rgw_opa_token str 文档未明确(无 default:) OPA 用于认证客户端请求的 Bearer token
rgw_opa_verify_ssl bool true RGW 是否校验 OPA server 的 SSL 证书

6.5.3 RGW-OPA 集成的工作方式

用户完成认证后,OPA 被用来检查该用户是否有权在资源上执行给定动作。OPA 返回 allow 或 deny 决策,回送给 RGW 并由 RGW 执行该决策。

POST /v1/data/ceph/authz HTTP/1.1
Host: opa.example.com:8181
Content-Type: application/json

{
    "input": {
        "method": "GET",
        "subuser": "subuser",
        "user_info": {
            "user_id": "john",
            "display_name": "John"  
        },
        "bucket_info": {
            "bucket": {
                "name": "Testbucket",
                "bucket_id": "testbucket" 
            },
            "owner": "john" 
        }             
    }
}

响应:

{"result": true}

RGW 向 endpoint /v1/data/ceph/authz 发起 POST 请求,其中 ceph 是 package 名,authz 是 rule 名。请求体中包含用户、资源以及要在资源上执行的动作信息。

input 字段 说明
method 要执行的 HTTP 方法(示例为 GET)
subuser 子用户
user_info.user_id 用户 id
user_info.display_name 用户显示名
bucket_info.bucket.name 桶名
bucket_info.bucket.bucket_id 桶 id
bucket_info.owner 桶的拥有者

💡 补充:源文只给出了这一个示例请求,未穷举 input 的全部字段。上表字段仅覆盖示例中出现的字段,更多字段文档未明确。

引自:Open Policy Agent Integration


七、多因素认证 MFA(mfa.rst)

versionadded:: Mimic

S3 的 MFA(multifactor authentication)特性允许用户要求在某些桶上删除对象时使用一次性密码(one-time password)。这些桶需要通过 S3 API 配置为启用 versioning 且启用 MFA。

基于时间的一次性密码(TOTP)token 可以通过 radosgw-admin 分配给用户。每个 token 有一个 secret seed 和一个分配给它的 serial id。Token 可以被添加到用户上、被列出、被移除,也可以被重新同步(re-synchronized)。

7.1 Multisite 注意事项

MFA IDs 设置在用户元数据上,但实际的 MFA 一次性密码配置驻留在本地 zone 的 OSD 中。因此,在 multi-site 环境中,建议为不同的 zone 使用不同的 token。

7.2 术语

术语 说明
TOTP Time-based One Time Password,基于时间的一次性密码
token serial 表示 TOTP token ID 的字符串
token seed 用于计算 TOTP 的秘密种子
totp seconds 生成 TOTP 所使用的时间分辨率
totp window 校验 token 时,在当前 token 之前和之后要检查的 TOTP token 数量
totp pin 某个时刻 TOTP token 的有效值

7.3 管理命令

操作 命令
创建新的 MFA TOTP token radosgw-admin mfa create --uid=<user-id> --totp-serial=<serial> --totp-seed=<seed> [ --totp-seed-type=<hex|base32> ] [ --totp-seconds=<num-seconds> ] [ --totp-window=<twindow> ]
列出 MFA TOTP tokens radosgw-admin mfa list --uid=<user-id>
查看 MFA TOTP token radosgw-admin mfa get --uid=<user-id> --totp-serial=<serial>
删除 MFA TOTP token radosgw-admin mfa remove --uid=<user-id> --totp-serial=<serial>
检查 MFA TOTP token radosgw-admin mfa check --uid=<user-id> --totp-serial=<serial> --totp-pin=<pin>
重新同步 MFA TOTP token radosgw-admin mfa resync --uid=<user-id> --totp-serial=<serial> --totp-pin=<prev-pin> --totp=pin=<current-pin>

创建:

# radosgw-admin mfa create --uid=<user-id> \
                           --totp-serial=<serial> \
                           --totp-seed=<seed> \
                           [ --totp-seed-type=<hex|base32> ] \
                           [ --totp-seconds=<num-seconds> ] \
                           [ --totp-window=<twindow> ]

列出:

# radosgw-admin mfa list --uid=<user-id>

查看:

# radosgw-admin mfa get --uid=<user-id> --totp-serial=<serial>

删除:

# radosgw-admin mfa remove --uid=<user-id> --totp-serial=<serial>

检查(测试 TOTP token pin,用于验证 TOTP 功能是否正常):

# radosgw-admin mfa check --uid=<user-id> --totp-serial=<serial> \
                          --totp-pin=<pin>

重新同步(用于时间偏移场景,需要喂入两个连续的 pin:前一个 pin 与当前 pin):

# radosgw-admin mfa resync --uid=<user-id> --totp-serial=<serial> \
                           --totp-pin=<prev-pin> --totp=pin=<current-pin>

💡 补充:源文 resync 命令中当前 pin 的参数写作 --totp=pin=,与前面的 --totp-pin= 形式不一致,疑为官方文档笔误(应为 --totp-pin=<current-pin>)。本册保留源文原样并在此提示。

引自:RGW Support for Multifactor Authentication


八、Lua 脚本扩展(lua-scripting.rst)

versionadded:: Pacific

该特性允许用户把执行上下文(execution context)指派给 Lua 脚本。

8.1 脚本上下文(Contexts)

Context 执行时机
prerequest 在每个操作执行之前执行脚本
postrequest 在每个操作执行之后执行脚本
background 在指定的时间间隔内执行
getdata 在对象被下载时,作用于对象数据
putdata 在对象被上传时,作用于对象数据
preRequest 在每个操作执行之前执行脚本
postRequest 在每个操作执行之后执行脚本

💡 补充:源文中 prerequest/postrequest 与 preRequest/postRequest 两组写法并存(前者用于 script put,后者用于 script get / script rm)。由于源文明确说明"字段名大小写不敏感"(见 8.4 节),两组写法实际等价,本册保留源文原样。

补充说明:

  • request(pre 或 post)或 data(get 或 put)上下文的脚本,可以被限制为只作用于属于特定 tenant 的用户的操作;
  • request 上下文脚本可以访问 request 中的字段,可以修改某些字段,也可以访问 Global RGW Table;
  • data 上下文脚本可以访问对象的内容,以及 request 字段和 Global RGW Table;
  • 所有上下文都可以使用 Lua 语言的全部特性。

8.2 资源限制

限制 默认值 调整方式
单个脚本在上下文中执行可用的内存 500K byte(包含 Lua 使用的所有库,但不包含由 RGW 自身管理、可从 Lua 访问的内存) rgw_lua_max_memory_per_state。注意:Lua 及其标准库的基本开销约 32K bytes。设为 0 表示禁用该限制
单个 Lua 脚本执行的最大运行时间 1000 毫秒。超出该运行时间的脚本会被终止 rgw_lua_max_runtime_per_state。设为 0 表示禁用运行时限制

⚠️ 警告 1:修改内存限制时要谨慎。如果当前内存使用量超过了新设的限制,background state 中此前存储的所有数据都会丢失。 ⚠️ 警告 2:禁用运行时限制可能导致脚本无界执行,从而造成过度的资源消耗,并可能影响 RADOS gateway 的可用性。

配置项 类型 默认值(rgw.yaml.in @ v20.2.4) 说明
rgw_lua_max_memory_per_state uint 128000 单个 lua state 可为自己分配的最大字节数。不包含任何可从 lua 访问、但由 RGW 管理的内存。设 0 则仅受系统限制
rgw_lua_max_runtime_per_state uint 1000 每个 Lua state 的最大运行时间(毫秒)。超出则脚本被终止。设 0 则无限制
rgw_lua_enable bool true 是否启用 lua scripting(startup 标志,需重启生效)
rgw_luarocks_location str /tmp/rgw_luarocks/$name luarocks 从 allowlist 安装包的目录(startup 标志,需重启生效)

💡 补充:源文正文写"一个脚本在上下文中执行最多可使用 500K byte 内存",而 rgw.yaml.in 中 rgw_lua_max_memory_per_state 的默认值为 128000(即 128K)。二者不一致,本册同时记录两处原始表述,不做裁决。

8.3 Lua 包管理(allowlist)

默认情况下脚本中可以使用所有 Lua 标准库。为了允许脚本使用额外的 Lua 模块,支持把包加入 allowlist:

  • 向 allowlist 添加 Lua 包、或从中移除包,并不会安装或卸载它。要使改动生效,需要调用 "reload" 命令。
  • 此外,allowlist 中的所有包会在 radosgw 重启时用 luarocks 包管理器重新安装。
  • 要添加含有需要编译的 C 源代码的包,使用 --allow-compilation 标志;此时主机上需要有可用的 C 编译器。
  • Lua 包安装在、并使用于 radosgw 本地的一个目录,即 allowlist 中的 Lua 包与主机上任何可用的 Lua 包是隔离的。默认该目录为 /tmp/luarocks/<entity name>,其前缀部分(/tmp/luarocks/)可通过 rgw_luarocks_location 配置项改到别处。
  • 注意:该参数不应设置为 luarocks 安装包的默认位置之一(例如 $HOME/.luarocks、/usr/lib64/lua、/usr/share/lua)。

8.4 脚本管理 CLI

上传脚本:

# radosgw-admin script put --infile={lua-file-path} --context={prerequest|postrequest|background|getdata|putdata} [--tenant={tenant-name}]

上传 background 上下文的脚本时,不应指定 tenant 名。

cephadm 环境下:

# cephadm shell radosgw-admin script put --infile=/rootfs/{lua-file-path} --context={prerequest|postrequest|background|getdata|putdata} [--tenant={tenant-name}]

打印脚本内容到标准输出:

# radosgw-admin script get --context={preRequest|postRequest|background|getdata|putdata} [--tenant={tenant-name}]

删除脚本:

# radosgw-admin script rm --context={preRequest|postRequest|background|getdata|putdata} [--tenant={tenant-name}]

8.5 包管理 CLI

操作 命令
添加包到 allowlist # radosgw-admin script-package add --package={package name} [--allow-compilation]
添加指定版本的包到 allowlist # radosgw-admin script-package add --package='{package name} {package version}' [--allow-compilation]
从 allowlist 移除包 # radosgw-admin script-package rm --package={package name}
从 allowlist 移除指定版本的包 # radosgw-admin script-package rm --package='{package name} {package version}'
打印 allowlist 中的包列表 # radosgw-admin script-package list
把 allowlist 的改动应用到所有 RGW # radosgw-admin script-package reload

规则:

  • 当添加一个已存在于列表中的包的不同版本时,新添加的版本会覆盖已有版本。
  • 添加包时不指定版本,则会添加该包的最新版本。
  • 移除包时不指定版本,则该包的任何已存在版本都会被移除。

8.6 Context Free Functions(与上下文无关的函数)

Debug Log

RGWDebugLog() 函数接受一个字符串,把它以优先级 20 打印到 debug log。每条日志消息带有前缀 Lua INFO:。该函数没有返回值。

8.7 Request Fields(逐项表格)

⚠️ 官方 warning:该特性是实验性的。字段可能在未来被移除或重命名。

说明(官方 note):

  • 尽管 Lua 是大小写敏感的语言,radosgw 提供的字段名是大小写不敏感的;函数名仍然大小写敏感。
  • 标记为 "optional" 的字段可能为 nil 值。
  • 标记为 "iterable" 的字段可以被 pairs() 函数使用,也可以配合 # 长度操作符。
  • 所有 table 字段都可以用方括号操作符 []。
  • time 类型的字段是字符串,格式为 %Y-%m-%d %H:%M:%S。
Field Type Description Iterable Writeable Optional
Request.RGWOp string radosgw operation no no no
Request.DecodedURI string decoded URI no no no
Request.ContentLength integer size of the request no no no
Request.GenericAttributes table string to string generic attributes map yes no no
Request.Response table response to the request no no no
Request.Response.HTTPStatusCode integer HTTP status code no yes no
Request.Response.HTTPStatus string HTTP status text no yes no
Request.Response.RGWCode integer radosgw error code no yes no
Request.Response.Message string response message no yes no
Request.SwiftAccountName string swift account name no no yes
Request.Bucket table info on the bucket no no no
Request.Bucket.Tenant string tenant of the bucket no no yes
Request.Bucket.Name string bucket name(仅在 prerequest 上下文中可写) no yes no
Request.Bucket.Marker string bucket marker (initial id) no no yes
Request.Bucket.Id string bucket id no no yes
Request.Bucket.ZoneGroupId string zone group of the bucket no no yes
Request.Bucket.CreationTime time creation time of the bucket no no yes
Request.Bucket.MTime time modification time of the bucket no no yes
Request.Bucket.Quota table bucket quota no no yes
Request.Bucket.Quota.MaxSize integer bucket quota max size no no no
Request.Bucket.Quota.MaxObjects integer bucket quota max number of objects no no no
Reques.Bucket.Quota.Enabled boolean bucket quota is enabled no no no
Request.Bucket.Quota.Rounded boolean bucket quota is rounded to 4K no no no
Request.Bucket.PlacementRule table bucket placement rule no no yes
Request.Bucket.PlacementRule.Name string bucket placement rule name no no no
Request.Bucket.PlacementRule.StorageClass string bucket placement rule storage class no no no
Request.Bucket.User string owning user/account id no no yes
Request.Object table info on the object no no yes
Request.Object.Name string object name no no no
Request.Object.Instance string object version no no no
Request.Object.Id string object id no no no
Request.Object.Size integer object size no no no
Request.Object.MTime time object mtime no no no
Request.CopyFrom table information on copy operation no no yes
Request.CopyFrom.Tenant string tenant of the object copied from no no no
Request.CopyFrom.Bucket string bucket of the object copied from no no no
Request.CopyFrom.Object table object copied from. See: Request.Object no no yes
Request.ObjectOwner table object owner no no no
Request.ObjectOwner.DisplayName string object owner display name no no no
Request.ObjectOwner.User string owning user/account id. See: Request.Bucket.User no no yes
Request.ZoneGroup.Name string name of zone group no no no
Request.ZoneGroup.Endpoint string endpoint of zone group no no no
Request.UserAcl table user ACL no no no
Request.UserAcl.Owner table user ACL owner. See: Request.ObjectOwner no no no
Request.UserAcl.Grants table user ACL map of string to grant(注意:没有 Id 的 grants 在迭代时不会出现,且通过方括号只能访问其中的一个) yes no no
Request.UserAcl.Grants["<name>"] table user ACL grant no no no
Request.UserAcl.Grants["<name>"].Type integer user ACL grant type no no no
Request.UserAcl.Grants["<name>"].User string user ACL grant user/account id no no no
Request.UserAcl.Grants["<name>"].GroupType integer user ACL grant group type no no yes
Request.UserAcl.Grants["<name>"].Referer string user ACL grant referer no no yes
Request.BucketAcl table bucket ACL. See: Request.UserAcl no no no
Request.ObjectAcl table object ACL. See: Request.UserAcl no no no
Request.Environment table string to string environment map yes no no
Request.Policy table policy no no yes
Request.Policy.Text string policy text no no no
Request.Policy.Id string policy Id no no yes
Request.Policy.Statements table list of string statements yes no no
Request.UserPolicies table list of user policies yes no no
Request.UserPolicies[<index>] table user policy. See: Request.Policy no no no
Request.RGWId string radosgw host id: <host>-<zone>-<zonegroup> no no no
Request.HTTP table HTTP header no no no
Request.HTTP.Parameters table string to string parameter map yes no no
Request.HTTP.Resources table string to string resource map yes no no
Request.HTTP.Metadata table string to string metadata map yes yes no
Request.HTTP.StorageClass string storage class no yes yes
Request.HTTP.Host string host name no no no
Request.HTTP.Method string HTTP method no no no
Request.HTTP.URI string URI no no no
Request.HTTP.QueryString string HTTP query string no no no
Request.HTTP.Domain string domain name no no no
Request.Time time request time no no no
Request.Dialect string "S3" or "Swift" no no no
Request.Id string request Id no no no
Request.TransactionId string transaction Id no no no
Request.Tags table object tags map yes no no
Request.User table user that triggered the request no no no
Request.User.Tenant string triggering user tenant no no no
Request.User.Id string triggering user id no no no
Request.Trace table info on trace no no no
Request.Trace.Enable boolean tracing is enabled no yes no

💡 补充:源文表格中 Reques.Bucket.Quota.Enabled 少了一个 t(应为 Request.Bucket.Quota.Enabled)。本册保留源文原始拼写以忠实记录,实际书写时请使用 Request.Bucket.Quota.Enabled。

可写字段汇总(这是 Lua 能做"改写/拦截"的能力边界):

可写字段 上下文限制
Request.Response.HTTPStatusCode 无限制说明
Request.Response.HTTPStatus 无限制说明
Request.Response.RGWCode 无限制说明
Request.Response.Message 无限制说明
Request.Bucket.Name 仅 prerequest 上下文
Request.HTTP.Metadata 无限制说明
Request.HTTP.StorageClass 无限制说明
Request.Trace.Enable 无限制说明

8.8 Request Functions

Operations Log

Request.Log() 函数把请求打印到 operations log。该函数没有参数,成功返回 0,失败返回错误码。

Tracing

Tracing 函数只能在 postrequest 上下文中使用。

函数 说明
Request.Trace.SetAttribute(<key>, <value>) 为请求的 trace 设置属性。第一个参数 key 必须是字符串;第二个参数 value 可以是字符串或数字(integer 或 double)。之后可以用该属性定位特定的 traces
Request.Trace.AddEvent(<name>, <attributes>) 向请求 trace 的第一个 span 添加一个事件。事件由事件名、事件时间和零个或多个事件属性定义。该函数接受一个或两个参数:第一个是包含事件 name 的字符串,其次是事件 attributes(对无属性的事件可省略)。事件属性必须是一个字符串 table

8.9 请求阻断与错误处理

脚本执行错误

如果 Lua 脚本因语法错误或运行时错误失败,RGW 会记录该错误,而触发脚本的请求仍会照常通过。

请求阻断与返回值

脚本的返回值决定 RGW 如何处理该请求:

目的 做法
阻断请求 脚本必须返回值 RGW_ABORT_REQUEST。RGW 将其解释为 -EPERM,并停止处理该请求
继续请求 不返回值,或返回任何其它值/类型,都被视为成功

返回值上下文

Lua 脚本的返回值只在 prerequest 上下文中被评估,在其它任何 RGW 请求处理上下文中都被忽略。

被 Lua 阻断的请求,其 HTTP 响应状态码默认为 403 (Forbidden)。该响应码可用 Request.Response.HTTPStatusCode 与 Request.Response.HTTPStatus 修改。

如果请求以这种方式被 abort,那么 data 和 postrequest 上下文也会被 abort。

8.10 Background Context

background 上下文可用于分析、监控、为其它上下文的执行缓存数据等目的。

  • Background 脚本执行的默认间隔为 5 秒。

8.11 Data Context

getdata 与 putdata 两个上下文都拥有以下字段:

字段 说明
Data 只读且可迭代(逐字节)。如果对象被分多个 chunk 上传或读取,则 Data 字段一次只持有一个 chunk 的数据
Offset 该 chunk 在整个对象中的偏移量
Request 字段与 background 的 RGW table 在这些上下文中也可用

8.12 Global RGW Table

RGW Lua table 可从所有上下文访问,它会保存在执行期间写入其中的数据,以便后续在**其它执行(同一上下文或不同上下文)**中读取和使用。

属性 说明
作用域 每个 RGW 实例都有自己私有的、短暂的(ephemeral)RGW Lua table,守护进程重启时丢失。注意 background 上下文脚本会在每个实例上运行
容量上限 table 中最多 100,000 个条目。每个条目是 string key,其 value 的组合长度不超过 1KB。若条目数或条目大小超出这些限制,Lua 脚本会带错误中止
索引与值类型 RGW Lua table 使用 string 索引,可存储类型为 string、integer、double、boolean 的值

Increment / Decrement 函数

由于 RGW table 中的条目可能被多处同时访问,需要一种原子地增减其中数值的方法:

函数 说明
RGW.increment(<key>, [value]) 把 key 的值增加 value;若未提供 value,则增加 1
RGW.decrement(<key>, [value]) 把 key 的值减少 value;若未提供 value,则减少 1
  • 如果 key 的值不是数值类型,脚本执行会失败;
  • 如果尝试用非数值做增量或减量,脚本执行会失败。

8.13 Lua 代码示例

示例 1:在 copy 场景下打印源对象与目标对象信息

function print_object(object)
  RGWDebugLog("  Name: " .. object.Name)
  RGWDebugLog("  Instance: " .. object.Instance)
  RGWDebugLog("  Id: " .. object.Id)
  RGWDebugLog("  Size: " .. object.Size)
  RGWDebugLog("  MTime: " .. object.MTime)
end

if Request.CopyFrom and Request.Object and Request.CopyFrom.Object then
  RGWDebugLog("copy from object:")
  print_object(Request.CopyFrom.Object)
  RGWDebugLog("to object:")
  print_object(Request.Object)
end

示例 2:用"通用函数"打印 ACLs

function print_owner(owner)
  RGWDebugLog("Owner:")
  RGWDebugLog("  Display Name: " .. owner.DisplayName)
  RGWDebugLog("  Id: " .. owner.User.Id)
  RGWDebugLog("  Tenant: " .. owner.User.Tenant)
end

function print_acl(acl_type)
  index = acl_type .. "ACL"
  acl = Request[index]
  if acl then
    RGWDebugLog(acl_type .. "ACL Owner")
    print_owner(acl.Owner)
    RGWDebugLog("  there are " .. #acl.Grants .. " grant for owner")
    for k,v in pairs(acl.Grants) do
      RGWDebugLog("    Grant Key: " .. k)
      RGWDebugLog("    Grant Type: " .. v.Type)
      RGWDebugLog("    Grant Group Type: " .. v.GroupType)
      RGWDebugLog("    Grant Referer: " .. v.Referer)
      RGWDebugLog("    Grant User Tenant: " .. v.User.Tenant)
      RGWDebugLog("    Grant User Id: " .. v.User.Id)
    end
  else
    RGWDebugLog("no " .. acl_type .. " ACL in request: " .. Request.Id)
  end 
end

print_acl("User")
print_acl("Bucket")
print_acl("Object")

示例 3:仅在出错时使用 operations log

if Request.Response.HTTPStatusCode ~= 200 then
  RGWDebugLog("request is bad, use ops log")
  rc = Request.Log()
  RGWDebugLog("ops log return code: " .. rc)
end

示例 4:改写错误消息(响应内容改写)

if Request.Response.HTTPStatusCode == 500 then
  Request.Response.Message = "<Message> something bad happened :-( </Message>"
end

示例 5:给对象追加客户端原本没有发送的 metadata

在 prerequest 上下文中:

if Request.RGWOp == 'put_obj' then
  Request.HTTP.Metadata["x-amz-meta-mydata"] = "my value"
end

在 postrequest 上下文中查看 metadata:

RGWDebugLog("number of metadata entries is: " .. #Request.HTTP.Metadata)
for k, v in pairs(Request.HTTP.Metadata) do
  RGWDebugLog("key=" .. k .. ", " .. "value=" .. v)
end

示例 6:用模块构造基于 Unix socket、JSON 编码的 "access log"

第一步:把下列包加入 allowlist:

# radosgw-admin script-package add --package=lua-cjson --allow-compilation
# radosgw-admin script-package add --package=luasocket --allow-compilation

第二步:启动一个监听 Unix socket 的服务端,例如用 netcat:

# rm -f /tmp/socket       
# nc -vklU /tmp/socket

第三步:重启 radosgw,并把下面脚本上传到 postrequest 上下文:

if Request.RGWOp == "get_obj" then
  local json = require("cjson")
  local socket = require("socket")
  local unix = require("socket.unix")
  local s = assert(unix())
  E = {}

  msg = {bucket = (Request.Bucket or (Request.CopyFrom or E).Bucket).Name,
    time = Request.Time,
    operation = Request.RGWOp,
    http_status = Request.Response.HTTPStatusCode,
    error_code = Request.Response.HTTPStatus,
    object_size = Request.Object.Size,
    trans_id = Request.TransactionId}

  assert(s:connect("/tmp/socket"))
  assert(s:send(json.encode(msg).."\n"))
  assert(s:close())
end

示例 7:只对特定桶的请求开启 tracing

Tracing 默认是禁用的,所以应为这个特定桶启用 tracing:

if Request.Bucket.Name == "my-bucket" then
    Request.Trace.Enable = true
end

如果 RGW 上已启用 tracing,则 Request.Trace.Enable 为 true,因此应对所有不匹配该桶名的请求关闭 tracing。在 prerequest 上下文中:

if Request.Bucket.Name ~= "my-bucket" then
    Request.Trace.Enable = false
end

注意:修改 Request.Trace.Enable 不会改变 tracer 本身的状态,只是针对该请求启用或禁用 tracing。

示例 8:为请求 trace 追加信息

在 postrequest 上下文中,可以向请求的 trace 添加属性和事件:

Request.Trace.AddEvent("lua script execution started")

Request.Trace.SetAttribute("HTTPStatusCode", Request.Response.HTTPStatusCode)

event_attrs = {}
for k,v in pairs(Request.GenericAttributes) do
  event_attrs[k] = v
end

Request.Trace.AddEvent("second event", event_attrs)

示例 9:用熵值检测对象是否被加密

对象的熵值可用于判断对象是否被加密。下面的脚本计算上传对象的熵与大小,并打印到 debug log。在 putdata 上下文中加入:

function object_entropy()
	local byte_hist = {}
	local byte_hist_size = 256 
	for i = 1,byte_hist_size do
		byte_hist[i] = 0 
	end 
	local total = 0 

	for i, c in pairs(Data)  do
		local byte = c:byte() + 1 
		byte_hist[byte] = byte_hist[byte] + 1 
		total = total + 1 
	end 

	entropy = 0 

	for _, count in ipairs(byte_hist) do
		if count ~= 0 then
			local p = 1.0 * count / total
			entropy = entropy - (p * math.log(p)/math.log(byte_hist_size))
		end 
	end 

	return entropy
end

local full_name = Request.Bucket.Name.."\\"..Request.Object.Name
RGWDebugLog("entropy of chunk of: " .. full_name .. " at offset:" .. tostring(Offset)  ..  " is: " .. tostring(object_entropy()))
RGWDebugLog("payload size of chunk of: " .. full_name .. " is: " .. #Data)

示例 10:阻断请求(鉴权 / 限流的骨架)

💡 补充:源文没有直接给出"阻断"的完整示例,但给出了阻断机制(返回 RGW_ABORT_REQUEST)与可写响应字段。下面把二者组合成一个可直接使用的骨架(属本册补充,非源文原文):

-- prerequest 上下文:只放行指定桶,其它一律 403
if Request.Bucket.Name ~= "allowed-bucket" then
  Request.Response.HTTPStatusCode = 403
  Request.Response.HTTPStatus = "Forbidden"
  Request.Response.Message = "blocked by lua policy"
  return RGW_ABORT_REQUEST
end

配合 RGW table 的原子计数,可做简单限流:

-- prerequest 上下文:对某用户做每实例计数(注意 RGW table 是每个实例私有的)
local key = "cnt:" .. Request.User.Id
RGW.increment(key)
if RGW[key] > 100 then
  Request.Response.HTTPStatusCode = 429
  Request.Response.HTTPStatus = "Too Many Requests"
  return RGW_ABORT_REQUEST
end

⚠️ 该限流示例基于每个 RGW 实例私有的 RGW table,因此是单实例维度的计数,不是集群全局限流。

8.14 调试与限制小结

主题 要点
调试手段 RGWDebugLog()(优先级 20,前缀 Lua INFO:);Request.Log()(写入 operations log)
脚本错误的影响 语法/运行时错误只记录日志,请求照常通过 —— 这意味着 Lua 逻辑出错不会变成显式拒绝,需要靠日志发现
阻断 仅在 prerequest 返回 RGW_ABORT_REQUEST 才有效;默认 403,可改写
阻断的连带影响 请求被 abort 时,data 与 postrequest 上下文也会 abort
内存限制 默认 500K(正文)/ 128000(rgw_lua_max_memory_per_state 默认值),改小可能导致 background state 数据丢失
运行时间限制 默认 1000 ms,禁用可能导致 RGW 可用性受损
字段稳定性 Request Fields 是实验性特性,字段可能被移除或重命名
包隔离 allowlist 的包安装在 radosgw 本地目录,与主机 Lua 包隔离;新增/移除需 script-package reload
background 上下文 不能指定 tenant;在每个 RGW 实例上都运行;默认 5 秒间隔
RGW table 每实例私有、重启丢失;上限 100,000 条目,单条目 ≤1KB;仅 string 索引;值类型限 string/integer/double/boolean
Tracing 函数 仅 postrequest 可用;Request.Trace.Enable 只影响当前请求,不改 tracer 状态

引自:Lua Scripting


九、硬件加速

9.1 Intel QAT(qat-accel.rst)

Intel QAT(QuickAssist Technology)可以通过把实际的加密与压缩请求卸载(offload)到硬件 QuickAssist 加速器来提供扩展的加速服务;对这类特定的计算密集型负载,硬件加速器在成本与功耗上都比通用 CPU 更高效。

9.1.1 QAT 在软件栈中的位置

  • 应用开发者通过 QAT API 访问 QuickAssist 特性,它是 QuickAssist 技术的顶层 API,负责客户应用与 QuickAssist 加速驱动之间的对接;
  • QAT API 访问 QuickAssist driver,驱动再去驱动 QuickAssist Accelerator 硬件;驱动负责把加速服务暴露给应用软件;
  • 用户可以直接写 QAT API,也可以经由已被他人(包括 Intel)使能的框架来使用 QAT,例如 zlib*、OpenSSL* libcrypto*、以及 Linux* Kernel Crypto Framework。

9.1.2 环境准备

  1. 需要 QuickAssist Accelerator 硬件,且内核态的 QAT 驱动必须已加载以驱动该硬件。
    • out-of-tree QAT 驱动包可从 Intel Quickassist Technology 下载;
    • QATlib 可从 qatlib 下载,用于 in-tree QAT driver。
    • ⚠️ out-of-tree QAT 驱动正在逐步迁移到 in-tree driver + QATlib。
  2. QAT 加密的实现直接基于 QAT API(包含在驱动包中);而 QAT 压缩支持依赖 QATzip 项目 —— 一个构建在 QAT API 之上的用户态库。截至撰写时(2024 年 7 月),QATzip 加速 gzip 的压缩与解压。

9.1.3 实现方式差异

能力 实现路径 说明
QAT 加密 基于原生 QAT API 的 crypto plugin 加入 crypto framework 虽然 Ceph 已合并 OpenSSL support for RGW encryption,且 Intel 提供了 QAT Engine for OpenSSL,但该 QAT Engine 目前只支持链式操作(chained operations),意味着 Ceph 无法基于 OpenSSL crypto plugin 利用 QAT 硬件特性做 crypto 操作,因此额外加入了基于原生 QAT API 的插件
QAT 压缩 基于用户态 QATzip 库,通过一个 QAT 加速的通用工具类实现(不是 compressor plugin) 该通用工具类可以透明地加速已有的压缩类型,但截至撰写时只支持 zlib compressor。也就是说,若有 QAT 硬件,可用该工具类加速 zlib compressor

9.1.4 配置步骤

① 前置条件

For out-of-tree QAT:

  • 确保已安装版本 v1.7.L.4.14.0 或更高的 out-of-tree QAT 驱动;
  • 为 QAT 驱动包根目录设置环境变量 ICP_ROOT;
  • 必须修改 QAT 配置文件。例如对 Intel QuickAssist Adapter 8970 产品,修改 /etc/ 下的 c6xx_dev0/1/2.conf 并保持一致:
#...
# User Process Instance Section
##############################################
[CEPH]
NumberCyInstances = 1
NumberDcInstances = 1
NumProcesses = 8
LimitDevAccess = 1
# Crypto - User instance #0
Cy0Name = "SSL0"
Cy0IsPolled = 1
# List of core affinities
Cy0CoreAffinity = 0
 
# Data Compression - User instance #0
Dc0Name = "Dc0"
Dc0IsPolled = 1
# List of core affinities
Dc0CoreAffinity = 0

For in-tree QAT:

  • 确保系统满足 QATlib System Requirements;
  • Intel VT-d 与 SR-IOV 参数必须在平台 BIOS 中启用;
  • 某些 QATlib 特性需要较新的内核驱动或固件版本;
  • 支持的平台需包含 4xxx Intel Communications device 或更新;
  • 必须启用 intel_iommu 参数,可用以下命令验证与设置:
cat /proc/cmdline | grep intel_iommu=on
sudo sh -c 'echo "@qat - memlock 204800" >> /etc/security/limits.conf'
sudo su -l $USER

② QAT-based Encryption for RGW

必须设置 CMake 选项 WITH_QATDRV=ON。从源码构建时:

cd ceph
./do_cmake.sh -DWITH_QATDRV=ON
cd build
ininja

⚠️ QAT 配置文件中的 section 名必须是 CEPH,因为 Ceph crypto 源码中把 section name 设置为 CEPH。 💡 补充:源文中的构建命令写作 ininja,疑为 ninja 的笔误,本册保留原样并在此提示。

编辑 Ceph 配置文件(通常是 ceph.conf)以使用 QAT-based crypto plugin:

plugin crypto accelerator = crypto_qat

③ QAT Support for Compression

For out-of-tree QAT:

  • 构建前确保 QAT 驱动与 QATzip 都已安装;
  • 除 ICP_ROOT 外,把环境变量 QZ_ROOT 设置为 QATzip 源码树的根目录;
  • 配置以下 CMake 选项:
./do_cmake.sh -DWITH_QATDRV=ON -DWITH_QATZIP=ON -DWITH_SYSTEM_QATZIP=ON -DWITH_QATLIB=OFF
  • 设置环境变量以明确 QAT 配置文件中 User Process Instance Section 的 section 名:
export QAT_SECTION_NAME=CEPH

For in-tree QAT:

  • 确保系统满足 QATlib System Requirements;QATlib 可从预编译包或源码安装。安装后可运行 cpa_sample_code 检查 QAT 环境是否正常;
  • 若使用 QATlib 源码,Ceph 的 cmake 构建默认启用 qatlib 和 qatzip 选项,常规编译已包含 QAT-compressor 相关代码:
./do_cmake.sh
  • 若使用系统上的预编译包,构建 Ceph 时需配置:
./do_cmake.sh -DWITH_SYSTEM_QATLIB=ON -DWITH_SYSTEM_QATZIP=ON

For both:

编辑 Ceph 的 central config DB 或配置文件(通常是 ceph.conf)以启用 QAT 对 zlib 压缩的支持:

qat compressor enabled=true

设置 RGW 的压缩方法:

# for storage class(STANDARD)
radosgw-admin zone placement modify --rgw-zone=default --placement-id=default-placement --compression=zlib
# or create a new storage class(COLD) and define data pool(default.rgw.cold.data)
radosgw-admin zonegroup placement add --rgw-zonegroup default --placement-id default-placement --storage-class COLD
radosgw-admin zone placement add --rgw-zone default --placement-id default-placement --storage-class COLD --compression zlib --data-pool default.rgw.cold.data

9.1.5 QAT 配置项

以下 QAT 相关设置可加到 Ceph 配置文件(通常 ceph.conf)的 [client.rgw.{instance-name}] 段下。

配置项 类型 默认值(*.yaml.in @ v20.2.4) 说明
qat_compressor_enabled bool false(来自 src/common/options/global.yaml.in) 若可用,启用 Intel QAT 对压缩的加速支持
qat_compressor_session_max_number uint 256(来自 src/common/options/global.yaml.in) 使用 QAT compressor 时,Qatzip 内的最大 session 数
qat_compressor_busy_polling bool false(来自 src/common/options/global.yaml.in) 开启 QAT busy polling 以降低延迟,代价是可能增加 CPU 使用
plugin_crypto_accelerator str crypto_isal(来自 src/common/options/global.yaml.in) 使用的 crypto accelerator 库;启用 QAT 加密时设为 crypto_qat

9.1.6 验证方式

  • 安装 QATlib 后运行 cpa_sample_code,可检查 QAT 环境是否正常;
  • 用 cat /proc/cmdline | grep intel_iommu=on 验证 intel_iommu 已启用。

引自:QAT Acceleration for Encryption and Compression


9.2 鲲鹏 UADK(uadk-accel.rst)

UADK 是一个让用户以统一、安全、高效的方式访问硬件加速器的框架,由 UACCE、libwd 以及许多其它算法库组成。

9.2.1 UADK 在软件栈中的位置

UADK 是一个通用用户态加速器框架,使用 SVA(Shared Virtual Addressing,共享虚拟寻址) 为加密与压缩算法的硬件加速提供统一编程接口。

UADK 包含 UACCE(Unified/User-space-access-intended Accelerator Framework),它使支持 SVA 的硬件加速器能够适配 UADK。

目前 HiSilicon Kunpeng(鲲鹏)硬件加速器已注册到 UACCE。通过 UADK 框架,用户可以使用硬件加速器而非 CPU 来运行加密与压缩算法,从而释放 CPU 算力、提升计算性能。

用户可以通过在字符设备上做用户态操作来访问硬件加速器,也可以经由已支持 UADK 的框架来使用(例如 OpenSSL* libcrypto*、DPDK、Linux* Kernel Crypto Framework)。

软件栈结构:

        +----------------------------------+
        |                apps              |
        +----+------------------------+----+
             |                        |
             |                        |
     +-------+--------+       +-------+-------+
     |   scheduler    |       | alg libraries |
     +-------+--------+       +-------+-------+
             |                         |
             |                         |
             |                         |
             |                +--------+------+
             |                | vendor drivers|
             |                +-+-------------+
             |                  |
             |                  |
          +--+------------------+--+
          |         libwd          |
  User    +----+-------------+-----+
  --------------------------------------------------
  Kernel    +--+-----+   +------+
            | uacce  |   | smmu |
            +---+----+   +------+
                |
            +---+------------------+
            | vendor kernel driver |
            +----------------------+
  --------------------------------------------------
           +----------------------+
           |   HW Accelerators    |
           +----------------------+

9.2.2 环境要求

UADK 由 UACCE、vendor drivers 和算法层组成。UADK 要求硬件加速器支持 SVA,操作系统支持 IOMMU 与 SVA。硬件加速器由内核态驱动以不同的字符设备形式注册到 UACCE。

9.2.3 配置

① 内核要求

必须确保所使用的 Linux kernel 版本支持 UACCE,应为 5.9 或更高,且启用 SVA(Shared Virtual Addressing)。UACCE 可以构建为可加载模块,也可以构建进内核。以下是为 HiSilicon Kunpeng 平台构建带硬件加速器的 UACCE 示例:

CONFIG_IOMMU_SVA_LIB=y
CONFIG_ARM_SMMU=y
CONFIG_ARM_SMMU_V3=y
CONFIG_ARM_SMMU_V3_SVA=y
CONFIG_PCI_PASID=y
CONFIG_UACCE=y
CONFIG_CRYPTO_DEV_HISI_QM=y
CONFIG_CRYPTO_DEV_HISI_ZIP=y

确保上述内核配置项全部被选中。

② UADK 启用

如果架构是 aarch64,会自动下载 UADK 源码构建静态库。在其它 CPU 架构上构建时,可通过在编译命令行中加入 -DWITH_UADK=true 来启用 UADK。注意 UADK 可能并不兼容所有架构。

③ 手工构建 UADK

若架构为 aarch64,UADK 会自动启用,无需手工构建。以下是手工构建流程(供开发者研究其构建方式):

git clone https://github.com/Linaro/uadk.git
cd uadk
mkdir build
./autogen.sh
./configure --prefix=$PWD/build
make
make install

⚠️ 不使用 --prefix 时,UADK 默认安装到 /usr/local/lib。若遇到错误 cannot find -lnuma,请安装 libnuma-dev 包。

④ 启用配置

编辑 Ceph 配置文件(通常 ceph.conf)以启用 UADK 对 zlib 压缩的支持:

uadk_compressor_enabled=true

9.2.4 UADK 配置项

配置项 类型 默认值(src/common/options/global.yaml.in @ v20.2.4) 说明
uadk_compressor_enabled bool false 若可用,启用 UADK 对压缩的加速支持

源文明确说明:global.yaml.in 中 uadk_compressor_enabled 的默认值为 false。

9.2.5 验证方式

💡 补充:源文未给出 UADK 的显式验证命令(QAT 页面给出了 cpa_sample_code)。可确认的验证手段是内核侧检查 CONFIG_UACCE 等配置项是否被选中,以及确认 zlib 压缩已按 9.2.3 ④ 启用。

引自:UADK Acceleration for Compression


十、RGW 管理 API 入口(api.rst)

页面标题为 librgw (Python)。

rgw python 模块提供类文件(file-like)的访问方式来访问 rgw。

API Reference:源文以 Sphinx 的 automodule 指令自动生成 rgw 模块的 API 文档,成员包括 LibRGWFS 与 FileHandle。

.. automodule:: rgw
    :members: LibRGWFS, FileHandle

💡 补充:api.rst 源文本身极短(仅 17 行),具体的 API 签名与文档由构建期从 rgw python 模块自动生成,因此本册范围内无法展开 LibRGWFS 与 FileHandle 的方法列表,文档未明确。

引自:librgw (Python)


十一、安全配置检查清单

汇总本册涉及的能力、对应配置项、建议值与风险。所有默认值均取自 src/common/options/*.yaml.in @ v20.2.4;"建议值"一列中属于本册补充建议的,均在备注中说明。

# 能力 配置项 默认值 建议值 风险
1 加密请求必须走 SSL rgw_crypt_require_ssl true true 关闭后密钥头可能以明文传输
2 抑制可能泄露客户端密钥的日志 rgw_crypt_suppress_logs true true 关闭后日志可能打印客户端密钥
3 SSL 终止代理后的安全判定 rgw_trust_forwarded_https 文档未明确 仅在前方确有受信任代理时设为 true 无代理却开启 → 任意用户可伪造 Forwarded / X-Forwarded-Proto 头
4 SSE-KMS 后端选择 rgw_crypt_s3_kms_backend barbican 按实际 KMS 选 vault / kmip / barbican 选错会导致取密钥失败
5 SSE-S3 后端 rgw_crypt_sse_s3_backend vault vault(唯一合法值) 无其它选择
6 Vault 认证方式 rgw_crypt_vault_auth token agent(生产) 用 token 且不用 agent 刷新 → token 过期后无法解密;绝不能用 root token
7 Vault SSL 校验 rgw_crypt_vault_verify_ssl true true 设为 false 官方明确称"非常危险"
8 Vault token 文件权限 rgw_crypt_vault_token_file 文档未明确 仅 radosgw 可读(如 0600 + 属主 ceph) 权限过宽导致密钥泄露
9 SSE-S3 Vault SSL 校验 rgw_crypt_sse_s3_vault_verify_ssl true true 同 #7
10 SSE-KMS 与 SSE-S3 隔离 rgw_crypt_vault_prefix / rgw_crypt_sse_s3_vault_prefix 文档未明确 指向不同容器 不隔离时桶拥有者可能碰到 SSE-S3 密钥
11 测试用自动加密 rgw_crypt_default_encryption_key 文档未明确(level: dev) 生产禁用 ceph.conf 不是安全的密钥存储方式,泄露即视为已泄露
12 KMIP 密钥命名空间收敛 rgw_crypt_kmip_kms_key_template 文档未明确 设为带固定前缀的模板(如 pykmip-$keyid) 不设时 Ceph 可见全部 kmip 密钥
13 STS 启用 rgw_s3_auth_use_sts false 需要 STS/STSLite 时设 true 未启用则临时凭证无法认证
14 STS 会话密钥 rgw_sts_key 文档未明确 用 ceph-authtool --gen-print-key 生成;同 zone / 同 realm 内所有实例一致 各实例不一致 → 临时凭证跨实例失效
15 STS 会话时长上限 rgw_sts_max_session_duration 43200 按业务设定,不宜过大 过大 → 凭证泄露后暴露窗口长
16 AssumeRole* 会话时长下限 rgw_sts_min_session_duration 900 保持默认或按需上调 过小 → 频繁换取凭证
17 S3 认证引擎顺序 rgw_s3_auth_order sts, external, local 明确指定,避免隐式回退 顺序不当可能导致认证绕过到非预期引擎
18 禁用 presigned URL rgw_s3_auth_disable_signature_url false 高安全场景设 true 开启后所有 V2/V4 预签名请求被拒
19 Keystone admin token rgw_keystone_admin_token 文档未明确(DEPRECATED) 改用 rgw_keystone_admin_token_path 或 admin user/password 明文写在配置文件中
20 Keystone admin 密码 rgw_keystone_admin_password 文档未明确(DEPRECATED) 改用 rgw_keystone_admin_password_path 明文写在配置文件中
21 Keystone 可接受角色 rgw_keystone_accepted_roles Member, admin 按最小权限收敛 过宽 → 不该被服务的用户也能访问
22 Keystone SSL 校验 rgw_keystone_verify_ssl true true 关闭后不校验 Keystone 证书
23 Keystone token 缓存 rgw_keystone_token_cache_size 10000 按并发调整 过小 → 频繁访问 Keystone
24 service token 放行过期 token rgw_keystone_service_token_enabled false 仅在确有长运行进程需求时开启 开启后过期 token 可被放行
25 service token 可接受角色 rgw_keystone_service_token_accepted_roles admin 收敛到专门的 service role 过宽 → 更多用户可让过期 token 生效
26 过期 token 缓存时长 rgw_keystone_expired_token_cache_expiration 3600 必须低于 Keystone 的 [token]/allow_expired_window 超过则该机制失效/行为异常
27 LDAP 启用 rgw_s3_auth_use_ldap false 需要 LDAP 时设 true —
28 LDAP 传输加密 rgw_ldap_uri 文档未明确 必须用 ldaps://<fqdn>:<port> 用 ldap:// 会明文传输凭证
29 LDAP 搜索范围收敛 rgw_ldap_searchdn / rgw_ldap_searchfilter cn=users,cn=accounts,dc=example,dc=com / 文档未明确 限定到具体 OU / 组 过宽 → 目录内所有用户都可认证
30 LDAP 用户名冲突 — — 禁止 LDAP 用户与本地用户重名 RGW 无法区分,会当作同一用户处理
31 LDAP 日志级别 — — 把 rgw 日志级别设得足够低 日志可能泄露 base64 编码的凭证/access token
32 OPA 授权 rgw_use_opa_authz false 有 OPA 时设 true 未启用则不做额外授权检查
33 OPA SSL 校验 rgw_opa_verify_ssl true true 关闭后不校验 OPA 证书
34 OPA bearer token rgw_opa_token 文档未明确 妥善保管 泄露 → 可伪造对 OPA 的查询
35 Lua 脚本总开关 rgw_lua_enable true 不用时关闭 开启时脚本在每个请求上执行,可能拖慢网关
36 Lua 内存上限 rgw_lua_max_memory_per_state 128000 谨慎调整 改小到低于当前用量 → background state 数据全部丢失
37 Lua 运行时间上限 rgw_lua_max_runtime_per_state 1000 保持默认,不要设为 0 设为 0 → 无界执行,可能拖垮 RGW 可用性
38 Lua 包安装目录 rgw_luarocks_location /tmp/rgw_luarocks/$name 不要设为 $HOME/.luarocks、/usr/lib64/lua、/usr/share/lua 设为默认位置会与主机 Lua 包混在一起
39 QAT 压缩加速 qat_compressor_enabled false 有 QAT 硬件时设 true 无硬件却开启 → 行为异常
40 QAT 加密加速器 plugin_crypto_accelerator crypto_isal 需要 QAT 加密时设 crypto_qat(且需 WITH_QATDRV=ON 构建、QAT 配置 section 名为 CEPH) 未以 WITH_QATDRV=ON 构建则插件不可用
41 UADK 压缩加速 uadk_compressor_enabled false aarch64 + 鲲鹏加速器时设 true 架构不兼容时 UADK 不可用
42 MFA 多站点 — — 不同 zone 使用不同 token MFA OTP 配置驻留在本地 zone 的 OSD,多站点共用 token 会出现不一致
43 桶策略不支持用户级策略 — — 账户拥有者需直接授权到具体用户 把整个账户授权给桶 = 授权给该账户下所有用户
44 桶策略字符串插值 — — 不要在桶策略里依赖插值 桶策略不支持字符串插值(role policy 支持)

文档未明确项

本册范围内,官方源文未给出明确说明的事项,汇总如下(不编造、不推断):

# 事项 所属章节 / 页面
1 SSE-C 的密钥本体请求头名与 MD5 头名(AWS 中为 x-amz-server-side-encryption-customer-key 等) encryption
2 SSE 各模式的完整请求头清单(encryption.rst 只指向 Amazon 规范,未逐项列出) encryption
3 rgw_crypt_default_encryption_key 的默认值(rgw.yaml.in 中无 default: 字段,level 为 dev) encryption
4 rgw_trust_forwarded_https 的默认值 encryption
5 rgw_keystone_api_version 的完整定义(未在 rgw.yaml.in / global.yaml.in 中找到该条目) keystone
6 rgw_barbican_url、rgw_keystone_barbican_*、rgw_keystone_admin_*、rgw_keystone_url 等一批 str 配置的默认值(均无 default: 字段) barbican、keystone
7 rgw_crypt_vault_token_file、rgw_crypt_vault_addr、rgw_crypt_vault_prefix、rgw_crypt_vault_namespace、各 SSL 路径配置的默认值 vault
8 rgw_crypt_kmip_* 中除 rgw_crypt_kmip_s3_key_template($keyid)外的默认值 kmip
9 rgw_ldap_uri 的默认值(default: 字段为空)与 rgw_ldap_searchfilter 的默认值 ldap-auth
10 rgw_opa_url、rgw_opa_token 的默认值 opa
11 OPA input 结构中除 method、subuser、user_info.*、bucket_info.* 之外的其它字段 opa
12 IAM API 中全部 60 个 action 的 Remarks(官方表格该列全为空) iam
13 IAM policy 与 bucket policy 的优先级裁决规则(源文未给出显式规则) iam、bucketpolicy
14 AssumeRole / AssumeRoleWithWebIdentity / GetSessionToken 的响应结构字段清单(源文只给出 response['Credentials']['AccessKeyId' | 'SecretAccessKey' | 'SessionToken'] 的用法,未给出完整响应 schema) STS、STSLite
15 rgw_sts_key 的默认值(无 default: 字段) STS、STSLite
16 IAM 中 sts:authentication condition key 的完整语义(仅出现 BoolIfExists 一种用法) STSLite
17 lua-scripting 中"500K byte"与 rgw_lua_max_memory_per_state 默认值 128000 的不一致如何裁决 lua-scripting
18 background 上下文的执行间隔是否可配置、用哪个配置项(源文只说"默认间隔为 5 秒") lua-scripting
19 Reques.Bucket.Quota.Enabled 是否为 Request.Bucket.Quota.Enabled 的拼写错误(源文原样保留) lua-scripting
20 Lua RGW table 是否支持跨实例共享(源文明确说每实例私有,但未说明是否提供共享机制) lua-scripting
21 UADK 的显式验证命令(QAT 给出了 cpa_sample_code,UADK 未给) uadk-accel
22 api.rst(librgw Python)中 LibRGWFS 与 FileHandle 的方法清单(由构建期 automodule 生成,rst 源文中无内容) api
23 MFA 中 MFA 与桶 versioning 的具体绑定方式(源文只说"桶需通过 S3 api 配置为 versioning 且 MFA 启用",未给出具体 API/命令) mfa
24 Vault agent 场景下 secret-id 的正确获取命令(源文该处与 role-id 命令重复,疑为笔误) vault