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

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

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

目标

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

准备工作

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

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

确保您具有以下 IAM 角色

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

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

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

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

工作负载身份池支持各种身份提供方,包括 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 Service,您必须创建一个有凭据的客户端。然后,您可以使用此具有凭据的客户端发出证书请求:

    1. 启动存储了凭据的 CA Service 客户端:

      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 privatecaPool 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 角色。

后续步骤