3826 lines
165 KiB
Markdown
3826 lines
165 KiB
Markdown
# 第 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](https://docs.ceph.com/en/tentacle/radosgw/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](https://docs.ceph.com/en/tentacle/radosgw/encryption/)
|
||
|
||
### 1.5 自动加密(仅供测试)
|
||
|
||
可以在 `ceph.conf` 中设置 `rgw crypt default encryption key`,强制加密**所有没有另行指定加密模式的对象**。该配置接受一个 base64 编码的 256 位密钥。
|
||
|
||
```ini
|
||
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](https://docs.ceph.com/en/tentacle/radosgw/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 命令行工具,需要先设置:
|
||
|
||
```bash
|
||
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](https://docs.ceph.com/en/tentacle/radosgw/vault/)
|
||
|
||
#### 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**:
|
||
|
||
```bash
|
||
vault policy write rgw-kv-policy -<<EOF
|
||
path "secret/data/*" {
|
||
capabilities = ["read"]
|
||
}
|
||
EOF
|
||
```
|
||
|
||
**Transit secret engine**:
|
||
|
||
```bash
|
||
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,可能需要):
|
||
|
||
```bash
|
||
vault policy write old-rgw-transit-policy -<<EOF
|
||
path "transit/export/encryption-key/*" {
|
||
capabilities = ["read"]
|
||
}
|
||
EOF
|
||
```
|
||
|
||
##### Token 认证
|
||
|
||
> ⚠️ 官方 note:**生产环境绝不要使用 root token 配合 Ceph。**
|
||
|
||
```ini
|
||
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 认证机制。
|
||
|
||
```ini
|
||
rgw crypt vault auth = agent
|
||
rgw crypt vault addr = http://127.0.0.1:8100
|
||
```
|
||
|
||
配置 AppRole:
|
||
|
||
```bash
|
||
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`):
|
||
|
||
```bash
|
||
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`):
|
||
|
||
```bash
|
||
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`):
|
||
|
||
```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"
|
||
}
|
||
```
|
||
|
||
启动常驻守护进程:
|
||
|
||
```bash
|
||
/usr/local/bin/vault agent -config=/usr/local/etc/vault/rgw-agent.hcl
|
||
```
|
||
|
||
启动后,agent 应在 localhost 的 8100 端口监听,可用 `vault` 命令与之交互验证。
|
||
|
||
#### 2.1.4 Vault Namespaces
|
||
|
||
Vault 企业版支持 namespace(命名空间)概念,用于在同一个组织内为不同团队做集中管理并保证彼此隔离(租户)。
|
||
|
||
```ini
|
||
rgw crypt vault namespace = tenant1
|
||
```
|
||
|
||
#### 2.1.5 在 Vault 中创建密钥
|
||
|
||
> ⚠️ 服务端加密密钥必须 256 位长、base64 编码。
|
||
|
||
**KV engine**:
|
||
|
||
```bash
|
||
vault kv put secret/myproject/mybucketkey key=$(openssl rand -base64 32)
|
||
```
|
||
|
||
样例输出:
|
||
|
||
```text
|
||
====== 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**:
|
||
|
||
```bash
|
||
vault write -f transit/keys/mybucketkey
|
||
```
|
||
|
||
该命令创建一个 keyring,默认包含类型为 `aes256-gcm96` 的密钥。验证:
|
||
|
||
```bash
|
||
vault read transit/keys/mybucketkey
|
||
```
|
||
|
||
样例输出:
|
||
|
||
```text
|
||
Key Value
|
||
--- -----
|
||
derived false
|
||
exportable false
|
||
name mybucketkey
|
||
type aes256-gcm96
|
||
```
|
||
|
||
#### 2.1.6 配置 Ceph Object Gateway
|
||
|
||
```ini
|
||
# 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` 结尾(这是此前文档记载的配置),会自动选中此模式** | 兼容旧部署 |
|
||
|
||
```ini
|
||
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 要带上路径):
|
||
|
||
```bash
|
||
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)**:
|
||
|
||
```bash
|
||
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](https://docs.ceph.com/en/tentacle/radosgw/vault/)
|
||
|
||
---
|
||
|
||
### 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 客户端库。
|
||
|
||
准备环境:
|
||
|
||
```bash
|
||
virtualenv $HOME/my-kmip-env
|
||
source $HOME/my-kmip-env/bin/activate
|
||
pip install pykmip
|
||
```
|
||
|
||
准备客户端配置文件:
|
||
|
||
```bash
|
||
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 里执行):
|
||
|
||
```python
|
||
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
|
||
|
||
```ini
|
||
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 上传对象
|
||
|
||
```bash
|
||
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](https://docs.ceph.com/en/tentacle/radosgw/kmip/)
|
||
|
||
---
|
||
|
||
### 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 取回密钥使用的新用户,例如:
|
||
|
||
```text
|
||
user = rgwcrypt-user
|
||
pass = rgwcrypt-password
|
||
tenant = rgwcrypt
|
||
```
|
||
|
||
#### 2.3.3 在 Barbican 中创建密钥
|
||
|
||
> ⚠️ 服务端加密密钥必须 256 位长、base64 编码。
|
||
> 请求 Barbican 必须在 `X-Auth-Token` 头中带有效的 Keystone token。
|
||
|
||
```http
|
||
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"
|
||
}
|
||
```
|
||
|
||
响应:
|
||
|
||
```json
|
||
{"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`:
|
||
|
||
```http
|
||
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
|
||
}
|
||
}
|
||
```
|
||
|
||
响应:
|
||
|
||
```json
|
||
{"acl_ref": "http://barbican.example.com:9311/v1/secrets/d1e7ef3b-f841-4b7c-90b2-b7d90ca2d723/acl"}
|
||
```
|
||
|
||
#### 2.3.4 配置 Ceph Object Gateway
|
||
|
||
```ini
|
||
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 时:
|
||
|
||
```ini
|
||
rgw keystone barbican tenant = rgwcrypt
|
||
```
|
||
|
||
使用 API version 3 时:
|
||
|
||
```ini
|
||
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](https://docs.ceph.com/en/tentacle/radosgw/barbican/)
|
||
|
||
---
|
||
|
||
## 三、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](https://docs.ceph.com/en/tentacle/radosgw/iam/)
|
||
|
||
---
|
||
|
||
## 四、角色与 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
|
||
|
||
```bash
|
||
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 |
|
||
|
||
示例:
|
||
|
||
```bash
|
||
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\"\]\}\]\}
|
||
```
|
||
|
||
响应:
|
||
|
||
```json
|
||
{
|
||
"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
|
||
|
||
```bash
|
||
radosgw-admin role delete --role-name={role-name}
|
||
```
|
||
|
||
| 参数 | 说明 | 类型 |
|
||
|---|---|---|
|
||
| `role-name` | Role 的名称 | String |
|
||
|
||
```bash
|
||
radosgw-admin role delete --role-name=S3Access1
|
||
```
|
||
|
||
> 注意:**只有当 role 没有挂载任何权限策略时才能删除它。**
|
||
|
||
##### 获取 Role
|
||
|
||
```bash
|
||
radosgw-admin role get --role-name={role-name}
|
||
```
|
||
|
||
| 参数 | 说明 | 类型 |
|
||
|---|---|---|
|
||
| `role-name` | Role 的名称 | String |
|
||
|
||
```bash
|
||
radosgw-admin role get --role-name=S3Access1
|
||
```
|
||
|
||
响应同创建时的 JSON 结构(`id` / `name` / `path` / `arn` / `create_date` / `max_session_duration` / `assume_role_policy_document`)。
|
||
|
||
##### 列出 Role
|
||
|
||
```bash
|
||
radosgw-admin role list [--path-prefix ={path prefix}]
|
||
```
|
||
|
||
| 参数 | 说明 | 类型 |
|
||
|---|---|---|
|
||
| `path-prefix` | 用于过滤 role 的路径前缀。**不指定则列出所有 role** | String |
|
||
|
||
```bash
|
||
radosgw-admin role list --path-prefix="/application"
|
||
```
|
||
|
||
响应为 role 对象数组。
|
||
|
||
##### 更新 Role 的 Assume Role Policy Document
|
||
|
||
```bash
|
||
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 |
|
||
|
||
```bash
|
||
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
|
||
|
||
```bash
|
||
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 |
|
||
|
||
```bash
|
||
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`:
|
||
|
||
```bash
|
||
radosgw-admin role-policy put --role-name=S3Access1 --policy-name=Policy1 --infile policy-document.json
|
||
```
|
||
|
||
##### 列出 Role 上挂载的 Policy 名称
|
||
|
||
```bash
|
||
radosgw-admin role policy get --role-name={role-name}
|
||
```
|
||
|
||
| 参数 | 说明 | 类型 |
|
||
|---|---|---|
|
||
| `role-name` | Role 的名称 | String |
|
||
|
||
```bash
|
||
radosgw-admin role-policy list --role-name=S3Access1
|
||
```
|
||
|
||
```json
|
||
[
|
||
"Policy1"
|
||
]
|
||
```
|
||
|
||
> 💡 补充:源文中该小节的命令原型写作 `radosgw-admin role policy get`,但给出的示例却是 `radosgw-admin role-policy list`。二者在官方源文中并存,实际使用时以 `radosgw-admin role-policy list` 为准。
|
||
|
||
##### 获取 Role 上挂载的指定 Policy
|
||
|
||
```bash
|
||
radosgw-admin role policy get --role-name={role-name} --policy-name={policy-name}
|
||
```
|
||
|
||
| 参数 | 说明 | 类型 |
|
||
|---|---|---|
|
||
| `role-name` | Role 的名称 | String |
|
||
| `policy-name` | Policy 的名称 | String |
|
||
|
||
```bash
|
||
radosgw-admin role-policy get --role-name=S3Access1 --policy-name=Policy1
|
||
```
|
||
|
||
```json
|
||
{
|
||
"Permission policy": "{\"Version\":\"2012-10-17\",\"Statement\":[{\"Effect\":\"Allow\",\"Action\":[\"s3:*\"],\"Resource\":\"arn:aws:s3:::example_bucket\"}]}"
|
||
}
|
||
```
|
||
|
||
##### 删除 Role 上挂载的 Policy
|
||
|
||
```bash
|
||
radosgw-admin role policy delete --role-name={role-name} --policy-name={policy-name}
|
||
```
|
||
|
||
| 参数 | 说明 | 类型 |
|
||
|---|---|---|
|
||
| `role-name` | Role 的名称 | String |
|
||
| `policy-name` | Policy 的名称 | String |
|
||
|
||
```bash
|
||
radosgw-admin role-policy delete --role-name=S3Access1 --policy-name=Policy1
|
||
```
|
||
|
||
##### 更新 Role
|
||
|
||
```bash
|
||
radosgw-admin role update --role-name={role-name} --max-session-duration={max-session-duration}
|
||
```
|
||
|
||
| 参数 | 说明 | 类型 |
|
||
|---|---|---|
|
||
| `role-name` | Role 的名称 | String |
|
||
| `max-session-duration` | Role 的最大会话时长 | String |
|
||
|
||
```bash
|
||
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 的用户:
|
||
|
||
```bash
|
||
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)**:
|
||
|
||
```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)**:
|
||
|
||
```xml
|
||
<PolicyNames>
|
||
<member>Policy1</member>
|
||
</PolicyNames>
|
||
```
|
||
|
||
**GetRolePolicy 响应(XML)**:
|
||
|
||
```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)**:
|
||
|
||
```xml
|
||
<TagRoleResponse>
|
||
<ResponseMetadata>
|
||
<RequestId>tx000000000000000000004-00611f337e-1027-default</RequestId>
|
||
</ResponseMetadata>
|
||
</TagRoleResponse>
|
||
```
|
||
|
||
**ListRoleTags 响应(XML)**:
|
||
|
||
```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)**:
|
||
|
||
```xml
|
||
<UntagRoleResponse>
|
||
<ResponseMetadata>
|
||
<RequestId>tx000000000000000000007-00611f337e-1027-default</RequestId>
|
||
</ResponseMetadata>
|
||
</UntagRoleResponse>
|
||
```
|
||
|
||
**UpdateRole 响应(XML)**:
|
||
|
||
```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 标签
|
||
|
||
```python
|
||
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](https://docs.ceph.com/en/tentacle/radosgw/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:
|
||
|
||
```json
|
||
{"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:
|
||
|
||
```json
|
||
{"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:
|
||
|
||
```json
|
||
{"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。
|
||
|
||
```bash
|
||
radosgw-admin caps add --uid="TESTER" --caps="roles=*"
|
||
```
|
||
|
||
完整流程(TESTER1 assume 由 TESTER 创建的 role,按挂载在 role 上的权限策略访问 TESTER 拥有的 S3 资源):
|
||
|
||
```python
|
||
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)
|
||
|
||
```python
|
||
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 取配置文档:
|
||
|
||
```bash
|
||
curl -k -v \
|
||
-X GET \
|
||
-H "Content-Type: application/x-www-form-urlencoded" \
|
||
"http://localhost:8000/auth/realms/quickstart/.well-known/openid-configuration" \
|
||
| jq .
|
||
```
|
||
|
||
3. 从步骤 2 的响应中取 `jwks_uri` 的值,获取 IDP 的证书:
|
||
|
||
```bash
|
||
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 .
|
||
```
|
||
|
||
4. 把响应中 `x5c` 的内容复制到文件 `certificate.crt`,在开头加 `-----BEGIN CERTIFICATE-----`,结尾加 `-----END CERTIFICATE-----`。
|
||
|
||
5. 用 OpenSSL 取证书指纹:
|
||
|
||
```bash
|
||
openssl x509 -in certificate.crt -fingerprint -noout
|
||
```
|
||
|
||
6. 得到 SHA1 指纹,形如:
|
||
|
||
```text
|
||
SHA1 Fingerprint=F7:D7:B3:51:5D:D0:D3:19:DD:21:9A:43:A9:EA:72:7A:D6:06:52:87
|
||
```
|
||
|
||
7. 去掉冒号得到最终 thumbprint,可作为在 IAM 中创建 OpenID Connect Provider 实体的输入:
|
||
|
||
```text
|
||
F7D7B3515DD0D319DD219A43A9EA727AD6065287
|
||
```
|
||
|
||
> 引自:[STS in Ceph](https://docs.ceph.com/en/tentacle/radosgw/STS/)
|
||
|
||
---
|
||
|
||
### 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:
|
||
|
||
```bash
|
||
radosgw-admin caps add --uid="TESTER" --caps="user-policy=*"
|
||
```
|
||
|
||
需要挂到用户 `TESTER1` 上的策略:
|
||
|
||
```json
|
||
{"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 配置
|
||
|
||
最小配置:
|
||
|
||
```ini
|
||
[client.radosgw.gateway]
|
||
rgw sts key = {sts key for encrypting the session token}
|
||
rgw s3 auth use sts = true
|
||
```
|
||
|
||
与 Keystone 配合的完整配置集:
|
||
|
||
```ini
|
||
[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 配合的完整配置集:
|
||
|
||
```ini
|
||
[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`**:
|
||
|
||
```bash
|
||
ceph-authtool --gen-print-key
|
||
```
|
||
|
||
生成的 key 会是 base64 编码(编码后的 key 还会包含 key type 与 timestamp)。
|
||
|
||
> 注意:**STS 与 S3 API 默认共存于同一命名空间,可通过同一 endpoint 访问。**
|
||
|
||
#### 4.3.6 STS Lite + Keystone 完整示例
|
||
|
||
**步骤 1**:生成 EC2 凭证。
|
||
|
||
```bash
|
||
openstack ec2 credentials create
|
||
```
|
||
|
||
```text
|
||
+------------+--------------------------------------------------------+
|
||
| 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` 换取临时凭证。
|
||
|
||
```python
|
||
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 调用。
|
||
|
||
```python
|
||
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'` 这段):
|
||
|
||
```python
|
||
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](https://docs.ceph.com/en/tentacle/radosgw/STSLite/)
|
||
|
||
---
|
||
|
||
### 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 的示例:
|
||
|
||
```json
|
||
{
|
||
"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` 会失败。
|
||
|
||
```json
|
||
{
|
||
"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)**:
|
||
|
||
```json
|
||
{
|
||
"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)**:
|
||
|
||
```json
|
||
{
|
||
"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)**:
|
||
|
||
```json
|
||
{
|
||
"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)**:
|
||
|
||
```json
|
||
{
|
||
"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)**:
|
||
|
||
```json
|
||
{
|
||
"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 上附着的标签。
|
||
|
||
```json
|
||
{
|
||
"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)上的标签。
|
||
|
||
```json
|
||
{
|
||
"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`:
|
||
|
||
```python
|
||
# -*- 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](https://docs.ceph.com/en/tentacle/radosgw/session-tags/)
|
||
|
||
---
|
||
|
||
## 五、桶策略(bucketpolicy.rst)
|
||
|
||
> *Bucket policies 在 Ceph 的 Luminous 版本中引入。*
|
||
|
||
Ceph Object Gateway 支持 **Amazon S3 policy 语言的一个子集**,应用于桶。
|
||
|
||
### 5.1 创建与删除
|
||
|
||
桶策略**通过标准 S3 操作管理,而不是 `radosgw-admin`**。用 `s3cmd` 设置/删除策略:
|
||
|
||
```bash
|
||
$ 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](https://docs.ceph.com/en/tentacle/radosgw/bucketpolicy/)
|
||
|
||
---
|
||
|
||
## 六、外部身份源集成
|
||
|
||
### 6.1 OpenStack Keystone(keystone.rst)
|
||
|
||
可以把 Ceph Object Gateway 与 OpenStack 的身份服务 Keystone 集成。这让网关**接受 Keystone 作为用户权威(users authority)**:
|
||
|
||
- 被 Keystone 授权访问网关的用户,**会在 Ceph Object Gateway 上被自动创建**(如果此前不存在);
|
||
- 被 Keystone 判定有效的 token,网关也认为有效。
|
||
|
||
#### 6.1.1 配置项
|
||
|
||
```ini
|
||
[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 权限。
|
||
|
||
```ini
|
||
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` 替换为:
|
||
|
||
```ini
|
||
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:
|
||
|
||
```bash
|
||
openstack service create --name=swift \
|
||
--description="Swift Service" \
|
||
object-store
|
||
```
|
||
|
||
```text
|
||
+-------------+----------------------------------+
|
||
| Field | Value |
|
||
+-------------+----------------------------------+
|
||
| description | Swift Service |
|
||
| enabled | True |
|
||
| id | 37c4c0e79571404cb4644201a4a6e5ee |
|
||
| name | swift |
|
||
| type | object-store |
|
||
+-------------+----------------------------------+
|
||
```
|
||
|
||
```bash
|
||
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
|
||
```
|
||
|
||
```text
|
||
+--------------+------------------------------------------+
|
||
| 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 |
|
||
+--------------+------------------------------------------+
|
||
```
|
||
|
||
查看:
|
||
|
||
```bash
|
||
$ 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 的桶,需要启用:
|
||
|
||
```ini
|
||
rgw swift account in url = true
|
||
```
|
||
|
||
Keystone 的 object-store endpoint 也必须相应配置为包含 `AUTH_%(project_id)s` 后缀:
|
||
|
||
```bash
|
||
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](https://docs.ceph.com/en/tentacle/radosgw/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 连接:
|
||
|
||
```bash
|
||
# 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**
|
||
|
||
```text
|
||
"objectclass=inetorgperson"
|
||
```
|
||
|
||
Object Gateway 仍会按常规用 token 中的用户名和 `rgw_ldap_dnattr` 的值生成 search filter,然后与 `rgw_search_filter` 中的 partial filter 组合。取决于用户名与设置,最终 search filter 可能是:
|
||
|
||
```text
|
||
"(&(uid=hari)(objectclass=inetorgperson))"
|
||
```
|
||
|
||
因此用户 `hari` 只有在:① 在 LDAP 目录中被找到;② object class 为 `inetorgperson`;③ 提供了有效密码 时,才会被授予访问。
|
||
|
||
**② 指定 complete filter(完整过滤器)**
|
||
|
||
完整过滤器**必须包含 `@USERNAME@` token**,认证时会替换为用户名。此时**不再使用** `rgw_ldap_dnattr` 参数。例如,把有效用户限制在特定组:
|
||
|
||
```text
|
||
"(&(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。
|
||
|
||
```bash
|
||
# export RGW_ACCESS_KEY_ID="<username>"
|
||
# export RGW_SECRET_ACCESS_KEY="<password>"
|
||
# radosgw-token --encode
|
||
```
|
||
|
||
也可以手工生成 —— 把下面这段 JSON 做 base64 编码即可(当没有安装 `radosgw-token` 工具时):
|
||
|
||
```json
|
||
{
|
||
"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 指定在客户端或环境变量中:
|
||
|
||
```bash
|
||
# export AWS_ACCESS_KEY_ID=<base64-encoded token generated by radosgw-token>
|
||
# export AWS_SECRET_ACCESS_KEY="" # 定义为空字符串,否则工具可能抱怨缺少环境变量
|
||
```
|
||
|
||
> ⚠️ **access token 是 base64 编码的 JSON 结构,其中包含明文形式的 LDAP 凭证。除非你想分享你的明文密码,否则不要分享它!**
|
||
|
||
> 引自:[LDAP Authentication](https://docs.ceph.com/en/tentacle/radosgw/ldap-auth/)
|
||
|
||
---
|
||
|
||
### 6.3 OpenID Connect Provider(oidc.rst)
|
||
|
||
要在 RGW 与 IDP 之间建立信任,必须**先在 RGW 中创建描述 OpenID Connect Provider 的实体**。
|
||
|
||
调用这些 REST admin API 需要创建带 admin caps 的用户:
|
||
|
||
```bash
|
||
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 |
|
||
|
||
```text
|
||
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 |
|
||
|
||
```text
|
||
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 |
|
||
|
||
```text
|
||
POST "<hostname>?Action=Action=GetOpenIDConnectProvider
|
||
&OpenIDConnectProviderArn=arn:aws:iam:::oidc-provider/localhost:8080/auth/realms/quickstart"
|
||
```
|
||
|
||
#### 6.3.4 ListOpenIDConnectProviders
|
||
|
||
列出所有 IDP 的信息。
|
||
|
||
| 参数 | 说明 | 类型 |
|
||
|---|---|---|
|
||
| None | 无参数 | — |
|
||
|
||
```text
|
||
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 |
|
||
|
||
```text
|
||
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 |
|
||
|
||
```text
|
||
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 |
|
||
|
||
```text
|
||
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](https://docs.ceph.com/en/tentacle/radosgw/oidc/)
|
||
|
||
---
|
||
|
||
### 6.4 Keycloak(keycloak.rst)
|
||
|
||
如果 Keycloak 被配置为 OpenID Connect Identity Provider,移动应用和 Web 应用就可以用它来认证用户。利用认证过程返回的 web token,应用可以调用 `AssumeRoleWithWebIdentity`,获得一组临时 S3 凭证,并用这些凭证发起 S3 调用。
|
||
|
||
#### 6.4.1 集成步骤
|
||
|
||
**① 配置 Keycloak 与 RGW 通信** —— 添加以下配置项:
|
||
|
||
```ini
|
||
[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:
|
||
|
||
```bash
|
||
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):
|
||
|
||
```bash
|
||
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`:
|
||
|
||
```bash
|
||
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](https://docs.ceph.com/en/tentacle/radosgw/keycloak/)
|
||
|
||
---
|
||
|
||
### 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
|
||
|
||
```ini
|
||
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 执行该决策。
|
||
|
||
```http
|
||
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"
|
||
}
|
||
}
|
||
}
|
||
```
|
||
|
||
响应:
|
||
|
||
```json
|
||
{"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](https://docs.ceph.com/en/tentacle/radosgw/opa/)
|
||
|
||
---
|
||
|
||
## 七、多因素认证 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>` |
|
||
|
||
**创建**:
|
||
|
||
```bash
|
||
# 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> ]
|
||
```
|
||
|
||
**列出**:
|
||
|
||
```bash
|
||
# radosgw-admin mfa list --uid=<user-id>
|
||
```
|
||
|
||
**查看**:
|
||
|
||
```bash
|
||
# radosgw-admin mfa get --uid=<user-id> --totp-serial=<serial>
|
||
```
|
||
|
||
**删除**:
|
||
|
||
```bash
|
||
# radosgw-admin mfa remove --uid=<user-id> --totp-serial=<serial>
|
||
```
|
||
|
||
**检查**(测试 TOTP token pin,用于验证 TOTP 功能是否正常):
|
||
|
||
```bash
|
||
# radosgw-admin mfa check --uid=<user-id> --totp-serial=<serial> \
|
||
--totp-pin=<pin>
|
||
```
|
||
|
||
**重新同步**(用于时间偏移场景,**需要喂入两个连续的 pin**:前一个 pin 与当前 pin):
|
||
|
||
```bash
|
||
# 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](https://docs.ceph.com/en/tentacle/radosgw/mfa/)
|
||
|
||
---
|
||
|
||
## 八、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
|
||
|
||
**上传脚本**:
|
||
|
||
```bash
|
||
# radosgw-admin script put --infile={lua-file-path} --context={prerequest|postrequest|background|getdata|putdata} [--tenant={tenant-name}]
|
||
```
|
||
|
||
> 上传 `background` 上下文的脚本时,**不应指定 tenant 名**。
|
||
|
||
cephadm 环境下:
|
||
|
||
```bash
|
||
# cephadm shell radosgw-admin script put --infile=/rootfs/{lua-file-path} --context={prerequest|postrequest|background|getdata|putdata} [--tenant={tenant-name}]
|
||
```
|
||
|
||
**打印脚本内容到标准输出**:
|
||
|
||
```bash
|
||
# radosgw-admin script get --context={preRequest|postRequest|background|getdata|putdata} [--tenant={tenant-name}]
|
||
```
|
||
|
||
**删除脚本**:
|
||
|
||
```bash
|
||
# 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 场景下打印源对象与目标对象信息
|
||
|
||
```lua
|
||
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
|
||
|
||
```lua
|
||
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
|
||
|
||
```lua
|
||
if Request.Response.HTTPStatusCode ~= 200 then
|
||
RGWDebugLog("request is bad, use ops log")
|
||
rc = Request.Log()
|
||
RGWDebugLog("ops log return code: " .. rc)
|
||
end
|
||
```
|
||
|
||
#### 示例 4:改写错误消息(响应内容改写)
|
||
|
||
```lua
|
||
if Request.Response.HTTPStatusCode == 500 then
|
||
Request.Response.Message = "<Message> something bad happened :-( </Message>"
|
||
end
|
||
```
|
||
|
||
#### 示例 5:给对象追加客户端原本没有发送的 metadata
|
||
|
||
在 `prerequest` 上下文中:
|
||
|
||
```lua
|
||
if Request.RGWOp == 'put_obj' then
|
||
Request.HTTP.Metadata["x-amz-meta-mydata"] = "my value"
|
||
end
|
||
```
|
||
|
||
在 `postrequest` 上下文中查看 metadata:
|
||
|
||
```lua
|
||
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:
|
||
|
||
```bash
|
||
# radosgw-admin script-package add --package=lua-cjson --allow-compilation
|
||
# radosgw-admin script-package add --package=luasocket --allow-compilation
|
||
```
|
||
|
||
**第二步**:启动一个监听 Unix socket 的服务端,例如用 netcat:
|
||
|
||
```bash
|
||
# rm -f /tmp/socket
|
||
# nc -vklU /tmp/socket
|
||
```
|
||
|
||
**第三步**:重启 radosgw,并把下面脚本上传到 `postrequest` 上下文:
|
||
|
||
```lua
|
||
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:
|
||
|
||
```lua
|
||
if Request.Bucket.Name == "my-bucket" then
|
||
Request.Trace.Enable = true
|
||
end
|
||
```
|
||
|
||
如果 RGW 上**已启用 tracing**,则 `Request.Trace.Enable` 为 true,因此应对所有不匹配该桶名的请求关闭 tracing。在 `prerequest` 上下文中:
|
||
|
||
```lua
|
||
if Request.Bucket.Name ~= "my-bucket" then
|
||
Request.Trace.Enable = false
|
||
end
|
||
```
|
||
|
||
> 注意:**修改 `Request.Trace.Enable` 不会改变 tracer 本身的状态**,只是针对该请求启用或禁用 tracing。
|
||
|
||
#### 示例 8:为请求 trace 追加信息
|
||
|
||
在 `postrequest` 上下文中,可以向请求的 trace 添加属性和事件:
|
||
|
||
```lua
|
||
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` 上下文中加入:
|
||
|
||
```lua
|
||
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`)与可写响应字段。下面把二者组合成一个可直接使用的骨架(属本册补充,非源文原文):
|
||
|
||
```lua
|
||
-- 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 的原子计数,可做简单限流:
|
||
|
||
```lua
|
||
-- 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](https://docs.ceph.com/en/tentacle/radosgw/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` 并保持一致:
|
||
|
||
```ini
|
||
#...
|
||
# 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` 参数**,可用以下命令验证与设置:
|
||
|
||
```bash
|
||
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`**。从源码构建时:
|
||
|
||
```bash
|
||
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:
|
||
|
||
```ini
|
||
plugin crypto accelerator = crypto_qat
|
||
```
|
||
|
||
**③ QAT Support for Compression**
|
||
|
||
*For out-of-tree QAT*:
|
||
|
||
- 构建前确保 QAT 驱动与 QATzip 都已安装;
|
||
- 除 `ICP_ROOT` 外,把环境变量 **`QZ_ROOT`** 设置为 QATzip 源码树的根目录;
|
||
- 配置以下 CMake 选项:
|
||
|
||
```bash
|
||
./do_cmake.sh -DWITH_QATDRV=ON -DWITH_QATZIP=ON -DWITH_SYSTEM_QATZIP=ON -DWITH_QATLIB=OFF
|
||
```
|
||
|
||
- 设置环境变量以明确 QAT 配置文件中 User Process Instance Section 的 section 名:
|
||
|
||
```bash
|
||
export QAT_SECTION_NAME=CEPH
|
||
```
|
||
|
||
*For in-tree QAT*:
|
||
|
||
- 确保系统满足 QATlib System Requirements;QATlib 可从预编译包或源码安装。安装后可运行 **`cpa_sample_code`** 检查 QAT 环境是否正常;
|
||
- 若使用 QATlib 源码,Ceph 的 `cmake` 构建**默认启用 qatlib 和 qatzip 选项**,常规编译已包含 QAT-compressor 相关代码:
|
||
|
||
```bash
|
||
./do_cmake.sh
|
||
```
|
||
|
||
- 若使用系统上的预编译包,构建 Ceph 时需配置:
|
||
|
||
```bash
|
||
./do_cmake.sh -DWITH_SYSTEM_QATLIB=ON -DWITH_SYSTEM_QATZIP=ON
|
||
```
|
||
|
||
*For both*:
|
||
|
||
编辑 Ceph 的 central config DB 或配置文件(通常是 `ceph.conf`)以启用 QAT 对 **zlib** 压缩的支持:
|
||
|
||
```ini
|
||
qat compressor enabled=true
|
||
```
|
||
|
||
设置 RGW 的压缩方法:
|
||
|
||
```bash
|
||
# 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](https://docs.ceph.com/en/tentacle/radosgw/qat-accel/)
|
||
|
||
---
|
||
|
||
### 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**)。
|
||
|
||
软件栈结构:
|
||
|
||
```text
|
||
+----------------------------------+
|
||
| 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 示例:
|
||
|
||
```bash
|
||
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 会自动启用,**无需手工构建**。以下是手工构建流程(供开发者研究其构建方式):
|
||
|
||
```bash
|
||
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** 压缩的支持:
|
||
|
||
```ini
|
||
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](https://docs.ceph.com/en/tentacle/radosgw/uadk-accel/)
|
||
|
||
---
|
||
|
||
## 十、RGW 管理 API 入口(api.rst)
|
||
|
||
页面标题为 **librgw (Python)**。
|
||
|
||
`rgw` python 模块提供**类文件(file-like)的访问方式**来访问 rgw。
|
||
|
||
**API Reference**:源文以 Sphinx 的 `automodule` 指令自动生成 `rgw` 模块的 API 文档,成员包括 **`LibRGWFS`** 与 **`FileHandle`**。
|
||
|
||
```text
|
||
.. automodule:: rgw
|
||
:members: LibRGWFS, FileHandle
|
||
```
|
||
|
||
> 💡 补充:`api.rst` 源文本身极短(仅 17 行),具体的 API 签名与文档由构建期从 `rgw` python 模块自动生成,因此**本册范围内无法展开** `LibRGWFS` 与 `FileHandle` 的方法列表,**文档未明确**。
|
||
|
||
> 引自:[librgw (Python)](https://docs.ceph.com/en/tentacle/radosgw/api/)
|
||
|
||
---
|
||
|
||
## 十一、安全配置检查清单
|
||
|
||
汇总本册涉及的能力、对应配置项、建议值与风险。所有默认值均取自 `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` |
|