From e7aaedb9ae8633f639b7c0685f73ee7ac2fbfb4c Mon Sep 17 00:00:00 2001 From: Sally O'Malley Date: Fri, 30 Nov 2018 09:52:10 -0500 Subject: [PATCH] add secret for kubeadmin pre-idp user --- README.md | 2 +- cmd/openshift-install/create.go | 10 ++- .../kubeadmin-password-secret.yaml.template | 7 ++ docs/user/environment-variables.md | 6 -- pkg/asset/cluster/cluster.go | 14 +++- pkg/asset/ignition/bootstrap/bootstrap.go | 2 +- pkg/asset/ignition/machine/node.go | 2 +- pkg/asset/installconfig/installconfig.go | 14 +--- pkg/asset/installconfig/password.go | 39 --------- pkg/asset/manifests/tectonic.go | 12 ++- pkg/asset/manifests/template.go | 3 +- pkg/asset/password/password.go | 79 +++++++++++++++++++ .../tectonic/kubeadmin-password-secret.go | 65 +++++++++++++++ pkg/asset/templates/templates.go | 4 + pkg/types/installconfig.go | 14 +--- 15 files changed, 195 insertions(+), 78 deletions(-) create mode 100644 data/data/manifests/tectonic/kubeadmin-password-secret.yaml.template delete mode 100644 pkg/asset/installconfig/password.go create mode 100644 pkg/asset/password/password.go create mode 100644 pkg/asset/templates/content/tectonic/kubeadmin-password-secret.go diff --git a/README.md b/README.md index 6edf7873cbf..d6b71a7de9d 100644 --- a/README.md +++ b/README.md @@ -29,7 +29,7 @@ If you don't have [terraform](https://www.terraform.io/), run the following to c hack/get-terraform.sh ``` -The installer will show a series of prompts for user-specific information (e.g. admin password) and use reasonable defaults for everything else. In non-interactive contexts, prompts can be bypassed by providing appropriately-named environment variables. Refer to the [user documentation](docs/user) for more information. +The installer will show a series of prompts for user-specific information and use reasonable defaults for everything else. In non-interactive contexts, prompts can be bypassed by providing appropriately-named environment variables. Refer to the [user documentation](docs/user) for more information. ### Connect to the cluster diff --git a/cmd/openshift-install/create.go b/cmd/openshift-install/create.go index 5ba6517f63a..1ea5247d554 100644 --- a/cmd/openshift-install/create.go +++ b/cmd/openshift-install/create.go @@ -2,6 +2,7 @@ package main import ( "context" + "io/ioutil" "os/exec" "path/filepath" "strings" @@ -249,7 +250,12 @@ func logComplete(directory string) error { return err } kubeconfig := filepath.Join(absDir, "auth", "kubeconfig") - logrus.Infof("Install complete! Run 'export KUBECONFIG=%s' to manage your cluster.", kubeconfig) - logrus.Info("After exporting your kubeconfig, run 'oc -h' for a list of OpenShift client commands.") + pwFile := filepath.Join(absDir, "auth", "kubeadmin-password") + pw, err := ioutil.ReadFile(pwFile) + if err != nil { + return err + } + logrus.Infof("kubeadmin user password: %s", pw) + logrus.Infof("Install complete! The kubeconfig is located here: %s", kubeconfig) return nil } diff --git a/data/data/manifests/tectonic/kubeadmin-password-secret.yaml.template b/data/data/manifests/tectonic/kubeadmin-password-secret.yaml.template new file mode 100644 index 00000000000..45334d9aa8c --- /dev/null +++ b/data/data/manifests/tectonic/kubeadmin-password-secret.yaml.template @@ -0,0 +1,7 @@ +kind: Secret +apiVersion: v1 +metadata: + namespace: kube-system + name: kubeadmin +data: + kubeadmin: {{.Base64EncodedKubeadminPwHash}} diff --git a/docs/user/environment-variables.md b/docs/user/environment-variables.md index 12c19af1236..b744cea4e72 100644 --- a/docs/user/environment-variables.md +++ b/docs/user/environment-variables.md @@ -20,12 +20,6 @@ The installer accepts a number of environment variable that allow the interactiv For libvirt, choose a name that is unique enough to be used as a prefix during cluster deletion. For example, if you use `demo` as your cluster name, `openshift-install destroy cluster` may destroy all domains, networks, pools, and volumes that begin with `demo`. -* `OPENSHIFT_INSTALL_EMAIL_ADDRESS`: - The email address of the cluster administrator. - This will be used to log in to the console. -* `OPENSHIFT_INSTALL_PASSWORD`: - The password of the cluster administrator. - This will be used to log in to the console. * `OPENSHIFT_INSTALL_PLATFORM`: The platform onto which the cluster will be installed. Valid values are `aws` and `libvirt`. diff --git a/pkg/asset/cluster/cluster.go b/pkg/asset/cluster/cluster.go index af0d65a2782..c88d166979d 100644 --- a/pkg/asset/cluster/cluster.go +++ b/pkg/asset/cluster/cluster.go @@ -16,6 +16,7 @@ import ( "github.com/openshift/installer/pkg/asset/cluster/openstack" "github.com/openshift/installer/pkg/asset/installconfig" "github.com/openshift/installer/pkg/asset/kubeconfig" + "github.com/openshift/installer/pkg/asset/password" "github.com/openshift/installer/pkg/terraform" "github.com/openshift/installer/pkg/types" ) @@ -25,6 +26,11 @@ const ( metadataFileName = "metadata.json" ) +var ( + // kubeadminPasswordPath is the path where kubeadmin user password is stored. + kubeadminPasswordPath = filepath.Join("auth", "kubeadmin-password") +) + // Cluster uses the terraform executable to launch a cluster // with the given terraform tfvar and generated templates. type Cluster struct { @@ -45,6 +51,7 @@ func (c *Cluster) Dependencies() []asset.Asset { &installconfig.InstallConfig{}, &TerraformVariables{}, &kubeconfig.Admin{}, + &password.KubeadminPassword{}, } } @@ -53,7 +60,8 @@ func (c *Cluster) Generate(parents asset.Parents) (err error) { installConfig := &installconfig.InstallConfig{} terraformVariables := &TerraformVariables{} adminKubeconfig := &kubeconfig.Admin{} - parents.Get(installConfig, terraformVariables, adminKubeconfig) + kubeadminPassword := &password.KubeadminPassword{} + parents.Get(installConfig, terraformVariables, adminKubeconfig, kubeadminPassword) // Copy the terraform.tfvars to a temp directory where the terraform will be invoked within. tmpDir, err := ioutil.TempDir("", "openshift-install-") @@ -85,6 +93,10 @@ func (c *Cluster) Generate(parents asset.Parents) (err error) { logrus.Error(err2) } } + c.FileList = append(c.FileList, &asset.File{ + Filename: kubeadminPasswordPath, + Data: []byte(kubeadminPassword.Password), + }) // serialize metadata and stuff it into c.FileList }() diff --git a/pkg/asset/ignition/bootstrap/bootstrap.go b/pkg/asset/ignition/bootstrap/bootstrap.go index e52113cf6f3..146e12f4455 100644 --- a/pkg/asset/ignition/bootstrap/bootstrap.go +++ b/pkg/asset/ignition/bootstrap/bootstrap.go @@ -104,7 +104,7 @@ func (a *Bootstrap) Generate(dependencies asset.Parents) error { a.Config.Passwd.Users = append( a.Config.Passwd.Users, - igntypes.PasswdUser{Name: "core", SSHAuthorizedKeys: []igntypes.SSHAuthorizedKey{igntypes.SSHAuthorizedKey(installConfig.Config.Admin.SSHKey)}}, + igntypes.PasswdUser{Name: "core", SSHAuthorizedKeys: []igntypes.SSHAuthorizedKey{igntypes.SSHAuthorizedKey(installConfig.Config.SSHKey)}}, ) data, err := json.Marshal(a.Config) diff --git a/pkg/asset/ignition/machine/node.go b/pkg/asset/ignition/machine/node.go index 0bebbcd5480..3dee540c0c1 100644 --- a/pkg/asset/ignition/machine/node.go +++ b/pkg/asset/ignition/machine/node.go @@ -39,7 +39,7 @@ func pointerIgnitionConfig(installConfig *types.InstallConfig, rootCA []byte, ro Passwd: ignition.Passwd{ Users: []ignition.PasswdUser{{ Name: "core", - SSHAuthorizedKeys: []ignition.SSHAuthorizedKey{ignition.SSHAuthorizedKey(installConfig.Admin.SSHKey)}, + SSHAuthorizedKeys: []ignition.SSHAuthorizedKey{ignition.SSHAuthorizedKey(installConfig.SSHKey)}, }}, }, } diff --git a/pkg/asset/installconfig/installconfig.go b/pkg/asset/installconfig/installconfig.go index de7d437c0da..359056f7b4a 100644 --- a/pkg/asset/installconfig/installconfig.go +++ b/pkg/asset/installconfig/installconfig.go @@ -37,8 +37,6 @@ var _ asset.WritableAsset = (*InstallConfig)(nil) func (a *InstallConfig) Dependencies() []asset.Asset { return []asset.Asset{ &clusterID{}, - &emailAddress{}, - &password{}, &sshPublicKey{}, &baseDomain{}, &clusterName{}, @@ -50,8 +48,6 @@ func (a *InstallConfig) Dependencies() []asset.Asset { // Generate generates the install-config.yml file. func (a *InstallConfig) Generate(parents asset.Parents) error { clusterID := &clusterID{} - emailAddress := &emailAddress{} - password := &password{} sshPublicKey := &sshPublicKey{} baseDomain := &baseDomain{} clusterName := &clusterName{} @@ -59,8 +55,6 @@ func (a *InstallConfig) Generate(parents asset.Parents) error { platform := &platform{} parents.Get( clusterID, - emailAddress, - password, sshPublicKey, baseDomain, clusterName, @@ -72,12 +66,8 @@ func (a *InstallConfig) Generate(parents asset.Parents) error { ObjectMeta: metav1.ObjectMeta{ Name: clusterName.ClusterName, }, - ClusterID: clusterID.ClusterID, - Admin: types.Admin{ - Email: emailAddress.EmailAddress, - Password: password.Password, - SSHKey: sshPublicKey.Key, - }, + ClusterID: clusterID.ClusterID, + SSHKey: sshPublicKey.Key, BaseDomain: baseDomain.BaseDomain, Networking: types.Networking{ Type: "OpenshiftSDN", diff --git a/pkg/asset/installconfig/password.go b/pkg/asset/installconfig/password.go deleted file mode 100644 index 34c502c2c56..00000000000 --- a/pkg/asset/installconfig/password.go +++ /dev/null @@ -1,39 +0,0 @@ -package installconfig - -import ( - survey "gopkg.in/AlecAivazis/survey.v1" - - "github.com/openshift/installer/pkg/asset" -) - -type password struct { - Password string -} - -var _ asset.Asset = (*password)(nil) - -// Dependencies returns no dependencies. -func (a *password) Dependencies() []asset.Asset { - return []asset.Asset{} -} - -// Generate queries for the password from the user. -func (a *password) Generate(asset.Parents) error { - p, err := asset.GenerateUserProvidedAsset( - a.Name(), - &survey.Question{ - Prompt: &survey.Password{ - Message: "Password", - Help: "The password of the cluster administrator. This will be used to log in to the console.", - }, - }, - "OPENSHIFT_INSTALL_PASSWORD", - ) - a.Password = p - return err -} - -// Name returns the human-friendly name of the asset. -func (a *password) Name() string { - return "Password" -} diff --git a/pkg/asset/manifests/tectonic.go b/pkg/asset/manifests/tectonic.go index 98d30ae7670..285e5a8c2bb 100644 --- a/pkg/asset/manifests/tectonic.go +++ b/pkg/asset/manifests/tectonic.go @@ -11,6 +11,7 @@ import ( "github.com/openshift/installer/pkg/asset" "github.com/openshift/installer/pkg/asset/installconfig" "github.com/openshift/installer/pkg/asset/machines" + "github.com/openshift/installer/pkg/asset/password" "github.com/openshift/installer/pkg/asset/templates/content/tectonic" ) @@ -40,9 +41,11 @@ func (t *Tectonic) Dependencies() []asset.Asset { &ClusterK8sIO{}, &machines.Worker{}, &machines.Master{}, + &password.KubeadminPassword{}, &tectonic.BindingDiscovery{}, &tectonic.CloudCredsSecret{}, + &tectonic.KubeadminPasswordSecret{}, &tectonic.RoleCloudCredsSecretReader{}, } } @@ -50,10 +53,11 @@ func (t *Tectonic) Dependencies() []asset.Asset { // Generate generates the respective operator config.yml files func (t *Tectonic) Generate(dependencies asset.Parents) error { installConfig := &installconfig.InstallConfig{} + kubeadminPassword := &password.KubeadminPassword{} clusterk8sio := &ClusterK8sIO{} worker := &machines.Worker{} master := &machines.Master{} - dependencies.Get(installConfig, clusterk8sio, worker, master) + dependencies.Get(installConfig, clusterk8sio, worker, master, kubeadminPassword) var cloudCreds cloudCredsSecretData platform := installConfig.Config.Platform.Name() switch platform { @@ -91,18 +95,22 @@ func (t *Tectonic) Generate(dependencies asset.Parents) error { } templateData := &tectonicTemplateData{ - CloudCreds: cloudCreds, + CloudCreds: cloudCreds, + Base64EncodedKubeadminPwHash: base64.StdEncoding.EncodeToString(kubeadminPassword.PasswordHash), } bindingDiscovery := &tectonic.BindingDiscovery{} cloudCredsSecret := &tectonic.CloudCredsSecret{} + kubeadminPasswordSecret := &tectonic.KubeadminPasswordSecret{} roleCloudCredsSecretReader := &tectonic.RoleCloudCredsSecretReader{} dependencies.Get( bindingDiscovery, cloudCredsSecret, + kubeadminPasswordSecret, roleCloudCredsSecretReader) assetData := map[string][]byte{ "99_binding-discovery.yaml": []byte(bindingDiscovery.Files()[0].Data), + "99_kubeadmin-password-secret.yaml": applyTemplateData(kubeadminPasswordSecret.Files()[0].Data, templateData), "99_openshift-cluster-api_cluster.yaml": clusterk8sio.Raw, "99_openshift-cluster-api_master-machines.yaml": master.MachinesRaw, "99_openshift-cluster-api_master-user-data-secret.yaml": master.UserDataSecretRaw, diff --git a/pkg/asset/manifests/template.go b/pkg/asset/manifests/template.go index 71dbdefecd8..265ae844eff 100644 --- a/pkg/asset/manifests/template.go +++ b/pkg/asset/manifests/template.go @@ -36,5 +36,6 @@ type bootkubeTemplateData struct { } type tectonicTemplateData struct { - CloudCreds cloudCredsSecretData + CloudCreds cloudCredsSecretData + Base64EncodedKubeadminPwHash string } diff --git a/pkg/asset/password/password.go b/pkg/asset/password/password.go new file mode 100644 index 00000000000..c12668eb3b4 --- /dev/null +++ b/pkg/asset/password/password.go @@ -0,0 +1,79 @@ +package password + +import ( + "crypto/rand" + "math/big" + + "github.com/openshift/installer/pkg/asset" + "golang.org/x/crypto/bcrypt" +) + +// KubeadminPassword is the asset for the kubeadmin user password +type KubeadminPassword struct { + Password string + PasswordHash []byte +} + +var _ asset.Asset = (*KubeadminPassword)(nil) + +// Dependencies returns no dependencies. +func (a *KubeadminPassword) Dependencies() []asset.Asset { + return []asset.Asset{} +} + +// Generate the kubeadmin password +func (a *KubeadminPassword) Generate(asset.Parents) error { + err := a.generateRandomPasswordHash(23) + if err != nil { + return err + } + return nil +} + +// generateRandomPasswordHash generates a hash of a random ASCII password +// 5char-5char-5char-5char +func (a *KubeadminPassword) generateRandomPasswordHash(length int) error { + const ( + lowerLetters = "abcdefghijkmnopqrstuvwxyz" + upperLetters = "ABCDEFGHIJKLMNPQRSTUVWXYZ" + digits = "23456789" + all = lowerLetters + upperLetters + digits + ) + var password string + for i := 0; i < length; i++ { + n, err := rand.Int(rand.Reader, big.NewInt(int64(len(all)))) + if err != nil { + return err + } + newchar := string(all[n.Int64()]) + if password == "" { + password = newchar + } + if i < length-1 { + n, err = rand.Int(rand.Reader, big.NewInt(int64(len(password)+1))) + if err != nil { + return err + } + j := n.Int64() + password = password[0:j] + newchar + password[j:] + } + } + pw := []rune(password) + for _, replace := range []int{5, 11, 17} { + pw[replace] = '-' + } + if a.Password == "" { + a.Password = string(pw) + } + bytes, err := bcrypt.GenerateFromPassword([]byte(password), bcrypt.DefaultCost) + if err != nil { + return err + } + a.PasswordHash = bytes + return nil +} + +// Name returns the human-friendly name of the asset. +func (a *KubeadminPassword) Name() string { + return "Kubeadmin Password" +} diff --git a/pkg/asset/templates/content/tectonic/kubeadmin-password-secret.go b/pkg/asset/templates/content/tectonic/kubeadmin-password-secret.go new file mode 100644 index 00000000000..25cb282237c --- /dev/null +++ b/pkg/asset/templates/content/tectonic/kubeadmin-password-secret.go @@ -0,0 +1,65 @@ +package tectonic + +import ( + "os" + "path/filepath" + + "github.com/openshift/installer/pkg/asset" + "github.com/openshift/installer/pkg/asset/templates/content" +) + +const ( + kubeadminPasswordSecretFileName = "kubeadmin-password-secret.yaml.template" +) + +var _ asset.WritableAsset = (*KubeadminPasswordSecret)(nil) + +// KubeadminPasswordSecret is the constant to represent contents of kubeadmin-password-secret.yaml.template file +type KubeadminPasswordSecret struct { + fileName string + FileList []*asset.File +} + +// Dependencies returns all of the dependencies directly needed by the asset +func (t *KubeadminPasswordSecret) Dependencies() []asset.Asset { + return []asset.Asset{} +} + +// Name returns the human-friendly name of the asset. +func (t *KubeadminPasswordSecret) Name() string { + return "KubeadminPasswordSecret" +} + +// Generate generates the actual files by this asset +func (t *KubeadminPasswordSecret) Generate(parents asset.Parents) error { + t.fileName = kubeadminPasswordSecretFileName + data, err := content.GetTectonicTemplate(t.fileName) + if err != nil { + return err + } + t.FileList = []*asset.File{ + { + Filename: filepath.Join(content.TemplateDir, t.fileName), + Data: []byte(data), + }, + } + return nil +} + +// Files returns the files generated by the asset. +func (t *KubeadminPasswordSecret) Files() []*asset.File { + return t.FileList +} + +// Load returns the asset from disk. +func (t *KubeadminPasswordSecret) Load(f asset.FileFetcher) (bool, error) { + file, err := f.FetchByName(filepath.Join(content.TemplateDir, kubeadminPasswordSecretFileName)) + if err != nil { + if os.IsNotExist(err) { + return false, nil + } + return false, err + } + t.FileList = []*asset.File{file} + return true, nil +} diff --git a/pkg/asset/templates/templates.go b/pkg/asset/templates/templates.go index eb23f3f6df7..493d59fefb2 100644 --- a/pkg/asset/templates/templates.go +++ b/pkg/asset/templates/templates.go @@ -40,6 +40,7 @@ func (m *Templates) Dependencies() []asset.Asset { &bootkube.HostEtcdServiceKubeSystem{}, &tectonic.BindingDiscovery{}, &tectonic.CloudCredsSecret{}, + &tectonic.KubeadminPasswordSecret{}, &tectonic.RoleCloudCredsSecretReader{}, } } @@ -64,6 +65,7 @@ func (m *Templates) Generate(dependencies asset.Parents) error { bindingDiscovery := &tectonic.BindingDiscovery{} cloudCredsSecret := &tectonic.CloudCredsSecret{} + kubeadminPasswordSecret := &tectonic.KubeadminPasswordSecret{} roleCloudCredsSecretReader := &tectonic.RoleCloudCredsSecretReader{} dependencies.Get( @@ -84,6 +86,7 @@ func (m *Templates) Generate(dependencies asset.Parents) error { hostEtcdServiceKubeSystem, bindingDiscovery, cloudCredsSecret, + kubeadminPasswordSecret, roleCloudCredsSecretReader) m.FileList = []*asset.File{} @@ -105,6 +108,7 @@ func (m *Templates) Generate(dependencies asset.Parents) error { m.FileList = append(m.FileList, bindingDiscovery.Files()...) m.FileList = append(m.FileList, cloudCredsSecret.Files()...) + m.FileList = append(m.FileList, kubeadminPasswordSecret.Files()...) m.FileList = append(m.FileList, roleCloudCredsSecretReader.Files()...) return nil diff --git a/pkg/types/installconfig.go b/pkg/types/installconfig.go index 5d2391aa23e..273faae25e5 100644 --- a/pkg/types/installconfig.go +++ b/pkg/types/installconfig.go @@ -29,8 +29,8 @@ type InstallConfig struct { // ClusterID is the ID of the cluster. ClusterID string `json:"clusterID"` - // Admin is the configuration for the admin user. - Admin Admin `json:"admin"` + // SSHKey is the public ssh key to provide access to instances. + SSHKey string `json:"sshKey"` // BaseDomain is the base domain to which the cluster should belong. BaseDomain string `json:"baseDomain"` @@ -60,16 +60,6 @@ func (c *InstallConfig) MasterCount() int { return 1 } -// Admin is the configuration for the admin user. -type Admin struct { - // Email is the email address of the admin user. - Email string `json:"email"` - // Password is the password of the admin user. - Password string `json:"password"` - // SSHKey to use for the access to compute instances. - SSHKey string `json:"sshKey,omitempty"` -} - // Platform is the configuration for the specific platform upon which to perform // the installation. Only one of the platform configuration should be set. type Platform struct {