Skip to content

Commit

Permalink
Use Kubeconfig Writer for IamAuth (#7593)
Browse files Browse the repository at this point in the history
* Use kubeconfig writer for iamauth kubeconfig

---------

Co-authored-by: Mitali Paygude <[email protected]>
  • Loading branch information
tatlat and mitalipaygude authored Feb 15, 2024
1 parent 44d1394 commit d42b907
Show file tree
Hide file tree
Showing 11 changed files with 181 additions and 70 deletions.
46 changes: 28 additions & 18 deletions pkg/awsiamauth/installer.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import (
"github.com/aws/eks-anywhere/pkg/cluster"
"github.com/aws/eks-anywhere/pkg/crypto"
"github.com/aws/eks-anywhere/pkg/filewriter"
"github.com/aws/eks-anywhere/pkg/kubeconfig"
"github.com/aws/eks-anywhere/pkg/logger"
"github.com/aws/eks-anywhere/pkg/types"
)
Expand All @@ -23,11 +24,12 @@ type KubernetesClient interface {

// Installer provides the necessary behavior for installing the AWS IAM Authenticator.
type Installer struct {
certgen crypto.CertificateGenerator
templateBuilder *TemplateBuilder
clusterID uuid.UUID
k8s KubernetesClient
writer filewriter.FileWriter
certgen crypto.CertificateGenerator
templateBuilder *TemplateBuilder
clusterID uuid.UUID
k8s KubernetesClient
writer filewriter.FileWriter
kubeconfigWriter kubeconfig.Writer
}

// NewInstaller creates a new installer instance.
Expand All @@ -36,13 +38,15 @@ func NewInstaller(
clusterID uuid.UUID,
k8s KubernetesClient,
writer filewriter.FileWriter,
kubeconfigWriter kubeconfig.Writer,
) *Installer {
return &Installer{
certgen: certgen,
templateBuilder: &TemplateBuilder{},
clusterID: clusterID,
k8s: k8s,
writer: writer,
certgen: certgen,
templateBuilder: &TemplateBuilder{},
clusterID: clusterID,
k8s: k8s,
writer: writer,
kubeconfigWriter: kubeconfigWriter,
}
}

Expand Down Expand Up @@ -163,6 +167,17 @@ func (i *Installer) GenerateManagementAWSIAMKubeconfig(
) error {
fileName := fmt.Sprintf("%s-aws.kubeconfig", cluster.Name)

fsOptions := []filewriter.FileOptionsFunc{filewriter.PersistentFile, filewriter.Permission0600}
fh, path, err := i.writer.Create(
fileName,
fsOptions...,
)
if err != nil {
return err
}

defer fh.Close()

decodedKubeconfigSecretValue, err := i.k8s.GetAWSIAMKubeconfigSecretValue(
ctx,
cluster,
Expand All @@ -172,16 +187,11 @@ func (i *Installer) GenerateManagementAWSIAMKubeconfig(
return fmt.Errorf("generating aws-iam-authenticator kubeconfig: %v", err)
}

writtenFile, err := i.writer.Write(
fileName,
decodedKubeconfigSecretValue,
filewriter.PersistentFile,
filewriter.Permission0600,
)
err = i.kubeconfigWriter.WriteKubeconfigContent(ctx, cluster.Name, decodedKubeconfigSecretValue, fh)
if err != nil {
return fmt.Errorf("writing aws-iam-authenticator kubeconfig to %s: %v", writtenFile, err)
return fmt.Errorf("writing aws-iam-authenticator kubeconfig to %s: %v", path, err)
}

logger.V(3).Info("Generated aws-iam-authenticator kubeconfig", "kubeconfig", writtenFile)
logger.V(3).Info("Generated aws-iam-authenticator kubeconfig", "kubeconfig", path)
return nil
}
81 changes: 68 additions & 13 deletions pkg/awsiamauth/installer_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,15 @@ package awsiamauth_test
import (
"context"
"errors"
"fmt"
"os"
"strings"
"testing"

"github.com/golang/mock/gomock"
"github.com/google/uuid"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/utils/pointer"

"github.com/aws/eks-anywhere/internal/test"
"github.com/aws/eks-anywhere/pkg/api/v1alpha1"
Expand All @@ -17,6 +20,7 @@ import (
cryptomocks "github.com/aws/eks-anywhere/pkg/crypto/mocks"
"github.com/aws/eks-anywhere/pkg/filewriter"
filewritermock "github.com/aws/eks-anywhere/pkg/filewriter/mocks"
kubeconfigmocks "github.com/aws/eks-anywhere/pkg/kubeconfig/mocks"
"github.com/aws/eks-anywhere/pkg/types"
)

Expand All @@ -38,6 +42,7 @@ func TestInstallAWSIAMAuth(t *testing.T) {

var kubeconfig []byte
writer := filewritermock.NewMockFileWriter(ctrl)
kwriter := kubeconfigmocks.NewMockWriter(ctrl)
writer.EXPECT().Write(gomock.Any(), gomock.Any(), gomock.Any()).DoAndReturn(
func(fileName string, content []byte, f ...filewriter.FileOptionsFunc) (string, error) {
kubeconfig = content
Expand Down Expand Up @@ -80,7 +85,7 @@ func TestInstallAWSIAMAuth(t *testing.T) {
},
}

installer := awsiamauth.NewInstaller(certs, clusterID, k8s, writer)
installer := awsiamauth.NewInstaller(certs, clusterID, k8s, writer, kwriter)

err := installer.InstallAWSIAMAuth(context.Background(), &types.Cluster{}, &types.Cluster{}, spec)
if err != nil {
Expand Down Expand Up @@ -172,8 +177,8 @@ func TestInstallAWSIAMAuthErrors(t *testing.T) {
},
},
}

installer := awsiamauth.NewInstaller(certs, clusterID, k8s, writer)
kwriter := kubeconfigmocks.NewMockWriter(ctrl)
installer := awsiamauth.NewInstaller(certs, clusterID, k8s, writer, kwriter)

err := installer.InstallAWSIAMAuth(context.Background(), &types.Cluster{}, &types.Cluster{}, spec)
if err == nil {
Expand Down Expand Up @@ -205,7 +210,8 @@ func TestCreateAndInstallAWSIAMAuthCASecret(t *testing.T) {
certs := cryptomocks.NewMockCertificateGenerator(ctrl)
certs.EXPECT().GenerateIamAuthSelfSignCertKeyPair().Return([]byte("ca-cert"), []byte("ca-key"), nil)

installer := awsiamauth.NewInstaller(certs, clusterID, k8s, writer)
kwriter := kubeconfigmocks.NewMockWriter(ctrl)
installer := awsiamauth.NewInstaller(certs, clusterID, k8s, writer, kwriter)

err := installer.CreateAndInstallAWSIAMAuthCASecret(context.Background(), &types.Cluster{}, "test-cluster")
if err != nil {
Expand Down Expand Up @@ -248,7 +254,8 @@ func TestCreateAndInstallAWSIAMAuthCASecretErrors(t *testing.T) {
certs := cryptomocks.NewMockCertificateGenerator(ctrl)
certs.EXPECT().GenerateIamAuthSelfSignCertKeyPair().Return([]byte("ca-cert"), []byte("ca-key"), nil)

installer := awsiamauth.NewInstaller(certs, clusterID, k8s, writer)
kwriter := kubeconfigmocks.NewMockWriter(ctrl)
installer := awsiamauth.NewInstaller(certs, clusterID, k8s, writer, kwriter)

err := installer.CreateAndInstallAWSIAMAuthCASecret(context.Background(), &types.Cluster{}, "test-cluster")

Expand Down Expand Up @@ -280,7 +287,8 @@ func TestUpgradeAWSIAMAuth(t *testing.T) {
},
)

installer := awsiamauth.NewInstaller(certs, clusterID, k8s, writer)
kwriter := kubeconfigmocks.NewMockWriter(ctrl)
installer := awsiamauth.NewInstaller(certs, clusterID, k8s, writer, kwriter)

spec := &cluster.Spec{
Config: &cluster.Config{
Expand Down Expand Up @@ -328,16 +336,26 @@ func TestGenerateManagementAWSIAMKubeconfig(t *testing.T) {
ctrl := gomock.NewController(t)
certs := cryptomocks.NewMockCertificateGenerator(ctrl)
clusterID := uuid.MustParse("36db102f-9e1e-4ca4-8300-271d30b14161")
ctx := context.Background()

k8s := NewMockKubernetesClient(ctrl)
k8s.EXPECT().GetAWSIAMKubeconfigSecretValue(gomock.Any(), gomock.Any(), gomock.Any()).Return([]byte("kubeconfig"), nil)
secretValue := []byte("kubeconfig")
k8s.EXPECT().GetAWSIAMKubeconfigSecretValue(gomock.Any(), gomock.Any(), gomock.Any()).Return(secretValue, nil)

cluster := &types.Cluster{
Name: "cluster-name",
}
writer := filewritermock.NewMockFileWriter(ctrl)
writer.EXPECT().Write(gomock.Any(), gomock.Any(), gomock.Any()).Return("kubeconfig", nil)
fileName := fmt.Sprintf("%s-aws.kubeconfig", cluster.Name)
path := "testpath"
fileWriter := os.NewFile(uintptr(*pointer.Uint(0)), "test")

installer := awsiamauth.NewInstaller(certs, clusterID, k8s, writer)
writer.EXPECT().Create(fileName, gomock.AssignableToTypeOf([]filewriter.FileOptionsFunc{})).Return(fileWriter, path, nil)
kwriter := kubeconfigmocks.NewMockWriter(ctrl)
installer := awsiamauth.NewInstaller(certs, clusterID, k8s, writer, kwriter)
kwriter.EXPECT().WriteKubeconfigContent(ctx, cluster.Name, secretValue, fileWriter)

err := installer.GenerateManagementAWSIAMKubeconfig(context.Background(), &types.Cluster{})
err := installer.GenerateManagementAWSIAMKubeconfig(context.Background(), cluster)
if err != nil {
t.Fatal(err)
}
Expand All @@ -349,13 +367,50 @@ func TestGenerateManagementAWSIAMKubeconfigError(t *testing.T) {
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"))
k8s.EXPECT().GetAWSIAMKubeconfigSecretValue(gomock.Any(), gomock.Any(), gomock.Any()).Return(nil, errors.New("test"))

cluster := &types.Cluster{
Name: "cluster-name",
}
writer := filewritermock.NewMockFileWriter(ctrl)
fileName := fmt.Sprintf("%s-aws.kubeconfig", cluster.Name)
path := "testpath"
fileWriter := os.NewFile(uintptr(*pointer.Uint(0)), "test")

writer.EXPECT().Create(fileName, gomock.AssignableToTypeOf([]filewriter.FileOptionsFunc{})).Return(fileWriter, path, nil)
kwriter := kubeconfigmocks.NewMockWriter(ctrl)
installer := awsiamauth.NewInstaller(certs, clusterID, k8s, writer, kwriter)

err := installer.GenerateManagementAWSIAMKubeconfig(context.Background(), cluster)
if err == nil {
t.Fatal(err)
}
}

func TestGenerateAWSIAMKubeconfigError(t *testing.T) {
ctrl := gomock.NewController(t)
certs := cryptomocks.NewMockCertificateGenerator(ctrl)
clusterID := uuid.MustParse("36db102f-9e1e-4ca4-8300-271d30b14161")
ctx := context.Background()

k8s := NewMockKubernetesClient(ctrl)
secretValue := []byte("kubeconfig")
k8s.EXPECT().GetAWSIAMKubeconfigSecretValue(gomock.Any(), gomock.Any(), gomock.Any()).Return(secretValue, nil)

cluster := &types.Cluster{
Name: "cluster-name",
}
writer := filewritermock.NewMockFileWriter(ctrl)
fileName := fmt.Sprintf("%s-aws.kubeconfig", cluster.Name)
path := "testpath"
fileWriter := os.NewFile(uintptr(*pointer.Uint(0)), "test")

installer := awsiamauth.NewInstaller(certs, clusterID, k8s, writer)
writer.EXPECT().Create(fileName, gomock.AssignableToTypeOf([]filewriter.FileOptionsFunc{})).Return(fileWriter, path, nil)
kwriter := kubeconfigmocks.NewMockWriter(ctrl)
installer := awsiamauth.NewInstaller(certs, clusterID, k8s, writer, kwriter)
kwriter.EXPECT().WriteKubeconfigContent(ctx, cluster.Name, secretValue, fileWriter).Return(errors.New("test"))

err := installer.GenerateManagementAWSIAMKubeconfig(context.Background(), &types.Cluster{})
err := installer.GenerateManagementAWSIAMKubeconfig(context.Background(), cluster)
if err == nil {
t.Fatal(err)
}
Expand Down
8 changes: 5 additions & 3 deletions pkg/dependencies/factory.go
Original file line number Diff line number Diff line change
Expand Up @@ -951,8 +951,9 @@ func (f *Factory) WithCiliumTemplater() *Factory {
return f
}

func (f *Factory) WithAwsIamAuth() *Factory {
f.WithKubectl().WithWriter()
// WithAwsIamAuth builds dependencies for AWS IAM Auth.
func (f *Factory) WithAwsIamAuth(clusterConfig *v1alpha1.Cluster) *Factory {
f.WithKubectl().WithWriter().WithKubeconfigWriter(clusterConfig)

f.buildSteps = append(f.buildSteps, func(ctx context.Context) error {
if f.dependencies.AwsIamAuth != nil {
Expand All @@ -971,6 +972,7 @@ func (f *Factory) WithAwsIamAuth() *Factory {
clusterId,
awsiamauth.NewRetrierClient(f.dependencies.Kubectl, opts...),
f.dependencies.Writer,
f.dependencies.KubeconfigWriter,
)
return nil
})
Expand Down Expand Up @@ -1067,7 +1069,7 @@ func (f *Factory) clusterManagerOpts(timeoutOpts *ClusterManagerTimeoutOptions)

// WithClusterManager builds a cluster manager based on the cluster config and timeout options.
func (f *Factory) WithClusterManager(clusterConfig *v1alpha1.Cluster, timeoutOpts *ClusterManagerTimeoutOptions) *Factory {
f.WithClusterctl().WithNetworking(clusterConfig).WithWriter().WithDiagnosticBundleFactory().WithAwsIamAuth().WithFileReader().WithUnAuthKubeClient().WithKubernetesRetrierClient().WithEKSAInstaller()
f.WithClusterctl().WithNetworking(clusterConfig).WithWriter().WithDiagnosticBundleFactory().WithAwsIamAuth(clusterConfig).WithFileReader().WithUnAuthKubeClient().WithKubernetesRetrierClient().WithEKSAInstaller()

f.buildSteps = append(f.buildSteps, func(ctx context.Context) error {
if f.dependencies.ClusterManager != nil {
Expand Down
2 changes: 1 addition & 1 deletion pkg/dependencies/factory_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -691,7 +691,7 @@ func TestFactoryBuildWithAwsIamAuthNoTimeout(t *testing.T) {
deps, err := dependencies.NewFactory().
WithLocalExecutables().
WithNoTimeouts().
WithAwsIamAuth().
WithAwsIamAuth(tt.clusterSpec.Cluster).
Build(context.Background())

tt.Expect(err).To(BeNil())
Expand Down
1 change: 1 addition & 0 deletions pkg/kubeconfig/kubeconfig.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import (
// Writer reads the kubeconfig secret on a cluster and copies the contents to a writer.
type Writer interface {
WriteKubeconfig(ctx context.Context, clusterName, kubeconfig string, w io.Writer) error
WriteKubeconfigContent(ctx context.Context, clusterName string, content []byte, w io.Writer) error
}

// FromClusterFormat defines the format of the kubeconfig of the.
Expand Down
11 changes: 10 additions & 1 deletion pkg/kubeconfig/kubeconfig_writer.go
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,16 @@ func (kr ClusterAPIKubeconfigSecretWriter) WriteKubeconfig(ctx context.Context,
return err
}

if _, err := io.Copy(w, bytes.NewReader(rawKubeconfig)); err != nil {
if err := kr.WriteKubeconfigContent(ctx, clusterName, rawKubeconfig, w); err != nil {
return err
}

return nil
}

// WriteKubeconfigContent copies a raw kubeconfig to an io.Writer.
func (kr ClusterAPIKubeconfigSecretWriter) WriteKubeconfigContent(ctx context.Context, clusterName string, content []byte, w io.Writer) error {
if _, err := io.Copy(w, bytes.NewReader(content)); err != nil {
return err
}

Expand Down
14 changes: 14 additions & 0 deletions pkg/kubeconfig/mocks/writer.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

13 changes: 11 additions & 2 deletions pkg/providers/docker/docker.go
Original file line number Diff line number Diff line change
Expand Up @@ -607,14 +607,23 @@ func (kr KubeconfigWriter) WriteKubeconfig(ctx context.Context, clusterName, kub
return err
}

if err := kr.WriteKubeconfigContent(ctx, clusterName, rawkubeconfig, w); err != nil {
return err
}

return nil
}

// WriteKubeconfigContent retrieves the contents of the specified cluster's kubeconfig from a secret and copies it to an io.Writer.
func (kr KubeconfigWriter) WriteKubeconfigContent(ctx context.Context, clusterName string, content []byte, w io.Writer) error {
port, err := kr.docker.GetDockerLBPort(ctx, clusterName)
if err != nil {
return err
}

updateKubeconfig(&rawkubeconfig, port)
updateKubeconfig(&content, port)

if _, err := io.Copy(w, bytes.NewReader(rawkubeconfig)); err != nil {
if _, err := io.Copy(w, bytes.NewReader(content)); err != nil {
return err
}

Expand Down
Loading

0 comments on commit d42b907

Please sign in to comment.