3.5. 启用 Operator 以支持使用 AWS STS 的基于 CCO 的工作流


作为 Operator 作者设计在 Operator Lifecycle Manager (OLM)上运行的项目,您可以通过自定义项目来支持 Cloud Credential Operator (CCO),使 Operator 能够对启用了 STS 的 OpenShift Container Platform 集群上的 AWS 进行身份验证。

使用此方法时,Operator 负责创建 CredentialsRequest 对象,这意味着 Operator 需要 RBAC 权限来创建这些对象。然后,Operator 必须能够读取生成的 Secret 对象。

注意

默认情况下,与 Operator 部署相关的 pod 会挂载 serviceAccountToken 卷,以便在生成的 Secret 对象中引用服务帐户令牌。

先决条件

  • OpenShift Container Platform 4.14 或更高版本
  • 处于 STS 模式的集群
  • 基于 OLM 的 Operator 项目

流程

  1. 更新 Operator 项目的 ClusterServiceVersion (CSV)对象:

    1. 确保 Operator 有 RBAC 权限来创建 CredentialsRequests 对象:

      例 3.1. clusterPermissions 列表示例

      # ...
      install:
        spec:
          clusterPermissions:
          - rules:
            - apiGroups:
              - "cloudcredential.openshift.io"
              resources:
              - credentialsrequests
              verbs:
              - create
              - delete
              - get
              - list
              - patch
              - update
              - watch
      Copy to Clipboard Toggle word wrap
    2. 添加以下注解来声明对使用 AWS STS 的基于 CCO 工作流的方法的支持:

      # ...
      metadata:
       annotations:
         features.operators.openshift.io/token-auth-aws: "true"
      Copy to Clipboard Toggle word wrap
  2. 更新 Operator 项目代码:

    1. 从 pod 上由 Subscription 对象设置的环境变量获取角色 ARN。例如:

      // Get ENV var
      roleARN := os.Getenv("ROLEARN")
      setupLog.Info("getting role ARN", "role ARN = ", roleARN)
      webIdentityTokenPath := "/var/run/secrets/openshift/serviceaccount/token"
      Copy to Clipboard Toggle word wrap
    2. 确保具有 CredentialsRequest 对象已准备好修补并应用。例如:

      例 3.2. CredentialsRequest 对象创建示例

      import (
         minterv1 "github.com/openshift/cloud-credential-operator/pkg/apis/cloudcredential/v1"
         corev1 "k8s.io/api/core/v1"
         metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
      )
      
      var in = minterv1.AWSProviderSpec{
         StatementEntries: []minterv1.StatementEntry{
            {
               Action: []string{
                  "s3:*",
               },
               Effect:   "Allow",
               Resource: "arn:aws:s3:*:*:*",
            },
         },
      	STSIAMRoleARN: "<role_arn>",
      }
      
      var codec = minterv1.Codec
      var ProviderSpec, _ = codec.EncodeProviderSpec(in.DeepCopyObject())
      
      const (
         name      = "<credential_request_name>"
         namespace = "<namespace_name>"
      )
      
      var CredentialsRequestTemplate = &minterv1.CredentialsRequest{
         ObjectMeta: metav1.ObjectMeta{
             Name:      name,
             Namespace: "openshift-cloud-credential-operator",
         },
         Spec: minterv1.CredentialsRequestSpec{
            ProviderSpec: ProviderSpec,
            SecretRef: corev1.ObjectReference{
               Name:      "<secret_name>",
               Namespace: namespace,
            },
            ServiceAccountNames: []string{
               "<service_account_name>",
            },
            CloudTokenPath:   "",
         },
      }
      Copy to Clipboard Toggle word wrap

      另外,如果您从 YAML 表单的 CredentialsRequest 对象开始(例如,作为 Operator 项目代码的一部分),您可以以不同的方式处理它:

      例 3.3. 以 YAML 格式创建 CredentialsRequest 对象示例

      // CredentialsRequest is a struct that represents a request for credentials
      type CredentialsRequest struct {
        APIVersion string `yaml:"apiVersion"`
        Kind       string `yaml:"kind"`
        Metadata   struct {
           Name      string `yaml:"name"`
           Namespace string `yaml:"namespace"`
        } `yaml:"metadata"`
        Spec struct {
           SecretRef struct {
              Name      string `yaml:"name"`
              Namespace string `yaml:"namespace"`
           } `yaml:"secretRef"`
           ProviderSpec struct {
              APIVersion     string `yaml:"apiVersion"`
              Kind           string `yaml:"kind"`
              StatementEntries []struct {
                 Effect   string   `yaml:"effect"`
                 Action   []string `yaml:"action"`
                 Resource string   `yaml:"resource"`
              } `yaml:"statementEntries"`
              STSIAMRoleARN   string `yaml:"stsIAMRoleARN"`
           } `yaml:"providerSpec"`
      
           // added new field
            CloudTokenPath   string `yaml:"cloudTokenPath"`
        } `yaml:"spec"`
      }
      
      // ConsumeCredsRequestAddingTokenInfo is a function that takes a YAML filename and two strings as arguments
      // It unmarshals the YAML file to a CredentialsRequest object and adds the token information.
      func ConsumeCredsRequestAddingTokenInfo(fileName, tokenString, tokenPath string) (*CredentialsRequest, error) {
        // open a file containing YAML form of a CredentialsRequest
        file, err := os.Open(fileName)
        if err != nil {
           return nil, err
        }
        defer file.Close()
      
        // create a new CredentialsRequest object
        cr := &CredentialsRequest{}
      
        // decode the yaml file to the object
        decoder := yaml.NewDecoder(file)
        err = decoder.Decode(cr)
        if err != nil {
           return nil, err
        }
      
        // assign the string to the existing field in the object
        cr.Spec.CloudTokenPath = tokenPath
      
        // return the modified object
        return cr, nil
      }
      Copy to Clipboard Toggle word wrap
      注意

      目前不支持在 Operator 捆绑包中添加 CredentialsRequest 对象。

    3. 在凭证请求中添加角色 ARN 和 Web 身份令牌路径,并在 Operator 初始化过程中应用它:

      例 3.4. 在 Operator 初始化过程中应用 CredentialsRequest 对象示例

      // apply credentialsRequest on install
      credReq := credreq.CredentialsRequestTemplate
      credReq.Spec.CloudTokenPath = webIdentityTokenPath
      
      c := mgr.GetClient()
      if err := c.Create(context.TODO(), credReq); err != nil {
         if !errors.IsAlreadyExists(err) {
            setupLog.Error(err, "unable to create CredRequest")
            os.Exit(1)
         }
      }
      Copy to Clipboard Toggle word wrap
    4. 确保 Operator 可以等待 Secret 对象从 CCO 显示,如下例所示,以及您在 Operator 中协调的其他项目:

      例 3.5. 等待 Secret 对象示例

      // WaitForSecret is a function that takes a Kubernetes client, a namespace, and a v1 "k8s.io/api/core/v1" name as arguments
      // It waits until the secret object with the given name exists in the given namespace
      // It returns the secret object or an error if the timeout is exceeded
      func WaitForSecret(client kubernetes.Interface, namespace, name string) (*v1.Secret, error) {
        // set a timeout of 10 minutes
        timeout := time.After(10 * time.Minute) 
      1
      
      
        // set a polling interval of 10 seconds
        ticker := time.NewTicker(10 * time.Second)
      
        // loop until the timeout or the secret is found
        for {
           select {
           case <-timeout:
              // timeout is exceeded, return an error
              return nil, fmt.Errorf("timed out waiting for secret %s in namespace %s", name, namespace)
                 // add to this error with a pointer to instructions for following a manual path to a Secret that will work on STS
           case <-ticker.C:
              // polling interval is reached, try to get the secret
              secret, err := client.CoreV1().Secrets(namespace).Get(context.Background(), name, metav1.GetOptions{})
              if err != nil {
                 if errors.IsNotFound(err) {
                    // secret does not exist yet, continue waiting
                    continue
                 } else {
                    // some other error occurred, return it
                    return nil, err
                 }
              } else {
                 // secret is found, return it
                 return secret, nil
              }
           }
        }
      }
      Copy to Clipboard Toggle word wrap
      1
      timeout 值基于 CCO 检测添加的 CredentialsRequest 对象并生成 Secret 对象的速度。您可能会考虑降低时间或为集群管理员创建自定义反馈,这可能会导致 Operator 尚未访问云资源的原因。
    5. 通过从凭证请求中读取 CCO 创建的 secret 并设置 AWS 配置,并创建包含该 secret 数据的 AWS 配置文件:

      例 3.6. AWS 配置创建示例

      func SharedCredentialsFileFromSecret(secret *corev1.Secret) (string, error) {
         var data []byte
         switch {
         case len(secret.Data["credentials"]) > 0:
             data = secret.Data["credentials"]
         default:
             return "", errors.New("invalid secret for aws credentials")
         }
      
      
         f, err := ioutil.TempFile("", "aws-shared-credentials")
         if err != nil {
             return "", errors.Wrap(err, "failed to create file for shared credentials")
         }
         defer f.Close()
         if _, err := f.Write(data); err != nil {
             return "", errors.Wrapf(err, "failed to write credentials to %s", f.Name())
         }
         return f.Name(), nil
      }
      Copy to Clipboard Toggle word wrap
      重要

      secret 被假定为存在,但在使用此 secret 时,您的 Operator 代码应等待和重试,以提供 CCO 创建 secret 的时间。

      另外,等待周期最终应该超时,并警告用户 OpenShift Container Platform 集群版本,因此 CCO 可能会是一个较早的版本,它不支持使用 STS 检测的 CredentialsRequest 对象工作流。在这种情况下,指示用户必须使用其他方法添加 secret。

    6. 配置 AWS SDK 会话,例如:

      例 3.7. AWS SDK 会话配置示例

      sharedCredentialsFile, err := SharedCredentialsFileFromSecret(secret)
      if err != nil {
         // handle error
      }
      options := session.Options{
         SharedConfigState: session.SharedConfigEnable,
         SharedConfigFiles: []string{sharedCredentialsFile},
      }
      Copy to Clipboard Toggle word wrap
返回顶部
Red Hat logoGithubredditYoutubeTwitter

学习

尝试、购买和销售

社区

关于红帽文档

通过我们的产品和服务,以及可以信赖的内容,帮助红帽用户创新并实现他们的目标。 了解我们当前的更新.

让开源更具包容性

红帽致力于替换我们的代码、文档和 Web 属性中存在问题的语言。欲了解更多详情,请参阅红帽博客.

關於紅帽

我们提供强化的解决方案,使企业能够更轻松地跨平台和环境(从核心数据中心到网络边缘)工作。

Theme

© 2025 Red Hat