Skip to content

Commit cf8c193

Browse files
committed
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.
1 parent baf4eb6 commit cf8c193

File tree

3 files changed

+100
-23
lines changed

3 files changed

+100
-23
lines changed

pkg/cloudprovider/providers/openstack/BUILD

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,10 +23,13 @@ go_library(
2323
"//pkg/util/mount:go_default_library",
2424
"//staging/src/k8s.io/api/core/v1:go_default_library",
2525
"//staging/src/k8s.io/apimachinery/pkg/api/resource:go_default_library",
26+
"//staging/src/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library",
2627
"//staging/src/k8s.io/apimachinery/pkg/types:go_default_library",
2728
"//staging/src/k8s.io/apimachinery/pkg/util/net:go_default_library",
2829
"//staging/src/k8s.io/apimachinery/pkg/util/sets:go_default_library",
2930
"//staging/src/k8s.io/apimachinery/pkg/util/wait:go_default_library",
31+
"//staging/src/k8s.io/client-go/kubernetes:go_default_library",
32+
"//staging/src/k8s.io/client-go/tools/clientcmd:go_default_library",
3033
"//staging/src/k8s.io/client-go/util/cert:go_default_library",
3134
"//staging/src/k8s.io/cloud-provider:go_default_library",
3235
"//staging/src/k8s.io/cloud-provider/node/helpers:go_default_library",

pkg/cloudprovider/providers/openstack/openstack.go

Lines changed: 84 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -42,8 +42,11 @@ import (
4242
"gopkg.in/gcfg.v1"
4343

4444
"k8s.io/api/core/v1"
45+
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
4546
"k8s.io/apimachinery/pkg/types"
4647
netutil "k8s.io/apimachinery/pkg/util/net"
48+
"k8s.io/client-go/kubernetes"
49+
"k8s.io/client-go/tools/clientcmd"
4750
certutil "k8s.io/client-go/util/cert"
4851
cloudprovider "k8s.io/cloud-provider"
4952
nodehelpers "k8s.io/cloud-provider/node/helpers"
@@ -145,17 +148,19 @@ type OpenStack struct {
145148
// Config is used to read and store information from the cloud configuration file
146149
type Config struct {
147150
Global struct {
148-
AuthURL string `gcfg:"auth-url"`
149-
Username string
150-
UserID string `gcfg:"user-id"`
151-
Password string
152-
TenantID string `gcfg:"tenant-id"`
153-
TenantName string `gcfg:"tenant-name"`
154-
TrustID string `gcfg:"trust-id"`
155-
DomainID string `gcfg:"domain-id"`
156-
DomainName string `gcfg:"domain-name"`
157-
Region string
158-
CAFile string `gcfg:"ca-file"`
151+
AuthURL string `gcfg:"auth-url"`
152+
Username string
153+
UserID string `gcfg:"user-id"`
154+
Password string
155+
TenantID string `gcfg:"tenant-id"`
156+
TenantName string `gcfg:"tenant-name"`
157+
TrustID string `gcfg:"trust-id"`
158+
DomainID string `gcfg:"domain-id"`
159+
DomainName string `gcfg:"domain-name"`
160+
Region string
161+
CAFile string `gcfg:"ca-file"`
162+
SecretName string `gcfg:"secret-name"`
163+
SecretNamespace string `gcfg:"secret-namespace"`
159164
}
160165
LoadBalancer LoadBalancerOpts
161166
BlockStorage BlockStorageOpts
@@ -231,6 +236,9 @@ func configFromEnv() (cfg Config, ok bool) {
231236
cfg.Global.DomainName = os.Getenv("OS_USER_DOMAIN_NAME")
232237
}
233238

239+
cfg.Global.SecretName = os.Getenv("SECRET_NAME")
240+
cfg.Global.SecretNamespace = os.Getenv("SECRET_NAMESPACE")
241+
234242
ok = cfg.Global.AuthURL != "" &&
235243
cfg.Global.Username != "" &&
236244
cfg.Global.Password != "" &&
@@ -245,6 +253,58 @@ func configFromEnv() (cfg Config, ok bool) {
245253
return
246254
}
247255

256+
func createKubernetesClient() (*kubernetes.Clientset, error) {
257+
klog.Info("Creating kubernetes API client.")
258+
259+
// create in-cluster config
260+
cfg, err := clientcmd.BuildConfigFromFlags("", "")
261+
if err != nil {
262+
return nil, err
263+
}
264+
265+
client, err := kubernetes.NewForConfig(cfg)
266+
if err != nil {
267+
return nil, err
268+
}
269+
270+
v, err := client.Discovery().ServerVersion()
271+
if err != nil {
272+
return nil, err
273+
}
274+
275+
klog.Infof("Kubernetes API client created, server version %s", fmt.Sprintf("v%v.%v", v.Major, v.Minor))
276+
return client, nil
277+
}
278+
279+
// setConfigFromSecret allows setting up the config from k8s secret
280+
func setConfigFromSecret(cfg *Config) error {
281+
secretName := cfg.Global.SecretName
282+
secretNamespace := cfg.Global.SecretNamespace
283+
284+
k8sClient, err := createKubernetesClient()
285+
if err != nil {
286+
return fmt.Errorf("failed to get kubernetes client: %v", err)
287+
}
288+
289+
secret, err := k8sClient.CoreV1().Secrets(secretNamespace).Get(secretName, metav1.GetOptions{})
290+
if err != nil {
291+
klog.Warningf("Cannot get secret %s in namespace %s. error: %q", secretName, secretNamespace, err)
292+
return err
293+
}
294+
295+
if content, ok := secret.Data["clouds.conf"]; ok {
296+
err = gcfg.ReadStringInto(cfg, string(content))
297+
if err != nil {
298+
klog.Errorf("Cannot parse data from the secret.")
299+
return fmt.Errorf("cannot parse data from the secret")
300+
}
301+
return nil
302+
}
303+
304+
klog.Errorf("Cannot find \"clouds.conf\" key in the secret.")
305+
return fmt.Errorf("cannot find \"clouds.conf\" key in the secret")
306+
}
307+
248308
func readConfig(config io.Reader) (Config, error) {
249309
if config == nil {
250310
return Config{}, fmt.Errorf("no OpenStack cloud provider config file given")
@@ -259,7 +319,19 @@ func readConfig(config io.Reader) (Config, error) {
259319
cfg.Metadata.SearchOrder = fmt.Sprintf("%s,%s", configDriveID, metadataID)
260320

261321
err := gcfg.ReadInto(&cfg, config)
262-
return cfg, err
322+
if err != nil {
323+
return cfg, err
324+
}
325+
326+
if cfg.Global.SecretName != "" && cfg.Global.SecretNamespace != "" {
327+
klog.Infof("Set credentials from secret %s in namespace %s", cfg.Global.SecretName, cfg.Global.SecretNamespace)
328+
err = setConfigFromSecret(&cfg)
329+
if err != nil {
330+
return cfg, err
331+
}
332+
}
333+
334+
return cfg, nil
263335
}
264336

265337
// caller is a tiny helper for conditional unwind logic

pkg/volume/cinder/cinder_test.go

Lines changed: 13 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -304,17 +304,19 @@ func getOpenstackCloudProvider() (*openstack.OpenStack, error) {
304304
func getOpenstackConfig() openstack.Config {
305305
cfg := openstack.Config{
306306
Global: struct {
307-
AuthURL string `gcfg:"auth-url"`
308-
Username string
309-
UserID string `gcfg:"user-id"`
310-
Password string
311-
TenantID string `gcfg:"tenant-id"`
312-
TenantName string `gcfg:"tenant-name"`
313-
TrustID string `gcfg:"trust-id"`
314-
DomainID string `gcfg:"domain-id"`
315-
DomainName string `gcfg:"domain-name"`
316-
Region string
317-
CAFile string `gcfg:"ca-file"`
307+
AuthURL string `gcfg:"auth-url"`
308+
Username string
309+
UserID string `gcfg:"user-id"`
310+
Password string
311+
TenantID string `gcfg:"tenant-id"`
312+
TenantName string `gcfg:"tenant-name"`
313+
TrustID string `gcfg:"trust-id"`
314+
DomainID string `gcfg:"domain-id"`
315+
DomainName string `gcfg:"domain-name"`
316+
Region string
317+
CAFile string `gcfg:"ca-file"`
318+
SecretName string `gcfg:"secret-name"`
319+
SecretNamespace string `gcfg:"secret-namespace"`
318320
}{
319321
Username: "user",
320322
Password: "pass",

0 commit comments

Comments
 (0)