diff --git a/cmd/clusterctl/examples/vsphere/cluster.yaml.template b/cmd/clusterctl/examples/vsphere/cluster.yaml.template index 4de3a24b0c..0782e75847 100644 --- a/cmd/clusterctl/examples/vsphere/cluster.yaml.template +++ b/cmd/clusterctl/examples/vsphere/cluster.yaml.template @@ -15,4 +15,5 @@ spec: kind: "VsphereClusterProviderConfig" vsphereUser: "" vspherePassword: "" - vsphereServer: "" \ No newline at end of file + vsphereServer: "" + vsphereCredentialSecret: "" \ No newline at end of file diff --git a/docs/design/vsphereCredentials.md b/docs/design/vsphereCredentials.md new file mode 100644 index 0000000000..d71d3f6c9c --- /dev/null +++ b/docs/design/vsphereCredentials.md @@ -0,0 +1,49 @@ +## Passing vsphere credentials +For the cluster-api vsphere provider to work, the users need to provide the vsphere credentials to access the infrastructure. There are 2 ways how the users can provide these credentials. + +* Using kubernetes `secrets` + * Create a secret that contains 2 keys namely `username` and `password` in the same namespace as the desired `Cluster` object. + ``` + apiVersion: v1 + kind: Secret + metadata: + name: my-vc-credentials + type: Opaque + data: + # base64 encoded fields + username: YWRtaW5pc3RyYXRvckB2c3BoZXJlLmxvY2Fs + password: c2FtcGxl + ``` + + * Set the `vsphereCredentialSecret` property in the `ProviderSpec` part of the `Cluster` definition + ``` + apiVersion: "cluster.k8s.io/v1alpha1" + kind: Cluster + metadata: + name: sample-cluster + spec: + ... + providerSpec: + value: + ... + # Credentials provided via secrets + vsphereCredentialSecret: "my-vc-credentials" + ``` + +* Using plain text credential in the `ProviderSpec` part of the `Cluster` definition + ``` + apiVersion: "cluster.k8s.io/v1alpha1" + kind: Cluster + metadata: + name: sample-cluster + spec: + ... + providerSpec: + value: + ... + # Credentials provided as plain text + vsphereUser: "administrator@vsphere.local" + vspherePassword: "sample" + ``` + +__Note:__ If `vsphereCredentialSecret` field is set to a non empty string then the controller will ignore the `vsphereUser` and `vspherePassword` fields even if they are set. \ No newline at end of file diff --git a/pkg/apis/vsphereproviderconfig/v1alpha1/vsphereclusterproviderconfig_types.go b/pkg/apis/vsphereproviderconfig/v1alpha1/vsphereclusterproviderconfig_types.go index 4cc79e3793..041718fd57 100644 --- a/pkg/apis/vsphereproviderconfig/v1alpha1/vsphereclusterproviderconfig_types.go +++ b/pkg/apis/vsphereproviderconfig/v1alpha1/vsphereclusterproviderconfig_types.go @@ -47,9 +47,10 @@ type VsphereClusterProviderConfig struct { metav1.TypeMeta `json:",inline"` metav1.ObjectMeta `json:"metadata,omitempty"` - VsphereUser string `json:"vsphereUser"` - VspherePassword string `json:"vspherePassword"` - VsphereServer string `json:"vsphereServer"` + VsphereUser string `json:"vsphereUser"` + VspherePassword string `json:"vspherePassword"` + VsphereServer string `json:"vsphereServer"` + VsphereCredentialSecret string `json:"vsphereCredentialSecret"` } // +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object diff --git a/pkg/cloud/vsphere/constants/constants.go b/pkg/cloud/vsphere/constants/constants.go index 995716140e..9d2a884b84 100644 --- a/pkg/cloud/vsphere/constants/constants.go +++ b/pkg/cloud/vsphere/constants/constants.go @@ -20,5 +20,7 @@ const ( RequeueAfterSeconds = 20 * time.Second KubeConfigSecretName = "%s-kubeconfig" KubeConfigSecretData = "admin-kubeconfig" + VsphereUserKey = "username" + VspherePasswordKey = "password" ClusterIsNullErr = "cluster is nil, make sure machines have `clusters.k8s.io/cluster-name` label set and the name references a valid cluster name in the same namespace" ) diff --git a/pkg/cloud/vsphere/provisioner/govmomi/session.go b/pkg/cloud/vsphere/provisioner/govmomi/session.go index 83c4aa4075..d2a750f4f1 100644 --- a/pkg/cloud/vsphere/provisioner/govmomi/session.go +++ b/pkg/cloud/vsphere/provisioner/govmomi/session.go @@ -25,7 +25,11 @@ func (pv *Provisioner) sessionFromProviderConfig(cluster *clusterv1.Cluster, mac if err != nil { return nil, err } - if ses, ok := pv.sessioncache[vsphereConfig.VsphereServer+vsphereConfig.VsphereUser]; ok { + username, password, err := pv.GetVsphereCredentials(cluster) + if err != nil { + return nil, err + } + if ses, ok := pv.sessioncache[vsphereConfig.VsphereServer+username]; ok { s, ok := ses.(SessionContext) if ok { // Test if the session is valid and return @@ -41,7 +45,7 @@ func (pv *Provisioner) sessionFromProviderConfig(cluster *clusterv1.Cluster, mac return nil, fmt.Errorf("error parsing vSphere URL %s : [%s]", soapURL, err) } // Set the credentials - soapURL.User = url.UserPassword(vsphereConfig.VsphereUser, vsphereConfig.VspherePassword) + soapURL.User = url.UserPassword(username, password) // Temporarily setting the insecure flag True // TODO(ssurana): handle the certs better sc.session, err = govmomi.NewClient(ctx, soapURL, true) @@ -51,6 +55,6 @@ func (pv *Provisioner) sessionFromProviderConfig(cluster *clusterv1.Cluster, mac sc.context = &ctx finder := find.NewFinder(sc.session.Client, false) sc.finder = finder - pv.sessioncache[vsphereConfig.VsphereServer+vsphereConfig.VsphereUser] = sc + pv.sessioncache[vsphereConfig.VsphereServer+username] = sc return &sc, nil } diff --git a/pkg/cloud/vsphere/provisioner/govmomi/utils.go b/pkg/cloud/vsphere/provisioner/govmomi/utils.go index 19f612d01d..dc5bfcf5f4 100644 --- a/pkg/cloud/vsphere/provisioner/govmomi/utils.go +++ b/pkg/cloud/vsphere/provisioner/govmomi/utils.go @@ -190,3 +190,27 @@ func (pv *Provisioner) GetKubeConfig(cluster *clusterv1.Cluster) (string, error) } return string(secret.Data[constants.KubeConfigSecretData]), nil } + +func (pv *Provisioner) GetVsphereCredentials(cluster *clusterv1.Cluster) (string, string, error) { + vsphereConfig, err := vsphereutils.GetClusterProviderSpec(cluster.Spec.ProviderSpec) + if err != nil { + return "", "", err + } + // If the vsphereCredentialSecret is specified then read that secret to get the credentials + if vsphereConfig.VsphereCredentialSecret != "" { + klog.V(4).Infof("Fetching vsphere credentials from secret %s", vsphereConfig.VsphereCredentialSecret) + secret, err := pv.k8sClient.Core().Secrets(cluster.Namespace).Get(vsphereConfig.VsphereCredentialSecret, metav1.GetOptions{}) + if err != nil { + klog.Warningf("Error reading secret %s", vsphereConfig.VsphereCredentialSecret) + return "", "", err + } + if username, ok := secret.Data[constants.VsphereUserKey]; ok { + if password, ok := secret.Data[constants.VspherePasswordKey]; ok { + return string(username), string(password), nil + } + } + return "", "", fmt.Errorf("Improper secret: Secret %s should have the keys `%s` and `%s` defined in it", vsphereConfig.VsphereCredentialSecret, constants.VsphereUserKey, constants.VspherePasswordKey) + } + return vsphereConfig.VsphereUser, vsphereConfig.VspherePassword, nil + +}