From 3c9369c43b898fd3aa6394daf1a3c42a0fe400fd Mon Sep 17 00:00:00 2001 From: shirady <57721533+shirady@users.noreply.github.com> Date: Wed, 25 Oct 2023 14:11:06 +0300 Subject: [PATCH] Add default backingstore for AWS STS with CCO 1. Edit the volumeMount and volume to the operator deployment so that the operator can assume the role with web identity. 2. Add annotation to the CSV to claim support for STS 3. Get ROLEARN and web identitiy token path to the credentials request for the CCO (cloud credential operator). - Get the role ARN from the environment variable set on the pod by the subscription config and set the webIdentityTokenPath as const - Add the role ARN and web identity token path to the credentials request and apply it during operator initialization. 4. Add the option aws-sts-arn to pass the role arn (for testing) Signed-off-by: shirady <57721533+shirady@users.noreply.github.com> --- deploy/crds/noobaa.io_backingstores.yaml | 1 + deploy/crds/noobaa.io_namespacestores.yaml | 1 + deploy/crds/noobaa.io_noobaas.yaml | 1 + deploy/internal/deployment-endpoint.yaml | 12 +-- deploy/internal/statefulset-core.yaml | 16 ++-- deploy/operator.yaml | 24 ++--- .../noobaa/v1alpha1/backingstore_types.go | 1 + pkg/bundle/deploy.go | 67 ++++++++------ pkg/olm/olm.go | 4 +- pkg/operator/operator.go | 20 +++++ pkg/options/options.go | 9 +- pkg/system/phase2_creating.go | 47 ++++++++-- pkg/system/phase4_configuring.go | 88 +++++++++++++++++-- 13 files changed, 225 insertions(+), 66 deletions(-) diff --git a/deploy/crds/noobaa.io_backingstores.yaml b/deploy/crds/noobaa.io_backingstores.yaml index c9136ca4c..7e00fae2d 100644 --- a/deploy/crds/noobaa.io_backingstores.yaml +++ b/deploy/crds/noobaa.io_backingstores.yaml @@ -50,6 +50,7 @@ spec: description: AWSS3Spec specifies a backing store of type aws-s3 properties: awsSTSRoleARN: + description: AWSSTSRoleARN allows to Assume Role and use AssumeRoleWithWebIdentity type: string region: description: Region is the AWS region diff --git a/deploy/crds/noobaa.io_namespacestores.yaml b/deploy/crds/noobaa.io_namespacestores.yaml index 6bd327c8f..12861f964 100644 --- a/deploy/crds/noobaa.io_namespacestores.yaml +++ b/deploy/crds/noobaa.io_namespacestores.yaml @@ -53,6 +53,7 @@ spec: description: AWSS3Spec specifies a namespace store of type aws-s3 properties: awsSTSRoleARN: + description: AWSSTSRoleARN allows to Assume Role and use AssumeRoleWithWebIdentity type: string region: description: Region is the AWS region diff --git a/deploy/crds/noobaa.io_noobaas.yaml b/deploy/crds/noobaa.io_noobaas.yaml index f846a9303..787b6e629 100644 --- a/deploy/crds/noobaa.io_noobaas.yaml +++ b/deploy/crds/noobaa.io_noobaas.yaml @@ -1109,6 +1109,7 @@ spec: description: AWSS3Spec specifies a backing store of type aws-s3 properties: awsSTSRoleARN: + description: AWSSTSRoleARN allows to Assume Role and use AssumeRoleWithWebIdentity type: string region: description: Region is the AWS region diff --git a/deploy/internal/deployment-endpoint.yaml b/deploy/internal/deployment-endpoint.yaml index b30181c1c..7e8759abd 100644 --- a/deploy/internal/deployment-endpoint.yaml +++ b/deploy/internal/deployment-endpoint.yaml @@ -32,13 +32,15 @@ spec: secret: secretName: noobaa-s3-serving-cert optional: true - - name: oidc-token + # This service account token can be used to provide identity outside the cluster. + # For example, this token can be used with AssumeRoleWithWebIdentity to authenticate with AWS using IAM OIDC provider and STS. + - name: bound-sa-token projected: sources: - serviceAccountToken: - path: oidc-token - expirationSeconds: 3600 - audience: api + path: token + # For testing purposes change the audience to api + audience: openshift - name: noobaa-auth-token secret: secretName: noobaa-endpoints @@ -135,7 +137,7 @@ spec: mountPath: /etc/noobaa-server readOnly: true # used for aws sts endpoint type - - name: oidc-token + - name: bound-sa-token mountPath: /var/run/secrets/openshift/serviceaccount readOnly: true readinessProbe: # must be configured to support rolling updates diff --git a/deploy/internal/statefulset-core.yaml b/deploy/internal/statefulset-core.yaml index 2ed1cc89e..b45ea7ad8 100644 --- a/deploy/internal/statefulset-core.yaml +++ b/deploy/internal/statefulset-core.yaml @@ -36,14 +36,16 @@ spec: - name: noobaa-server secret: secretName: noobaa-server - optional: true - - name: oidc-token + optional: true + # This service account token can be used to provide identity outside the cluster. + # For example, this token can be used with AssumeRoleWithWebIdentity to authenticate with AWS using IAM OIDC provider and STS. + - name: bound-sa-token projected: sources: - serviceAccountToken: - path: oidc-token - expirationSeconds: 3600 - audience: api + path: token + # For testing purposes change the audience to api + audience: openshift containers: #----------------# # CORE CONTAINER # @@ -62,8 +64,8 @@ spec: - name: noobaa-server mountPath: /etc/noobaa-server readOnly: true - - mountPath: /var/run/secrets/openshift/serviceaccount - name: oidc-token + - name: bound-sa-token + mountPath: /var/run/secrets/openshift/serviceaccount readOnly: true resources: requests: diff --git a/deploy/operator.yaml b/deploy/operator.yaml index 9e4035c1f..09beae205 100644 --- a/deploy/operator.yaml +++ b/deploy/operator.yaml @@ -18,13 +18,15 @@ spec: seccompProfile: type: RuntimeDefault volumes: - - name: oidc-token + # This service account token can be used to provide identity outside the cluster. + # For example, this token can be used with AssumeRoleWithWebIdentity to authenticate with AWS using IAM OIDC provider and STS. + - name: bound-sa-token projected: sources: - serviceAccountToken: - path: oidc-token - expirationSeconds: 3600 - audience: api + path: token + # For testing purposes change the audience to api + audience: openshift - name: socket emptyDir: {} - name: noobaa-ca-inject @@ -38,8 +40,13 @@ spec: - name: noobaa-operator image: NOOBAA_OPERATOR_IMAGE volumeMounts: - - mountPath: /etc/pki/ca-trust/extracted/pem - name: noobaa-ca-inject + - name: bound-sa-token + mountPath: /var/run/secrets/openshift/serviceaccount + readOnly: true + - name: noobaa-ca-inject + mountPath: /etc/pki/ca-trust/extracted/pem + - name: socket + mountPath: /var/lib/cosi resources: limits: cpu: "250m" @@ -55,11 +62,6 @@ spec: valueFrom: fieldRef: fieldPath: metadata.namespace - volumeMounts: - - mountPath: /var/run/secrets/openshift/serviceaccount - name: oidc-token - - mountPath: /var/lib/cosi - name: socket - name: objectstorage-provisioner-sidecar image: COSI_SIDECAR_IMAGE args: diff --git a/pkg/apis/noobaa/v1alpha1/backingstore_types.go b/pkg/apis/noobaa/v1alpha1/backingstore_types.go index 6310f0893..62c8a4f92 100644 --- a/pkg/apis/noobaa/v1alpha1/backingstore_types.go +++ b/pkg/apis/noobaa/v1alpha1/backingstore_types.go @@ -159,6 +159,7 @@ type AWSS3Spec struct { // +optional SSLDisabled bool `json:"sslDisabled,omitempty"` + // AWSSTSRoleARN allows to Assume Role and use AssumeRoleWithWebIdentity // +optional AWSSTSRoleARN *string `json:"awsSTSRoleARN,omitempty"` } diff --git a/pkg/bundle/deploy.go b/pkg/bundle/deploy.go index 9ccbd02e0..4d4142cd3 100644 --- a/pkg/bundle/deploy.go +++ b/pkg/bundle/deploy.go @@ -275,7 +275,7 @@ spec: ` -const Sha256_deploy_crds_noobaa_io_backingstores_yaml = "d92994d0619470c8787780028098eafe53c696c4f55c40be055db48f491014bd" +const Sha256_deploy_crds_noobaa_io_backingstores_yaml = "75e58f314ed8c77725491c3e8c922c00b9e84c0df7f5062dc61f31cc2557b41c" const File_deploy_crds_noobaa_io_backingstores_yaml = `--- apiVersion: apiextensions.k8s.io/v1 @@ -329,6 +329,7 @@ spec: description: AWSS3Spec specifies a backing store of type aws-s3 properties: awsSTSRoleARN: + description: AWSSTSRoleARN allows to Assume Role and use AssumeRoleWithWebIdentity type: string region: description: Region is the AWS region @@ -937,7 +938,7 @@ spec: status: {} ` -const Sha256_deploy_crds_noobaa_io_namespacestores_yaml = "3e3c72d1ec1448ab07b48ca5cae04da5cdbddc25a8de4c3c52469884fcb2035d" +const Sha256_deploy_crds_noobaa_io_namespacestores_yaml = "2114c643ec1ba3dd804d1eb12849da90185eba26e2df8dfb9876148394e129be" const File_deploy_crds_noobaa_io_namespacestores_yaml = `--- apiVersion: apiextensions.k8s.io/v1 @@ -994,6 +995,7 @@ spec: description: AWSS3Spec specifies a namespace store of type aws-s3 properties: awsSTSRoleARN: + description: AWSSTSRoleARN allows to Assume Role and use AssumeRoleWithWebIdentity type: string region: description: Region is the AWS region @@ -1469,7 +1471,7 @@ spec: status: {} ` -const Sha256_deploy_crds_noobaa_io_noobaas_yaml = "ff8f0cf9e0a1429984e9518f0a143634644cfd0b1a955449d36917550ea060ce" +const Sha256_deploy_crds_noobaa_io_noobaas_yaml = "8f111f5299fc274593af4efb9565b34f36d36bd49f3666b19279504411ed0df0" const File_deploy_crds_noobaa_io_noobaas_yaml = `--- apiVersion: apiextensions.k8s.io/v1 @@ -2582,6 +2584,7 @@ spec: description: AWSS3Spec specifies a backing store of type aws-s3 properties: awsSTSRoleARN: + description: AWSSTSRoleARN allows to Assume Role and use AssumeRoleWithWebIdentity type: string region: description: Region is the AWS region @@ -3682,7 +3685,7 @@ data: su postgres -c "bash -x /usr/bin/run-postgresql" ` -const Sha256_deploy_internal_deployment_endpoint_yaml = "bd3efd480e3a73ebdc64acfbde114f938283604a7a8291d94a280b535a5c81cd" +const Sha256_deploy_internal_deployment_endpoint_yaml = "b3dab0839de6aa382833772ab278feb245067b27591dee988b29184de90961b6" const File_deploy_internal_deployment_endpoint_yaml = `apiVersion: apps/v1 kind: Deployment @@ -3718,13 +3721,15 @@ spec: secret: secretName: noobaa-s3-serving-cert optional: true - - name: oidc-token + # This service account token can be used to provide identity outside the cluster. + # For example, this token can be used with AssumeRoleWithWebIdentity to authenticate with AWS using IAM OIDC provider and STS. + - name: bound-sa-token projected: sources: - serviceAccountToken: - path: oidc-token - expirationSeconds: 3600 - audience: api + path: token + # For testing purposes change the audience to api + audience: openshift - name: noobaa-auth-token secret: secretName: noobaa-endpoints @@ -3821,7 +3826,7 @@ spec: mountPath: /etc/noobaa-server readOnly: true # used for aws sts endpoint type - - name: oidc-token + - name: bound-sa-token mountPath: /var/run/secrets/openshift/serviceaccount readOnly: true readinessProbe: # must be configured to support rolling updates @@ -4701,7 +4706,7 @@ spec: noobaa-s3-svc: "true" ` -const Sha256_deploy_internal_statefulset_core_yaml = "0489de0ea1cd3904274a8f58a5bca789592eab4991e911ce7703d238a223a168" +const Sha256_deploy_internal_statefulset_core_yaml = "39dbe4e822b69f1998cea34437e97cc58db4f577e9143eb6db087f2da083f73e" const File_deploy_internal_statefulset_core_yaml = `apiVersion: apps/v1 kind: StatefulSet @@ -4741,14 +4746,16 @@ spec: - name: noobaa-server secret: secretName: noobaa-server - optional: true - - name: oidc-token + optional: true + # This service account token can be used to provide identity outside the cluster. + # For example, this token can be used with AssumeRoleWithWebIdentity to authenticate with AWS using IAM OIDC provider and STS. + - name: bound-sa-token projected: sources: - serviceAccountToken: - path: oidc-token - expirationSeconds: 3600 - audience: api + path: token + # For testing purposes change the audience to api + audience: openshift containers: #----------------# # CORE CONTAINER # @@ -4767,8 +4774,8 @@ spec: - name: noobaa-server mountPath: /etc/noobaa-server readOnly: true - - mountPath: /var/run/secrets/openshift/serviceaccount - name: oidc-token + - name: bound-sa-token + mountPath: /var/run/secrets/openshift/serviceaccount readOnly: true resources: requests: @@ -5835,7 +5842,7 @@ spec: sourceNamespace: default ` -const Sha256_deploy_operator_yaml = "439f5d9032805eeff3de6520c9baa1b178f1b044091c432f1196349ffb7f544e" +const Sha256_deploy_operator_yaml = "8f48fc9ae60e1ef5f25d6b1a7a6cd0a6e76f055eb0fc3f4dd5068d304ed111be" const File_deploy_operator_yaml = `apiVersion: apps/v1 kind: Deployment @@ -5857,13 +5864,15 @@ spec: seccompProfile: type: RuntimeDefault volumes: - - name: oidc-token + # This service account token can be used to provide identity outside the cluster. + # For example, this token can be used with AssumeRoleWithWebIdentity to authenticate with AWS using IAM OIDC provider and STS. + - name: bound-sa-token projected: sources: - serviceAccountToken: - path: oidc-token - expirationSeconds: 3600 - audience: api + path: token + # For testing purposes change the audience to api + audience: openshift - name: socket emptyDir: {} - name: noobaa-ca-inject @@ -5877,8 +5886,13 @@ spec: - name: noobaa-operator image: NOOBAA_OPERATOR_IMAGE volumeMounts: - - mountPath: /etc/pki/ca-trust/extracted/pem - name: noobaa-ca-inject + - name: bound-sa-token + mountPath: /var/run/secrets/openshift/serviceaccount + readOnly: true + - name: noobaa-ca-inject + mountPath: /etc/pki/ca-trust/extracted/pem + - name: socket + mountPath: /var/lib/cosi resources: limits: cpu: "250m" @@ -5894,11 +5908,6 @@ spec: valueFrom: fieldRef: fieldPath: metadata.namespace - volumeMounts: - - mountPath: /var/run/secrets/openshift/serviceaccount - name: oidc-token - - mountPath: /var/lib/cosi - name: socket - name: objectstorage-provisioner-sidecar image: COSI_SIDECAR_IMAGE args: diff --git a/pkg/olm/olm.go b/pkg/olm/olm.go index 5371ae886..9643ba6c4 100644 --- a/pkg/olm/olm.go +++ b/pkg/olm/olm.go @@ -243,6 +243,8 @@ func GenerateCSV(opConf *operator.Conf, csvParams *generateCSVParams) *operv1.Cl // csv.Annotations["createdAt"] = ??? csv.Annotations["alm-examples"] = string(almExamples) csv.Annotations["operators.openshift.io/infrastructure-features"] = "ֿ'[\"disconnected\"]'" + // annotation for OpenShift AWS STS cluster + csv.Annotations["features.operators.openshift.io/token-auth-aws"] = "true" csv.Spec.Version.Version = semver.MustParse(version.Version) csv.Spec.Description = bundle.File_deploy_olm_description_md csv.Spec.Icon[0].Data = bundle.File_deploy_olm_noobaa_icon_base64 @@ -277,7 +279,7 @@ func GenerateCSV(opConf *operator.Conf, csvParams *generateCSVParams) *operv1.Cl if csvParams != nil { if csvParams.IsForODF { - // add anotation to hide the operator in OCP console + // add annotations to hide the operator in OCP console csv.Annotations["operators.operatorframework.io/operator-type"] = "non-standalone" // add env vars for noobaa-core and noobaa-db images diff --git a/pkg/operator/operator.go b/pkg/operator/operator.go index b01d7dcf4..eb61d4a39 100644 --- a/pkg/operator/operator.go +++ b/pkg/operator/operator.go @@ -138,6 +138,16 @@ func RunUpgrade(cmd *cobra.Command, args []string) { c.Deployment.Spec.Template.Spec.Containers[0].Env = operatorContainer.Env } + AWSSTSARNEnv, _ := cmd.Flags().GetString("aws-sts-arn") + if AWSSTSARNEnv != "" { + operatorContainer := c.Deployment.Spec.Template.Spec.Containers[0] + operatorContainer.Env = append(operatorContainer.Env, corev1.EnvVar{ + Name: "ROLEARN", + Value: AWSSTSARNEnv, + }) + c.Deployment.Spec.Template.Spec.Containers[0].Env = operatorContainer.Env + } + noDeploy, _ := cmd.Flags().GetBool("no-deploy") if !noDeploy { operatorContainer := c.Deployment.Spec.Template.Spec.Containers[0] @@ -200,6 +210,16 @@ func RunInstall(cmd *cobra.Command, args []string) { c.Deployment.Spec.Template.Spec.Containers[0].Env = operatorContainer.Env } + AWSSTSARNEnv, _ := cmd.Flags().GetString("aws-sts-arn") + if AWSSTSARNEnv != "" { + operatorContainer := c.Deployment.Spec.Template.Spec.Containers[0] + operatorContainer.Env = append(operatorContainer.Env, corev1.EnvVar{ + Name: "ROLEARN", + Value: AWSSTSARNEnv, + }) + c.Deployment.Spec.Template.Spec.Containers[0].Env = operatorContainer.Env + } + noDeploy, _ := cmd.Flags().GetBool("no-deploy") if !noDeploy { operatorContainer := c.Deployment.Spec.Template.Spec.Containers[0] diff --git a/pkg/options/options.go b/pkg/options/options.go index 98f8ebbb0..b63866683 100644 --- a/pkg/options/options.go +++ b/pkg/options/options.go @@ -170,6 +170,10 @@ var AutoscalerType = "" // it can be overridden for testing or different namespace. var PrometheusNamespace = "" +// AWSSTSARN is used in an AWS STS cluster to assume role ARN +// it can be overridden for testing. +var AWSSTSARN = "" + // SubDomainNS returns a unique subdomain for the namespace func SubDomainNS() string { return Namespace + ".noobaa.io" @@ -318,9 +322,12 @@ func init() { &AutoscalerType, "autoscaler-type", AutoscalerType, "The type of autoscaler (hpav2, keda)", ) - FlagSet.StringVar( &PrometheusNamespace, "prometheus-namespace", PrometheusNamespace, "namespace with installed prometheus for autoscaler", ) + FlagSet.StringVar( + &AWSSTSARN, "aws-sts-arn", + AWSSTSARN, "The AWS STS Role ARN which will assume role", + ) } diff --git a/pkg/system/phase2_creating.go b/pkg/system/phase2_creating.go index 21894a459..1ad1cb4b3 100644 --- a/pkg/system/phase2_creating.go +++ b/pkg/system/phase2_creating.go @@ -3,10 +3,12 @@ package system import ( "encoding/json" "fmt" + "os" "reflect" "strings" "time" + "github.com/aws/aws-sdk-go/aws/arn" "github.com/libopenstorage/secrets" nbv1 "github.com/noobaa/noobaa-operator/v5/pkg/apis/noobaa/v1alpha1" "github.com/noobaa/noobaa-operator/v5/pkg/bundle" @@ -33,7 +35,11 @@ import ( "sigs.k8s.io/controller-runtime/pkg/client" ) -const upgradeJobBackoffLimit = int32(4) +const ( + upgradeJobBackoffLimit = int32(4) + webIdentityTokenPath string = "/var/run/secrets/openshift/serviceaccount/token" + roleARNEnvVar string = "ROLEARN" +) // ReconcilePhaseCreating runs the reconcile phase func (r *Reconciler) ReconcilePhaseCreating() error { @@ -677,6 +683,19 @@ func (r *Reconciler) ReconcileRGWCredentials() error { // ReconcileAWSCredentials creates a CredentialsRequest resource if cloud credentials operator is available func (r *Reconciler) ReconcileAWSCredentials() error { + // check if we have the env var ROLEARN that indicates that this is an OpenShift AWS STS cluster + // cluster admin set this env (either in the UI in ARN details or via Subscription yaml) and set the mode to manual + // olm will then copy the env from the subscription to the operator deployment (which is where your operator can pick it up from) + roleARN := os.Getenv(roleARNEnvVar) + r.Logger.Infof("Getting role ARN: %s = %s", roleARNEnvVar, roleARN) + if roleARN != "" { + if !arn.IsARN(roleARN) { + r.Logger.Errorf("error with cloud credentials request, provided role ARN is invalid: %q", roleARN) + return fmt.Errorf("provided role ARN is invalid: %q", roleARN) + } + r.IsAWSSTSCluster = true + } + arnPrefix := "arn:aws:s3:::" awsRegion, err := util.GetAWSRegion() if err != nil { @@ -697,9 +716,17 @@ func (r *Reconciler) ReconcileAWSCredentials() error { return err } bucketName = strings.TrimPrefix(awsProviderSpec.StatementEntries[0].Resource, arnPrefix) - r.Logger.Infof("found existing credential request for bucket %s", bucketName) - r.DefaultBackingStore.Spec.AWSS3 = &nbv1.AWSS3Spec{ - TargetBucket: bucketName, + r.Logger.Infof("found existing credential request for bucket %s, role ARN: %s", bucketName, roleARN) + // since AWSSTSRoleARN is *string we will add the adderss of the variable roleARN only if it is not empty + if r.IsAWSSTSCluster { + r.DefaultBackingStore.Spec.AWSS3 = &nbv1.AWSS3Spec{ + TargetBucket: bucketName, + AWSSTSRoleARN: &roleARN, + } + } else { + r.DefaultBackingStore.Spec.AWSS3 = &nbv1.AWSS3Spec{ + TargetBucket: bucketName, + } } return nil } @@ -721,16 +748,26 @@ func (r *Reconciler) ReconcileAWSCredentials() error { // fix creds request according to bucket name awsProviderSpec.StatementEntries[0].Resource = arnPrefix + bucketName awsProviderSpec.StatementEntries[1].Resource = arnPrefix + bucketName + "/*" + // add fields related to STS to creds request (role ARN) + if r.IsAWSSTSCluster { + awsProviderSpec.STSIAMRoleARN = roleARN + } + updatedProviderSpec, err := codec.EncodeProviderSpec(awsProviderSpec) if err != nil { r.Logger.Error("error encoding providerSpec for cloud credentials request") return err } r.AWSCloudCreds.Spec.ProviderSpec = updatedProviderSpec + // add fields related to STS to creds request (path) + if r.IsAWSSTSCluster { + r.AWSCloudCreds.Spec.CloudTokenPath = webIdentityTokenPath + } r.Own(r.AWSCloudCreds) err = r.Client.Create(r.Ctx, r.AWSCloudCreds) if err != nil { - r.Logger.Errorf("got error when trying to create credentials request for bucket %s. %v", bucketName, err) + r.Logger.Errorf("got error when trying to create credentials request for bucket %s (STSIAMRoleARN %s). %v", + bucketName, roleARN, err) return err } r.DefaultBackingStore.Spec.AWSS3 = &nbv1.AWSS3Spec{ diff --git a/pkg/system/phase4_configuring.go b/pkg/system/phase4_configuring.go index 3c9fcce74..a282bbf35 100644 --- a/pkg/system/phase4_configuring.go +++ b/pkg/system/phase4_configuring.go @@ -7,6 +7,7 @@ import ( "net" "net/http" "net/url" + "os" "strconv" "strings" "time" @@ -37,6 +38,7 @@ import ( "github.com/aws/aws-sdk-go/aws/credentials" "github.com/aws/aws-sdk-go/aws/session" "github.com/aws/aws-sdk-go/service/s3" + "github.com/aws/aws-sdk-go/service/sts" ) const ( @@ -45,6 +47,7 @@ const ( ibmCosBucketCred = "ibm-cloud-cos-creds" topologyConstraintsEnabledKubeVersion = "1.26.0" minutesToWaitForDefaultBSCreation = 10 + credentialsKey = "credentials" ) type gcpAuthJSON struct { @@ -814,13 +817,61 @@ func (r *Reconciler) prepareAWSBackingStore() error { region = "us-east-1" } r.Logger.Infof("identified aws region %s", region) - s3Config := &aws.Config{ - Credentials: credentials.NewStaticCredentials( - cloudCredsSecret.StringData["aws_access_key_id"], - cloudCredsSecret.StringData["aws_secret_access_key"], - "", - ), - Region: ®ion, + var s3Config *aws.Config + if r.IsAWSSTSCluster { // handle STS case first + // get credentials + if len(cloudCredsSecret.StringData[credentialsKey]) == 0 { + return fmt.Errorf("invalid secret for aws sts credentials (should contain %s under data)", + credentialsKey) + } + data := cloudCredsSecret.StringData[credentialsKey] + info, err := r.getInfoFromAwsStsSecret(data) + if err != nil { + return fmt.Errorf("could not get the credentials from the aws sts secret %v", err) + } + roleARNInput := info["role_arn"] + webIdentityTokenPathInput := info["web_identity_token_file"] + r.Logger.Info("Initiating a Session with AWS") + sess, err := session.NewSession() + if err != nil { + return fmt.Errorf("could not create AWS Session %v", err) + } + stsClient := sts.New(sess) + r.Logger.Infof("AssumeRoleWithWebIdentityInput, roleARN = %s webIdentityTokenPath = %s, ", + roleARNInput, webIdentityTokenPathInput) + webIdentityTokenPathOutput, err := os.ReadFile(webIdentityTokenPathInput) + if err != nil { + return fmt.Errorf("could not read WebIdentityToken from path %s, %v", + webIdentityTokenPathInput, err) + } + WebIdentityToken := string(webIdentityTokenPathOutput) + input := &sts.AssumeRoleWithWebIdentityInput{ + RoleArn: aws.String(roleARNInput), + RoleSessionName: aws.String(r.AWSSTSRoleSessionName), + WebIdentityToken: aws.String(WebIdentityToken), + } + result, err := stsClient.AssumeRoleWithWebIdentity(input) + if err != nil { + return fmt.Errorf("could not use AWS AssumeRoleWithWebIdentity with role name %s and web identity token file %s, %v", + roleARNInput, webIdentityTokenPathInput, err) + } + s3Config = &aws.Config{ + Credentials: credentials.NewStaticCredentials( + *result.Credentials.AccessKeyId, + *result.Credentials.SecretAccessKey, + *result.Credentials.SessionToken, + ), + Region: ®ion, + } + } else { // handle AWS long-lived credentials (not STS) + s3Config = &aws.Config{ + Credentials: credentials.NewStaticCredentials( + cloudCredsSecret.StringData["aws_access_key_id"], + cloudCredsSecret.StringData["aws_secret_access_key"], + "", + ), + Region: ®ion, + } } bucketName := r.DefaultBackingStore.Spec.AWSS3.TargetBucket @@ -1259,6 +1310,29 @@ func (r *Reconciler) ReconcileOBCStorageClass() error { return nil } +// getInfoFromAwsStsSecret would return map with keys of role_arn and web_identity_token_file and their values +// After decoding this field should see structure: +// [default] +// sts_regional_endpoints = regional +// role_arn = arn:aws:iam::>account-id>:role/ +// web_identity_token_file = /var/run/secrets/openshift/serviceaccount/token +func (r *Reconciler) getInfoFromAwsStsSecret(data string) (map[string]string, error) { + lines := strings.Split(data, "\n") + + result := make(map[string]string) + lines = lines[2:] + for _, pair := range lines { + kv := strings.Split(pair, " =") + if len(kv) != 2 { + r.Logger.Errorf("invalid key-value pair: %s", pair) + } + key := strings.TrimSpace(kv[0]) + value := strings.TrimSpace(kv[1]) + result[key] = value + } + return result, nil +} + func (r *Reconciler) createS3BucketForBackingStore(s3Config *aws.Config, bucketName string) error { s3Session, err := session.NewSession(s3Config) if err != nil {