颁发用于证明第三方身份的证书

本教程演示了如何使用身份反射工作负载身份池颁发用于证明第三方身份的证书。

您可以使用身份反射来创建与证书请求者的经过验证的身份相符的证书。使用身份反射,您可以限制无特权证书请求者仅请求主题备用名称 (SAN) 与其凭据中的身份对应的证书。

目标

本教程介绍了如何将 CA 服务与工作负载身份池搭配使用,以联合第三方身份并获取用于证明此身份的证书。

准备工作

在开始之前,请确保您了解以下概念:

  • 工作负载身份池:借助工作负载身份池,您可以管理第三方身份提供方。如需了解详情,请参阅管理工作负载身份池和提供方
  • 工作负载身份联合:工作负载身份联合利用工作负载身份池向第三方身份授予对 Google Cloud 服务的访问权限。如需了解详情,请参阅工作负载身份联合
  • Security Token Service (STS):借助 Security Token Service,您可以将第三方凭据换成第一方(Google Cloud)令牌。如需了解详情,请参阅 Security Token Service
  • 身份反射:借助身份反射功能,证书请求者的经过验证的身份会反映在所请求的证书中。如需了解详情,请参阅身份反射

确保您拥有以下 IAM 角色

  • 如需管理证书授权机构 (CA) 和 CA 池,以及请求证书,您必须拥有 CA Service Operation Manager (privateca.caManager) 角色。如需详细了解 CA 服务的 IAM 角色,请参阅使用 IAM 进行访问权限控制
  • 如需管理工作负载身份池和提供方,您必须拥有 Workload Identity Pool Admin (iam.workloadIdentityPoolAdmin) 角色。
  • 如需创建服务账号,您必须拥有 Service Account Admin (iam.serviceAccountAdmin) 角色。

如需了解如何授予 IAM 角色,请参阅管理对项目、文件夹和组织的访问权限。您可以向 Google 账号、服务账号、Google 群组、Google Workspace 账号或 Cloud Identity 网域授予所需的 IAM 角色。

设置工作负载身份池和提供方

本教程介绍了如何将 Google OpenID Connect (OIDC) 提供程序与服务账号结合使用,以充当第三方身份。Google 账号 OIDC 提供程序充当第三方身份提供程序 (IDP),Google Cloud 服务账号是此 IDP 断言的第三方身份示例。

Workload Identity 池支持各种身份提供方,包括 Microsoft Azure/本地 Active Directory、AWS 和基于 SAML 的身份提供方。

如需设置工作负载身份池和提供方,请执行以下操作: 1. 如需表示一组受信任的联合身份,请创建工作负载身份池:

```
gcloud iam workload-identity-pools create IDENTITY_POOL_ID --location global --display-name "tutorial-wip"
```

Replace the following:

- <var>IDENTITY_POOL_ID</var>: The unique identifier of the new workload
  identity pool.
  1. 为您的第三方身份提供方创建工作负载身份池提供方:

    gcloud iam workload-identity-pools providers create-oidc PROVIDER_ID --location global --workload-identity-pool IDENTITY_POOL_ID --display-name "tutorial-oidc" --attribute-mapping "google.subject=assertion.sub" --issuer-uri="https://accounts.google.com"
    

    替换以下内容:

    • PROVIDER_ID:您要在工作负载身份池中创建的身份提供方的唯一标识符。

    您可以根据自己的应用场景自定义以下标志:

    • attribute-mapping:此标志用于设置第三方声明与 Google 主账号声明 google.subject 之间的映射。google.subject 是必需的映射,您可以使用 CEL 表达式将其设置为任何声明或声明组合。如需了解详情,请参阅定义属性映射和条件
    • issuer-uri:对于 OIDC 提供程序,此标志是一个可公开访问的端点,Google 会通过该端点验证第三方令牌。如需了解详情,请参阅准备外部身份提供方

    如需详细了解如何设置工作负载身份提供程序,请参阅配置工作负载身份联合

创建 CA 池和颁发 CA

本部分介绍了如何创建 CA 池并向其中添加根 CA。您可以使用此 CA 池来颁发反映身份的证书。如果您想使用现有的 CA 池和 CA,可以跳过此部分。

您还可以选择创建从属 CA,而不是根 CA。创建根 CA 有助于缩短此流程。

  1. DevOps 层级创建 CA 池:

    gcloud privateca pools create CA_POOL_ID --location LOCATION --tier devops
    

    替换以下内容:

    • CA_POOL_ID - 颁发证书的 CA Service CA 池的 ID。
    • LOCATION - CA 池的位置。

    如需详细了解如何创建 CA 池,请参阅创建 CA 池

  2. 创建根 CA:

    gcloud privateca roots create CA_ID --pool CA_POOL_ID  --location LOCATION --subject "CN=test,O=test-org"
    

    替换以下内容:

    • CA_ID - 颁发证书的证书授权机构的 ID。
    • CA_POOL_ID - 颁发证书的 CA Service CA 池的 ID。
    • LOCATION - CA 池的位置。

    如需详细了解如何创建根 CA,请参阅创建根 CA

  3. 启用从工作负载身份池联合的身份,以便从 CA 池签发证书。若要实现身份反射,CreateCertificate 的请求者需要拥有 CA Service Workload Certificate Requester (roles/privateca.workloadCertificateRequester) IAM 角色。

    您可以以各种精细程度表示工作负载身份池正文,从单个正文到跨提供程序的池中所有身份。如需了解详情,请查看可用的正文或正文集(使用 Google Cloud CLI 标签页),以便找到最适合您的用例的正文。

    gcloud privateca pools add-iam-policy-binding CA_POOL_ID --location LOCATION --role roles/privateca.workloadCertificateRequester --member "principalSet://iam.googleapis.com/projects/PROJECT_NUMBER/locations/global/workloadIdentityPools/IDENTITY_POOL_ID/*"
    

    替换以下内容:

    • PROJECT_NUMBER - 您创建工作负载身份池的项目的编号。

创建一个代表第三方身份的服务账号

以下过程假定服务账号代表第三方。本部分介绍了如何使用 GenerateIdToken IAM 端点以 OIDC 令牌的形式检索第三方身份。根据您的用例,您可能需要执行不同的步骤来获取您选择的第三方身份令牌。

gcloud iam service-accounts create SERVICE_ACCOUNT

替换以下内容:

  • SERVICE_ACCOUNT - 代表第三方身份的服务账号的 ID。

颁发用于证明第三方身份的证书

开始之前,请确保您具有 Service Account Token Creator (roles/iam.serviceAccountTokenCreator) IAM 角色。您需要此 IAM 角色才能调用 GenerateIdToken API。

如需获取用于证明第三方身份的证书,请执行以下操作:

  1. 从第三方身份提供商获取第三方身份令牌。

    curl

    export ID_TOKEN=`curl -d '{"audience":"//iam.googleapis.com/projects/PROJECT_NUMBER/locations/global/workloadIdentityPools/IDENTITY_POOL_ID/providers/PROVIDER_ID"}' -H 'Content-Type: application/json' -H "Authorization: Bearer $(gcloud auth print-access-token)" https://iamcredentials.googleapis.com/v1/projects/-/serviceAccounts/SERVICE_ACCOUNT@PROJECT_ID.iam.gserviceaccount.com:generateIdToken | python3 -c "import sys;import json;print(json.load(sys.stdin)['token'])"`
    

    替换以下内容:

    • PROJECT_ID - 您要创建资源的项目的 Google Cloud 项目 ID。

    客户端库

    如需以程序化方式访问第三方令牌,您可以从文件来源凭据或网址来源凭据中获取令牌。如需了解详情,请参阅使用客户端库、gcloud CLI 或 Terraform 进行身份验证。在本教程中,我们将遵循文件来源凭据工作流程。

    将您的凭据加载到证书请求方可读取的路径中:

    curl -d '{"audience":"//iam.googleapis.com/projects/PROJECT_NUMBER/locations/global/workloadIdentityPools/IDENTITY_POOL_ID/providers/PROVIDER_ID"}' -H 'Content-Type: application/json' -H "Authorization: Bearer $(gcloud auth print-access-token)" https://iamcredentials.googleapis.com/v1/projects/-/serviceAccounts/SERVICE_ACCOUNT@PROJECT_ID.iam.gserviceaccount.com:generateIdToken | python3 -c "import sys;import json;       print(json.load(sys.stdin)['token']) > /tmp/oidc_token.txt
    

    替换以下内容:

    • PROJECT_ID:您要在其中创建资源的项目的 ID。
  2. 使用 STS token 端点将第三方令牌换成联合 OAuth 令牌:

    curl

    export STS_TOKEN=`curl -L -X POST 'https://sts.googleapis.com/v1/token' -H 'Content-Type: application/json' \
    -d '{
        "grant_type": "urn:ietf:params:oauth:grant-type:token-exchange",
        "audience": "//iam.googleapis.com/projects/PROJECT_NUMBER/locations/global/workloadIdentityPools/IDENTITY_POOL_ID/providers/PROVIDER_ID",
        "requested_token_type": "urn:ietf:params:oauth:token-type:access_token",
        "scope": "https://www.googleapis.com/auth/cloud-platform",
        "subject_token": "'$ID_TOKEN'",
        "subject_token_type": "urn:ietf:params:oauth:token-type:jwt"
    }' | python3 -c "import sys;import json; print(json.load(sys.stdin)['access_token'])"`
    

    客户端库

    1. 创建一个名为 oidc_token.txt 的凭据配置文件,以便证书请求代码可以读取该文件以执行令牌交换。
    gcloud iam workload-identity-pools create-cred-config projects/PROJECT_NUMBER/locations/global/workloadIdentityPools/IDENTITY_POOL_ID --output-file=/tmp/cred_config.json --credential-source-file=/tmp/oidc_token.txt
    
    1. 读取 oidc_token.txt 文件以在客户端库中设置授权机制:

    Python

    import json
    
    from google.auth import identity_pool
    
    with open('/tmp/cred_config.json', 'r') as f:
      json_config_info = json.loads(f.read())
    credentials = identity_pool.Credentials.from_info(json_config_info)
    scoped_credentials = credentials.with_scopes(
        ['https://www.googleapis.com/auth/cloud-platform'])
    
  3. 使用 REFLECTED_SPIFFE 主题请求模式向 CA Service 发出请求:

    curl

    1. 可选:如果您没有 CSR,请通过执行以下命令创建 CSR。

      export TUTORIAL_CSR=$(openssl req -newkey rsa:2048 -nodes -subj / -keyout tutorial_do_not_use.key)
      
    2. 使用 CSR、有效期和反映的正文请求模式请求证书:

      curl -H "Authorization: Bearer $(echo $STS_TOKEN)" https://privateca.googleapis.com/v1/projects/PROJECT_NUMBER/locations/LOCATION/caPools/CA_POOL_ID/certificates\?alt\=json  -X POST -H "Content-Type: application/json" -H 'Accept: application/json' --data '{"lifetime": "100s", "pemCsr": "'$TUTORIAL_CSR'", "subjectMode": "REFLECTED_SPIFFE"}'
      

    客户端库

    如需将第一方令牌转发到 CA 服务,您必须创建一个有凭据的客户端。然后,您可以使用此已获授权的客户端发出证书请求:

    1. 启动具有凭据的 CA 服务客户端:

      Python

      caServiceClient = privateca_v1.CertificateAuthorityServiceClient(credentials=scoped_credentials)
      
    2. 请求证书。

      Python

      如需向 CA Service 进行身份验证,请设置应用默认凭据。 如需了解详情,请参阅为本地开发环境设置身份验证

      import google.cloud.security.privateca_v1 as privateca_v1
      from google.protobuf import duration_pb2
      
      
      def create_certificate(
          project_id: str,
          location: str,
          ca_pool_name: str,
          ca_name: str,
          certificate_name: str,
          common_name: str,
          domain_name: str,
          certificate_lifetime: int,
          public_key_bytes: bytes,
      ) -> None:
          """
          Create a Certificate which is issued by the Certificate Authority present in the CA Pool.
          The key used to sign the certificate is created by the Cloud KMS.
      
          Args:
              project_id: project ID or project number of the Cloud project you want to use.
              location: location you want to use. For a list of locations, see: https://cloud.google.com/certificate-authority-service/docs/locations.
              ca_pool_name: set a unique name for the CA pool.
              ca_name: the name of the certificate authority which issues the certificate.
              certificate_name: set a unique name for the certificate.
              common_name: a title for your certificate.
              domain_name: fully qualified domain name for your certificate.
              certificate_lifetime: the validity of the certificate in seconds.
              public_key_bytes: public key used in signing the certificates.
          """
      
          caServiceClient = privateca_v1.CertificateAuthorityServiceClient()
      
          # The public key used to sign the certificate can be generated using any crypto library/framework.
          # Also you can use Cloud KMS to retrieve an already created public key.
          # For more info, see: https://cloud.google.com/kms/docs/retrieve-public-key.
      
          # Set the Public Key and its format.
          public_key = privateca_v1.PublicKey(
              key=public_key_bytes,
              format_=privateca_v1.PublicKey.KeyFormat.PEM,
          )
      
          subject_config = privateca_v1.CertificateConfig.SubjectConfig(
              subject=privateca_v1.Subject(common_name=common_name),
              subject_alt_name=privateca_v1.SubjectAltNames(dns_names=[domain_name]),
          )
      
          # Set the X.509 fields required for the certificate.
          x509_parameters = privateca_v1.X509Parameters(
              key_usage=privateca_v1.KeyUsage(
                  base_key_usage=privateca_v1.KeyUsage.KeyUsageOptions(
                      digital_signature=True,
                      key_encipherment=True,
                  ),
                  extended_key_usage=privateca_v1.KeyUsage.ExtendedKeyUsageOptions(
                      server_auth=True,
                      client_auth=True,
                  ),
              ),
          )
      
          # Create certificate.
          certificate = privateca_v1.Certificate(
              config=privateca_v1.CertificateConfig(
                  public_key=public_key,
                  subject_config=subject_config,
                  x509_config=x509_parameters,
              ),
              lifetime=duration_pb2.Duration(seconds=certificate_lifetime),
          )
      
          # Create the Certificate Request.
          request = privateca_v1.CreateCertificateRequest(
              parent=caServiceClient.ca_pool_path(project_id, location, ca_pool_name),
              certificate_id=certificate_name,
              certificate=certificate,
              issuing_certificate_authority_id=ca_name,
          )
          result = caServiceClient.create_certificate(request=request)
      
          print("Certificate creation result:", result)
      
      

    3. 验证证书。您的证书应具有包含单个 URI SAN 的主题。用于证明身份的 SAN 采用以下格式:

      spiffe://IDENTITY_POOL_ID.PROJECT_NUMBER.global.workload.id.goog/subject/<oidc_subject_number>
      

      您需要进行如下替换:

      • IDENTITY_POOL_ID:工作负载身份池的唯一标识符。
      • PROJECT_NUMBER - 您在其中创建了工作负载身份池的项目的编号。

清理

为避免因您按照本文档创建的 CA 服务资源导致您的 Google Cloud 账号产生费用,请使用 Google Cloud CLI 执行以下操作:

  1. 删除您创建的 CA。

    1. 停用 CA:

      gcloud privateca roots disable CA_ID --pool CA_POOL_ID  --location LOCATION
      

      您需要进行如下替换:

      • CA_ID:CA 的唯一标识符。
      • CA_POOL_ID:CA 池的唯一标识符。
      • LOCATION:CA 池的位置。
    2. 删除 CA:

      gcloud privateca roots delete CA_ID --pool CA_POOL_ID  --location LOCATION --ignore-active-certificates
      

      您需要进行如下替换:

      • CA_ID:CA 的唯一标识符。
      • CA_POOL_ID:CA 池的唯一标识符。
      • LOCATION:CA 池的位置。
  2. 删除您创建的 CA 池。

    gcloud privateca pools delete CA_POOL_ID --location LOCATION
    

    您需要进行如下替换:

    • CA_POOL_ID:CA 池的唯一标识符。
    • LOCATION:CA 池的位置。

    如需详细了解 gcloud privateca pools delete 命令,请参阅 gcloud privateca pools delete

  3. 删除您创建的工作负载身份池:

    gcloud iam workload-identity-pools delete IDENTITY_POOL_ID --location global
    

    您需要进行如下替换:

    • IDENTITY_POOL_ID:工作负载身份池的唯一标识符。
  4. 删除您创建的服务账号:

    gcloud iam service-accounts delete SERVICE_ACCOUNT@PROJECT_ID.iam.gserviceaccount.com
    

    您需要进行如下替换:

    • SERVICE_ACCOUNT:工作负载身份池的唯一标识符。
    • PROJECT_ID:拥有服务账号的项目。

CA Service Workload Certificate Requester (privateca.workloadCertificateRequester) IAM 角色会将颁发的证书的主题限制为仅包含请求者的身份。确保仅向使用身份反射功能的用户或工作负载授予 CA Service Workload Certificate Requester (privateca.workloadCertificateRequester) IAM 角色。为了遵循最小权限原则,您可以避免授予 CA Service Certificate Requester (privateca.certificateRequester) IAM 角色。

后续步骤