Allow to read OpenStack config from the secret

Currently OpenStack cloud provider reads user credentials from config
file, where data is stored in clear text. This approach is not recommended,
as it is a serious security issue.

This commit add an ability to read the config from secrets, if necessary.
To do so, two new parameters are added to the config: SecretNamespace and
SecretName. If they are specified, the provider will try to read config
from the secret.
This commit is contained in:
Mike Fedosin 2019-03-05 18:20:27 +01:00
parent baf4eb67ac
commit cf8c193b87
3 changed files with 100 additions and 23 deletions

View File

@ -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",

View File

@ -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

View File

@ -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",