diff --git a/pkg/cloudprovider/providers/openstack/BUILD b/pkg/cloudprovider/providers/openstack/BUILD index 934af2f750..3348196794 100644 --- a/pkg/cloudprovider/providers/openstack/BUILD +++ b/pkg/cloudprovider/providers/openstack/BUILD @@ -23,10 +23,13 @@ go_library( "//pkg/util/mount:go_default_library", "//staging/src/k8s.io/api/core/v1:go_default_library", "//staging/src/k8s.io/apimachinery/pkg/api/resource:go_default_library", + "//staging/src/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library", "//staging/src/k8s.io/apimachinery/pkg/types:go_default_library", "//staging/src/k8s.io/apimachinery/pkg/util/net:go_default_library", "//staging/src/k8s.io/apimachinery/pkg/util/sets:go_default_library", "//staging/src/k8s.io/apimachinery/pkg/util/wait:go_default_library", + "//staging/src/k8s.io/client-go/kubernetes:go_default_library", + "//staging/src/k8s.io/client-go/tools/clientcmd:go_default_library", "//staging/src/k8s.io/client-go/util/cert:go_default_library", "//staging/src/k8s.io/cloud-provider:go_default_library", "//staging/src/k8s.io/cloud-provider/node/helpers:go_default_library", diff --git a/pkg/cloudprovider/providers/openstack/openstack.go b/pkg/cloudprovider/providers/openstack/openstack.go index bc17f41a39..775fc7899b 100644 --- a/pkg/cloudprovider/providers/openstack/openstack.go +++ b/pkg/cloudprovider/providers/openstack/openstack.go @@ -42,8 +42,11 @@ import ( "gopkg.in/gcfg.v1" "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/types" netutil "k8s.io/apimachinery/pkg/util/net" + "k8s.io/client-go/kubernetes" + "k8s.io/client-go/tools/clientcmd" certutil "k8s.io/client-go/util/cert" cloudprovider "k8s.io/cloud-provider" nodehelpers "k8s.io/cloud-provider/node/helpers" @@ -145,17 +148,19 @@ type OpenStack struct { // Config is used to read and store information from the cloud configuration file type Config struct { Global struct { - AuthURL string `gcfg:"auth-url"` - Username string - UserID string `gcfg:"user-id"` - Password string - TenantID string `gcfg:"tenant-id"` - TenantName string `gcfg:"tenant-name"` - TrustID string `gcfg:"trust-id"` - DomainID string `gcfg:"domain-id"` - DomainName string `gcfg:"domain-name"` - Region string - CAFile string `gcfg:"ca-file"` + AuthURL string `gcfg:"auth-url"` + Username string + UserID string `gcfg:"user-id"` + Password string + TenantID string `gcfg:"tenant-id"` + TenantName string `gcfg:"tenant-name"` + TrustID string `gcfg:"trust-id"` + DomainID string `gcfg:"domain-id"` + DomainName string `gcfg:"domain-name"` + Region string + CAFile string `gcfg:"ca-file"` + SecretName string `gcfg:"secret-name"` + SecretNamespace string `gcfg:"secret-namespace"` } LoadBalancer LoadBalancerOpts BlockStorage BlockStorageOpts @@ -231,6 +236,9 @@ func configFromEnv() (cfg Config, ok bool) { cfg.Global.DomainName = os.Getenv("OS_USER_DOMAIN_NAME") } + cfg.Global.SecretName = os.Getenv("SECRET_NAME") + cfg.Global.SecretNamespace = os.Getenv("SECRET_NAMESPACE") + ok = cfg.Global.AuthURL != "" && cfg.Global.Username != "" && cfg.Global.Password != "" && @@ -245,6 +253,58 @@ func configFromEnv() (cfg Config, ok bool) { return } +func createKubernetesClient() (*kubernetes.Clientset, error) { + klog.Info("Creating kubernetes API client.") + + // create in-cluster config + cfg, err := clientcmd.BuildConfigFromFlags("", "") + if err != nil { + return nil, err + } + + client, err := kubernetes.NewForConfig(cfg) + if err != nil { + return nil, err + } + + v, err := client.Discovery().ServerVersion() + if err != nil { + return nil, err + } + + klog.Infof("Kubernetes API client created, server version %s", fmt.Sprintf("v%v.%v", v.Major, v.Minor)) + return client, nil +} + +// setConfigFromSecret allows setting up the config from k8s secret +func setConfigFromSecret(cfg *Config) error { + secretName := cfg.Global.SecretName + secretNamespace := cfg.Global.SecretNamespace + + k8sClient, err := createKubernetesClient() + if err != nil { + return fmt.Errorf("failed to get kubernetes client: %v", err) + } + + secret, err := k8sClient.CoreV1().Secrets(secretNamespace).Get(secretName, metav1.GetOptions{}) + if err != nil { + klog.Warningf("Cannot get secret %s in namespace %s. error: %q", secretName, secretNamespace, err) + return err + } + + if content, ok := secret.Data["clouds.conf"]; ok { + err = gcfg.ReadStringInto(cfg, string(content)) + if err != nil { + klog.Errorf("Cannot parse data from the secret.") + return fmt.Errorf("cannot parse data from the secret") + } + return nil + } + + klog.Errorf("Cannot find \"clouds.conf\" key in the secret.") + return fmt.Errorf("cannot find \"clouds.conf\" key in the secret") +} + func readConfig(config io.Reader) (Config, error) { if config == nil { return Config{}, fmt.Errorf("no OpenStack cloud provider config file given") @@ -259,7 +319,19 @@ func readConfig(config io.Reader) (Config, error) { cfg.Metadata.SearchOrder = fmt.Sprintf("%s,%s", configDriveID, metadataID) err := gcfg.ReadInto(&cfg, config) - return cfg, err + if err != nil { + return cfg, err + } + + if cfg.Global.SecretName != "" && cfg.Global.SecretNamespace != "" { + klog.Infof("Set credentials from secret %s in namespace %s", cfg.Global.SecretName, cfg.Global.SecretNamespace) + err = setConfigFromSecret(&cfg) + if err != nil { + return cfg, err + } + } + + return cfg, nil } // caller is a tiny helper for conditional unwind logic diff --git a/pkg/volume/cinder/cinder_test.go b/pkg/volume/cinder/cinder_test.go index d75d629d3b..b502c470b0 100644 --- a/pkg/volume/cinder/cinder_test.go +++ b/pkg/volume/cinder/cinder_test.go @@ -304,17 +304,19 @@ func getOpenstackCloudProvider() (*openstack.OpenStack, error) { func getOpenstackConfig() openstack.Config { cfg := openstack.Config{ Global: struct { - AuthURL string `gcfg:"auth-url"` - Username string - UserID string `gcfg:"user-id"` - Password string - TenantID string `gcfg:"tenant-id"` - TenantName string `gcfg:"tenant-name"` - TrustID string `gcfg:"trust-id"` - DomainID string `gcfg:"domain-id"` - DomainName string `gcfg:"domain-name"` - Region string - CAFile string `gcfg:"ca-file"` + AuthURL string `gcfg:"auth-url"` + Username string + UserID string `gcfg:"user-id"` + Password string + TenantID string `gcfg:"tenant-id"` + TenantName string `gcfg:"tenant-name"` + TrustID string `gcfg:"trust-id"` + DomainID string `gcfg:"domain-id"` + DomainName string `gcfg:"domain-name"` + Region string + CAFile string `gcfg:"ca-file"` + SecretName string `gcfg:"secret-name"` + SecretNamespace string `gcfg:"secret-namespace"` }{ Username: "user", Password: "pass",