diff --git a/pkg/api/constant.go b/pkg/api/constant.go index 0c3441baad..86925d5924 100644 --- a/pkg/api/constant.go +++ b/pkg/api/constant.go @@ -78,6 +78,8 @@ const ( ClusterProfileSetEnv = "CLUSTER_PROFILE_SET_NAME" ClusterProfileParam = "CLUSTER_PROFILE" ClusterProfileSecretNameParam = "CLUSTER_PROFILE_SECRET_NAME" + STSHubRoleARNParam = "CI_STS_HUB_ROLE_ARN" + STSTargetRoleARNParam = "CI_STS_TARGET_ROLE_ARN" // SkipCensoringLabel is the label we use to mark a secret as not needing to be censored SkipCensoringLabel = "ci.openshift.io/skip-censoring" diff --git a/pkg/api/types.go b/pkg/api/types.go index ef236c00dc..37bcf2731a 100644 --- a/pkg/api/types.go +++ b/pkg/api/types.go @@ -3023,12 +3023,14 @@ func (cpl *ClusterProfilesList) Resolve() error { type ClusterProfilesMap map[ClusterProfile]ClusterProfileDetails type ClusterProfileDetails struct { - Name ClusterProfile `yaml:"name,omitempty" json:"name,omitempty"` - Owners []ClusterProfileOwners `yaml:"owners,omitempty" json:"owners,omitempty"` - ClusterType string `yaml:"cluster_type,omitempty" json:"cluster_type,omitempty"` - LeaseType string `yaml:"lease_type,omitempty" json:"lease_type,omitempty"` - Secret string `yaml:"secret,omitempty" json:"secret,omitempty"` - ConfigMap string `yaml:"config_map,omitempty" json:"config_map,omitempty"` + Name ClusterProfile `yaml:"name,omitempty" json:"name,omitempty"` + Owners []ClusterProfileOwners `yaml:"owners,omitempty" json:"owners,omitempty"` + ClusterType string `yaml:"cluster_type,omitempty" json:"cluster_type,omitempty"` + LeaseType string `yaml:"lease_type,omitempty" json:"lease_type,omitempty"` + Secret string `yaml:"secret,omitempty" json:"secret,omitempty"` + ConfigMap string `yaml:"config_map,omitempty" json:"config_map,omitempty"` + HubRoleARN string `yaml:"hub_role_arn,omitempty" json:"hub_role_arn,omitempty"` + TargetRoleARN string `yaml:"target_role_arn,omitempty" json:"target_role_arn,omitempty"` } type ClusterProfileKonfluxOwner struct { diff --git a/pkg/steps/lease.go b/pkg/steps/lease.go index 37eafb59d9..73cf82a0a0 100644 --- a/pkg/steps/lease.go +++ b/pkg/steps/lease.go @@ -47,6 +47,8 @@ type leaseStep struct { clusterProfileSetName string clusterProfileName string clusterProfileSecretName string + stsHubRoleARN string + stsTargetRoleARN string } func LeaseStep(client *lease.Client, leases []api.StepLease, wrapped api.Step, namespace func() string, metricsAgent *metrics.MetricsAgent, @@ -94,6 +96,10 @@ func (s *leaseStep) Provides() api.ParameterMap { parameters[api.ClusterProfileParam] = func() (string, error) { return s.clusterProfileName, nil } // nolint:unparam parameters[api.ClusterProfileSecretNameParam] = func() (string, error) { return s.clusterProfileSecretName, nil } + // nolint:unparam + parameters[api.STSHubRoleARNParam] = func() (string, error) { return s.stsHubRoleARN, nil } + // nolint:unparam + parameters[api.STSTargetRoleARNParam] = func() (string, error) { return s.stsTargetRoleARN, nil } for _, l := range s.leases { // nolint:unparam @@ -274,6 +280,9 @@ func (s *leaseStep) handleClusterProfile(ctx context.Context, l *stepLease, name return fmt.Errorf("resolve cluster profile %s: %w", s.clusterProfileName, err) } + s.stsHubRoleARN = cpDetails.HubRoleARN + s.stsTargetRoleARN = cpDetails.TargetRoleARN + if err := s.importClusterProfileSecret(ctx, cpDetails.Secret); err != nil { return fmt.Errorf("import secret %s for cluster profile %s: %w", cpDetails.Secret, s.clusterProfileName, err) } diff --git a/pkg/steps/lease_test.go b/pkg/steps/lease_test.go index 94ec92cec3..191bcc655e 100644 --- a/pkg/steps/lease_test.go +++ b/pkg/steps/lease_test.go @@ -295,6 +295,8 @@ func TestAcquireLeases(t *testing.T) { api.ClusterProfileSetEnv: "", api.ClusterProfileParam: "", api.ClusterProfileSecretNameParam: "", + api.STSHubRoleARNParam: "", + api.STSTargetRoleARNParam: "", "lease-0": "res-type-0", "lease-1": "res-type-1", "parameter": "map", @@ -341,6 +343,8 @@ func TestAcquireLeases(t *testing.T) { api.ClusterProfileSetEnv: "", api.ClusterProfileParam: "aws", api.ClusterProfileSecretNameParam: "cluster-secrets-aws", + api.STSHubRoleARNParam: "", + api.STSTargetRoleARNParam: "", api.DefaultLeaseEnv: "us-east-1", }, wantSecrets: corev1.SecretList{ @@ -416,6 +420,8 @@ func TestAcquireLeases(t *testing.T) { api.ClusterProfileSetEnv: "", api.ClusterProfileParam: "aws", api.ClusterProfileSecretNameParam: "cluster-secrets-aws", + api.STSHubRoleARNParam: "", + api.STSTargetRoleARNParam: "", api.DefaultLeaseEnv: "us-east-1", "FOOBAR_RESOURCE": "foobar-res-0", }, @@ -453,6 +459,75 @@ func TestAcquireLeases(t *testing.T) { "releaseone owner foobar-res-0 free", }, }, + { + name: "Cluster profile lease with STS", + leases: []api.StepLease{{ + ResourceType: "aws", + Env: api.DefaultLeaseEnv, + Count: 1, + ClusterProfile: "aws", + }}, + resources: map[string]*common.Resource{ + "acquireWaitWithPriority_aws_free_leased_random": { + Name: "us-east-1--aws-quota-slice-0", + }, + }, + objects: []ctrlruntimeclient.Object{&corev1.Secret{ + ObjectMeta: v1.ObjectMeta{ + Namespace: "ci", + Name: "cluster-secrets-aws", + }, + Data: map[string][]byte{ + "k1": []byte("v1"), + }, + }}, + clusterProfiles: map[string]*api.ClusterProfileDetails{ + "aws": { + Secret: "cluster-secrets-aws", + LeaseType: "aws-quota-slice", + HubRoleARN: "arn:aws:iam::111:role/hub", + TargetRoleARN: "arn:aws:iam::222:role/target", + }, + }, + wantProvides: map[string]string{ + "parameter": "map", + api.ClusterProfileSetEnv: "", + api.ClusterProfileParam: "aws", + api.ClusterProfileSecretNameParam: "cluster-secrets-aws", + api.STSHubRoleARNParam: "arn:aws:iam::111:role/hub", + api.STSTargetRoleARNParam: "arn:aws:iam::222:role/target", + api.DefaultLeaseEnv: "us-east-1", + }, + wantSecrets: corev1.SecretList{ + Items: []corev1.Secret{ + { + ObjectMeta: v1.ObjectMeta{ + Namespace: "ci", + Name: "cluster-secrets-aws", + ResourceVersion: "999", + }, + Data: map[string][]byte{ + "k1": []byte("v1"), + }, + }, + { + ObjectMeta: v1.ObjectMeta{ + Namespace: ns, + Name: "cluster-secrets-aws", + ResourceVersion: "1", + }, + Data: map[string][]byte{ + "k1": []byte("v1"), + }, + Immutable: ptr.To(true), + }, + }, + }, + wantCalls: []string{ + "acquireWaitWithPriority owner aws free leased random", + "releaseone owner us-east-1--aws-quota-slice-0 free", + }, + }, { name: "Nested cluster profile", leases: []api.StepLease{{ @@ -490,6 +565,8 @@ func TestAcquireLeases(t *testing.T) { api.ClusterProfileSetEnv: "aws-set", api.ClusterProfileParam: "aws", api.ClusterProfileSecretNameParam: "cluster-secrets-aws", + api.STSHubRoleARNParam: "", + api.STSTargetRoleARNParam: "", api.DefaultLeaseEnv: "us-east-1", }, wantSecrets: corev1.SecretList{ diff --git a/pkg/steps/multi_stage/gen.go b/pkg/steps/multi_stage/gen.go index 70af8f8187..1e08f1c782 100644 --- a/pkg/steps/multi_stage/gen.go +++ b/pkg/steps/multi_stage/gen.go @@ -2,7 +2,7 @@ package multi_stage import ( "fmt" - "path/filepath" + "path" "strings" "github.com/sirupsen/logrus" @@ -25,6 +25,10 @@ const ( profileVolumeName = "cluster-profile" vpnContainerName = "vpn-client" leaseProxyScriptsMountPath = "/opt/scripts/lease-proxy" + stsTokenVolumeName = "aws-sts-token" + stsTokenMountPath = "/var/run/secrets/aws/sts-token" + stsConfigVolumeName = "aws-sts-config" + stsConfigMountPath = "/var/run/secrets/aws/config" ) func (s *multiStageTestStep) generateObservers( @@ -223,9 +227,9 @@ func (s *multiStageTestStep) generatePods( } } else if needsKubeConfig { container.Env = append(container.Env, []coreapi.EnvVar{ - {Name: "KUBECONFIG", Value: filepath.Join(SecretMountPath, "kubeconfig")}, - {Name: "KUBECONFIGMINIMAL", Value: filepath.Join(SecretMountPath, "kubeconfig-minimal")}, - {Name: "KUBEADMIN_PASSWORD_FILE", Value: filepath.Join(SecretMountPath, "kubeadmin-password")}, + {Name: "KUBECONFIG", Value: path.Join(SecretMountPath, "kubeconfig")}, + {Name: "KUBECONFIGMINIMAL", Value: path.Join(SecretMountPath, "kubeconfig-minimal")}, + {Name: "KUBEADMIN_PASSWORD_FILE", Value: path.Join(SecretMountPath, "kubeadmin-password")}, }...) } shmSize := allResources.Requests.Name(api.ShmResource, resource.BinarySI) @@ -233,11 +237,15 @@ func (s *multiStageTestStep) generatePods( addDshmVolume(shmSize, pod, container) } if s.profile != "" { + if !needsKubeConfig && s.stsHubRoleARN != "" && s.stsTargetRoleARN != "" { + errs = append(errs, fmt.Errorf("step %s sets no_kubeconfig but the test has STS enabled (hub_role_arn=%s, target_role_arn=%s); STS requires kubeconfig", step.As, s.stsHubRoleARN, s.stsTargetRoleARN)) + continue + } profileSecret, err := s.profileSecretName() if err != nil { return nil, nil, fmt.Errorf("get profile secret name: %w", err) } - addProfile(profileSecret, s.profile, pod) + addProfile(profileSecret, s.profile, s.stsHubRoleARN, s.stsTargetRoleARN, pod) } if step.Cli != "" { dependency := api.StepDependency{Name: fmt.Sprintf("%s:cli", api.ReleaseStreamFor(step.Cli))} @@ -286,7 +294,7 @@ func isKubeconfigNeeded(step *api.LiteralTestStep, opts *generatePodOptions) boo func addSecretWrapper(pod *coreapi.Pod, vpnConf *vpnConf, skipKubeconfig bool, genPodOpts *generatePodOptions) { volume := "entrypoint-wrapper" dir := "/tmp/entrypoint-wrapper" - bin := filepath.Join(dir, "entrypoint-wrapper") + bin := path.Join(dir, "entrypoint-wrapper") pod.Spec.Volumes = append(pod.Spec.Volumes, coreapi.Volume{ Name: volume, VolumeSource: coreapi.VolumeSource{ @@ -448,10 +456,10 @@ func getClusterClaimPodParams(secretVolumeMounts []coreapi.VolumeMount, testName foundMountPath = true retMount = append(retMount, secretVolumeMount) if secretName == api.HiveAdminKubeconfigSecret { - retEnv = append(retEnv, coreapi.EnvVar{Name: "KUBECONFIG", Value: filepath.Join(secretVolumeMount.MountPath, api.HiveAdminKubeconfigSecretKey)}) + retEnv = append(retEnv, coreapi.EnvVar{Name: "KUBECONFIG", Value: path.Join(secretVolumeMount.MountPath, api.HiveAdminKubeconfigSecretKey)}) } if secretName == api.HiveAdminPasswordSecret { - retEnv = append(retEnv, coreapi.EnvVar{Name: "KUBEADMIN_PASSWORD_FILE", Value: filepath.Join(secretVolumeMount.MountPath, api.HiveAdminPasswordSecretKey)}) + retEnv = append(retEnv, coreapi.EnvVar{Name: "KUBEADMIN_PASSWORD_FILE", Value: path.Join(secretVolumeMount.MountPath, api.HiveAdminPasswordSecretKey)}) } break } @@ -483,7 +491,7 @@ func addDshmVolume(shmSize *resource.Quantity, pod *coreapi.Pod, container *core }) } -func addProfile(name string, profile api.ClusterProfile, pod *coreapi.Pod) { +func addProfile(name string, profile api.ClusterProfile, hubRoleARN, targetRoleARN string, pod *coreapi.Pod) { pod.Spec.Volumes = append(pod.Spec.Volumes, coreapi.Volume{ Name: profileVolumeName, VolumeSource: coreapi.VolumeSource{ @@ -507,6 +515,59 @@ func addProfile(name string, profile api.ClusterProfile, pod *coreapi.Pod) { Name: ClusterProfileMountEnv, Value: ClusterProfileMountPath, }}...) + + if hubRoleARN != "" && targetRoleARN != "" { + addSTSVolumes(pod) + } +} + +func addSTSVolumes(pod *coreapi.Pod) { + expirationSeconds := int64(86400) + + pod.Spec.Volumes = append(pod.Spec.Volumes, coreapi.Volume{ + Name: stsTokenVolumeName, + VolumeSource: coreapi.VolumeSource{ + Projected: &coreapi.ProjectedVolumeSource{ + Sources: []coreapi.VolumeProjection{{ + ServiceAccountToken: &coreapi.ServiceAccountTokenProjection{ + Audience: "sts.amazonaws.com", + ExpirationSeconds: &expirationSeconds, + Path: "token", + }, + }}, + }, + }, + }) + + stsConfigCMName := stsConfigMapName(pod.Labels[MultiStageTestLabel]) + pod.Spec.Volumes = append(pod.Spec.Volumes, coreapi.Volume{ + Name: stsConfigVolumeName, + VolumeSource: coreapi.VolumeSource{ + ConfigMap: &coreapi.ConfigMapVolumeSource{ + LocalObjectReference: coreapi.LocalObjectReference{ + Name: stsConfigCMName, + }, + }, + }, + }) + + container := &pod.Spec.Containers[0] + container.VolumeMounts = append(container.VolumeMounts, + coreapi.VolumeMount{ + Name: stsTokenVolumeName, + MountPath: stsTokenMountPath, + ReadOnly: true, + }, + coreapi.VolumeMount{ + Name: stsConfigVolumeName, + MountPath: stsConfigMountPath, + ReadOnly: true, + }, + ) + container.Env = append(container.Env, + coreapi.EnvVar{Name: "AWS_CONFIG_FILE", Value: path.Join(stsConfigMountPath, "config")}, + coreapi.EnvVar{Name: "AWS_SDK_LOAD_CONFIG", Value: "1"}, + ) } func addCliInjector(imagestream string, pod *coreapi.Pod) { @@ -527,7 +588,7 @@ func addCliInjector(imagestream string, pod *coreapi.Pod) { // this line to pick appropriate oc version (i.e. oc.rhel9). // Additionally, we need to check the existence of path because releases < 4.15 does not have oc.rhel8, // and we fall back to old path due to backwards compatibility. - Args: []string{"-c", fmt.Sprintf("ARCH=$(uname -m | sed 's/x86_64/amd64/;s/aarch64/arm64/'); if [[ -e /usr/share/openshift/linux_${ARCH}/oc.rhel8 ]]; then /bin/cp /usr/share/openshift/linux_${ARCH}/oc.rhel8 %s; else /bin/cp /usr/share/openshift/linux_${ARCH}/oc %s; fi", filepath.Join(CliMountPath, "oc"), CliMountPath)}, + Args: []string{"-c", fmt.Sprintf("ARCH=$(uname -m | sed 's/x86_64/amd64/;s/aarch64/arm64/'); if [[ -e /usr/share/openshift/linux_${ARCH}/oc.rhel8 ]]; then /bin/cp /usr/share/openshift/linux_${ARCH}/oc.rhel8 %s; else /bin/cp /usr/share/openshift/linux_${ARCH}/oc %s; fi", path.Join(CliMountPath, "oc"), CliMountPath)}, VolumeMounts: []coreapi.VolumeMount{{ Name: volumeName, MountPath: CliMountPath, diff --git a/pkg/steps/multi_stage/gen_test.go b/pkg/steps/multi_stage/gen_test.go index b86f8b8b0f..4c0c67b90f 100644 --- a/pkg/steps/multi_stage/gen_test.go +++ b/pkg/steps/multi_stage/gen_test.go @@ -72,6 +72,8 @@ func TestGeneratePods(t *testing.T) { secretVolumeMounts []coreapi.VolumeMount leaseProxyServerAvailable bool paramsFunc func() api.Parameters + stsHubRoleARN string + stsTargetRoleARN string }{ { name: "generate pods", @@ -161,6 +163,36 @@ func TestGeneratePods(t *testing.T) { }, leaseProxyServerAvailable: true, }, + { + name: "STS volumes", + config: &api.ReleaseBuildConfiguration{ + Tests: []api.TestStepConfiguration{{ + As: "e2e-aws", + MultiStageTestConfigurationLiteral: &api.MultiStageTestConfigurationLiteral{ + ClusterProfile: api.ClusterProfileAWS, + Test: []api.LiteralTestStep{{ + As: "step0", From: "src", Commands: "command0", + Timeout: &prowapi.Duration{Duration: time.Hour}, + GracePeriod: &prowapi.Duration{Duration: 20 * time.Second}, + }}, + }}, + }, + }, + env: []coreapi.EnvVar{ + {Name: "RELEASE_IMAGE_INITIAL", Value: "release:initial"}, + {Name: "RELEASE_IMAGE_LATEST", Value: "release:latest"}, + {Name: "LEASED_RESOURCE", Value: "uuid"}, + }, + paramsFunc: func() api.Parameters { + params := api.NewDeferredParameters(nil) + params.Add(api.ClusterProfileSecretNameParam, func() (string, error) { + return "cluster-secrets-aws-5", nil + }) + return params + }, + stsHubRoleARN: "arn:aws:iam::111111111111:role/ci-step-runner", + stsTargetRoleARN: "arn:aws:iam::222222222222:role/ci-profile-aws-5", + }, } { t.Run(tc.name, func(t *testing.T) { t.Parallel() @@ -172,6 +204,8 @@ func TestGeneratePods(t *testing.T) { js := jobSpec() step := newMultiStageTestStep(tc.config.Tests[0], tc.config, params, nil, &js, nil, "node-name", "", nil, false, nil, tc.leaseProxyServerAvailable, wait.Backoff{}) + step.stsHubRoleARN = tc.stsHubRoleARN + step.stsTargetRoleARN = tc.stsTargetRoleARN step.test[0].Resources = resourceRequirements ret, _, err := step.generatePods(tc.config.Tests[0].MultiStageTestConfigurationLiteral.Test, tc.env, tc.secretVolumes, tc.secretVolumeMounts, nil) diff --git a/pkg/steps/multi_stage/init.go b/pkg/steps/multi_stage/init.go index 3d925922ab..289e9a1288 100644 --- a/pkg/steps/multi_stage/init.go +++ b/pkg/steps/multi_stage/init.go @@ -223,6 +223,31 @@ func (s *multiStageTestStep) createCommandConfigMaps(ctx context.Context) error return nil } +func stsConfigMapName(testName string) string { + return fmt.Sprintf("%s-sts-config", testName) +} + +func (s *multiStageTestStep) createSTSConfigMap(ctx context.Context) error { + logrus.Infof("Creating STS AWS config for test %q (hub=%s, target=%s)", s.name, s.stsHubRoleARN, s.stsTargetRoleARN) + name := stsConfigMapName(s.name) + + configContent := fmt.Sprintf("[profile hub]\nweb_identity_token_file = %s/token\nrole_arn = %s\n\n[default]\nrole_arn = %s\nsource_profile = hub\n", stsTokenMountPath, s.stsHubRoleARN, s.stsTargetRoleARN) + + yes := true + cm := &coreapi.ConfigMap{ + ObjectMeta: meta.ObjectMeta{ + Name: name, + Namespace: s.jobSpec.Namespace(), + }, + Data: map[string]string{"config": configContent}, + Immutable: &yes, + } + if err := s.client.Delete(ctx, cm); err != nil && !kerrors.IsNotFound(err) { + return fmt.Errorf("could not delete STS config configmap %s: %w", name, err) + } + return s.client.Create(ctx, cm) +} + func (s *multiStageTestStep) setupRBAC(ctx context.Context) error { labels := map[string]string{MultiStageTestLabel: s.name} ns := s.jobSpec.Namespace() diff --git a/pkg/steps/multi_stage/init_test.go b/pkg/steps/multi_stage/init_test.go index 1952398240..6c036a4540 100644 --- a/pkg/steps/multi_stage/init_test.go +++ b/pkg/steps/multi_stage/init_test.go @@ -401,3 +401,60 @@ func TestSetupRBAC(t *testing.T) { }) } } + +func TestCreateSTSConfigMap(t *testing.T) { + executor := &testhelper_kube.FakePodExecutor{ + LoggingClient: loggingclient.New( + fakectrlruntimeclient.NewClientBuilder().Build(), nil), + } + fakeClient := &testhelper_kube.FakePodClient{ + FakePodExecutor: executor, + } + s := &multiStageTestStep{ + name: "e2e-aws", + stsHubRoleARN: "arn:aws:iam::111111111111:role/ci-step-runner", + stsTargetRoleARN: "arn:aws:iam::222222222222:role/ci-profile-aws-5", + client: fakeClient, + jobSpec: &api.JobSpec{}, + } + s.jobSpec.SetNamespace("ci-op-test") + + if err := s.createSTSConfigMap(context.Background()); err != nil { + t.Fatalf("createSTSConfigMap() error: %v", err) + } + + cm := &corev1.ConfigMap{} + if err := executor.Get(context.Background(), ctrlruntimeclient.ObjectKey{ + Namespace: "ci-op-test", + Name: "e2e-aws-sts-config", + }, cm); err != nil { + t.Fatalf("failed to get created configmap: %v", err) + } + + config, ok := cm.Data["config"] + if !ok { + t.Fatal("configmap missing 'config' key") + } + + if !strings.Contains(config, "arn:aws:iam::111111111111:role/ci-step-runner") { + t.Error("config missing hub role ARN") + } + if !strings.Contains(config, "arn:aws:iam::222222222222:role/ci-profile-aws-5") { + t.Error("config missing target role ARN") + } + if !strings.Contains(config, "source_profile = hub") { + t.Error("config missing source_profile = hub") + } + if !strings.Contains(config, "web_identity_token_file = /var/run/secrets/aws/sts-token/token") { + t.Error("config missing web_identity_token_file") + } + if cm.Immutable == nil || !*cm.Immutable { + t.Error("configmap should be immutable") + } +} + +func TestSTSConfigMapName(t *testing.T) { + if got := stsConfigMapName("e2e-aws"); got != "e2e-aws-sts-config" { + t.Errorf("stsConfigMapName(e2e-aws) = %s, want e2e-aws-sts-config", got) + } +} diff --git a/pkg/steps/multi_stage/multi_stage.go b/pkg/steps/multi_stage/multi_stage.go index f6610ee26b..93a0165cf2 100644 --- a/pkg/steps/multi_stage/multi_stage.go +++ b/pkg/steps/multi_stage/multi_stage.go @@ -132,6 +132,8 @@ type multiStageTestStep struct { requireNestedPodman bool leaseProxyServerAvailable bool leaseProxyClientConfigMapBackoff wait.Backoff + stsHubRoleARN string + stsTargetRoleARN string } func MultiStageTestStep( @@ -219,6 +221,32 @@ func (s *multiStageTestStep) profileSecretName() (string, error) { return cpSecretName, nil } +func (s *multiStageTestStep) retrieveSTSRoleARNParams() error { + if s.params == nil { + return nil + } + + hubRole, err := s.params.Get(api.STSHubRoleARNParam) + if err != nil { + return fmt.Errorf("get %s: %w", api.STSHubRoleARNParam, err) + } + + if hubRole != "" { + s.stsHubRoleARN = hubRole + } + + targetRole, err := s.params.Get(api.STSTargetRoleARNParam) + if err != nil { + return fmt.Errorf("get %s: %w", api.STSTargetRoleARNParam, err) + } + + if targetRole != "" { + s.stsTargetRoleARN = targetRole + } + + return nil +} + func (s *multiStageTestStep) Inputs() (api.InputDefinition, error) { return nil, nil } @@ -240,6 +268,10 @@ func (s *multiStageTestStep) run(ctx context.Context) error { s.profile = clusterProfile } + if err := s.retrieveSTSRoleARNParams(); err != nil { + return fmt.Errorf("retrieve STS role ARN params: %w", err) + } + if s.profile != "" { if err := s.getProfileData(ctx); err != nil { return err @@ -293,6 +325,11 @@ func (s *multiStageTestStep) run(ctx context.Context) error { if err := s.createCommandConfigMaps(ctx); err != nil { return fmt.Errorf("failed to create command configmap: %w", err) } + if s.stsHubRoleARN != "" && s.stsTargetRoleARN != "" { + if err := s.createSTSConfigMap(ctx); err != nil { + return fmt.Errorf("failed to create STS config configmap: %w", err) + } + } if err := s.setupRBAC(ctx); err != nil { return fmt.Errorf("failed to create RBAC objects: %w", err) } diff --git a/pkg/steps/multi_stage/testdata/zz_fixture_TestGeneratePods_STS_volumes.yaml b/pkg/steps/multi_stage/testdata/zz_fixture_TestGeneratePods_STS_volumes.yaml new file mode 100644 index 0000000000..be2a64e201 --- /dev/null +++ b/pkg/steps/multi_stage/testdata/zz_fixture_TestGeneratePods_STS_volumes.yaml @@ -0,0 +1,201 @@ +- metadata: + annotations: + ci-operator.openshift.io/container-sub-tests: test + ci-operator.openshift.io/save-container-logs: "true" + ci.openshift.io/job-spec: "" + creationTimestamp: null + labels: + OPENSHIFT_CI: "true" + ci.openshift.io/jobid: prow_job_id + ci.openshift.io/jobname: job + ci.openshift.io/jobtype: postsubmit + ci.openshift.io/metadata.branch: base_ref + ci.openshift.io/metadata.org: org + ci.openshift.io/metadata.repo: repo + ci.openshift.io/metadata.step: step0 + ci.openshift.io/metadata.target: target + ci.openshift.io/metadata.variant: variant + ci.openshift.io/multi-stage-test: e2e-aws + created-by-ci: "true" + name: e2e-aws-step0 + namespace: namespace + spec: + containers: + - args: + - /tools/entrypoint + command: + - /tmp/entrypoint-wrapper/entrypoint-wrapper + env: + - name: BUILD_ID + value: build id + - name: CI + value: "true" + - name: JOB_NAME + value: job + - name: JOB_SPEC + value: '{"type":"postsubmit","job":"job","buildid":"build id","prowjobid":"prow + job id","refs":{"org":"org","repo":"repo","base_ref":"base ref","base_sha":"base + sha"},"decoration_config":{"timeout":"1h0m0s","grace_period":"20s","utility_images":{"entrypoint":"entrypoint","sidecar":"sidecar"}}}' + - name: JOB_TYPE + value: postsubmit + - name: OPENSHIFT_CI + value: "true" + - name: PROW_JOB_ID + value: prow job id + - name: PULL_BASE_REF + value: base ref + - name: PULL_BASE_SHA + value: base sha + - name: PULL_REFS + value: base ref:base sha + - name: REPO_NAME + value: repo + - name: REPO_OWNER + value: org + - name: SRC_BASE + value: org/repo + - name: SRC_HOST + value: github.com + - name: GIT_CONFIG_COUNT + value: "1" + - name: GIT_CONFIG_KEY_0 + value: safe.directory + - name: GIT_CONFIG_VALUE_0 + value: '*' + - name: ENTRYPOINT_OPTIONS + value: '{"timeout":3600000000000,"grace_period":20000000000,"artifact_dir":"/logs/artifacts","args":["/bin/bash","-c","#!/bin/bash\nset + -eu\ncommand0"],"container_name":"test","process_log":"/logs/process-log.txt","marker_file":"/logs/marker-file.txt","metadata_file":"/logs/artifacts/metadata.json"}' + - name: ARTIFACT_DIR + value: /logs/artifacts + - name: NAMESPACE + value: namespace + - name: JOB_NAME_SAFE + value: e2e-aws + - name: JOB_NAME_HASH + value: 5e8c9 + - name: UNIQUE_HASH + value: 5e8c9 + - name: RELEASE_IMAGE_INITIAL + value: release:initial + - name: RELEASE_IMAGE_LATEST + value: release:latest + - name: LEASED_RESOURCE + value: uuid + - name: KUBECONFIG + value: /var/run/secrets/ci.openshift.io/multi-stage/kubeconfig + - name: KUBECONFIGMINIMAL + value: /var/run/secrets/ci.openshift.io/multi-stage/kubeconfig-minimal + - name: KUBEADMIN_PASSWORD_FILE + value: /var/run/secrets/ci.openshift.io/multi-stage/kubeadmin-password + - name: CLUSTER_PROFILE_NAME + value: aws + - name: CLUSTER_TYPE + value: aws + - name: CLUSTER_PROFILE_DIR + value: /var/run/secrets/ci.openshift.io/cluster-profile + - name: AWS_CONFIG_FILE + value: /var/run/secrets/aws/config/config + - name: AWS_SDK_LOAD_CONFIG + value: "1" + - name: SHARED_DIR + value: /var/run/secrets/ci.openshift.io/multi-stage + - name: LEASE_PROXY_CLIENT_SH + value: /opt/scripts/lease-proxy/client.sh + image: pipeline:src + name: test + resources: {} + terminationMessagePolicy: FallbackToLogsOnError + volumeMounts: + - mountPath: /logs + name: logs + - mountPath: /tools + name: tools + - mountPath: /alabama + name: home + - mountPath: /tmp/entrypoint-wrapper + name: entrypoint-wrapper + - mountPath: /dev/shm + name: dshm + - mountPath: /var/run/secrets/ci.openshift.io/cluster-profile + name: cluster-profile + - mountPath: /var/run/secrets/aws/sts-token + name: aws-sts-token + readOnly: true + - mountPath: /var/run/secrets/aws/config + name: aws-sts-config + readOnly: true + - mountPath: /var/run/secrets/ci.openshift.io/multi-stage + name: e2e-aws + - mountPath: /opt/scripts/lease-proxy + name: lease-proxy + readOnly: true + - env: + - name: JOB_SPEC + - name: SIDECAR_OPTIONS + value: '{"gcs_options":{"items":["/logs/artifacts"],"sub_dir":"artifacts/e2e-aws/step0","dry_run":false},"entries":[{"args":["/bin/bash","-c","#!/bin/bash\nset + -eu\ncommand0"],"container_name":"test","process_log":"/logs/process-log.txt","marker_file":"/logs/marker-file.txt","metadata_file":"/logs/artifacts/metadata.json"}],"ignore_interrupts":true,"censoring_options":{}}' + image: sidecar + name: sidecar + resources: {} + terminationMessagePolicy: FallbackToLogsOnError + volumeMounts: + - mountPath: /logs + name: logs + initContainers: + - args: + - --copy-mode-only + image: entrypoint + name: place-entrypoint + resources: {} + volumeMounts: + - mountPath: /tools + name: tools + - args: + - /bin/entrypoint-wrapper + - /tmp/entrypoint-wrapper/entrypoint-wrapper + command: + - cp + image: quay-proxy.ci.openshift.org/openshift/ci:ci_entrypoint-wrapper_latest + name: cp-entrypoint-wrapper + resources: {} + terminationMessagePolicy: FallbackToLogsOnError + volumeMounts: + - mountPath: /tmp/entrypoint-wrapper + name: entrypoint-wrapper + nodeName: node-name + restartPolicy: Never + serviceAccountName: e2e-aws + terminationGracePeriodSeconds: 25 + volumes: + - emptyDir: {} + name: logs + - emptyDir: {} + name: tools + - emptyDir: {} + name: home + - emptyDir: {} + name: entrypoint-wrapper + - emptyDir: + medium: Memory + sizeLimit: 2G + name: dshm + - name: cluster-profile + secret: + secretName: cluster-secrets-aws-5 + - name: aws-sts-token + projected: + sources: + - serviceAccountToken: + audience: sts.amazonaws.com + expirationSeconds: 86400 + path: token + - configMap: + name: e2e-aws-sts-config + name: aws-sts-config + - name: e2e-aws + secret: + secretName: e2e-aws + - configMap: + name: lease-proxy + name: lease-proxy + status: {} diff --git a/pkg/steps/pod.go b/pkg/steps/pod.go index 7f42ffb7c6..414ac2672c 100644 --- a/pkg/steps/pod.go +++ b/pkg/steps/pod.go @@ -4,7 +4,7 @@ import ( "context" "errors" "fmt" - "path/filepath" + "path" "time" "github.com/sirupsen/logrus" @@ -294,12 +294,12 @@ func (s *podStep) generatePodForStep(image string, containerResources coreapi.Re { Name: NamePerTest(api.HiveAdminKubeconfigSecret, s.config.As), ReadOnly: true, - MountPath: filepath.Join(testSecretDefaultPath, NamePerTest(api.HiveAdminKubeconfigSecret, s.config.As)), + MountPath: path.Join(testSecretDefaultPath, NamePerTest(api.HiveAdminKubeconfigSecret, s.config.As)), }, { Name: NamePerTest(api.HiveAdminPasswordSecret, s.config.As), ReadOnly: true, - MountPath: filepath.Join(testSecretDefaultPath, NamePerTest(api.HiveAdminPasswordSecret, s.config.As)), + MountPath: path.Join(testSecretDefaultPath, NamePerTest(api.HiveAdminPasswordSecret, s.config.As)), }, }...) secretVolumes = append(secretVolumes, []coreapi.Volume{ @@ -335,8 +335,8 @@ func (s *podStep) generatePodForStep(image string, containerResources coreapi.Re container.VolumeMounts = append(container.VolumeMounts, secretVolumeMounts...) if s.clusterClaim != nil { container.Env = append(container.Env, []coreapi.EnvVar{ - {Name: "KUBECONFIG", Value: filepath.Join(filepath.Join(testSecretDefaultPath, NamePerTest(api.HiveAdminKubeconfigSecret, s.config.As)), api.HiveAdminKubeconfigSecretKey)}, - {Name: "KUBEADMIN_PASSWORD_FILE", Value: filepath.Join(filepath.Join(testSecretDefaultPath, NamePerTest(api.HiveAdminPasswordSecret, s.config.As)), api.HiveAdminPasswordSecretKey)}, + {Name: "KUBECONFIG", Value: path.Join(path.Join(testSecretDefaultPath, NamePerTest(api.HiveAdminKubeconfigSecret, s.config.As)), api.HiveAdminKubeconfigSecretKey)}, + {Name: "KUBEADMIN_PASSWORD_FILE", Value: path.Join(path.Join(testSecretDefaultPath, NamePerTest(api.HiveAdminPasswordSecret, s.config.As)), api.HiveAdminPasswordSecretKey)}, }...) } pod.Spec.Volumes = append(pod.Spec.Volumes, secretVolumes...)