diff --git a/Makefile b/Makefile index 62bafd150a..94a6f9a26a 100644 --- a/Makefile +++ b/Makefile @@ -48,7 +48,7 @@ UPLOAD_CMD=$(KOPS_ROOT)/hack/upload ${UPLOAD_ARGS} # Unexport environment variables that can affect tests and are not used in builds unexport AWS_ACCESS_KEY_ID AWS_REGION AWS_SECRET_ACCESS_KEY AWS_SESSION_TOKEN CNI_VERSION_URL DNS_IGNORE_NS_CHECK DNSCONTROLLER_IMAGE DO_ACCESS_TOKEN GOOGLE_APPLICATION_CREDENTIALS unexport KOPS_BASE_URL KOPS_CLUSTER_NAME KOPS_RUN_OBSOLETE_VERSION KOPS_STATE_STORE KOPS_STATE_S3_ACL KUBE_API_VERSIONS NODEUP_URL OPENSTACK_CREDENTIAL_FILE SKIP_PACKAGE_UPDATE -unexport SKIP_REGION_CHECK S3_ACCESS_KEY_ID S3_ENDPOINT S3_REGION S3_SECRET_ACCESS_KEY HCLOUD_TOKEN SCW_ACCESS_KEY SCW_SECRET_KEY SCW_DEFAULT_PROJECT_ID +unexport SKIP_REGION_CHECK S3_ACCESS_KEY_ID S3_ENDPOINT S3_REGION S3_SECRET_ACCESS_KEY HCLOUD_TOKEN SCW_ACCESS_KEY SCW_SECRET_KEY SCW_DEFAULT_PROJECT_ID SCW_PROFILE unexport AZURE_CLIENT_ID AZURE_CLIENT_SECRET AZURE_STORAGE_ACCOUNT AZURE_STORAGE_KEY AZURE_SUBSCRIPTION_ID AZURE_TENANT_ID diff --git a/hack/update-expected.sh b/hack/update-expected.sh index 56833c35eb..7744b65cd4 100755 --- a/hack/update-expected.sh +++ b/hack/update-expected.sh @@ -30,7 +30,7 @@ unset KOPS_BASE_URL DNSCONTROLLER_IMAGE KOPSCONTROLLER_IMAGE KUBE_APISERVER_HEAL unset AWS_ACCESS_KEY_ID AWS_REGION AWS_SECRET_ACCESS_KEY AWS_SESSION_TOKEN CNI_VERSION_URL DNS_IGNORE_NS_CHECK DO_ACCESS_TOKEN GOOGLE_APPLICATION_CREDENTIALS HCLOUD_TOKEN unset KOPS_CLUSTER_NAME KOPS_RUN_OBSOLETE_VERSION KOPS_STATE_STORE KOPS_STATE_S3_ACL KUBE_API_VERSIONS NODEUP_URL OPENSTACK_CREDENTIAL_FILE PROTOKUBE_IMAGE SKIP_PACKAGE_UPDATE unset SKIP_REGION_CHECK S3_ACCESS_KEY_ID S3_ENDPOINT S3_REGION S3_SECRET_ACCESS_KEY -unset SCW_ACCESS_KEY SCW_SECRET_KEY SCW_DEFAULT_PROJECT_ID +unset SCW_ACCESS_KEY SCW_SECRET_KEY SCW_DEFAULT_PROJECT_ID SCW_PROFILE unset AZURE_CLIENT_ID AZURE_CLIENT_SECRET AZURE_STORAGE_ACCOUNT AZURE_STORAGE_KEY AZURE_SUBSCRIPTION_ID AZURE_TENANT_ID # Run the tests in "autofix mode" diff --git a/nodeup/pkg/bootstrap/install.go b/nodeup/pkg/bootstrap/install.go index bb0c29a66a..24750804dd 100644 --- a/nodeup/pkg/bootstrap/install.go +++ b/nodeup/pkg/bootstrap/install.go @@ -25,6 +25,7 @@ import ( "k8s.io/klog/v2" "k8s.io/kops/pkg/systemd" "k8s.io/kops/upup/pkg/fi" + "k8s.io/kops/upup/pkg/fi/cloudup/scaleway" "k8s.io/kops/upup/pkg/fi/nodeup/install" "k8s.io/kops/upup/pkg/fi/nodeup/nodetasks" "k8s.io/kops/util/pkg/distributions" @@ -133,10 +134,14 @@ func (i *Installation) buildEnvFile() *nodetasks.InstallFile { envVars["AZURE_STORAGE_ACCOUNT"] = os.Getenv("AZURE_STORAGE_ACCOUNT") } - if os.Getenv("SCW_SECRET_KEY") != "" { - envVars["SCW_ACCESS_KEY"] = os.Getenv("SCW_ACCESS_KEY") - envVars["SCW_SECRET_KEY"] = os.Getenv("SCW_SECRET_KEY") - envVars["SCW_DEFAULT_PROJECT_ID"] = os.Getenv("SCW_DEFAULT_PROJECT_ID") + if os.Getenv("SCW_PROFILE") != "" || os.Getenv("SCW_SECRET_KEY") != "" { + profile, err := scaleway.CreateValidScalewayProfile() + if err != nil { + return nil + } + envVars["SCW_ACCESS_KEY"] = fi.ValueOf(profile.AccessKey) + envVars["SCW_SECRET_KEY"] = fi.ValueOf(profile.SecretKey) + envVars["SCW_DEFAULT_PROJECT_ID"] = fi.ValueOf(profile.DefaultProjectID) } sysconfig := "" diff --git a/nodeup/pkg/model/protokube.go b/nodeup/pkg/model/protokube.go index 9e463bcc2f..931bb0083c 100644 --- a/nodeup/pkg/model/protokube.go +++ b/nodeup/pkg/model/protokube.go @@ -29,6 +29,7 @@ import ( "k8s.io/kops/pkg/rbac" "k8s.io/kops/pkg/systemd" "k8s.io/kops/upup/pkg/fi" + "k8s.io/kops/upup/pkg/fi/cloudup/scaleway" "k8s.io/kops/upup/pkg/fi/nodeup/nodetasks" "k8s.io/kops/util/pkg/distributions" "k8s.io/kops/util/pkg/proxy" @@ -294,9 +295,15 @@ func (t *ProtokubeBuilder) buildEnvFile() (*nodetasks.File, error) { } if t.BootConfig.CloudProvider == kops.CloudProviderScaleway { - envVars["SCW_ACCESS_KEY"] = os.Getenv("SCW_ACCESS_KEY") - envVars["SCW_SECRET_KEY"] = os.Getenv("SCW_SECRET_KEY") - envVars["SCW_DEFAULT_PROJECT_ID"] = os.Getenv("SCW_DEFAULT_PROJECT_ID") + if os.Getenv("SCW_PROFILE") != "" || os.Getenv("SCW_SECRET_KEY") != "" { + profile, err := scaleway.CreateValidScalewayProfile() + if err != nil { + return nil, err + } + envVars["SCW_ACCESS_KEY"] = fi.ValueOf(profile.AccessKey) + envVars["SCW_SECRET_KEY"] = fi.ValueOf(profile.SecretKey) + envVars["SCW_DEFAULT_PROJECT_ID"] = fi.ValueOf(profile.DefaultProjectID) + } } for _, envVar := range proxy.GetProxyEnvVars(t.NodeupConfig.Networking.EgressProxy) { diff --git a/pkg/model/bootstrapscript.go b/pkg/model/bootstrapscript.go index dda39e3781..e2dbb51a0d 100644 --- a/pkg/model/bootstrapscript.go +++ b/pkg/model/bootstrapscript.go @@ -26,9 +26,9 @@ import ( "strconv" "strings" - "k8s.io/apimachinery/pkg/util/errors" "k8s.io/klog/v2" "k8s.io/kops/pkg/apis/kops/model" + "k8s.io/kops/upup/pkg/fi/cloudup/scaleway" "k8s.io/kops/upup/pkg/fi/utils" "sigs.k8s.io/yaml" @@ -215,30 +215,13 @@ func (b *BootstrapScript) buildEnvironmentVariables(cluster *kops.Cluster) (map[ } if cluster.Spec.GetCloudProvider() == kops.CloudProviderScaleway { - errList := []error(nil) - - // We make sure that the credentials env vars are defined - scwAccessKey := os.Getenv("SCW_ACCESS_KEY") - if scwAccessKey == "" { - errList = append(errList, fmt.Errorf("SCW_ACCESS_KEY has to be set as an environment variable")) + profile, err := scaleway.CreateValidScalewayProfile() + if err != nil { + return nil, err } - scwSecretKey := os.Getenv("SCW_SECRET_KEY") - if scwSecretKey == "" { - errList = append(errList, fmt.Errorf("SCW_SECRET_KEY has to be set as an environment variable")) - } - scwProjectID := os.Getenv("SCW_DEFAULT_PROJECT_ID") - if scwProjectID == "" { - errList = append(errList, fmt.Errorf("SCW_DEFAULT_PROJECT_ID has to be set as an environment variable")) - } - - // In theory all these variables will have been checked in NewScwCloud already - if len(errList) != 0 { - return nil, errors.NewAggregate(errList) - } - - env["SCW_ACCESS_KEY"] = scwAccessKey - env["SCW_SECRET_KEY"] = scwSecretKey - env["SCW_DEFAULT_PROJECT_ID"] = scwProjectID + env["SCW_ACCESS_KEY"] = fi.ValueOf(profile.AccessKey) + env["SCW_SECRET_KEY"] = fi.ValueOf(profile.SecretKey) + env["SCW_DEFAULT_PROJECT_ID"] = fi.ValueOf(profile.DefaultProjectID) } return env, nil diff --git a/protokube/pkg/protokube/scaleway_volumes.go b/protokube/pkg/protokube/scaleway_volumes.go index 27565da145..77f43671c4 100644 --- a/protokube/pkg/protokube/scaleway_volumes.go +++ b/protokube/pkg/protokube/scaleway_volumes.go @@ -57,19 +57,12 @@ func NewScwCloudProvider() (*ScwCloudProvider, error) { } klog.V(4).Infof("Found zone of the running server: %v", zone) - region, err := scaleway.ParseRegionFromZone(zone) - if err != nil { - return nil, fmt.Errorf("unable to parse Scaleway region: %s", err) - } - klog.V(4).Infof("Found region of the running server: %v", region) - privateIP := metadata.PrivateIP klog.V(4).Infof("Found first private net IP of the running server: %q", privateIP) scwClient, err := scw.NewClient( scw.WithUserAgent(scaleway.KopsUserAgentPrefix+kopsv.Version), scw.WithEnv(), - scw.WithDefaultRegion(region), scw.WithDefaultZone(zone), ) if err != nil { diff --git a/upup/pkg/fi/cloudup/scaleway/cloud.go b/upup/pkg/fi/cloudup/scaleway/cloud.go index a348364dbb..dd86e0e10c 100644 --- a/upup/pkg/fi/cloudup/scaleway/cloud.go +++ b/upup/pkg/fi/cloudup/scaleway/cloud.go @@ -18,7 +18,6 @@ package scaleway import ( "fmt" - "os" "strings" iam "github.com/scaleway/scaleway-sdk-go/api/iam/v1alpha1" @@ -26,7 +25,6 @@ import ( "github.com/scaleway/scaleway-sdk-go/api/lb/v1" "github.com/scaleway/scaleway-sdk-go/scw" v1 "k8s.io/api/core/v1" - "k8s.io/apimachinery/pkg/util/errors" "k8s.io/klog/v2" kopsv "k8s.io/kops" "k8s.io/kops/dnsprovider/pkg/dnsprovider" @@ -94,43 +92,28 @@ type scwCloudImplementation struct { lbAPI *lb.ZonedAPI } -// NewScwCloud returns a Cloud with a Scaleway Client using the env vars SCW_ACCESS_KEY, SCW_SECRET_KEY and SCW_DEFAULT_PROJECT_ID +// NewScwCloud returns a Cloud with a Scaleway Client using the env vars SCW_PROFILE or +// SCW_ACCESS_KEY, SCW_SECRET_KEY and SCW_DEFAULT_PROJECT_ID func NewScwCloud(tags map[string]string) (ScwCloud, error) { - errList := []error(nil) - region, err := scw.ParseRegion(tags["region"]) if err != nil { - errList = append(errList, fmt.Errorf("error parsing Scaleway region: %w", err)) + return nil, err } zone, err := scw.ParseZone(tags["zone"]) if err != nil { - errList = append(errList, fmt.Errorf("error parsing Scaleway zone: %w", err)) + return nil, err } - - // We make sure that the credentials env vars are defined - scwAccessKey := os.Getenv("SCW_ACCESS_KEY") - if scwAccessKey == "" { - errList = append(errList, fmt.Errorf("SCW_ACCESS_KEY has to be set as an environment variable")) - } - scwSecretKey := os.Getenv("SCW_SECRET_KEY") - if scwSecretKey == "" { - errList = append(errList, fmt.Errorf("SCW_SECRET_KEY has to be set as an environment variable")) - } - scwProjectID := os.Getenv("SCW_DEFAULT_PROJECT_ID") - if scwProjectID == "" { - errList = append(errList, fmt.Errorf("SCW_DEFAULT_PROJECT_ID has to be set as an environment variable")) - } - - if len(errList) != 0 { - return nil, errors.NewAggregate(errList) + profile, err := CreateValidScalewayProfile() + if err != nil { + return nil, err } scwClient, err := scw.NewClient( + scw.WithProfile(profile), scw.WithUserAgent(KopsUserAgentPrefix+kopsv.Version), - scw.WithEnv(), ) if err != nil { - return nil, fmt.Errorf("error building client for Scaleway Cloud: %w", err) + return nil, fmt.Errorf("creating client for Scaleway Cloud: %w", err) } return &scwCloudImplementation{ diff --git a/upup/pkg/fi/cloudup/scaleway/utils.go b/upup/pkg/fi/cloudup/scaleway/utils.go index d1775d510d..4a34a2b846 100644 --- a/upup/pkg/fi/cloudup/scaleway/utils.go +++ b/upup/pkg/fi/cloudup/scaleway/utils.go @@ -20,10 +20,13 @@ import ( "errors" "fmt" "net/http" + "os" "strings" "github.com/scaleway/scaleway-sdk-go/scw" + k8serrors "k8s.io/apimachinery/pkg/util/errors" "k8s.io/kops/pkg/apis/kops" + "k8s.io/kops/upup/pkg/fi" ) // isHTTPCodeError returns true if err is an http error with code statusCode @@ -64,3 +67,64 @@ func ParseRegionFromZone(zone scw.Zone) (region scw.Region, err error) { } return region, nil } + +func getScalewayProfile() (*scw.Profile, error) { + scwProfileName := os.Getenv("SCW_PROFILE") + if scwProfileName == "" { + return nil, nil + } + config, err := scw.LoadConfig() + if err != nil { + return nil, fmt.Errorf("loading Scaleway config file: %w", err) + } + profile, ok := config.Profiles[scwProfileName] + if !ok { + return nil, fmt.Errorf("could not find Scaleway profile %q", scwProfileName) + } + return profile, nil +} + +func checkCredentials(accessKey, secretKey, projectID string) []error { + errList := []error(nil) + if accessKey == "" { + errList = append(errList, fmt.Errorf("SCW_ACCESS_KEY has to be set")) + } + if secretKey == "" { + errList = append(errList, fmt.Errorf("SCW_SECRET_KEY has to be set")) + } + if projectID == "" { + errList = append(errList, fmt.Errorf("SCW_DEFAULT_PROJECT_ID has to be set")) + } + return errList +} + +func CreateValidScalewayProfile() (*scw.Profile, error) { + profile := &scw.Profile{ + AccessKey: fi.PtrTo(os.Getenv("SCW_ACCESS_KEY")), + SecretKey: fi.PtrTo(os.Getenv("SCW_SECRET_KEY")), + DefaultProjectID: fi.PtrTo(os.Getenv("SCW_DEFAULT_PROJECT_ID")), + } + + // If SCW_PROFILE is set, we load the credentials from the profile rather than from the environment + p, err := getScalewayProfile() + if err != nil { + return nil, err + } + if p != nil { + profile.AccessKey = p.AccessKey + profile.SecretKey = p.SecretKey + profile.DefaultProjectID = p.DefaultProjectID + } + + // We check that the profile has an access key, a secret key and a default project ID + if errList := checkCredentials(fi.ValueOf(profile.AccessKey), fi.ValueOf(profile.SecretKey), fi.ValueOf(profile.DefaultProjectID)); errList != nil { + errMsg := k8serrors.NewAggregate(errList).Error() + if scwProfileName := os.Getenv("SCW_PROFILE"); scwProfileName != "" { + errMsg += fmt.Sprintf(" in profile %q", scwProfileName) + } else { + errMsg += " in a Scaleway profile or as an environment variable" + } + return nil, fmt.Errorf(errMsg) + } + return profile, nil +} diff --git a/upup/pkg/fi/cloudup/template_functions.go b/upup/pkg/fi/cloudup/template_functions.go index 913cd9889f..dd49da0040 100644 --- a/upup/pkg/fi/cloudup/template_functions.go +++ b/upup/pkg/fi/cloudup/template_functions.go @@ -192,13 +192,25 @@ func (tf *TemplateFunctions) AddTo(dest template.FuncMap, secretStore fi.SecretS } dest["SCW_ACCESS_KEY"] = func() string { - return os.Getenv("SCW_ACCESS_KEY") + profile, err := scaleway.CreateValidScalewayProfile() + if err != nil { + return "" + } + return fi.ValueOf(profile.AccessKey) } dest["SCW_SECRET_KEY"] = func() string { - return os.Getenv("SCW_SECRET_KEY") + profile, err := scaleway.CreateValidScalewayProfile() + if err != nil { + return "" + } + return fi.ValueOf(profile.SecretKey) } dest["SCW_DEFAULT_PROJECT_ID"] = func() string { - return os.Getenv("SCW_DEFAULT_PROJECT_ID") + profile, err := scaleway.CreateValidScalewayProfile() + if err != nil { + return "" + } + return fi.ValueOf(profile.DefaultProjectID) } dest["SCW_DEFAULT_REGION"] = func() string { return tf.cloud.Region() diff --git a/util/pkg/env/standard.go b/util/pkg/env/standard.go index 41f97a03c5..3f1d16febe 100644 --- a/util/pkg/env/standard.go +++ b/util/pkg/env/standard.go @@ -22,6 +22,8 @@ import ( corev1 "k8s.io/api/core/v1" "k8s.io/kops/pkg/apis/kops" + "k8s.io/kops/upup/pkg/fi" + "k8s.io/kops/upup/pkg/fi/cloudup/scaleway" "k8s.io/kops/util/pkg/proxy" ) @@ -73,9 +75,12 @@ func BuildSystemComponentEnvVars(spec *kops.ClusterSpec) EnvVars { vars.addEnvVariableIfExist("AZURE_STORAGE_ACCOUNT") // Scaleway related values. - vars.addEnvVariableIfExist("SCW_ACCESS_KEY") - vars.addEnvVariableIfExist("SCW_SECRET_KEY") - vars.addEnvVariableIfExist("SCW_DEFAULT_PROJECT_ID") + profile, err := scaleway.CreateValidScalewayProfile() + if err == nil { + vars["SCW_ACCESS_KEY"] = fi.ValueOf(profile.AccessKey) + vars["SCW_SECRET_KEY"] = fi.ValueOf(profile.SecretKey) + vars["SCW_DEFAULT_PROJECT_ID"] = fi.ValueOf(profile.DefaultProjectID) + } return vars }