注意
本文档适用于 Ceph 的开发版本。
Ceph 中的 STS
安全令牌服务(Secure Token Service,STS)是 AWS 中的一项 Web 服务,用于返回一组临时安全凭证以验证联合用户。官方 AWS 文档链接在此处:https://docs.aws.amazon.com/STS/latest/APIReference/Welcome.html。
Ceph 对象网关实现了 STS API 的子集,提供用于身份和访问管理的临时凭证。这些临时凭证可用于执行后续的 S3 调用,这些调用将由 Ceph 对象网关中的 STS 引擎进行身份验证。临时凭证的权限可以通过作为参数传递给 STS API 的 IAM 策略进一步限制。
STS REST API
Ceph 对象网关中实现了以下 STS REST API
AssumeRole:返回一组可用于跨账户访问的临时凭证。临时凭证将具有角色所附带的权限策略和 AssumeRole API 所附带的策略都允许的权限。
- 参数
RoleArn (字符串/必需):要承担的角色的 ARN。
RoleSessionName (字符串/必需):所承担的角色会话的标识符。
Policy (字符串/可选):JSON 格式的 IAM 策略。
DurationSeconds (整数/可选):会话的持续时间(秒)。其默认值为 3600。
ExternalId (字符串/可选):在另一个账户中承担角色时可能使用的唯一 ID。
SerialNumber (字符串/可选):进行 AssumeRole 调用的用户关联的 MFA 设备的 ID 号。
TokenCode (字符串/可选):MFA 设备提供的值,如果所承担角色的信任策略需要 MFA。
AssumeRoleWithWebIdentity:为已通过 OpenID Connect /OAuth2.0 身份提供商的 Web/移动应用进行身份验证的用户返回一组临时凭证。目前已测试 Keycloak 并将其与 RGW 集成。
- 参数
RoleArn (字符串/必需):要承担的角色的 ARN。
RoleSessionName (字符串/必需):所承担的角色会话的标识符。
Policy (字符串/可选):JSON 格式的 IAM 策略。
DurationSeconds (整数/可选):会话的持续时间(秒)。其默认值为 3600。
ProviderId (字符串/可选):IDP 域名中完全限定的主机组件。仅对 OAuth2.0 令牌有效(对 OpenID Connect 令牌无效)。
WebIdentityToken (字符串/必需):OpenID Connect/ OAuth2.0 令牌,应用程序在与 IDP 进行身份验证后返回该令牌。
GetCallerIdentity:返回用于调用操作的 IAM 用户或角色的凭证的详细信息。
- 响应
Account 拥有或包含调用实体的账户 ID。
Arn 与调用实体关联的 ARN。
UserId 调用实体(用户或已承担的角色)的唯一标识符。
注意
执行 GetCallerIdentity 不需要任何权限。
在调用 AssumeRoleWithWebIdentity 之前,需要在 RGW 中创建 OpenID Connect 提供商实体(Web 应用程序通过其进行身份验证)。
IDP 与角色之间的信任通过向角色的信任策略添加条件来创建,该条件仅允许满足给定条件的应用程序访问。JWT 的所有声明都支持在角色的信任策略的条件中。使用条件中 'aud' 声明的策略示例如下所示
'''{"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>"}}}]}'''
上述条件中的 app_id 必须与传入令牌的 'aud' 声明匹配。
使用条件中 'sub' 声明的策略示例如下所示
"{\"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' 声明的策略示例如下所示
"{\"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>\"\}\}\}\]\}"
会为每个联合用户创建一个影子用户。用户 ID 源自传入 Web 令牌的 'sub' 字段。用户创建在一个单独的命名空间 - 'oidc' 中,这样用户 ID 就不会与 RGW 中的任何其他用户 ID 冲突。用户 ID 的格式为 - <tenant>$<user-namespace>$<sub>,其中 user-namespace 对于使用 oidc 提供商进行身份验证的用户是 'oidc'。
RGW 现在支持会话标签,这些标签可以在 Web 令牌中传递给 AssumeRoleWithWebIdentity 调用。有关会话标签的更多信息可以在这里找到 STS 中基于属性的访问控制的会话标签。
STS 配置
必须添加以下可配置选项以进行 STS 集成
- rgw_sts_key
用于加密/解密角色会话令牌的密钥。此密钥必须由 16 个十六进制字符组成,可以通过命令“openssl rand -hex 16”生成。区域中的所有 radosgw 实例都应使用相同的密钥。在多站点配置中,领域中的所有区域都应使用相同的密钥。
- 类型:
str
- rgw_s3_auth_use_sts
S3 身份验证是否使用 STS。
- 类型:
bool- 默认值:
false
注意
STS 和 S3 API 存在于同一命名空间中,S3 和 STS API 都可以通过同一端点访问。
示例
为了使示例正常工作,请确保用户
TESTER已分配roles功能radosgw-admin caps add --uid="TESTER" --caps="roles=*"以下是 AssumeRole API 调用的示例,其中显示了创建角色、为其分配策略(允许访问 S3 资源)、承担角色以获取临时凭证以及使用这些凭证访问 S3 资源的步骤。在此示例中,
TESTER1承担TESTER创建的角色,根据附加到角色的权限策略访问TESTER拥有的 S3 资源。import boto3 iam_client = boto3.client('iam', aws_access_key_id=<access_key of TESTER>, aws_secret_access_key=<secret_key of TESTER>, endpoint_url=<IAM URL>, region_name='' ) policy_document = '''{"Version":"2012-10-17","Statement":[{"Effect":"Allow","Principal":{"AWS":["arn:aws:iam:::user/TESTER1"]},"Action":["sts:AssumeRole"]}]}''' role_response = iam_client.create_role( AssumeRolePolicyDocument=policy_document, Path='/', RoleName='S3Access', ) role_policy = '''{"Version":"2012-10-17","Statement":{"Effect":"Allow","Action":"s3:*","Resource":"arn:aws:s3:::*"}}''' response = iam_client.put_role_policy( RoleName='S3Access', PolicyName='Policy1', PolicyDocument=role_policy ) sts_client = boto3.client('sts', aws_access_key_id=<access_key of TESTER1>, aws_secret_access_key=<secret_key of TESTER1>, endpoint_url=<STS URL>, region_name='', ) response = sts_client.assume_role( RoleArn=role_response['Role']['Arn'], RoleSessionName='Bob', DurationSeconds=3600 ) s3client = boto3.client('s3', aws_access_key_id = response['Credentials']['AccessKeyId'], aws_secret_access_key = response['Credentials']['SecretAccessKey'], aws_session_token = response['Credentials']['SessionToken'], endpoint_url=<S3 URL>, region_name='',) bucket_name = 'my-bucket' s3bucket = s3client.create_bucket(Bucket=bucket_name) resp = s3client.list_buckets()
以下是 AssumeRoleWithWebIdentity API 调用的示例,其中外部应用程序(其用户通过 OpenID Connect/ OAuth2 IDP(本例中为 Keycloak)进行身份验证)承担角色以获取临时凭证并根据角色的权限策略访问 S3 资源。
import boto3 iam_client = boto3.client('iam', aws_access_key_id=<access_key of TESTER>, aws_secret_access_key=<secret_key of TESTER>, endpoint_url=<IAM URL>, region_name='' ) 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()
以下是假设角色的 GetCallerIdentity API 调用的示例,其中显示了创建角色、承担角色以获取临时凭证以及使用这些凭证获取调用者身份的步骤。
import boto3 import json USER_ID = 'tester' ACCESS_KEY = 'TESTER' SECRET_KEY = 'test123' ENDPOINT_URL = 'https://:8000' REGION = 'us-east-1' ROLE_NAME = 'S3Access' ROLE_SESSION_NAME = 'Bob' DURATION_SECONDS = 3600 iam_client = boto3.client('iam', aws_access_key_id=ACCESS_KEY, aws_secret_access_key=SECRET_KEY, endpoint_url=ENDPOINT_URL, region_name=REGION ) trust_policy = json.dumps({ "Version": "2012-10-17", "Statement": [{ "Effect": "Allow", "Principal": { "AWS": [f"arn:aws:iam:::user/{USER_ID}"] }, "Action": ["sts:AssumeRole"] }] }) role_response = iam_client.create_role( RoleName=ROLE_NAME, Path='/xxx/policy/', AssumeRolePolicyDocument=trust_policy ) sts_client = boto3.client('sts', aws_access_key_id=ACCESS_KEY, aws_secret_access_key=SECRET_KEY, endpoint_url=ENDPOINT_URL, region_name=REGION ) response = sts_client.assume_role( RoleArn=role_response['Role']['Arn'], RoleSessionName=ROLE_SESSION_NAME, DurationSeconds=DURATION_SECONDS ) creds = response['Credentials'] session_sts = boto3.client('sts', aws_access_key_id=creds['AccessKeyId'], aws_secret_access_key=creds['SecretAccessKey'], aws_session_token=creds['SessionToken'], endpoint_url=ENDPOINT_URL, region_name=REGION ) identity = session_sts.get_caller_identity()
以下是使用用户凭证进行 GetCallerIdentity API 调用的示例
import boto3 sts = boto3.client('sts', aws_access_key_id=<access_key>, aws_secret_access_key=<secret_key>, endpoint_url='https://:8000', region_name='us-east-1' ) identity = sts.get_caller_identity()
如何获取 OpenID Connect 提供商 IDP 的指纹
获取 OpenID Connect 提供商的 URL 并添加
/.well-known/openid-configuration以获取 IDP 配置文档的 URL。例如,如果 IDP 的 URL 是 https://:8000/auth/realms/quickstart,则获取文档的 URL 是 https://:8000/auth/realms/quickstart/.well-known/openid-configuration使用以下 curl 命令从步骤 1 中描述的 URL 获取配置文档
curl -k -v \ -X GET \ -H "Content-Type: application/x-www-form-urlencoded" \ "https://:8000/auth/realms/quickstart/.well-known/openid-configuration" \ | jq .从步骤 2 的响应中,使用 “jwks_uri” 的值,使用以下代码获取 IDP 的证书
curl -k -v \ -X GET \ -H "Content-Type: application/x-www-form-urlencoded" \ "http://$KC_SERVER/$KC_CONTEXT/realms/$KC_REALM/protocol/openid-connect/certs" \ | jq .将上面响应中
x5c的结果复制到文件certificate.crt中,并在开头添加-----BEGIN CERTIFICATE-----,在末尾添加-----END CERTIFICATE-----。使用以下 OpenSSL 命令获取证书指纹
openssl x509 -in certificate.crt -fingerprint -noout步骤 5 中上述命令的结果将是 SHA1 指纹,如下所示
SHA1 Fingerprint=F7:D7:B3:51:5D:D0:D3:19:DD:21:9A:43:A9:EA:72:7A:D6:06:52:87
删除上述结果中的冒号以获取最终指纹,该指纹可用作在 IAM 中创建 OpenID Connect 提供商实体时的输入
F7D7B3515DD0D319DD219A43A9EA727AD6065287
RGW 中的角色
有关角色操作的更多信息可以在这里找到 角色。
RGW 中的 OpenID Connect 提供商
有关 OpenID Connect 提供商实体操作的更多信息可以在这里找到 RGW 中的 OpenID Connect 提供商。
Keycloak 与 RGW 集成
RGW 与 Keycloak 集成的步骤可以在这里找到 Keycloak 与 RadosGW 集成。
STSLite
STSLite 是在 STS 上构建的,相关文档可以在这里找到 STS Lite。