注意

本文档适用于 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

  1. AssumeRole:返回一组可用于跨账户访问的临时凭证。临时凭证将具有角色所附带的权限策略和 AssumeRole API 所附带的策略都允许的权限。

    参数

    RoleArn (字符串/必需):要承担的角色的 ARN。

    RoleSessionName (字符串/必需):所承担的角色会话的标识符。

    Policy (字符串/可选):JSON 格式的 IAM 策略。

    DurationSeconds (整数/可选):会话的持续时间(秒)。其默认值为 3600。

    ExternalId (字符串/可选):在另一个账户中承担角色时可能使用的唯一 ID。

    SerialNumber (字符串/可选):进行 AssumeRole 调用的用户关联的 MFA 设备的 ID 号。

    TokenCode (字符串/可选):MFA 设备提供的值,如果所承担角色的信任策略需要 MFA。

  2. 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 进行身份验证后返回该令牌。

  3. 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 都可以通过同一端点访问。

示例

  1. 为了使示例正常工作,请确保用户 TESTER 已分配 roles 功能

    radosgw-admin caps add --uid="TESTER" --caps="roles=*"
    
  2. 以下是 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()
    
  3. 以下是 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()
    
  4. 以下是假设角色的 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()
    
  5. 以下是使用用户凭证进行 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 的指纹

  1. 获取 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

  2. 使用以下 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 .
    
  3. 从步骤 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 .
    
  4. 将上面响应中 x5c 的结果复制到文件 certificate.crt 中,并在开头添加 -----BEGIN CERTIFICATE-----,在末尾添加 -----END CERTIFICATE-----

  5. 使用以下 OpenSSL 命令获取证书指纹

    openssl x509 -in certificate.crt -fingerprint -noout
    
  6. 步骤 5 中上述命令的结果将是 SHA1 指纹,如下所示

    SHA1 Fingerprint=F7:D7:B3:51:5D:D0:D3:19:DD:21:9A:43:A9:EA:72:7A:D6:06:52:87
    
  7. 删除上述结果中的冒号以获取最终指纹,该指纹可用作在 IAM 中创建 OpenID Connect 提供商实体时的输入

    F7D7B3515DD0D319DD219A43A9EA727AD6065287
    

RGW 中的角色

有关角色操作的更多信息可以在这里找到 角色

RGW 中的 OpenID Connect 提供商

有关 OpenID Connect 提供商实体操作的更多信息可以在这里找到 RGW 中的 OpenID Connect 提供商

Keycloak 与 RGW 集成

RGW 与 Keycloak 集成的步骤可以在这里找到 Keycloak 与 RadosGW 集成

STSLite

STSLite 是在 STS 上构建的,相关文档可以在这里找到 STS Lite

由 Ceph 基金会为您呈现

Ceph 文档是由非营利性 Ceph 基金会 资助和托管的社区资源。如果您希望支持这项工作和我们的其他努力,请考虑 立即加入