From 0100612ec0e7072d31d070a46f1943073d28bd16 Mon Sep 17 00:00:00 2001 From: Aliaksandr Panasiuk Date: Thu, 31 Oct 2024 15:26:55 +0100 Subject: [PATCH] #29 - implement support AWS provider for Cluster API --- cmd/cluster.go | 84 ++++-- cmd/cluster_capa.go | 175 +++++++++-- cmd/cluster_capz.go | 15 +- cmd/config.go | 470 +++++++++--------------------- cmd/flags.go | 88 +++--- cmd/k3d.go | 12 +- cmd/project.go | 10 - cmd/release.go | 75 ++--- cmd/secret.go | 15 - config/config.go | 1 - go.mod | 12 +- go.sum | 24 +- providers/aws_provider/aws.go | 334 ++++++++++++++------- providers/azure_provider/azure.go | 30 +- util/system.go | 2 +- util/validate.go | 12 - 16 files changed, 712 insertions(+), 647 deletions(-) diff --git a/cmd/cluster.go b/cmd/cluster.go index 2c0e282..c5630c6 100644 --- a/cmd/cluster.go +++ b/cmd/cluster.go @@ -10,6 +10,7 @@ import ( "github.com/urfave/cli/v2" "gopkg.in/yaml.v3" "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/util/json" "k8s.io/client-go/tools/clientcmd" clientcmdlatest "k8s.io/client-go/tools/clientcmd/api/latest" @@ -42,8 +43,15 @@ func newClusterCommands(conf *config.Config, ctx *cli.Context, workDir string) * func (cc *ClusterCommands) clusterCTL(args ...string) *util.SpecCMD { var envs []string - switch { - case cc.Conf.ClusterProvider == azure_provider.AzureClusterProvider: + switch cc.Conf.ClusterProvider { + case aws_provider.AWSClusterProvider: + envs = []string{ + "AWS_B64ENCODED_CREDENTIALS=", + "CAPA_EKS_IAM=true", + "CAPA_EKS_ADD_ROLES=true", + "EXP_MACHINE_POOL=true", + } + case azure_provider.AzureClusterProvider: envs = []string{ "EXP_AKS=true", "EXP_MACHINE_POOL=true", @@ -72,6 +80,15 @@ func (cc *ClusterCommands) kubectl(args ...string) *util.SpecCMD { } } +func createManifestFile(object interface{}, dir, fileName string) (string, error) { + data, err := json.Marshal(object) + if err != nil { + return "", err + } + + return util.CreateTempYAMLFile(dir, fileName, data) +} + func createClusterCTLConfigFile(output []byte) (string, error) { clusterCTL := &ClusterCTLConfig{} if err := yaml.Unmarshal(output, &clusterCTL); err != nil { @@ -99,7 +116,7 @@ func (cc *ClusterCommands) getClusterCTLConfig() (string, string, error) { cc.SpecCMD = cc.prepareHelmfile("--log-level", "error", "-l", "config=clusterctl", "template") cc.SpecCMD.DisableStdOut = true if err := releaseRunner(cc).runCMD(); err != nil { - return "", "", fmt.Errorf("Helmfile failed to render template by label release: config=clusterctl\n%s", + return "", "", fmt.Errorf("Helmfile failed to render template by release label: config=clusterctl\n%s", cc.SpecCMD.StderrBuf.String()) } @@ -254,6 +271,23 @@ func (cc *ClusterCommands) switchKubeContext() error { } switch cc.Conf.ClusterProvider { + case aws_provider.AWSClusterProvider: + //if err := cc.awsClusterContext(); err != nil { + // return err + //} + // + //_, currentContext, err := cc.getKubeContext() + //if err != nil { + // return err + //} + // + //cc.SpecCMD = cc.kubectl("config", "set-credentials", currentContext, + // "--exec-env", "AWS_CONFIG_FILE="+strings.Join(cc.Conf.AWSSharedConfigFile(cc.Conf.Profile), ""), + // "--exec-env", "AWS_SHARED_CREDENTIALS_FILE="+strings.Join(cc.Conf.AWSSharedCredentialsFile(cc.Conf.Profile), ""), + //) + //cc.SpecCMD.DisableStdOut = true + //cc.SpecCMD.Debug = true + //return releaseRunner(cc).runCMD() case azure_provider.AzureClusterProvider: clusterContext, err := cc.azureClusterContext() if err != nil { @@ -263,23 +297,6 @@ func (cc *ClusterCommands) switchKubeContext() error { if err := cc.mergeKubeConfig(clusterContext); err != nil { return err } - case aws_provider.AWSClusterProvider: - if err := cc.awsClusterContext(); err != nil { - return err - } - - _, currentContext, err := cc.getKubeContext() - if err != nil { - return err - } - - cc.SpecCMD = cc.kubectl("config", "set-credentials", currentContext, - "--exec-env", "AWS_CONFIG_FILE="+strings.Join(cc.Conf.AWSSharedConfigFile(cc.Conf.Profile), ""), - "--exec-env", "AWS_SHARED_CREDENTIALS_FILE="+strings.Join(cc.Conf.AWSSharedCredentialsFile(cc.Conf.Profile), ""), - ) - cc.SpecCMD.DisableStdOut = true - cc.SpecCMD.Debug = true - return releaseRunner(cc).runCMD() } return nil @@ -303,6 +320,15 @@ func (cc *ClusterCommands) provisionDestroyTargetCluster() error { } switch cc.Conf.ClusterProvider { + case aws_provider.AWSClusterProvider: + clusterContext, err := cc.AWSClusterContext() + if err != nil { + return err + } + + if err := cc.mergeKubeConfig(clusterContext); err != nil { + return err + } case azure_provider.AzureClusterProvider: clusterContext, err := cc.azureClusterContext() if err != nil { @@ -378,7 +404,14 @@ func CAPIInitAction(conf *config.Config) cli.AfterFunc { return err } - return cc.applyAzureClusterIdentity() + switch cc.Conf.ClusterProvider { + case aws_provider.AWSClusterProvider: + return cc.applyAWSClusterIdentity() + case azure_provider.AzureClusterProvider: + return cc.applyAzureClusterIdentity() + } + + return nil } } @@ -401,7 +434,14 @@ func CAPIUpdateAction(conf *config.Config) cli.ActionFunc { return err } - return cc.applyAzureClusterIdentity() + switch cc.Conf.ClusterProvider { + case aws_provider.AWSClusterProvider: + return cc.applyAWSClusterIdentity() + case azure_provider.AzureClusterProvider: + return cc.applyAzureClusterIdentity() + } + + return nil } } diff --git a/cmd/cluster_capa.go b/cmd/cluster_capa.go index 6cfdc1b..9661b52 100644 --- a/cmd/cluster_capa.go +++ b/cmd/cluster_capa.go @@ -1,38 +1,167 @@ package cmd import ( - "strings" + "os" + "path/filepath" - "rmk/util" + corev1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + v1 "k8s.io/client-go/applyconfigurations/core/v1" + + "rmk/providers/aws_provider" ) const ( awsFlagsCategory = "AWS authentication" + + awsIAMControllerCredentialsTemplate = `[default] +aws_access_key_id = {{ .AwsCredentialsProfile.AccessKeyID }} +aws_secret_access_key = {{ .AwsCredentialsProfile.SecretAccessKey }} +region = {{ .Region }} +{{- if .AwsCredentialsProfile.SessionToken }} +aws_session_token = {{ .AwsCredentialsProfile.SessionToken }} +{{- end }} +` + awsIAMControllerSecret = "aws-iam-controller-secret" + awsClusterStaticIdentityName = "aws-cluster-identity" + awsClusterStaticIdentityNamespace = "capa-system" + awsClusterStaticIdentitySecret = "aws-cluster-identity-secret" ) -func (cc *ClusterCommands) getAWSEksKubeConfig() *util.SpecCMD { - return &util.SpecCMD{ - Envs: []string{ - "AWS_PROFILE=" + cc.Conf.Profile, - "AWS_CONFIG_FILE=" + strings.Join(cc.Conf.AWSSharedConfigFile(cc.Conf.Profile), ""), - "AWS_SHARED_CREDENTIALS_FILE=" + strings.Join(cc.Conf.AWSSharedCredentialsFile(cc.Conf.Profile), ""), - }, - Args: []string{"eks", "--region", - cc.Conf.Region, - "update-kubeconfig", - "--name", - cc.Conf.Name + "-eks", - "--profile", - cc.Conf.Profile, +var awsClusterStaticIdentitySecretType = corev1.SecretTypeOpaque + +type AWSClusterStaticIdentityConfig struct { + *AWSClusterStaticIdentity + *v1.SecretApplyConfiguration + AWSIAMControllerSecret *v1.SecretApplyConfiguration + ManifestFiles []string + ManifestFilesDir string +} + +type AWSClusterStaticIdentity struct { + metav1.TypeMeta `json:",inline"` + metav1.ObjectMeta `json:"metadata,omitempty"` + + Spec AWSClusterStaticIdentitySpec `json:"spec,omitempty"` +} + +type AWSClusterStaticIdentitySpec struct { + AllowedNamespaces struct { + NamespaceList []string `json:"list"` + Selector *metav1.LabelSelector `json:"selector,omitempty"` + } `json:"allowedNamespaces,omitempty"` + SecretRef string `json:"secretRef"` +} + +func NewAWSClusterStaticIdentityConfig(ac *aws_provider.AwsConfigure) *AWSClusterStaticIdentityConfig { + acic := &AWSClusterStaticIdentityConfig{ + AWSClusterStaticIdentity: &AWSClusterStaticIdentity{ + TypeMeta: metav1.TypeMeta{ + Kind: "AWSClusterStaticIdentity", + APIVersion: "infrastructure.cluster.x-k8s.io/v1beta2", + }, + ObjectMeta: metav1.ObjectMeta{ + Name: awsClusterStaticIdentityName, + Namespace: awsClusterStaticIdentityNamespace, + Labels: map[string]string{"clusterctl.cluster.x-k8s.io/move-hierarchy": "true"}, + }, + Spec: AWSClusterStaticIdentitySpec{ + AllowedNamespaces: struct { + NamespaceList []string `json:"list"` + Selector *metav1.LabelSelector `json:"selector,omitempty"` + }(struct { + NamespaceList []string + Selector *metav1.LabelSelector + }{ + NamespaceList: []string{awsClusterStaticIdentityNamespace}, + }), + SecretRef: awsClusterStaticIdentitySecret, + }, }, - Command: "aws", - Ctx: cc.Ctx.Context, - Dir: cc.WorkDir, - Debug: true, + AWSIAMControllerSecret: v1.Secret(awsIAMControllerSecret, awsClusterStaticIdentityNamespace), + SecretApplyConfiguration: v1.Secret(awsClusterStaticIdentitySecret, awsClusterStaticIdentityNamespace), + ManifestFilesDir: filepath.Join("/tmp", awsClusterStaticIdentityName), + } + + profile, err := ac.RenderAWSConfigProfile(awsIAMControllerCredentialsTemplate) + if err != nil { + return nil + } + + acic.AWSIAMControllerSecret.Type = &awsClusterStaticIdentitySecretType + acic.AWSIAMControllerSecret.Data = map[string][]byte{"credentials": profile} + + acic.SecretApplyConfiguration.Type = &awsClusterStaticIdentitySecretType + acic.SecretApplyConfiguration.Data = map[string][]byte{ + "AccessKeyID": []byte(ac.AwsCredentialsProfile.AccessKeyID), + "SecretAccessKey": []byte(ac.AwsCredentialsProfile.SecretAccessKey), + } + + if len(ac.AwsCredentialsProfile.SessionToken) > 0 { + acic.SecretApplyConfiguration.Data["SessionToken"] = []byte(ac.AwsCredentialsProfile.SessionToken) + } + + return acic +} + +func (acic *AWSClusterStaticIdentityConfig) createAWSClusterIdentityManifestFiles() error { + if err := os.MkdirAll(acic.ManifestFilesDir, 0775); err != nil { + return err } + + fileCR, err := createManifestFile(acic.AWSClusterStaticIdentity, acic.ManifestFilesDir, awsClusterStaticIdentityName) + if err != nil { + return err + } + + acic.ManifestFiles = append(acic.ManifestFiles, fileCR) + + fileCRSecret, err := createManifestFile(acic.SecretApplyConfiguration, acic.ManifestFilesDir, awsClusterStaticIdentitySecret) + if err != nil { + return err + } + + acic.ManifestFiles = append(acic.ManifestFiles, fileCRSecret) + + fileIAMControllerSecret, err := createManifestFile(acic.AWSIAMControllerSecret, acic.ManifestFilesDir, awsIAMControllerSecret) + if err != nil { + return err + } + + acic.ManifestFiles = append(acic.ManifestFiles, fileIAMControllerSecret) + + return nil +} + +func (cc *ClusterCommands) applyAWSClusterIdentity() error { + var kubectlArgs = []string{"apply"} + + ac := aws_provider.NewAwsConfigure(cc.Ctx.Context, cc.Conf.Profile) + if err := ac.ReadAWSConfigProfile(); err != nil { + return err + } + + acic := NewAWSClusterStaticIdentityConfig(ac) + if err := acic.createAWSClusterIdentityManifestFiles(); err != nil { + return err + } + + for _, val := range acic.ManifestFiles { + kubectlArgs = append(kubectlArgs, "-f", val) + } + + cc.SpecCMD = cc.kubectl(kubectlArgs...) + if err := releaseRunner(cc).runCMD(); err != nil { + if err := os.RemoveAll(acic.ManifestFilesDir); err != nil { + return err + } + + return err + } + + return os.RemoveAll(acic.ManifestFilesDir) } -func (cc *ClusterCommands) awsClusterContext() error { - cc.SpecCMD = cc.getAWSEksKubeConfig() - return releaseRunner(cc).runCMD() +func (cc *ClusterCommands) AWSClusterContext() ([]byte, error) { + return aws_provider.NewAwsConfigure(cc.Ctx.Context, cc.Conf.Profile).GetAWSClusterContext(cc.Conf.Name) } diff --git a/cmd/cluster_capz.go b/cmd/cluster_capz.go index 5bc1aa4..5a30900 100644 --- a/cmd/cluster_capz.go +++ b/cmd/cluster_capz.go @@ -6,11 +6,9 @@ import ( corev1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/util/json" v1 "k8s.io/client-go/applyconfigurations/core/v1" "rmk/providers/azure_provider" - "rmk/util" ) const ( @@ -88,15 +86,6 @@ func NewAzureClusterIdentityConfig(ac *azure_provider.AzureConfigure) *AzureClus return acic } -func createManifestFile(object interface{}, dir, fileName string) (string, error) { - data, err := json.Marshal(object) - if err != nil { - return "", err - } - - return util.CreateTempYAMLFile(dir, fileName, data) -} - func (acic *AzureClusterIdentityConfig) createAzureClusterIdentityManifestFiles() error { if err := os.MkdirAll(acic.ManifestFilesDir, 0775); err != nil { return err @@ -138,6 +127,10 @@ func (cc *ClusterCommands) applyAzureClusterIdentity() error { cc.SpecCMD = cc.kubectl(kubectlArgs...) if err := releaseRunner(cc).runCMD(); err != nil { + if err := os.RemoveAll(acic.ManifestFilesDir); err != nil { + return err + } + return err } diff --git a/cmd/config.go b/cmd/config.go index 1038f38..d067792 100644 --- a/cmd/config.go +++ b/cmd/config.go @@ -28,20 +28,6 @@ func newConfigCommands(conf *config.Config, ctx *cli.Context, workDir string) *C return &ConfigCommands{&ReleaseCommands{Conf: conf, Ctx: ctx, WorkDir: workDir}} } -func (c *ConfigCommands) awsConfigure(profile string) *util.SpecCMD { - return &util.SpecCMD{ - Args: []string{"configure", "--profile", profile}, - Envs: []string{ - "AWS_CONFIG_FILE=" + strings.Join(c.Conf.AWSSharedConfigFile(profile), ""), - "AWS_SHARED_CREDENTIALS_FILE=" + strings.Join(c.Conf.AWSSharedCredentialsFile(profile), ""), - }, - Command: "aws", - Ctx: c.Ctx.Context, - Dir: c.WorkDir, - Debug: false, - } -} - func (c *ConfigCommands) helmPlugin() *util.SpecCMD { return &util.SpecCMD{ Args: []string{"plugin"}, @@ -53,67 +39,55 @@ func (c *ConfigCommands) helmPlugin() *util.SpecCMD { } } -func (c *ConfigCommands) rmkConfigInit() *util.SpecCMD { - exRMK, err := os.Executable() - if err != nil { - panic(err) - } +func (c *ConfigCommands) configAws(profile string) error { + ac := aws_provider.NewAwsConfigure(c.Ctx.Context, profile) + ac.ConfigSource = strings.Join(ac.AWSSharedConfigFile(profile), "") + ac.CredentialsSource = strings.Join(ac.AWSSharedCredentialsFile(profile), "") - return &util.SpecCMD{ - Args: []string{"config", "init"}, - Command: exRMK, - Dir: c.WorkDir, - Ctx: c.Ctx.Context, - Debug: true, + if util.IsExists(ac.ConfigSource, true) { + if err := ac.ReadAWSConfigProfile(); err != nil { + return err + } } -} -func (c *ConfigCommands) checkAwsEnv() (map[string]string, bool) { - awsEnvs := map[string]string{ - "region": "AWS_REGION", - "aws_access_key_id": "AWS_ACCESS_KEY_ID", - "aws_secret_access_key": "AWS_SECRET_ACCESS_KEY", - "aws_session_token": "AWS_SESSION_TOKEN", + if c.Ctx.IsSet("aws-access-key-id") && c.Ctx.IsSet("aws-secret-access-key") { + ac.AwsCredentialsProfile.AccessKeyID = c.Ctx.String("aws-access-key-id") + ac.AwsCredentialsProfile.SecretAccessKey = c.Ctx.String("aws-secret-access-key") + } else if c.Ctx.IsSet("aws-access-key-id") { + ac.AwsCredentialsProfile.AccessKeyID = c.Ctx.String("aws-access-key-id") + ac.AwsCredentialsProfile.SecretAccessKey = "" + } else if c.Ctx.IsSet("aws-secret-access-key") { + ac.AwsCredentialsProfile.AccessKeyID = "" + ac.AwsCredentialsProfile.SecretAccessKey = c.Ctx.String("aws-secret-access-key") } - for key, val := range awsEnvs { - value, ok := os.LookupEnv(val) - if !ok { - delete(awsEnvs, key) - } else { - awsEnvs[key] = value - } + if c.Ctx.IsSet("aws-region") { + ac.Region = c.Ctx.String("aws-region") } - if len(awsEnvs) > 0 { - return awsEnvs, true - } else { - return nil, false + if c.Ctx.IsSet("aws-session-token") { + ac.AwsCredentialsProfile.SessionToken = c.Ctx.String("aws-session-token") } -} -func (c *ConfigCommands) configAws() error { - if awsEnvs, ok := c.checkAwsEnv(); !ok { - c.SpecCMD = c.awsConfigure(c.Conf.Profile) - return releaseRunner(c).runCMD() + if err := ac.ValidateAWSCredentials(); err != nil { + return err } else { - for key, val := range awsEnvs { - c.SpecCMD = c.awsConfigure(c.Conf.Profile) - c.SpecCMD.Args = append(c.SpecCMD.Args, "set", key, val) - if err := releaseRunner(c).runCMD(); err != nil { - return err - } - } + c.Conf.AwsConfigure = ac + } - zap.S().Infof("AWS profile by name %s was created", c.Conf.Profile) - return nil + if err := ac.WriteAWSConfigProfile(); err != nil { + return err } + + return nil } func (c *ConfigCommands) configAwsMFA() error { var tokenExpiration time.Time currentTime := time.Now() regularProfile := c.Conf.Profile + regularProfileConfigSource := c.Conf.ConfigSource + regularProfileCredentialsSource := c.Conf.CredentialsSource if len(c.Conf.AWSMFATokenExpiration) > 0 { unixTime, err := strconv.ParseInt(c.Conf.AWSMFATokenExpiration, 10, 64) @@ -136,6 +110,41 @@ func (c *ConfigCommands) configAwsMFA() error { if len(c.Conf.MFADeviceSerialNumber) > 0 { zap.S().Infof("MFA device SerialNumber: %s", c.Conf.MFADeviceSerialNumber) + } else { + if len(c.Conf.AWSMFAProfile) > 0 && len(c.Conf.AWSMFATokenExpiration) > 0 { + ac := aws_provider.NewAwsConfigure(c.Ctx.Context, c.Conf.AWSMFAProfile) + ac.ConfigSource = strings.Join(ac.AWSSharedConfigFile(c.Conf.AWSMFAProfile), "") + ac.CredentialsSource = strings.Join(ac.AWSSharedCredentialsFile(c.Conf.AWSMFAProfile), "") + if util.IsExists(ac.ConfigSource, true) { + if err := ac.ReadAWSConfigProfile(); err != nil { + return err + } + } + + ac.Profile = strings.TrimSuffix(regularProfile, "-mfa") + ac.ConfigSource = strings.TrimSuffix(regularProfileConfigSource, "-mfa") + ac.CredentialsSource = strings.TrimSuffix(regularProfileCredentialsSource, "-mfa") + + if err := ac.WriteAWSConfigProfile(); err != nil { + return err + } + + if err := os.RemoveAll(strings.Join(c.Conf.AWSSharedConfigFile(c.Conf.AWSMFAProfile), "")); err != nil { + return err + } + + if err := os.RemoveAll(strings.Join(c.Conf.AWSSharedCredentialsFile(c.Conf.AWSMFAProfile), "")); err != nil { + return err + } + + c.Conf.AWSMFAProfile = "" + c.Conf.AWSMFATokenExpiration = "" + c.Conf.AwsConfigure.Profile = ac.Profile + c.Conf.AwsConfigure.ConfigSource = ac.ConfigSource + c.Conf.AwsConfigure.CredentialsSource = ac.CredentialsSource + + return nil + } } if currentTime.Before(tokenExpiration) { @@ -150,57 +159,34 @@ func (c *ConfigCommands) configAwsMFA() error { c.Conf.AWSMFAProfile = regularProfile + "-mfa" c.Conf.AWSMFATokenExpiration = strconv.FormatInt(c.Conf.Expiration.Unix(), 10) - MFAProfileArgs := map[string]string{ - "aws_access_key_id": c.Conf.MFAProfileCredentials.AccessKeyID, - "aws_secret_access_key": c.Conf.MFAProfileCredentials.SecretAccessKey, - "output": "text", - "region": c.Conf.Region, - } - - regularProfileArgs := map[string]string{ - "aws_access_key_id": c.Conf.MFAToken.AccessKeyId, - "aws_secret_access_key": c.Conf.MFAToken.SecretAccessKey, - "aws_session_token": c.Conf.MFAToken.SessionToken, - } + acMFA := aws_provider.NewAwsConfigure(c.Ctx.Context, regularProfile+"-mfa") + acMFA.AwsCredentialsProfile.AccessKeyID = c.Conf.MFAProfileCredentials.AccessKeyID + acMFA.AwsCredentialsProfile.SecretAccessKey = c.Conf.MFAProfileCredentials.SecretAccessKey + acMFA.ConfigSource = regularProfileConfigSource + "-mfa" + acMFA.CredentialsSource = regularProfileCredentialsSource + "-mfa" + acMFA.Region = c.Conf.Region - for key, val := range MFAProfileArgs { - c.SpecCMD = c.awsConfigure(c.Conf.AWSMFAProfile) - c.SpecCMD.Args = append(c.SpecCMD.Args, "set", key, val) - if err := releaseRunner(c).runCMD(); err != nil { - return err - } + if err := acMFA.WriteAWSConfigProfile(); err != nil { + return err } - for key, val := range regularProfileArgs { - c.SpecCMD = c.awsConfigure(regularProfile) - c.SpecCMD.Args = append(c.SpecCMD.Args, "set", key, val) - if err := releaseRunner(c).runCMD(); err != nil { - return err - } - } - } + acRegular := aws_provider.NewAwsConfigure(c.Ctx.Context, regularProfile) + acRegular.AwsCredentialsProfile.AccessKeyID = c.Conf.MFAToken.AccessKeyID + acRegular.AwsCredentialsProfile.SecretAccessKey = c.Conf.MFAToken.SecretAccessKey + acRegular.AwsCredentialsProfile.SessionToken = c.Conf.MFAToken.SessionToken + acRegular.ConfigSource = regularProfileConfigSource + acRegular.CredentialsSource = regularProfileCredentialsSource + acRegular.Region = c.Conf.Region - c.Conf.AwsConfigure.Profile = regularProfile - - return nil -} - -func (c *ConfigCommands) copyAWSProfile(profile string) error { - profileArgs := map[string]string{ - "aws_access_key_id": c.Conf.MFAProfileCredentials.AccessKeyID, - "aws_secret_access_key": c.Conf.MFAProfileCredentials.SecretAccessKey, - "output": "text", - "region": c.Conf.Region, - } - - for key, val := range profileArgs { - c.SpecCMD = c.awsConfigure(profile) - c.SpecCMD.Args = append(c.SpecCMD.Args, "set", key, val) - if err := releaseRunner(c).runCMD(); err != nil { + if err := acRegular.WriteAWSConfigProfile(); err != nil { return err } } + c.Conf.AwsConfigure.Profile = strings.TrimSuffix(regularProfile, "-mfa") + c.Conf.AwsConfigure.ConfigSource = strings.TrimSuffix(regularProfileConfigSource, "-mfa") + c.Conf.AwsConfigure.CredentialsSource = strings.TrimSuffix(regularProfileCredentialsSource, "-mfa") + return nil } @@ -280,14 +266,13 @@ func (c *ConfigCommands) configHelmPlugins() error { return nil } -func (c *ConfigCommands) rmkConfig() error { - c.SpecCMD = c.rmkConfigInit() - return releaseRunner(c).runCMD() -} - func initAWSProfile(c *cli.Context, conf *config.Config, gitSpec *git_handler.GitSpec) error { var profile string + conf.AwsConfigure = aws_provider.NewAwsConfigure(c.Context, gitSpec.ID) + conf.AWSMFAProfile = c.String("aws-mfa-profile") + conf.AWSMFATokenExpiration = c.String("aws-mfa-token-expiration") + // Detect if MFA is enabled if len(conf.AWSMFAProfile) > 0 && len(conf.AWSMFATokenExpiration) > 0 { profile = conf.AWSMFAProfile @@ -295,170 +280,67 @@ func initAWSProfile(c *cli.Context, conf *config.Config, gitSpec *git_handler.Gi profile = conf.Profile } - if c.Bool("reconfigure") { - if err := os.RemoveAll(strings.Join(conf.AWSSharedCredentialsFile(conf.Profile), "")); err != nil { - return err - } - - // Reconfigure regular AWS profile - if err := newConfigCommands(conf, c, util.GetPwdPath("")).configAws(); err != nil { - return err - } - - // Get CallerIdentity and region for regular AWS profile - if _, err := conf.AwsConfigure.GetAwsConfigure(conf.Profile); err != nil { - return err - } - - // Delete MFA profile - if strings.Contains(profile, "-mfa") { - if err := os.RemoveAll(strings.Join(conf.AWSSharedConfigFile(profile), "")); err != nil { - return err - } - - if err := os.RemoveAll(strings.Join(conf.AWSSharedCredentialsFile(profile), "")); err != nil { - return err - } - } - - // Reset ConfigFrom value for config for current environment - conf.ConfigNameFrom = gitSpec.ID - // Reset AWSMFAProfile value for config for current environment - conf.AWSMFAProfile = "" - // Reset AWSMFATokenExpiration value for config for current environment - conf.AWSMFATokenExpiration = "" - // Returning a regular profile value - profile = conf.Profile - - // Create new MFA profile - if err := newConfigCommands(conf, c, util.GetPwdPath("")).configAwsMFA(); err != nil { - return err - } + if err := newConfigCommands(conf, c, util.GetPwdPath("")).configAws(profile); err != nil { + return err } if ok, err := conf.AwsConfigure.GetAwsConfigure(profile); err != nil && ok { zap.S().Warnf("%s", err.Error()) - if err := newConfigCommands(conf, c, util.GetPwdPath("")).configAws(); err != nil { - return err - } - - if _, err := conf.AwsConfigure.GetAwsConfigure(profile); err != nil { - return err - } - - if err := newConfigCommands(conf, c, util.GetPwdPath("")).configAwsMFA(); err != nil { - return err - } - } else if !c.Bool("reconfigure") { - if err := newConfigCommands(conf, c, util.GetPwdPath("")).configAwsMFA(); err != nil { - return err - } } else if !ok && err != nil { return err } + if err := newConfigCommands(conf, c, util.GetPwdPath("")).configAwsMFA(); err != nil { + return err + } + return nil } -func getConfigFromEnvironment(c *cli.Context, conf *config.Config, gitSpec *git_handler.GitSpec) error { - // TODO: A possible solution is to check the current cluster provider with from and prohibit such inheritance - // currentClusterProvider := c.String("cluster-provider") +func initAzureProfile(c *cli.Context, conf *config.Config, gitSpec *git_handler.GitSpec) error { + ac := azure_provider.NewAzureConfigure() + asp := azure_provider.NewRawSP() - if len(c.String("config-from")) > 0 { - configPath := util.GetHomePath(util.RMKDir, util.RMKConfig, c.String("config-from")+".yaml") - - if err := conf.ReadConfigFile(configPath); err != nil { - zap.S().Errorf("RMK config %s.yaml not initialized, please check RMK configs of exists "+ - "via command 'rmk config list' and run command 'rmk config init' with specific parameters", - c.String("config-from")) + if util.IsExists( + util.GetHomePath(azure_provider.AzureHomeDir, azure_provider.AzurePrefix+gitSpec.ID+".json"), true) { + if err := ac.ReadSPCredentials(gitSpec.ID); err != nil { return err } + } - if c.String("cluster-provider") == aws_provider.AWSClusterProvider { - if err := c.Set("config-name-from", conf.Name); err != nil { - return err - } - - // Delete regular profile - if err := os.RemoveAll(strings.Join(conf.AWSSharedCredentialsFile(gitSpec.ID), "")); err != nil { - return err - } - - if len(conf.AWSMFAProfile) > 0 && len(conf.AWSMFATokenExpiration) > 0 { - regularProfile := conf.Profile - - // Get MFA profile credentials. - conf.AwsConfigure.Profile = conf.AWSMFAProfile - if err := conf.GetAWSCredentials(); err != nil { - return err - } - - // Copy MFA profile for current environment - conf.AwsConfigure.Profile = regularProfile - if err := newConfigCommands(conf, c, util.GetPwdPath("")).copyAWSProfile(gitSpec.ID); err != nil { - return err - } - } else { - // Delete config MFA profile - if err := os.RemoveAll(strings.Join(conf.AWSSharedConfigFile(gitSpec.ID+"-mfa"), "")); err != nil { - return err - } - - // Delete credentials MFA profile - if err := os.RemoveAll(strings.Join(conf.AWSSharedCredentialsFile(gitSpec.ID+"-mfa"), "")); err != nil { - return err - } - - // Get regular profile credentials. - if err := conf.GetAWSCredentials(); err != nil { - return err - } - - // Copy regular profile for current environment - if err := newConfigCommands(conf, c, util.GetPwdPath("")).copyAWSProfile(gitSpec.ID); err != nil { - return err - } - } - - // Reset AWSMFAProfile value for config for current environment - if err := c.Set("aws-mfa-profile", ""); err != nil { - return err - } - - // Reset AWSMFATokenExpiration value for config for current environment - if err := c.Set("aws-mfa-token-expiration", ""); err != nil { - return err - } - - conf.AwsConfigure.Profile = gitSpec.ID + if c.Bool("azure-service-principle") { + if err := json.NewDecoder(os.Stdin).Decode(&asp); err != nil { + return fmt.Errorf("unable to deserialize JSON from STDIN: %s", err.Error()) } - conf.ConfigNameFrom = c.String("config-name-from") + ac.MergeAzureRawSP(asp) + } - return nil + if c.IsSet("azure-client-id") { + ac.ClientID = c.String("azure-client-id") } - //TODO: deprecate after full list CAPI providers will be implemented - if err := util.ValidateGitHubToken(c, "required parameter --github-token not set"); err != nil { - return err + if c.IsSet("azure-client-secret") { + ac.ClientSecret = c.String("azure-client-secret") } - if err := util.ValidateNArg(c, 0); err != nil { - return err + if c.IsSet("azure-subscription-id") { + ac.SubscriptionID = c.String("azure-subscription-id") } - if !c.IsSet("config-name-from") { - if err := c.Set("config-name-from", gitSpec.ID); err != nil { - return err - } + if c.IsSet("azure-tenant-id") { + ac.TenantID = c.String("azure-tenant-id") } - if c.String("cluster-provider") == aws_provider.AWSClusterProvider { - conf.AwsConfigure = new(aws_provider.AwsConfigure) + if err := ac.ValidateSPCredentials(); err != nil { + return err + } else { + conf.AzureConfigure = ac } - conf.ConfigNameFrom = c.String("config-name-from") - conf.GitHubToken = c.String("github-token") + if err := ac.WriteSPCredentials(gitSpec.ID); err != nil { + return err + } return nil } @@ -470,11 +352,6 @@ func configDeleteAction(conf *config.Config) cli.ActionFunc { } switch { - case c.String("cluster-provider") == azure_provider.AzureClusterProvider: - if err := os.RemoveAll(util.GetHomePath(azure_provider.AzureHomeDir, - azure_provider.AzurePrefix+conf.Name+".json")); err != nil { - return err - } case c.String("cluster-provider") == aws_provider.AWSClusterProvider: // Delete MFA profile if len(conf.AWSMFAProfile) > 0 && len(conf.AWSMFATokenExpiration) > 0 { @@ -487,15 +364,20 @@ func configDeleteAction(conf *config.Config) cli.ActionFunc { } } - // Delete config MFA profile + // Delete config regular profile if err := os.RemoveAll(strings.Join(conf.AWSSharedConfigFile(conf.Profile), "")); err != nil { return err } - // Delete credentials MFA profile + // Delete credentials regular profile if err := os.RemoveAll(strings.Join(conf.AWSSharedCredentialsFile(conf.Profile), "")); err != nil { return err } + case c.String("cluster-provider") == azure_provider.AzureClusterProvider: + if err := os.RemoveAll(util.GetHomePath(azure_provider.AzureHomeDir, + azure_provider.AzurePrefix+conf.Name+".json")); err != nil { + return err + } } if err := os.RemoveAll(c.String("config")); err != nil { @@ -510,10 +392,6 @@ func configDeleteAction(conf *config.Config) cli.ActionFunc { func configInitAction(conf *config.Config, gitSpec *git_handler.GitSpec) cli.ActionFunc { return func(c *cli.Context) error { - if err := getConfigFromEnvironment(c, conf, gitSpec); err != nil { - return err - } - zap.S().Infof("loaded config file by path: %s", c.String("config")) start := time.Now() @@ -523,6 +401,7 @@ func configInitAction(conf *config.Config, gitSpec *git_handler.GitSpec) cli.Act conf.Environment = gitSpec.DefaultBranch conf.ClusterProvider = c.String("cluster-provider") conf.ProgressBar = c.Bool("progress-bar") + conf.GitHubToken = c.String("github-token") zap.S().Infof("RMK will use values for %s environment", conf.Environment) if c.Bool("slack-notifications") { @@ -538,73 +417,23 @@ func configInitAction(conf *config.Config, gitSpec *git_handler.GitSpec) cli.Act } switch conf.ClusterProvider { - case azure_provider.AzureClusterProvider: - conf.AwsConfigure = &aws_provider.AwsConfigure{} - ac := azure_provider.NewAzureConfigure() - asp := azure_provider.NewRawSP() - - if util.IsExists( - util.GetHomePath(azure_provider.AzureHomeDir, azure_provider.AzurePrefix+gitSpec.ID+".json"), true) { - if err := ac.ReadSPCredentials(gitSpec.ID); err != nil { - return err - } - } - - if c.Bool("azure-service-principle") { - if err := json.NewDecoder(os.Stdin).Decode(&asp); err != nil { - return fmt.Errorf("unable to deserialize json from stdin: %s", err.Error()) - } - - ac.MergeAzureRawSP(asp) - } - - if c.IsSet("azure-client-id") { - ac.ClientID = c.String("azure-client-id") - } - - if c.IsSet("azure-client-secret") { - ac.ClientSecret = c.String("azure-client-secret") - } - - if c.IsSet("azure-subscription-id") { - ac.SubscriptionID = c.String("azure-subscription-id") - } - - if c.IsSet("azure-tenant-id") { - ac.TenantID = c.String("azure-tenant-id") - } - - if err := ac.CheckSPCredentials(); err != nil { + case aws_provider.AWSClusterProvider: + conf.AzureConfigure = nil + if err := initAWSProfile(c, conf, gitSpec); err != nil { return err - } else { - conf.AzureConfigure = azure_provider.NewAzureConfigure() - conf.AzureConfigure.SubscriptionID = ac.SubscriptionID } - if err := ac.WriteSPCredentials(gitSpec.ID); err != nil { + conf.SopsAgeKeys = util.GetHomePath(util.RMKDir, util.SopsRootName, conf.Tenant+"-"+util.SopsRootName+"-"+aws_provider.AWSClusterProvider) + case azure_provider.AzureClusterProvider: + conf.AwsConfigure = nil + if err := initAzureProfile(c, conf, gitSpec); err != nil { return err } conf.SopsAgeKeys = util.GetHomePath(util.RMKDir, util.SopsRootName, conf.Tenant+"-"+util.SopsRootName+"-"+azure_provider.AzureClusterProvider) - case aws_provider.AWSClusterProvider: - conf.AzureConfigure = &azure_provider.AzureConfigure{} - conf.AwsConfigure.Profile = gitSpec.ID - conf.AWSMFAProfile = c.String("aws-mfa-profile") - conf.AWSMFATokenExpiration = c.String("aws-mfa-token-expiration") - - // AWS Profile init configuration with support MFA - if err := initAWSProfile(c, conf, gitSpec); err != nil { - return err - } - - //Formation of a unique bucket name, consisting of the prefix tenant of the repository, - //constant and the first 3 and last 2 numbers AWS account id - awsUID := conf.AccountID[0:3] + conf.AccountID[len(conf.AccountID)-2:] - conf.SopsAgeKeys = util.GetHomePath(util.RMKDir, util.SopsRootName, conf.Tenant+"-"+util.SopsRootName+"-"+awsUID) - conf.SopsBucketName = conf.Tenant + "-" + util.SopsRootName + "-" + awsUID case util.LocalClusterProvider: - conf.AwsConfigure = &aws_provider.AwsConfigure{} - conf.AzureConfigure = &azure_provider.AzureConfigure{} + conf.AwsConfigure = nil + conf.AzureConfigure = nil conf.SopsAgeKeys = util.GetHomePath(util.RMKDir, util.SopsRootName, conf.Tenant+"-"+util.SopsRootName+"-"+util.LocalClusterProvider) } @@ -616,25 +445,6 @@ func configInitAction(conf *config.Config, gitSpec *git_handler.GitSpec) cli.Act return err } - if conf.ClusterProvider == aws_provider.AWSClusterProvider { - //create s3 bucket for sops age keys - if err := conf.CreateBucket(conf.SopsBucketName); err != nil { - return err - } - - if err := conf.DownloadFromBucket("", conf.SopsBucketName, conf.SopsAgeKeys, conf.Tenant); err != nil { - return err - } - - if err := resolveDependencies(conf.InitConfig(), c, false); err != nil { - return err - } - - zap.S().Infof("time spent on initialization: %.fs", time.Since(start).Seconds()) - - return nil - } - if err := resolveDependencies(conf.InitConfig(), c, false); err != nil { return err } diff --git a/cmd/flags.go b/cmd/flags.go index fad404e..5acaf51 100644 --- a/cmd/flags.go +++ b/cmd/flags.go @@ -3,11 +3,40 @@ package cmd import ( "github.com/urfave/cli/v2" "github.com/urfave/cli/v2/altsrc" + "rmk/util" ) func flagsConfig() []cli.Flag { return []cli.Flag{ + &cli.StringFlag{ + Category: awsFlagsCategory, + Name: "aws-access-key-id", + Usage: "AWS access key ID for IAM user", + Aliases: []string{"awid"}, + EnvVars: []string{"RMK_AWS_ACCESS_KEY_ID", "AWS_ACCESS_KEY_ID"}, + }, + &cli.StringFlag{ + Category: awsFlagsCategory, + Name: "aws-region", + Usage: "AWS region for current AWS account", + Aliases: []string{"awr"}, + EnvVars: []string{"RMK_AWS_REGION", "AWS_REGION", "AWS_DEFAULT_REGION"}, + }, + &cli.StringFlag{ + Category: awsFlagsCategory, + Name: "aws-secret-access-key", + Usage: "AWS secret access key for IAM user", + Aliases: []string{"awsk"}, + EnvVars: []string{"RMK_AWS_SECRET_ACCESS_KEY", "AWS_SECRET_ACCESS_KEY"}, + }, + &cli.StringFlag{ + Category: awsFlagsCategory, + Name: "aws-session-token", + Usage: "AWS session token for IAM user", + Aliases: []string{"awst"}, + EnvVars: []string{"RMK_AWS_SESSION_TOKEN", "AWS_SESSION_TOKEN"}, + }, altsrc.NewStringFlag( &cli.StringFlag{ Name: "aws-mfa-profile", @@ -24,7 +53,7 @@ func flagsConfig() []cli.Flag { Category: azureFlagsCategory, Name: "azure-client-id", Usage: "Azure client ID for Service Principal", - Aliases: []string{"azc"}, + Aliases: []string{"azid"}, EnvVars: []string{"RMK_AZURE_CLIENT_ID", "AZURE_CLIENT_ID"}, }, &cli.StringFlag{ @@ -37,13 +66,13 @@ func flagsConfig() []cli.Flag { &cli.BoolFlag{ Category: azureFlagsCategory, Name: "azure-service-principle", - Usage: "Azure Service Principal stdin content", + Usage: "Azure service principal STDIN content", Aliases: []string{"azsp"}, }, &cli.StringFlag{ Category: azureFlagsCategory, Name: "azure-subscription-id", - Usage: "Azure subscription ID for current Azure platform domain", + Usage: "Azure subscription ID for current platform domain", Aliases: []string{"azs"}, EnvVars: []string{"RMK_AZURE_SUBSCRIPTION_ID", "AZURE_SUBSCRIPTION_ID"}, }, @@ -54,31 +83,14 @@ func flagsConfig() []cli.Flag { Aliases: []string{"azt"}, EnvVars: []string{"RMK_AZURE_TENANT_ID", "AZURE_TENANT_ID"}, }, - &cli.BoolFlag{ - Name: "reconfigure", - Usage: "force Cluster Providers credentials recreate", - Aliases: []string{"r"}, - }, &cli.StringFlag{ Name: "config", Hidden: true, }, - altsrc.NewStringFlag( - &cli.StringFlag{ - Name: "config-name-from", - Hidden: true, - }, - ), - &cli.StringFlag{ - Name: "config-from", - Usage: "inheritance of RMK config credentials from another RMK config", - Aliases: []string{"cf"}, - EnvVars: []string{"RMK_CONFIG_FROM"}, - }, altsrc.NewStringFlag( &cli.StringFlag{ Name: "github-token", - Usage: "personal access token for download GitHub artifacts", + Usage: "GitHub personal access token, required when using private repositories", Aliases: []string{"ght"}, EnvVars: []string{"RMK_GITHUB_TOKEN"}, }, @@ -102,33 +114,37 @@ func flagsConfig() []cli.Flag { ), altsrc.NewBoolFlag( &cli.BoolFlag{ - Name: "slack-notifications", - Usage: "enable Slack notifications", - Aliases: []string{"n"}, + Category: "Slack notifications", + Name: "slack-notifications", + Usage: "enable Slack notifications", + Aliases: []string{"n"}, }, ), altsrc.NewStringFlag( &cli.StringFlag{ - Name: "slack-webhook", - Usage: "URL for Slack webhook", - Aliases: []string{"sw"}, - EnvVars: []string{"RMK_SLACK_WEBHOOK"}, + Category: "Slack notifications", + Name: "slack-webhook", + Usage: "URL for Slack webhook", + Aliases: []string{"sw"}, + EnvVars: []string{"RMK_SLACK_WEBHOOK"}, }, ), altsrc.NewStringFlag( &cli.StringFlag{ - Name: "slack-channel", - Usage: "channel name for Slack notification", - Aliases: []string{"sc"}, - EnvVars: []string{"RMK_SLACK_CHANNEL"}, + Category: "Slack notifications", + Name: "slack-channel", + Usage: "channel name for Slack notifications", + Aliases: []string{"sc"}, + EnvVars: []string{"RMK_SLACK_CHANNEL"}, }, ), altsrc.NewStringSliceFlag( &cli.StringSliceFlag{ - Name: "slack-message-details", - Usage: "additional information for body of Slack message", - Aliases: []string{"smd"}, - EnvVars: []string{"RMK_SLACK_MESSAGE_DETAILS"}, + Category: "Slack notifications", + Name: "slack-message-details", + Usage: "additional information for body of Slack messages", + Aliases: []string{"smd"}, + EnvVars: []string{"RMK_SLACK_MESSAGE_DETAILS"}, }, ), } diff --git a/cmd/k3d.go b/cmd/k3d.go index f210740..092c60c 100644 --- a/cmd/k3d.go +++ b/cmd/k3d.go @@ -125,7 +125,7 @@ func (k *K3DCommands) listK3DClusters() error { k.SpecCMD.DisableStdOut = true if err := releaseRunner(k).runCMD(); err != nil { if strings.Contains(k.SpecCMD.StderrBuf.String(), "No nodes found for given cluster") { - return fmt.Errorf("cluster %s not running", util.CAPI) + return fmt.Errorf("%s management cluster not found", strings.ToUpper(util.CAPI)) } else { return fmt.Errorf("%s", k.SpecCMD.StderrBuf.String()) } @@ -159,11 +159,6 @@ func (k *K3DCommands) startStopK3DCluster() error { func K3DCreateAction(conf *config.Config) cli.ActionFunc { return func(c *cli.Context) error { - //TODO: deprecate after full list CAPI providers will be implemented - if err := util.ValidateGitHubToken(c, ""); err != nil { - return err - } - if err := util.ValidateNArg(c, 0); err != nil { return err } @@ -178,11 +173,6 @@ func K3DCreateAction(conf *config.Config) cli.ActionFunc { func K3DAction(conf *config.Config, action func(k3dRunner K3DRunner) error) cli.ActionFunc { return func(c *cli.Context) error { - //TODO: deprecate after full list CAPI providers will be implemented - if err := util.ValidateGitHubToken(c, ""); err != nil { - return err - } - if err := util.ValidateNArg(c, 0); err != nil { return err } diff --git a/cmd/project.go b/cmd/project.go index f6bc371..a371cf8 100644 --- a/cmd/project.go +++ b/cmd/project.go @@ -446,11 +446,6 @@ func (p *ProjectCommands) generateProject(gitSpec *git_handler.GitSpec) error { func projectGenerateAction(conf *config.Config, gitSpec *git_handler.GitSpec) cli.ActionFunc { return func(c *cli.Context) error { - //TODO: deprecate after full list CAPI providers will be implemented - if err := util.ValidateGitHubToken(c, ""); err != nil { - return err - } - if err := util.ValidateNArg(c, 0); err != nil { return err } @@ -461,11 +456,6 @@ func projectGenerateAction(conf *config.Config, gitSpec *git_handler.GitSpec) cl func projectUpdateAction(conf *config.Config, gitSpec *git_handler.GitSpec) cli.ActionFunc { return func(c *cli.Context) error { - //TODO: deprecate after full list CAPI providers will be implemented - if err := util.ValidateGitHubToken(c, ""); err != nil { - return err - } - if err := util.ValidateNArg(c, 0); err != nil { return err } diff --git a/cmd/release.go b/cmd/release.go index 3cd91a1..54f6cb6 100644 --- a/cmd/release.go +++ b/cmd/release.go @@ -20,6 +20,8 @@ import ( "rmk/config" "rmk/git_handler" "rmk/notification" + "rmk/providers/aws_provider" + "rmk/providers/azure_provider" "rmk/util" ) @@ -87,31 +89,33 @@ type HelmStatus struct { } func (rc *ReleaseCommands) runCMD() error { - if err := rc.SpecCMD.AddEnv(); err != nil { + if err := rc.SpecCMD.AddOSEnv(); err != nil { return err } if err := rc.SpecCMD.ExecCMD(); err != nil { - if rc.SpecCMD.Debug { - zap.S().Debugf("command: %s", rc.SpecCMD.CommandStr) - zap.S().Debugf("path: %s", rc.SpecCMD.Dir) - for _, val := range rc.SpecCMD.Envs { - zap.S().Debugf("env: %s", strings.ReplaceAll(val, rc.Conf.GitHubToken, "[rmk_sensitive]")) - } - } + rc.debugLevel() return err } + rc.debugLevel() + + return nil +} + +func (rc *ReleaseCommands) debugLevel() { if rc.SpecCMD.Debug { zap.S().Debugf("command: %s", rc.SpecCMD.CommandStr) zap.S().Debugf("path: %s", rc.SpecCMD.Dir) for _, val := range rc.SpecCMD.Envs { - zap.S().Debugf("env: %s", strings.ReplaceAll(val, rc.Conf.GitHubToken, "[rmk_sensitive]")) + if len(rc.Conf.GitHubToken) > 0 { + zap.S().Debugf("env: %s", strings.ReplaceAll(val, rc.Conf.GitHubToken, "[rmk_sensitive]")) + } else { + zap.S().Debugf("env: %s", val) + } } } - - return nil } func (rc *ReleaseCommands) nestedHelmfiles(envs ...string) []string { @@ -129,20 +133,38 @@ func (rc *ReleaseCommands) nestedHelmfiles(envs ...string) []string { } func (rc *ReleaseCommands) prepareHelmfile(args ...string) *util.SpecCMD { + var sensKeyWords []string + defaultArgs := []string{"--environment", rc.Conf.Environment} + // generating common environment variables envs := append([]string{}, "NAME="+rc.Conf.Name, - "TENANT="+rc.Conf.Tenant, + "ROOT_DOMAIN="+rc.Conf.RootDomain, "SOPS_AGE_KEY_FILE="+filepath.Join(rc.Conf.SopsAgeKeys, util.SopsAgeKeyFile), - "GITHUB_TOKEN="+rc.Conf.GitHubToken, - "AWS_PROFILE="+rc.Conf.Profile, - "AWS_CONFIG_FILE="+strings.Join(rc.Conf.AWSSharedConfigFile(rc.Conf.Profile), ""), - "AWS_SHARED_CREDENTIALS_FILE="+strings.Join(rc.Conf.AWSSharedCredentialsFile(rc.Conf.Profile), ""), - "AZURE_SUBSCRIPTION_ID="+rc.Conf.AzureConfigure.SubscriptionID, + "TENANT="+rc.Conf.Tenant, ) - envs = append(envs, "ROOT_DOMAIN="+rc.Conf.RootDomain) + if len(rc.Conf.GitHubToken) > 0 { + sensKeyWords = []string{rc.Conf.GitHubToken} + envs = append(envs, "GITHUB_TOKEN="+rc.Conf.GitHubToken) + } else { + sensKeyWords = []string{} + } + + // generating additional environment variables to specific cluster provider + switch rc.Conf.ClusterProvider { + case aws_provider.AWSClusterProvider: + envs = append(envs, + "AWS_PROFILE="+rc.Conf.Profile, + "AWS_CONFIG_FILE="+strings.Join(rc.Conf.AWSSharedConfigFile(rc.Conf.Profile), ""), + "AWS_SHARED_CREDENTIALS_FILE="+strings.Join(rc.Conf.AWSSharedCredentialsFile(rc.Conf.Profile), ""), + ) + case azure_provider.AzureClusterProvider: + envs = append(envs, + "AZURE_SUBSCRIPTION_ID="+rc.Conf.AzureConfigure.SubscriptionID, + ) + } for _, val := range rc.Conf.HooksMapping { keyTenantEnv := regexp.MustCompile(`[\-.]`).ReplaceAllString(val.Tenant, "_") @@ -170,7 +192,7 @@ func (rc *ReleaseCommands) prepareHelmfile(args ...string) *util.SpecCMD { Dir: rc.WorkDir, Envs: envs, Debug: true, - SensKeyWords: []string{rc.Conf.GitHubToken}, + SensKeyWords: sensKeyWords, } } @@ -503,11 +525,6 @@ func (sr *SpecRelease) checkStatusRelease() error { func releaseHelmfileAction(conf *config.Config) cli.ActionFunc { return func(c *cli.Context) error { - //TODO: deprecate after full list CAPI providers will be implemented - if err := util.ValidateGitHubToken(c, ""); err != nil { - return err - } - if err := util.ValidateNArg(c, 0); err != nil { return err } @@ -557,11 +574,6 @@ func releaseHelmfileAction(conf *config.Config) cli.ActionFunc { func releaseRollbackAction(conf *config.Config) cli.ActionFunc { return func(c *cli.Context) error { - //TODO: deprecate after full list CAPI providers will be implemented - if err := util.ValidateGitHubToken(c, ""); err != nil { - return err - } - if err := util.ValidateNArg(c, 0); err != nil { return err } @@ -595,11 +607,6 @@ func releaseRollbackAction(conf *config.Config) cli.ActionFunc { func releaseUpdateAction(conf *config.Config, gitSpec *git_handler.GitSpec) cli.ActionFunc { return func(c *cli.Context) error { - //TODO: deprecate after full list CAPI providers will be implemented - if err := util.ValidateGitHubToken(c, ""); err != nil { - return err - } - if err := util.ValidateNArg(c, 0); err != nil { return err } diff --git a/cmd/secret.go b/cmd/secret.go index 9edd670..f2947fd 100644 --- a/cmd/secret.go +++ b/cmd/secret.go @@ -379,11 +379,6 @@ func (sc *SecretCommands) runHelmSecretsCMD(secretFilePath string, returnCMDErro func secretMgrEncryptDecryptAction(conf *config.Config) cli.ActionFunc { return func(c *cli.Context) error { - //TODO: deprecate after full list CAPI providers will be implemented - if err := util.ValidateGitHubToken(c, ""); err != nil { - return err - } - if err := util.ValidateNArg(c, 0); err != nil { return err } @@ -407,11 +402,6 @@ func secretMgrGenerateAction(conf *config.Config) cli.ActionFunc { } func secretKeysCreateAction(conf *config.Config) cli.ActionFunc { return func(c *cli.Context) error { - //TODO: deprecate after full list CAPI providers will be implemented - if err := util.ValidateGitHubToken(c, ""); err != nil { - return err - } - if err := util.ValidateNArg(c, 0); err != nil { return err } @@ -446,11 +436,6 @@ func secretKeysUploadAction(conf *config.Config) cli.ActionFunc { func secretAction(conf *config.Config, action func(secretRunner SecretRunner) error) cli.ActionFunc { return func(c *cli.Context) error { - //TODO: deprecate after full list CAPI providers will be implemented - if err := util.ValidateGitHubToken(c, ""); err != nil { - return err - } - if err := util.ValidateNArg(c, 1); err != nil { return err } diff --git a/config/config.go b/config/config.go index 89c1e1d..c79b50e 100644 --- a/config/config.go +++ b/config/config.go @@ -26,7 +26,6 @@ type Config struct { Name string `yaml:"name,omitempty"` Tenant string `yaml:"tenant,omitempty"` Environment string `yaml:"environment,omitempty"` - ConfigNameFrom string `yaml:"config-name-from,omitempty"` RootDomain string `yaml:"root-domain,omitempty"` GitHubToken string `yaml:"github-token,omitempty"` ClusterProvider string `yaml:"cluster-provider"` diff --git a/go.mod b/go.mod index bd971bc..6a70fda 100644 --- a/go.mod +++ b/go.mod @@ -9,15 +9,13 @@ require ( github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/containerservice/armcontainerservice/v6 v6.1.0 github.com/Masterminds/semver v1.5.0 github.com/Masterminds/sprig/v3 v3.2.3 - github.com/aws/aws-sdk-go-v2 v1.24.1 + github.com/aws/aws-sdk-go-v2 v1.32.3 github.com/aws/aws-sdk-go-v2/config v1.26.3 github.com/aws/aws-sdk-go-v2/feature/s3/manager v1.15.11 - github.com/aws/aws-sdk-go-v2/service/dynamodb v1.26.8 - github.com/aws/aws-sdk-go-v2/service/ecr v1.24.7 + github.com/aws/aws-sdk-go-v2/service/eks v1.51.1 github.com/aws/aws-sdk-go-v2/service/iam v1.28.7 github.com/aws/aws-sdk-go-v2/service/s3 v1.48.0 github.com/aws/aws-sdk-go-v2/service/sts v1.26.7 - github.com/aws/smithy-go v1.19.0 github.com/cheggaaa/pb v1.0.29 github.com/ghodss/yaml v1.0.0 github.com/go-git/go-git/v5 v5.11.0 @@ -54,17 +52,17 @@ require ( github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.5.4 // indirect github.com/aws/aws-sdk-go-v2/credentials v1.16.14 // indirect github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.14.11 // indirect - github.com/aws/aws-sdk-go-v2/internal/configsources v1.2.10 // indirect - github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.5.10 // indirect + github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.22 // indirect + github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.22 // indirect github.com/aws/aws-sdk-go-v2/internal/ini v1.7.2 // indirect github.com/aws/aws-sdk-go-v2/internal/v4a v1.2.10 // indirect github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.10.4 // indirect github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.2.10 // indirect - github.com/aws/aws-sdk-go-v2/service/internal/endpoint-discovery v1.8.11 // indirect github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.10.10 // indirect github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.16.10 // indirect github.com/aws/aws-sdk-go-v2/service/sso v1.18.6 // indirect github.com/aws/aws-sdk-go-v2/service/ssooidc v1.21.6 // indirect + github.com/aws/smithy-go v1.22.0 // indirect github.com/bgentry/go-netrc v0.0.0-20140422174119-9fd32a8b3d3d // indirect github.com/cloudflare/circl v1.3.7 // indirect github.com/cpuguy83/go-md2man/v2 v2.0.2 // indirect diff --git a/go.sum b/go.sum index 9701f9e..b047807 100644 --- a/go.sum +++ b/go.sum @@ -227,8 +227,8 @@ github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 h1:0CwZNZbxp69SHPd github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs= github.com/aws/aws-sdk-go v1.44.122 h1:p6mw01WBaNpbdP2xrisz5tIkcNwzj/HysobNoaAHjgo= github.com/aws/aws-sdk-go v1.44.122/go.mod h1:y4AeaBuwd2Lk+GepC1E9v0qOiTws0MIWAX4oIKwKHZo= -github.com/aws/aws-sdk-go-v2 v1.24.1 h1:xAojnj+ktS95YZlDf0zxWBkbFtymPeDP+rvUQIH3uAU= -github.com/aws/aws-sdk-go-v2 v1.24.1/go.mod h1:LNh45Br1YAkEKaAqvmE1m8FUx6a5b/V0oAKV7of29b4= +github.com/aws/aws-sdk-go-v2 v1.32.3 h1:T0dRlFBKcdaUPGNtkBSwHZxrtis8CQU17UpNBZYd0wk= +github.com/aws/aws-sdk-go-v2 v1.32.3/go.mod h1:2SK5n0a2karNTv5tbP1SjsX0uhttou00v/HpXKM1ZUo= github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.5.4 h1:OCs21ST2LrepDfD3lwlQiOqIGp6JiEUqG84GzTDoyJs= github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.5.4/go.mod h1:usURWEKSNNAcAZuzRn/9ZYPT8aZQkR7xcCtunK/LkJo= github.com/aws/aws-sdk-go-v2/config v1.26.3 h1:dKuc2jdp10y13dEEvPqWxqLoc0vF3Z9FC45MvuQSxOA= @@ -239,26 +239,22 @@ github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.14.11 h1:c5I5iH+DZcH3xOIMlz3/tC github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.14.11/go.mod h1:cRrYDYAMUohBJUtUnOhydaMHtiK/1NZ0Otc9lIb6O0Y= github.com/aws/aws-sdk-go-v2/feature/s3/manager v1.15.11 h1:I6lAa3wBWfCz/cKkOpAcumsETRkFAl70sWi8ItcMEsM= github.com/aws/aws-sdk-go-v2/feature/s3/manager v1.15.11/go.mod h1:be1NIO30kJA23ORBLqPo1LttEM6tPNSEcjkd1eKzNW0= -github.com/aws/aws-sdk-go-v2/internal/configsources v1.2.10 h1:vF+Zgd9s+H4vOXd5BMaPWykta2a6Ih0AKLq/X6NYKn4= -github.com/aws/aws-sdk-go-v2/internal/configsources v1.2.10/go.mod h1:6BkRjejp/GR4411UGqkX8+wFMbFbqsUIimfK4XjOKR4= -github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.5.10 h1:nYPe006ktcqUji8S2mqXf9c/7NdiKriOwMvWQHgYztw= -github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.5.10/go.mod h1:6UV4SZkVvmODfXKql4LCbaZUpF7HO2BX38FgBf9ZOLw= +github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.22 h1:Jw50LwEkVjuVzE1NzkhNKkBf9cRN7MtE1F/b2cOKTUM= +github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.22/go.mod h1:Y/SmAyPcOTmpeVaWSzSKiILfXTVJwrGmYZhcRbhWuEY= +github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.22 h1:981MHwBaRZM7+9QSR6XamDzF/o7ouUGxFzr+nVSIhrs= +github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.22/go.mod h1:1RA1+aBEfn+CAB/Mh0MB6LsdCYCnjZm7tKXtnk499ZQ= github.com/aws/aws-sdk-go-v2/internal/ini v1.7.2 h1:GrSw8s0Gs/5zZ0SX+gX4zQjRnRsMJDJ2sLur1gRBhEM= github.com/aws/aws-sdk-go-v2/internal/ini v1.7.2/go.mod h1:6fQQgfuGmw8Al/3M2IgIllycxV7ZW7WCdVSqfBeUiCY= github.com/aws/aws-sdk-go-v2/internal/v4a v1.2.10 h1:5oE2WzJE56/mVveuDZPJESKlg/00AaS2pY2QZcnxg4M= github.com/aws/aws-sdk-go-v2/internal/v4a v1.2.10/go.mod h1:FHbKWQtRBYUz4vO5WBWjzMD2by126ny5y/1EoaWoLfI= -github.com/aws/aws-sdk-go-v2/service/dynamodb v1.26.8 h1:XKO0BswTDeZMLDBd/b5pCEZGttNXrzRUVtFvp2Ak/Vo= -github.com/aws/aws-sdk-go-v2/service/dynamodb v1.26.8/go.mod h1:N5tqZcYMM0N1PN7UQYJNWuGyO886OfnMhf/3MAbqMcI= -github.com/aws/aws-sdk-go-v2/service/ecr v1.24.7 h1:3iaT/LnGV6jNtbBkvHZDlzz7Ky3wMHDJAyFtGd5GUJI= -github.com/aws/aws-sdk-go-v2/service/ecr v1.24.7/go.mod h1:mtzCLxk6M+KZbkJdq3cUH9GCrudw8qCy5C3EHO+5vLc= +github.com/aws/aws-sdk-go-v2/service/eks v1.51.1 h1:OQjVHkANBbwE055NK49M/kelQbapsQOsSfUUWP1mi3w= +github.com/aws/aws-sdk-go-v2/service/eks v1.51.1/go.mod h1:9wMtzHTjYbK5MLzYBWSznUPsys/n9LapMwb6UhKOVPQ= github.com/aws/aws-sdk-go-v2/service/iam v1.28.7 h1:FKPRDYZOO0Eur19vWUL1B40Op0j89KQj3kARjrszMK8= github.com/aws/aws-sdk-go-v2/service/iam v1.28.7/go.mod h1:YzMYyQ7S4twfYzLjwP24G1RAxypozVZeNaG1r2jxRms= github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.10.4 h1:/b31bi3YVNlkzkBrm9LfpaKoaYZUxIAj4sHfOTmLfqw= github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.10.4/go.mod h1:2aGXHFmbInwgP9ZfpmdIfOELL79zhdNYNmReK8qDfdQ= github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.2.10 h1:L0ai8WICYHozIKK+OtPzVJBugL7culcuM4E4JOpIEm8= github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.2.10/go.mod h1:byqfyxJBshFk0fF9YmK0M0ugIO8OWjzH2T3bPG4eGuA= -github.com/aws/aws-sdk-go-v2/service/internal/endpoint-discovery v1.8.11 h1:e9AVb17H4x5FTE5KWIP5M1Du+9M86pS+Hw0lBUdN8EY= -github.com/aws/aws-sdk-go-v2/service/internal/endpoint-discovery v1.8.11/go.mod h1:B90ZQJa36xo0ph9HsoteI1+r8owgQH/U1QNfqZQkj1Q= github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.10.10 h1:DBYTXwIGQSGs9w4jKm60F5dmCQ3EEruxdc0MFh+3EY4= github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.10.10/go.mod h1:wohMUQiFdzo0NtxbBg0mSRGZ4vL3n0dKjLTINdcIino= github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.16.10 h1:KOxnQeWy5sXyS37fdKEvAsGHOr9fa/qvwxfJurR/BzE= @@ -271,8 +267,8 @@ github.com/aws/aws-sdk-go-v2/service/ssooidc v1.21.6 h1:Yf2MIo9x+0tyv76GljxzqA3W github.com/aws/aws-sdk-go-v2/service/ssooidc v1.21.6/go.mod h1:ykf3COxYI0UJmxcfcxcVuz7b6uADi1FkiUz6Eb7AgM8= github.com/aws/aws-sdk-go-v2/service/sts v1.26.7 h1:NzO4Vrau795RkUdSHKEwiR01FaGzGOH1EETJ+5QHnm0= github.com/aws/aws-sdk-go-v2/service/sts v1.26.7/go.mod h1:6h2YuIoxaMSCFf5fi1EgZAwdfkGMgDY+DVfa61uLe4U= -github.com/aws/smithy-go v1.19.0 h1:KWFKQV80DpP3vJrrA9sVAHQ5gc2z8i4EzrLhLlWXcBM= -github.com/aws/smithy-go v1.19.0/go.mod h1:NukqUGpCZIILqqiV0NIjeFh24kd/FAa4beRb6nbIUPE= +github.com/aws/smithy-go v1.22.0 h1:uunKnWlcoL3zO7q+gG2Pk53joueEOsnNB28QdMsmiMM= +github.com/aws/smithy-go v1.22.0/go.mod h1:irrKGvNn1InZwb2d7fkIRNucdfwR8R+Ts3wxYa/cJHg= github.com/bgentry/go-netrc v0.0.0-20140422174119-9fd32a8b3d3d h1:xDfNPAt8lFiC1UJrqV3uuy861HCTo708pDMbjHHdCas= github.com/bgentry/go-netrc v0.0.0-20140422174119-9fd32a8b3d3d/go.mod h1:6QX/PXZ00z/TKoufEY6K/a0k6AhaJrQKdFe6OfVXsa4= github.com/bwesterb/go-ristretto v1.2.3/go.mod h1:fUIoIZaG73pV5biE2Blr2xEzDoMj7NFEuV9ekS419A0= diff --git a/providers/aws_provider/aws.go b/providers/aws_provider/aws.go index 75a3f77..4d80426 100644 --- a/providers/aws_provider/aws.go +++ b/providers/aws_provider/aws.go @@ -10,54 +10,84 @@ import ( "net/http" "os" "path/filepath" + "reflect" "strconv" "strings" + "text/template" "time" "github.com/aws/aws-sdk-go-v2/aws" awshttp "github.com/aws/aws-sdk-go-v2/aws/transport/http" "github.com/aws/aws-sdk-go-v2/config" "github.com/aws/aws-sdk-go-v2/feature/s3/manager" - "github.com/aws/aws-sdk-go-v2/service/dynamodb" - ddbtype "github.com/aws/aws-sdk-go-v2/service/dynamodb/types" - "github.com/aws/aws-sdk-go-v2/service/ecr" + "github.com/aws/aws-sdk-go-v2/service/eks" + eksType "github.com/aws/aws-sdk-go-v2/service/eks/types" "github.com/aws/aws-sdk-go-v2/service/iam" "github.com/aws/aws-sdk-go-v2/service/s3" s3type "github.com/aws/aws-sdk-go-v2/service/s3/types" "github.com/aws/aws-sdk-go-v2/service/sts" - "github.com/aws/smithy-go" "go.uber.org/zap" + "k8s.io/client-go/tools/clientcmd" + "k8s.io/client-go/tools/clientcmd/api" "rmk/util" ) const ( AWSClusterProvider = "aws" + + AWSConfigTemplateFile = `[profile {{ .Profile }}] +region = {{ .Region }} +output = {{ .Output }} +` + + AWSCredentialsTemplateFile = `[{{ .Profile }}] +aws_access_key_id = {{ .AwsCredentialsProfile.AccessKeyID }} +aws_secret_access_key = {{ .AwsCredentialsProfile.SecretAccessKey }} +{{- if .AwsCredentialsProfile.SessionToken }} +aws_session_token = {{ .AwsCredentialsProfile.SessionToken }} +{{- end }} +` ) type AwsConfigure struct { - Profile string `yaml:"profile,omitempty"` - Region string `yaml:"region,omitempty"` - AccountID string `yaml:"account_id,omitempty"` - UserName string `yaml:"user_name,omitempty"` - MFADeviceSerialNumber string `yaml:"mfa_device,omitempty"` *MFAToken `yaml:"-"` + AccountID string `yaml:"account_id,omitempty"` + AwsCredentialsProfile `yaml:"-"` + ConfigSource string `yaml:"config-source"` + CredentialsSource string `yaml:"credentials-source"` + Ctx context.Context `yaml:"-"` + IAMUserName string `yaml:"user-name,omitempty"` + MFADeviceSerialNumber string `yaml:"mfa-device,omitempty"` MFAProfileCredentials aws.Credentials `yaml:"-"` + Output string `yaml:"output,omitempty"` + Profile string `yaml:"profile,omitempty"` + Region string `json:"aws-region,omitempty" yaml:"region,omitempty"` } type MFAToken struct { - AccessKeyId string + AccessKeyID string Expiration time.Time SecretAccessKey string SessionToken string } +type AwsCredentialsProfile struct { + AccessKeyID string `json:"aws-access-key-id,omitempty" yaml:"-"` + SecretAccessKey string `json:"aws-secret-access-key,omitempty" yaml:"-"` + SessionToken string `yaml:"-"` +} + func (a *AwsConfigure) AWSSharedConfigFile(profile string) []string { - return []string{util.GetHomePath(".aws", "config_"+profile)} + return []string{config.DefaultSharedConfigFilename() + "_" + profile} } func (a *AwsConfigure) AWSSharedCredentialsFile(profile string) []string { - return []string{util.GetHomePath(".aws", "credentials_"+profile)} + return []string{config.DefaultSharedCredentialsFilename() + "_" + profile} +} + +func NewAwsConfigure(ctx context.Context, profile string) *AwsConfigure { + return &AwsConfigure{Ctx: ctx, Output: "text", Profile: profile} } func (a *AwsConfigure) errorProxy(cfg aws.Config, err error) (aws.Config, error) { @@ -82,31 +112,128 @@ func (a *AwsConfigure) configOptions() []func(options *config.LoadOptions) error } } +func getTagStructName(i interface{}, name string) error { + if field, ok := reflect.TypeOf(i).Elem().FieldByName(name); ok { + return fmt.Errorf("profile option %s required", strings.TrimSuffix(field.Tag.Get("json"), ",omitempty")) + } else { + return fmt.Errorf("field with name %s not defined", name) + } +} + +// ValidateAWSCredentials will validate the required parameters for AWS authentication +func (a *AwsConfigure) ValidateAWSCredentials() error { + if len(a.AwsCredentialsProfile.AccessKeyID) == 0 { + return getTagStructName(&a.AwsCredentialsProfile, "AccessKeyID") + } + + if len(a.AwsCredentialsProfile.SecretAccessKey) == 0 { + return getTagStructName(&a.AwsCredentialsProfile, "SecretAccessKey") + } + + if len(a.Region) == 0 { + return getTagStructName(a, "Region") + } + + return nil +} + +// RenderAWSConfigProfile will render the AWS profile. +func (a *AwsConfigure) RenderAWSConfigProfile(temp string) ([]byte, error) { + tmpl, err := template.New("AWS config Profile").Parse(temp) + if err != nil { + return nil, err + } + + var credsFileStr bytes.Buffer + err = tmpl.Execute(&credsFileStr, a) + if err != nil { + return nil, err + } + + return credsFileStr.Bytes(), nil +} + +// RenderBase64EncodedAWSConfigProfile will render the AWS profile, encoded in base 64. +func (a *AwsConfigure) RenderBase64EncodedAWSConfigProfile(temp string) (string, error) { + configProfile, err := a.RenderAWSConfigProfile(temp) + if err != nil { + return "", err + } + + return base64.StdEncoding.EncodeToString(configProfile), nil +} + +func (a *AwsConfigure) ReadAWSConfigProfile() error { + cfg, err := config.LoadDefaultConfig(a.Ctx, a.configOptions()...) + if err != nil { + return err + } + + for _, val := range cfg.ConfigSources { + switch result := val.(type) { + case config.SharedConfig: + a.AwsCredentialsProfile.AccessKeyID = result.Credentials.AccessKeyID + a.AwsCredentialsProfile.SecretAccessKey = result.Credentials.SecretAccessKey + a.AwsCredentialsProfile.SessionToken = result.Credentials.SessionToken + a.Region = result.Region + } + } + + return nil +} + +func (a *AwsConfigure) WriteAWSConfigProfile() error { + var ( + err error + sharedFiles = make(map[string][]byte) + ) + + sharedFiles[a.ConfigSource], err = a.RenderAWSConfigProfile(AWSConfigTemplateFile) + if err != nil { + return err + } + + sharedFiles[a.CredentialsSource], err = a.RenderAWSConfigProfile(AWSCredentialsTemplateFile) + if err != nil { + return err + } + + for key, val := range sharedFiles { + if err := os.MkdirAll(filepath.Dir(key), 0755); err != nil { + return err + } + + if err := os.WriteFile(key, val, 0644); err != nil { + return err + } + } + + return nil +} + func (a *AwsConfigure) GetUserName() error { - ctx := context.TODO() - cfg, err := a.errorProxy(config.LoadDefaultConfig(ctx, a.configOptions()...)) + cfg, err := a.errorProxy(config.LoadDefaultConfig(a.Ctx, a.configOptions()...)) if err != nil { return err } - user, err := iam.NewFromConfig(cfg).GetUser(ctx, &iam.GetUserInput{}) + user, err := iam.NewFromConfig(cfg).GetUser(a.Ctx, &iam.GetUserInput{}) if err != nil { return err } - a.UserName = aws.ToString(user.User.UserName) + a.IAMUserName = aws.ToString(user.User.UserName) return nil } -func (a *AwsConfigure) GetAWSCredentials() error { - ctx := context.TODO() - cfg, err := a.errorProxy(config.LoadDefaultConfig(ctx, a.configOptions()...)) +func (a *AwsConfigure) GetMFACredentials() error { + cfg, err := a.errorProxy(config.LoadDefaultConfig(a.Ctx, a.configOptions()...)) if err != nil { return err } - if a.MFAProfileCredentials, err = cfg.Credentials.Retrieve(ctx); err != nil { + if a.MFAProfileCredentials, err = cfg.Credentials.Retrieve(a.Ctx); err != nil { return err } @@ -120,18 +247,17 @@ func (a *AwsConfigure) GetMFADevicesSerialNumbers() error { return err } - if err := a.GetAWSCredentials(); err != nil { + if err := a.GetMFACredentials(); err != nil { return err } - ctx := context.TODO() - cfg, err := a.errorProxy(config.LoadDefaultConfig(ctx, a.configOptions()...)) + cfg, err := a.errorProxy(config.LoadDefaultConfig(a.Ctx, a.configOptions()...)) if err != nil { return err } - mfaDevices, err := iam.NewFromConfig(cfg).ListMFADevices(ctx, - &iam.ListMFADevicesInput{UserName: aws.String(a.UserName)}) + mfaDevices, err := iam.NewFromConfig(cfg).ListMFADevices(a.Ctx, + &iam.ListMFADevicesInput{UserName: aws.String(a.IAMUserName)}) if err != nil { return err } @@ -155,8 +281,7 @@ func (a *AwsConfigure) GetMFADevicesSerialNumbers() error { } func (a *AwsConfigure) GetMFASessionToken() error { - ctx := context.TODO() - cfg, err := a.errorProxy(config.LoadDefaultConfig(ctx, a.configOptions()...)) + cfg, err := a.errorProxy(config.LoadDefaultConfig(a.Ctx, a.configOptions()...)) if err != nil { return err } @@ -165,7 +290,7 @@ func (a *AwsConfigure) GetMFASessionToken() error { return err } - token, err := sts.NewFromConfig(cfg).GetSessionToken(ctx, &sts.GetSessionTokenInput{ + token, err := sts.NewFromConfig(cfg).GetSessionToken(a.Ctx, &sts.GetSessionTokenInput{ DurationSeconds: aws.Int32(43200), SerialNumber: aws.String(a.MFADeviceSerialNumber), TokenCode: aws.String(util.ReadStdin("TOTP")), @@ -175,7 +300,7 @@ func (a *AwsConfigure) GetMFASessionToken() error { } a.MFAToken = &MFAToken{ - AccessKeyId: aws.ToString(token.Credentials.AccessKeyId), + AccessKeyID: aws.ToString(token.Credentials.AccessKeyId), Expiration: aws.ToTime(token.Credentials.Expiration), SecretAccessKey: aws.ToString(token.Credentials.SecretAccessKey), SessionToken: aws.ToString(token.Credentials.SessionToken), @@ -185,8 +310,7 @@ func (a *AwsConfigure) GetMFASessionToken() error { } func (a *AwsConfigure) GetAwsConfigure(profile string) (bool, error) { - ctx := context.TODO() - cfg, err := a.errorProxy(config.LoadDefaultConfig(ctx, + cfg, err := a.errorProxy(config.LoadDefaultConfig(a.Ctx, config.WithSharedConfigFiles(a.AWSSharedConfigFile(profile)), config.WithSharedCredentialsFiles(a.AWSSharedCredentialsFile(profile)), config.WithSharedConfigProfile(profile), @@ -196,7 +320,7 @@ func (a *AwsConfigure) GetAwsConfigure(profile string) (bool, error) { } client := sts.NewFromConfig(cfg) - identity, err := client.GetCallerIdentity(ctx, &sts.GetCallerIdentityInput{}) + identity, err := client.GetCallerIdentity(a.Ctx, &sts.GetCallerIdentityInput{}) if err != nil { return false, err } @@ -207,39 +331,99 @@ func (a *AwsConfigure) GetAwsConfigure(profile string) (bool, error) { return true, nil } -func (a *AwsConfigure) GetECRCredentials(region string) (map[string]string, error) { - ctx := context.TODO() - ecrCredentials := make(map[string]string) +func (a *AwsConfigure) GetAWSClusterContext(clusterName string) ([]byte, error) { + cfg, err := a.errorProxy(config.LoadDefaultConfig(a.Ctx, a.configOptions()...)) + if err != nil { + return nil, err + } - cfg, err := a.errorProxy(config.LoadDefaultConfig(ctx, a.configOptions()...)) + client := eks.NewFromConfig(cfg) + cluster, err := client.DescribeCluster(a.Ctx, &eks.DescribeClusterInput{Name: aws.String(clusterName)}) if err != nil { return nil, err } - // needed for specific AWS account where ECR used - cfg.Region = region + return a.generateUserKubeconfig(cluster.Cluster) +} + +func (a *AwsConfigure) generateUserKubeconfig(cluster *eksType.Cluster) ([]byte, error) { + var execEnvVars []api.ExecEnvVar - svc := ecr.NewFromConfig(cfg) - token, err := svc.GetAuthorizationToken(ctx, &ecr.GetAuthorizationTokenInput{}) + clusterName := aws.ToString(cluster.Name) + userName := a.getKubeConfigUserName(clusterName) + + cfg, err := a.generateBaseKubeConfig(cluster) if err != nil { - return nil, err + return nil, fmt.Errorf("creating base kubeconfig: %w", err) + } + + execEnvVars = append(execEnvVars, + api.ExecEnvVar{Name: "AWS_PROFILE", Value: a.Profile}, + api.ExecEnvVar{Name: "AWS_CONFIG_FILE", Value: strings.Join(a.AWSSharedConfigFile(a.Profile), "")}, + api.ExecEnvVar{Name: "AWS_SHARED_CREDENTIALS_FILE", Value: strings.Join(a.AWSSharedCredentialsFile(a.Profile), "")}, + ) + + // Version v1alpha1 was removed in Kubernetes v1.23. + // Version v1 was released in Kubernetes v1.23. + // Version v1beta1 was selected as it has the widest range of support + // This should be changed to v1 once EKS no longer supports Kubernetes