diff --git a/pkg/awsiamauth/client.go b/pkg/awsiamauth/client.go index 3500e3919d98..0597ccf9e841 100644 --- a/pkg/awsiamauth/client.go +++ b/pkg/awsiamauth/client.go @@ -99,3 +99,23 @@ func (c RetrierClient) GetClusterCACert(ctx context.Context, cluster *types.Clus return nil, fmt.Errorf("tls.crt not found in secret [%s]", secretName) } + +// GetAWSIAMKubeconfigSecretValue gets the AWS IAM kubeconfig value for a cluster from a secret. +func (c RetrierClient) GetAWSIAMKubeconfigSecretValue(ctx context.Context, cluster *types.Cluster, clusterName string) ([]byte, error) { + secret := &corev1.Secret{} + secretName := fmt.Sprintf("%s-aws-iam-kubeconfig", clusterName) + err := c.retrier.Retry( + func() error { + return c.client.GetObject(ctx, "secret", secretName, constants.EksaSystemNamespace, cluster.KubeconfigFile, secret) + }, + ) + if err != nil { + return nil, err + } + + if secretValue, ok := secret.Data["value"]; ok { + return secretValue, nil + } + + return nil, fmt.Errorf("AWS IAM kubeconfig token not found in secret [%s]", secretName) +} diff --git a/pkg/awsiamauth/client_test.go b/pkg/awsiamauth/client_test.go index ef4a0591f80b..e0396843695a 100644 --- a/pkg/awsiamauth/client_test.go +++ b/pkg/awsiamauth/client_test.go @@ -118,3 +118,44 @@ func TestRetrierClientGetClusterCACertNotFound(t *testing.T) { tt.Expect(cert).To(BeNil()) tt.Expect(err).To(MatchError(ContainSubstring("tls.crt not found in secret [test-cluster-ca]"))) } + +func TestRetrierGetAWSIAMKubeconfigSecretValueSuccess(t *testing.T) { + tt := newRetrierTest(t) + tt.c.EXPECT().GetObject(tt.ctx, "secret", "test-cluster-aws-iam-kubeconfig", "eksa-system", tt.cluster.KubeconfigFile, &corev1.Secret{}).Return(errors.New("error in GetObject")).Times(4) + tt.c.EXPECT(). + GetObject(tt.ctx, "secret", "test-cluster-aws-iam-kubeconfig", "eksa-system", tt.cluster.KubeconfigFile, &corev1.Secret{}). + DoAndReturn(func(_ context.Context, _, _, _, _ string, obj *corev1.Secret) error { + obj.Data = map[string][]byte{ + "value": []byte("val"), + } + return nil + }).Times(1) + + kubeconfig, err := tt.r.GetAWSIAMKubeconfigSecretValue(tt.ctx, tt.cluster, "test-cluster") + tt.Expect(kubeconfig).To(Equal([]byte("val"))) + tt.Expect(err).To(Succeed(), "retrierClient.GetObject() should succeed after 5 tries") +} + +func TestRetrierGetAWSIAMKubeconfigSecretValueError(t *testing.T) { + tt := newRetrierTest(t) + tt.c.EXPECT().GetObject(tt.ctx, "secret", "test-cluster-aws-iam-kubeconfig", "eksa-system", tt.cluster.KubeconfigFile, &corev1.Secret{}).Return(errors.New("error in GetObject")).Times(5) + tt.c.EXPECT().GetObject(tt.ctx, "secret", "test-cluster-aws-iam-kubeconfig", "eksa-system", tt.cluster.KubeconfigFile, &corev1.Secret{}).Return(nil).AnyTimes() + + kubeconfig, err := tt.r.GetAWSIAMKubeconfigSecretValue(tt.ctx, tt.cluster, "test-cluster") + tt.Expect(kubeconfig).To(BeNil()) + tt.Expect(err).To(MatchError(ContainSubstring("error in GetObject")), "retrierClient.GetObject() should fail after 5 tries") +} + +func TestRetrierGetAWSIAMKubeconfigSecretValueTokenError(t *testing.T) { + tt := newRetrierTest(t) + tt.c.EXPECT().GetObject(tt.ctx, "secret", "test-cluster-aws-iam-kubeconfig", "eksa-system", tt.cluster.KubeconfigFile, &corev1.Secret{}).Return(errors.New("error in GetObject")).Times(4) + tt.c.EXPECT(). + GetObject(tt.ctx, "secret", "test-cluster-aws-iam-kubeconfig", "eksa-system", tt.cluster.KubeconfigFile, &corev1.Secret{}). + DoAndReturn(func(_ context.Context, _, _, _, _ string, obj *corev1.Secret) error { + return nil + }).Times(1) + + kubeconfig, err := tt.r.GetAWSIAMKubeconfigSecretValue(tt.ctx, tt.cluster, "test-cluster") + tt.Expect(kubeconfig).To(BeNil()) + tt.Expect(err).To(MatchError(ContainSubstring("AWS IAM kubeconfig token not found in secret"))) +} diff --git a/pkg/awsiamauth/installer.go b/pkg/awsiamauth/installer.go index 3e7afe53b75c..e6441f169126 100644 --- a/pkg/awsiamauth/installer.go +++ b/pkg/awsiamauth/installer.go @@ -18,6 +18,7 @@ type KubernetesClient interface { Apply(ctx context.Context, cluster *types.Cluster, data []byte) error GetAPIServerURL(ctx context.Context, cluster *types.Cluster) (string, error) GetClusterCACert(ctx context.Context, cluster *types.Cluster, clusterName string) ([]byte, error) + GetAWSIAMKubeconfigSecretValue(ctx context.Context, cluster *types.Cluster, clusterName string) ([]byte, error) } // Installer provides the necessary behavior for installing the AWS IAM Authenticator. @@ -154,3 +155,33 @@ func (i *Installer) GenerateKubeconfig( return nil } + +// GenerateManagementAWSIAMKubeconfig generates the AWS IAM auth kubeconfig. +func (i *Installer) GenerateManagementAWSIAMKubeconfig( + ctx context.Context, + cluster *types.Cluster, +) error { + fileName := fmt.Sprintf("%s-aws.kubeconfig", cluster.Name) + + decodedKubeconfigSecretValue, err := i.k8s.GetAWSIAMKubeconfigSecretValue( + ctx, + cluster, + cluster.Name, + ) + if err != nil { + return fmt.Errorf("generating aws-iam-authenticator kubeconfig: %v", err) + } + + writtenFile, err := i.writer.Write( + fileName, + decodedKubeconfigSecretValue, + filewriter.PersistentFile, + filewriter.Permission0600, + ) + if err != nil { + return fmt.Errorf("writing aws-iam-authenticator kubeconfig to %s: %v", writtenFile, err) + } + + logger.V(3).Info("Generated aws-iam-authenticator kubeconfig", "kubeconfig", writtenFile) + return nil +} diff --git a/pkg/awsiamauth/installer_test.go b/pkg/awsiamauth/installer_test.go index 7a0a1f1060c4..ae11e2dc07ec 100644 --- a/pkg/awsiamauth/installer_test.go +++ b/pkg/awsiamauth/installer_test.go @@ -323,3 +323,40 @@ func TestUpgradeAWSIAMAuth(t *testing.T) { } test.AssertContentToFile(t, string(manifest), "testdata/UpgradeAWSIAMAuth-manifest.yaml") } + +func TestGenerateManagementAWSIAMKubeconfig(t *testing.T) { + ctrl := gomock.NewController(t) + certs := cryptomocks.NewMockCertificateGenerator(ctrl) + clusterID := uuid.MustParse("36db102f-9e1e-4ca4-8300-271d30b14161") + + k8s := NewMockKubernetesClient(ctrl) + k8s.EXPECT().GetAWSIAMKubeconfigSecretValue(gomock.Any(), gomock.Any(), gomock.Any()).Return([]byte("kubeconfig"), nil) + + writer := filewritermock.NewMockFileWriter(ctrl) + writer.EXPECT().Write(gomock.Any(), gomock.Any(), gomock.Any()).Return("kubeconfig", nil) + + installer := awsiamauth.NewInstaller(certs, clusterID, k8s, writer) + + err := installer.GenerateManagementAWSIAMKubeconfig(context.Background(), &types.Cluster{}) + if err != nil { + t.Fatal(err) + } +} + +func TestGenerateManagementAWSIAMKubeconfigError(t *testing.T) { + ctrl := gomock.NewController(t) + certs := cryptomocks.NewMockCertificateGenerator(ctrl) + clusterID := uuid.MustParse("36db102f-9e1e-4ca4-8300-271d30b14161") + + k8s := NewMockKubernetesClient(ctrl) + k8s.EXPECT().GetAWSIAMKubeconfigSecretValue(gomock.Any(), gomock.Any(), gomock.Any()).Return([]byte{}, errors.New("test")) + + writer := filewritermock.NewMockFileWriter(ctrl) + + installer := awsiamauth.NewInstaller(certs, clusterID, k8s, writer) + + err := installer.GenerateManagementAWSIAMKubeconfig(context.Background(), &types.Cluster{}) + if err == nil { + t.Fatal(err) + } +} diff --git a/pkg/awsiamauth/mock_test.go b/pkg/awsiamauth/mock_test.go index 9144943bd0de..49280b16679a 100644 --- a/pkg/awsiamauth/mock_test.go +++ b/pkg/awsiamauth/mock_test.go @@ -64,6 +64,21 @@ func (mr *MockKubernetesClientMockRecorder) GetAPIServerURL(ctx, cluster interfa return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetAPIServerURL", reflect.TypeOf((*MockKubernetesClient)(nil).GetAPIServerURL), ctx, cluster) } +// GetAWSIAMKubeconfigSecretValue mocks base method. +func (m *MockKubernetesClient) GetAWSIAMKubeconfigSecretValue(ctx context.Context, cluster *types.Cluster, clusterName string) ([]byte, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetAWSIAMKubeconfigSecretValue", ctx, cluster, clusterName) + ret0, _ := ret[0].([]byte) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// GetAWSIAMKubeconfigSecretValue indicates an expected call of GetAWSIAMKubeconfigSecretValue. +func (mr *MockKubernetesClientMockRecorder) GetAWSIAMKubeconfigSecretValue(ctx, cluster, clusterName interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetAWSIAMKubeconfigSecretValue", reflect.TypeOf((*MockKubernetesClient)(nil).GetAWSIAMKubeconfigSecretValue), ctx, cluster, clusterName) +} + // GetClusterCACert mocks base method. func (m *MockKubernetesClient) GetClusterCACert(ctx context.Context, cluster *types.Cluster, clusterName string) ([]byte, error) { m.ctrl.T.Helper() diff --git a/pkg/clustermanager/cluster_manager.go b/pkg/clustermanager/cluster_manager.go index d496ccd9bc09..15bd462a055c 100644 --- a/pkg/clustermanager/cluster_manager.go +++ b/pkg/clustermanager/cluster_manager.go @@ -123,6 +123,7 @@ type AwsIamAuth interface { InstallAWSIAMAuth(ctx context.Context, management, workload *types.Cluster, spec *cluster.Spec) error UpgradeAWSIAMAuth(ctx context.Context, cluster *types.Cluster, spec *cluster.Spec) error GenerateKubeconfig(ctx context.Context, management, workload *types.Cluster, spec *cluster.Spec) error + GenerateManagementAWSIAMKubeconfig(ctx context.Context, cluster *types.Cluster) error } // EKSAComponents allows to manage the eks-a components installation in a cluster. @@ -840,9 +841,9 @@ func (c *ClusterManager) InstallAwsIamAuth(ctx context.Context, management, work return c.awsIamAuth.InstallAWSIAMAuth(ctx, management, workload, spec) } -// GenerateIamAuthKubeconfig generates a kubeconfig for interacting with the cluster with aws-iam-authenticator client. -func (c *ClusterManager) GenerateIamAuthKubeconfig(ctx context.Context, management, workload *types.Cluster, spec *cluster.Spec) error { - return c.awsIamAuth.GenerateKubeconfig(ctx, management, workload, spec) +// GenerateAWSIAMKubeconfig generates a kubeconfig for interacting with the cluster with aws-iam-authenticator client. +func (c *ClusterManager) GenerateAWSIAMKubeconfig(ctx context.Context, cluster *types.Cluster) error { + return c.awsIamAuth.GenerateManagementAWSIAMKubeconfig(ctx, cluster) } func (c *ClusterManager) CreateAwsIamAuthCaSecret(ctx context.Context, managementCluster *types.Cluster, workloadClusterName string) error { diff --git a/pkg/clustermanager/mocks/client_and_networking.go b/pkg/clustermanager/mocks/client_and_networking.go index 9709f4aa6203..c0cd99b248ff 100644 --- a/pkg/clustermanager/mocks/client_and_networking.go +++ b/pkg/clustermanager/mocks/client_and_networking.go @@ -934,6 +934,20 @@ func (mr *MockAwsIamAuthMockRecorder) GenerateKubeconfig(arg0, arg1, arg2, arg3 return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GenerateKubeconfig", reflect.TypeOf((*MockAwsIamAuth)(nil).GenerateKubeconfig), arg0, arg1, arg2, arg3) } +// GenerateManagementAWSIAMKubeconfig mocks base method. +func (m *MockAwsIamAuth) GenerateManagementAWSIAMKubeconfig(arg0 context.Context, arg1 *types.Cluster) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GenerateManagementAWSIAMKubeconfig", arg0, arg1) + ret0, _ := ret[0].(error) + return ret0 +} + +// GenerateManagementAWSIAMKubeconfig indicates an expected call of GenerateManagementAWSIAMKubeconfig. +func (mr *MockAwsIamAuthMockRecorder) GenerateManagementAWSIAMKubeconfig(arg0, arg1 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GenerateManagementAWSIAMKubeconfig", reflect.TypeOf((*MockAwsIamAuth)(nil).GenerateManagementAWSIAMKubeconfig), arg0, arg1) +} + // InstallAWSIAMAuth mocks base method. func (m *MockAwsIamAuth) InstallAWSIAMAuth(arg0 context.Context, arg1, arg2 *types.Cluster, arg3 *cluster.Spec) error { m.ctrl.T.Helper() diff --git a/pkg/workflows/interfaces/interfaces.go b/pkg/workflows/interfaces/interfaces.go index 13ec38e67876..87469494a8ac 100644 --- a/pkg/workflows/interfaces/interfaces.go +++ b/pkg/workflows/interfaces/interfaces.go @@ -54,7 +54,7 @@ type ClusterManager interface { Upgrade(ctx context.Context, cluster *types.Cluster, currentManagementComponents, newManagementComponents *cluster.ManagementComponents, newSpec *cluster.Spec) (*types.ChangeDiff, error) InstallAwsIamAuth(ctx context.Context, managementCluster, workloadCluster *types.Cluster, clusterSpec *cluster.Spec) error CreateAwsIamAuthCaSecret(ctx context.Context, bootstrapCluster *types.Cluster, workloadClusterName string) error - GenerateIamAuthKubeconfig(ctx context.Context, management, workload *types.Cluster, spec *cluster.Spec) error + GenerateAWSIAMKubeconfig(ctx context.Context, cluster *types.Cluster) error DeletePackageResources(ctx context.Context, managementCluster *types.Cluster, clusterName string) error CreateRegistryCredSecret(ctx context.Context, mgmt *types.Cluster) error CreateNamespace(ctx context.Context, targetCluster *types.Cluster, namespace string) error diff --git a/pkg/workflows/interfaces/mocks/clients.go b/pkg/workflows/interfaces/mocks/clients.go index f941912895d9..d9329ef401e9 100644 --- a/pkg/workflows/interfaces/mocks/clients.go +++ b/pkg/workflows/interfaces/mocks/clients.go @@ -283,18 +283,18 @@ func (mr *MockClusterManagerMockRecorder) EKSAClusterSpecChanged(arg0, arg1, arg return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "EKSAClusterSpecChanged", reflect.TypeOf((*MockClusterManager)(nil).EKSAClusterSpecChanged), arg0, arg1, arg2) } -// GenerateIamAuthKubeconfig mocks base method. -func (m *MockClusterManager) GenerateIamAuthKubeconfig(arg0 context.Context, arg1, arg2 *types.Cluster, arg3 *cluster.Spec) error { +// GenerateAWSIAMKubeconfig mocks base method. +func (m *MockClusterManager) GenerateAWSIAMKubeconfig(arg0 context.Context, arg1 *types.Cluster) error { m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "GenerateIamAuthKubeconfig", arg0, arg1, arg2, arg3) + ret := m.ctrl.Call(m, "GenerateAWSIAMKubeconfig", arg0, arg1) ret0, _ := ret[0].(error) return ret0 } -// GenerateIamAuthKubeconfig indicates an expected call of GenerateIamAuthKubeconfig. -func (mr *MockClusterManagerMockRecorder) GenerateIamAuthKubeconfig(arg0, arg1, arg2, arg3 interface{}) *gomock.Call { +// GenerateAWSIAMKubeconfig indicates an expected call of GenerateAWSIAMKubeconfig. +func (mr *MockClusterManagerMockRecorder) GenerateAWSIAMKubeconfig(arg0, arg1 interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GenerateIamAuthKubeconfig", reflect.TypeOf((*MockClusterManager)(nil).GenerateIamAuthKubeconfig), arg0, arg1, arg2, arg3) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GenerateAWSIAMKubeconfig", reflect.TypeOf((*MockClusterManager)(nil).GenerateAWSIAMKubeconfig), arg0, arg1) } // GetCurrentClusterSpec mocks base method. diff --git a/pkg/workflows/management/create_test.go b/pkg/workflows/management/create_test.go index fe85f20220ab..15d6534bdcaa 100644 --- a/pkg/workflows/management/create_test.go +++ b/pkg/workflows/management/create_test.go @@ -532,6 +532,29 @@ func TestCreateSyncFailure(t *testing.T) { } } +func TestCreateAWSIAMFailure(t *testing.T) { + test := newCreateTest(t) + test.expectSetup() + test.expectPreflightValidationsToPass() + test.expectCreateBootstrap() + test.expectCAPIInstall(nil, nil, nil) + test.expectInstallEksaComponentsBootstrap(nil, nil, nil, nil) + test.clusterSpec.AWSIamConfig = &v1alpha1.AWSIamConfig{} + + test.clusterManager.EXPECT().CreateNamespace(test.ctx, test.bootstrapCluster, test.clusterSpec.Cluster.Namespace).Return(nil) + test.clusterCreator.EXPECT().CreateSync(test.ctx, test.clusterSpec, test.bootstrapCluster).Return(test.workloadCluster, nil) + test.clusterManager.EXPECT().GenerateAWSIAMKubeconfig(test.ctx, test.workloadCluster).Return(errors.New("test")) + + test.clusterManager.EXPECT().SaveLogsManagementCluster(test.ctx, test.clusterSpec, test.bootstrapCluster) + test.clusterManager.EXPECT().SaveLogsWorkloadCluster(test.ctx, test.provider, test.clusterSpec, test.workloadCluster) + test.writer.EXPECT().Write(fmt.Sprintf("%s-checkpoint.yaml", test.clusterSpec.Cluster.Name), gomock.Any()) + + err := test.run() + if err == nil { + t.Fatalf("Create.Run() expected to return an error %v", err) + } +} + func TestCreateEKSANamespaceFailure(t *testing.T) { test := newCreateTest(t) test.expectSetup() diff --git a/pkg/workflows/management/create_workload.go b/pkg/workflows/management/create_workload.go index e2e6b4174f82..298e38258305 100644 --- a/pkg/workflows/management/create_workload.go +++ b/pkg/workflows/management/create_workload.go @@ -34,7 +34,7 @@ func (s *createWorkloadClusterTask) Run(ctx context.Context, commandContext *tas if commandContext.ClusterSpec.AWSIamConfig != nil { logger.Info("Generating the aws iam kubeconfig file") - err = commandContext.ClusterManager.GenerateIamAuthKubeconfig(ctx, commandContext.BootstrapCluster, workloadCluster, commandContext.ClusterSpec) + err = commandContext.ClusterManager.GenerateAWSIAMKubeconfig(ctx, commandContext.WorkloadCluster) if err != nil { commandContext.SetError(err) return &workflows.CollectDiagnosticsTask{}