diff --git a/cmd/crc/cmd/console.go b/cmd/crc/cmd/console.go index bdd5c6a385..f95c42c8b6 100644 --- a/cmd/crc/cmd/console.go +++ b/cmd/crc/cmd/console.go @@ -123,7 +123,7 @@ func toConsoleClusterConfig(result *client.ConsoleResult) *clusterConfig { }, DeveloperCredentials: credentials{ Username: "developer", - Password: "developer", + Password: result.ClusterConfig.DeveloperPass, }, } } diff --git a/cmd/crc/cmd/console_test.go b/cmd/crc/cmd/console_test.go index 0910dfb809..ab83781c36 100644 --- a/cmd/crc/cmd/console_test.go +++ b/cmd/crc/cmd/console_test.go @@ -20,6 +20,7 @@ var DummyClusterConfig = types.ClusterConfig{ ClusterCACert: "MIIDODCCAiCgAwIBAgIIRVfCKNUa1wIwDQYJ", KubeConfig: "/tmp/kubeconfig", KubeAdminPass: "foobar", + DeveloperPass: "foobar", ClusterAPI: "https://foo.testing:6443", WebConsoleURL: "https://console.foo.testing:6443", ProxyConfig: nil, @@ -60,9 +61,9 @@ func TestConsolePlainError(t *testing.T) { } func TestConsoleWithPrintCredentialsPlainSuccess(t *testing.T) { - expectedOut := fmt.Sprintf(`To login as a regular user, run 'oc login -u developer -p developer %s'. + expectedOut := fmt.Sprintf(`To login as a regular user, run 'oc login -u developer -p %s %s'. To login as an admin, run 'oc login -u kubeadmin -p %s %s' -`, fakemachine.DummyClusterConfig.ClusterAPI, fakemachine.DummyClusterConfig.KubeAdminPass, fakemachine.DummyClusterConfig.ClusterAPI) +`, fakemachine.DummyClusterConfig.DeveloperPass, fakemachine.DummyClusterConfig.ClusterAPI, fakemachine.DummyClusterConfig.KubeAdminPass, fakemachine.DummyClusterConfig.ClusterAPI) out := new(bytes.Buffer) assert.NoError(t, runConsole(out, setUpClientForConsole(t), false, true, "")) assert.Equal(t, expectedOut, out.String()) @@ -70,9 +71,9 @@ To login as an admin, run 'oc login -u kubeadmin -p %s %s' func TestConsoleWithPrintCredentialsAndURLPlainSuccess(t *testing.T) { expectedOut := fmt.Sprintf(`%s -To login as a regular user, run 'oc login -u developer -p developer %s'. +To login as a regular user, run 'oc login -u developer -p %s %s'. To login as an admin, run 'oc login -u kubeadmin -p %s %s' -`, fakemachine.DummyClusterConfig.WebConsoleURL, fakemachine.DummyClusterConfig.ClusterAPI, fakemachine.DummyClusterConfig.KubeAdminPass, fakemachine.DummyClusterConfig.ClusterAPI) +`, fakemachine.DummyClusterConfig.WebConsoleURL, fakemachine.DummyClusterConfig.DeveloperPass, fakemachine.DummyClusterConfig.ClusterAPI, fakemachine.DummyClusterConfig.KubeAdminPass, fakemachine.DummyClusterConfig.ClusterAPI) out := new(bytes.Buffer) assert.NoError(t, runConsole(out, setUpClientForConsole(t), true, true, "")) assert.Equal(t, expectedOut, out.String()) @@ -80,22 +81,22 @@ To login as an admin, run 'oc login -u kubeadmin -p %s %s' func TestConsoleJSONSuccess(t *testing.T) { expectedJSONOut := fmt.Sprintf(`{ - "success": true, - "clusterConfig": { - "clusterType": "openshift", - "cacert": "%s", - "webConsoleUrl": "%s", - "url": "%s", - "adminCredentials": { - "username": "kubeadmin", - "password": "%s" - }, - "developerCredentials": { - "username": "developer", - "password": "developer" - } - } -}`, fakemachine.DummyClusterConfig.ClusterCACert, fakemachine.DummyClusterConfig.WebConsoleURL, fakemachine.DummyClusterConfig.ClusterAPI, fakemachine.DummyClusterConfig.KubeAdminPass) + "success": true, + "clusterConfig": { + "clusterType": "openshift", + "cacert": "%s", + "webConsoleUrl": "%s", + "url": "%s", + "adminCredentials": { + "username": "kubeadmin", + "password": "%s" + }, + "developerCredentials": { + "username": "developer", + "password": "%s" + } + } + }`, fakemachine.DummyClusterConfig.ClusterCACert, fakemachine.DummyClusterConfig.WebConsoleURL, fakemachine.DummyClusterConfig.ClusterAPI, fakemachine.DummyClusterConfig.KubeAdminPass, fakemachine.DummyClusterConfig.DeveloperPass) out := new(bytes.Buffer) assert.NoError(t, runConsole(out, setUpClientForConsole(t), false, false, jsonFormat)) assert.JSONEq(t, expectedJSONOut, out.String()) diff --git a/cmd/crc/cmd/start.go b/cmd/crc/cmd/start.go index 82c3a6513b..c8e9779e20 100644 --- a/cmd/crc/cmd/start.go +++ b/cmd/crc/cmd/start.go @@ -75,6 +75,7 @@ func runStart(ctx context.Context) (*types.StartResult, error) { NameServer: config.Get(crcConfig.NameServer).AsString(), PullSecret: cluster.NewInteractivePullSecretLoader(config), KubeAdminPassword: config.Get(crcConfig.KubeAdminPassword).AsString(), + DeveloperPassword: config.Get(crcConfig.DeveloperPassword).AsString(), Preset: crcConfig.GetPreset(config), IngressHTTPPort: config.Get(crcConfig.IngressHTTPPort).AsUInt(), IngressHTTPSPort: config.Get(crcConfig.IngressHTTPSPort).AsUInt(), @@ -140,7 +141,7 @@ func toClusterConfig(result *types.StartResult) *clusterConfig { }, DeveloperCredentials: credentials{ Username: "developer", - Password: "developer", + Password: result.ClusterConfig.DeveloperPass, }, } } diff --git a/cmd/crc/cmd/start_test.go b/cmd/crc/cmd/start_test.go index 43a466b892..d089c6c2c1 100644 --- a/cmd/crc/cmd/start_test.go +++ b/cmd/crc/cmd/start_test.go @@ -32,7 +32,7 @@ func TestRenderActionPlainSuccess(t *testing.T) { }, DeveloperCredentials: credentials{ Username: "developer", - Password: "developer", + Password: "secret", }, }, }, out, "")) @@ -118,7 +118,7 @@ Log in as administrator: Log in as user: Username: developer - Password: developer + Password: secret Use the 'oc' command line interface: $ eval $(crc oc-env) @@ -136,7 +136,7 @@ Log in as administrator: Log in as user: Username: developer - Password: developer + Password: secret Use the 'oc' command line interface: PS> & crc oc-env | Invoke-Expression @@ -154,7 +154,7 @@ Log in as administrator: Log in as user: Username: developer - Password: developer + Password: secret Use the 'oc' command line interface: > @FOR /f "tokens=*" %i IN ('crc oc-env') DO @call %i diff --git a/pkg/crc/api/api_client_test.go b/pkg/crc/api/api_client_test.go index 84b5f39010..619026c610 100644 --- a/pkg/crc/api/api_client_test.go +++ b/pkg/crc/api/api_client_test.go @@ -94,6 +94,7 @@ func TestStart(t *testing.T) { ClusterCACert: "MIIDODCCAiCgAwIBAgIIRVfCKNUa1wIwDQYJ", KubeConfig: "/tmp/kubeconfig", KubeAdminPass: "foobar", + DeveloperPass: "foobar", ClusterAPI: "https://foo.testing:6443", WebConsoleURL: "https://console.foo.testing:6443", ProxyConfig: nil, diff --git a/pkg/crc/api/api_http_test.go b/pkg/crc/api/api_http_test.go index f8219bf2ec..7096a8e44b 100644 --- a/pkg/crc/api/api_http_test.go +++ b/pkg/crc/api/api_http_test.go @@ -167,11 +167,11 @@ var testCases = []testCase{ // start { request: post("start"), - response: jSon(`{"Status":"","ClusterConfig":{"ClusterType":"openshift","ClusterCACert":"MIIDODCCAiCgAwIBAgIIRVfCKNUa1wIwDQYJ","KubeConfig":"/tmp/kubeconfig","KubeAdminPass":"foobar","ClusterAPI":"https://foo.testing:6443","WebConsoleURL":"https://console.foo.testing:6443","ProxyConfig":null},"KubeletStarted":true}`), + response: jSon(`{"Status":"","ClusterConfig":{"ClusterType":"openshift","ClusterCACert":"MIIDODCCAiCgAwIBAgIIRVfCKNUa1wIwDQYJ","KubeConfig":"/tmp/kubeconfig","KubeAdminPass":"foobar","DeveloperPass":"foobar","ClusterAPI":"https://foo.testing:6443","WebConsoleURL":"https://console.foo.testing:6443","ProxyConfig":null},"KubeletStarted":true}`), }, { request: get("start"), - response: jSon(`{"Status":"","ClusterConfig":{"ClusterType":"openshift","ClusterCACert":"MIIDODCCAiCgAwIBAgIIRVfCKNUa1wIwDQYJ","KubeConfig":"/tmp/kubeconfig","KubeAdminPass":"foobar","ClusterAPI":"https://foo.testing:6443","WebConsoleURL":"https://console.foo.testing:6443","ProxyConfig":null},"KubeletStarted":true}`), + response: jSon(`{"Status":"","ClusterConfig":{"ClusterType":"openshift","ClusterCACert":"MIIDODCCAiCgAwIBAgIIRVfCKNUa1wIwDQYJ","KubeConfig":"/tmp/kubeconfig","KubeAdminPass":"foobar","DeveloperPass":"foobar","ClusterAPI":"https://foo.testing:6443","WebConsoleURL":"https://console.foo.testing:6443","ProxyConfig":null},"KubeletStarted":true}`), }, // start with failure @@ -273,7 +273,7 @@ var testCases = []testCase{ // webconsoleurl { request: get("webconsoleurl"), - response: jSon(`{"ClusterConfig":{"ClusterType":"openshift","ClusterCACert":"MIIDODCCAiCgAwIBAgIIRVfCKNUa1wIwDQYJ","KubeConfig":"/tmp/kubeconfig","KubeAdminPass":"foobar","ClusterAPI":"https://foo.testing:6443","WebConsoleURL":"https://console.foo.testing:6443","ProxyConfig":null},"State":"Running"}`), + response: jSon(`{"ClusterConfig":{"ClusterType":"openshift","ClusterCACert":"MIIDODCCAiCgAwIBAgIIRVfCKNUa1wIwDQYJ","KubeConfig":"/tmp/kubeconfig","KubeAdminPass":"foobar","DeveloperPass":"foobar","ClusterAPI":"https://foo.testing:6443","WebConsoleURL":"https://console.foo.testing:6443","ProxyConfig":null},"State":"Running"}`), }, // webconsoleurl with failure diff --git a/pkg/crc/api/handlers.go b/pkg/crc/api/handlers.go index 82221f6620..870abf0621 100644 --- a/pkg/crc/api/handlers.go +++ b/pkg/crc/api/handlers.go @@ -127,6 +127,7 @@ func getStartConfig(cfg crcConfig.Storage, args client.StartConfig) types.StartC NameServer: cfg.Get(crcConfig.NameServer).AsString(), PullSecret: cluster.NewNonInteractivePullSecretLoader(cfg, args.PullSecretFile), KubeAdminPassword: cfg.Get(crcConfig.KubeAdminPassword).AsString(), + DeveloperPassword: cfg.Get(crcConfig.DeveloperPassword).AsString(), IngressHTTPPort: cfg.Get(crcConfig.IngressHTTPPort).AsUInt(), IngressHTTPSPort: cfg.Get(crcConfig.IngressHTTPSPort).AsUInt(), Preset: crcConfig.GetPreset(cfg), diff --git a/pkg/crc/cluster/kubeadmin_password.go b/pkg/crc/cluster/kubeadmin_password.go index f8beb9fb13..cc12fdae1f 100644 --- a/pkg/crc/cluster/kubeadmin_password.go +++ b/pkg/crc/cluster/kubeadmin_password.go @@ -17,33 +17,21 @@ import ( "golang.org/x/crypto/bcrypt" ) -// GenerateKubeAdminUserPassword creates and put updated kubeadmin password to ~/.crc/machine/crc/kubeadmin-password -func GenerateKubeAdminUserPassword() error { - logging.Infof("Generating new password for the kubeadmin user") - kubeAdminPasswordFile := constants.GetKubeAdminPasswordPath() - kubeAdminPassword, err := GenerateRandomPasswordHash(23) +// GenerateUserPassword creates and put updated password to ~/.crc/machine/crc/ directory +func GenerateUserPassword(passwordFile string, user string) error { + logging.Infof("Generating new password for the %s user", user) + password, err := GenerateRandomPasswordHash(23) if err != nil { - return fmt.Errorf("Cannot generate the kubeadmin user password: %w", err) + return fmt.Errorf("cannot generate the %s user password: %w", user, err) } - return os.WriteFile(kubeAdminPasswordFile, []byte(kubeAdminPassword), 0600) + return os.WriteFile(passwordFile, []byte(password), 0600) } -// UpdateKubeAdminUserPassword updates the htpasswd secret -func UpdateKubeAdminUserPassword(ctx context.Context, ocConfig oc.Config, newPassword string) error { - if newPassword != "" { - logging.Infof("Overriding password for kubeadmin user") - if err := os.WriteFile(constants.GetKubeAdminPasswordPath(), []byte(strings.TrimSpace(newPassword)), 0600); err != nil { - return err - } - } - - kubeAdminPassword, err := GetKubeadminPassword() +// UpdateUserPasswords updates the htpasswd secret +func UpdateUserPasswords(ctx context.Context, ocConfig oc.Config, newKubeAdminPassword string, newDeveloperPassword string) error { + credentials, err := resolveUserPasswords(newKubeAdminPassword, newDeveloperPassword, constants.GetKubeAdminPasswordPath(), constants.GetDeveloperPasswordPath()) if err != nil { - return fmt.Errorf("Cannot read the kubeadmin user password from file: %w", err) - } - credentials := map[string]string{ - "developer": "developer", - "kubeadmin": kubeAdminPassword, + return err } if err := WaitForOpenshiftResource(ctx, ocConfig, "secret"); err != nil { @@ -62,7 +50,7 @@ func UpdateKubeAdminUserPassword(ctx context.Context, ocConfig oc.Config, newPas return nil } - logging.Infof("Changing the password for the kubeadmin user") + logging.Infof("Changing the password for the users") expected, err := getHtpasswd(credentials, externals) if err != nil { return err @@ -72,14 +60,13 @@ func UpdateKubeAdminUserPassword(ctx context.Context, ocConfig oc.Config, newPas "-n", "openshift-config", "--type", "merge"} _, stderr, err = ocConfig.RunOcCommandPrivate(cmdArgs...) if err != nil { - return fmt.Errorf("Failed to update kubeadmin password %v: %s", err, stderr) + return fmt.Errorf("failed to update user passwords %v: %s", err, stderr) } return nil } -func GetKubeadminPassword() (string, error) { - kubeAdminPasswordFile := constants.GetKubeAdminPasswordPath() - rawData, err := os.ReadFile(kubeAdminPasswordFile) +func GetUserPassword(passwordFile string) (string, error) { + rawData, err := os.ReadFile(passwordFile) if err != nil { return "", err } @@ -192,3 +179,31 @@ func testBCryptPassword(password, hash string) (bool, error) { } return true, nil } + +func resolveUserPasswords(newKubeAdminPassword string, newDeveloperPassword string, kubeAdminPasswordPath string, developerPasswordPath string) (map[string]string, error) { + if newKubeAdminPassword != "" { + logging.Infof("Overriding password for kubeadmin user") + if err := os.WriteFile(kubeAdminPasswordPath, []byte(strings.TrimSpace(newKubeAdminPassword)), 0600); err != nil { + return nil, err + } + } + if newDeveloperPassword != "" { + logging.Infof("Overriding password for developer user") + if err := os.WriteFile(developerPasswordPath, []byte(strings.TrimSpace(newDeveloperPassword)), 0600); err != nil { + return nil, err + } + } + + kubeAdminPassword, err := GetUserPassword(kubeAdminPasswordPath) + if err != nil { + return nil, fmt.Errorf("cannot read the kubeadmin user password from file: %w", err) + } + developerPassword, err := GetUserPassword(developerPasswordPath) + if err != nil { + return nil, fmt.Errorf("cannot read the developer user password from file: %w", err) + } + return map[string]string{ + "developer": developerPassword, + "kubeadmin": kubeAdminPassword, + }, nil +} diff --git a/pkg/crc/cluster/kubeadmin_password_test.go b/pkg/crc/cluster/kubeadmin_password_test.go index 524d7be310..b484e27430 100644 --- a/pkg/crc/cluster/kubeadmin_password_test.go +++ b/pkg/crc/cluster/kubeadmin_password_test.go @@ -1,6 +1,8 @@ package cluster import ( + "os" + "path/filepath" "testing" "github.com/stretchr/testify/assert" @@ -56,3 +58,58 @@ func TestCompareFalseWithCustomEntries(t *testing.T) { assert.NoError(t, err) assert.True(t, ok) } + +func TestGenerateUserPassword_WhenValidFileProvided_ThenWritePasswordToFile(t *testing.T) { + // Given + dir := t.TempDir() + userPasswordPath := filepath.Join(dir, "test-user-password") + // When + err := GenerateUserPassword(userPasswordPath, "test-user") + // Then + assert.NoError(t, err) + actualPasswordFileContents, err := os.ReadFile(userPasswordPath) + assert.NoError(t, err) + assert.Equal(t, 23, len(actualPasswordFileContents)) +} + +var testResolveUserPasswordArguments = map[string]struct { + kubeAdminPasswordViaConfig string + developerPasswordViaConfig string + expectedKubeAdminPassword string + expectedDeveloperPassword string +}{ + "When no password configured in config, then read kubeadmin and developer passwords from password files": { + "", "", "kubeadmin-password-via-file", "developer-password-via-file", + }, + "When developer password configured in config, then use developer password from config": { + "", "developer-password-via-config", "kubeadmin-password-via-file", "developer-password-via-config", + }, + "When kube admin password configured in config, then use kube admin password from config": { + "kubeadmin-password-via-config", "", "kubeadmin-password-via-config", "developer-password-via-file", + }, + "When kube admin and developer password configured in config, then use kube admin and developer passwords from config": { + "kubeadmin-password-via-config", "developer-password-via-config", "kubeadmin-password-via-config", "developer-password-via-config", + }, +} + +func TestResolveUserPassword_WhenNothingProvided_ThenUsePasswordFromFiles(t *testing.T) { + for name, test := range testResolveUserPasswordArguments { + t.Run(name, func(t *testing.T) { + // Given + dir := t.TempDir() + kubeAdminPasswordPath := filepath.Join(dir, "kubeadmin-password") + err := os.WriteFile(kubeAdminPasswordPath, []byte("kubeadmin-password-via-file"), 0600) + assert.NoError(t, err) + developerPasswordPath := filepath.Join(dir, "developer-password") + err = os.WriteFile(developerPasswordPath, []byte("developer-password-via-file"), 0600) + assert.NoError(t, err) + + // When + credentials, err := resolveUserPasswords(test.kubeAdminPasswordViaConfig, test.developerPasswordViaConfig, kubeAdminPasswordPath, developerPasswordPath) + + // Then + assert.NoError(t, err) + assert.Equal(t, map[string]string{"developer": test.expectedDeveloperPassword, "kubeadmin": test.expectedKubeAdminPassword}, credentials) + }) + } +} diff --git a/pkg/crc/config/settings.go b/pkg/crc/config/settings.go index d4236d6a5c..ce44450014 100644 --- a/pkg/crc/config/settings.go +++ b/pkg/crc/config/settings.go @@ -29,6 +29,7 @@ const ( ConsentTelemetry = "consent-telemetry" EnableClusterMonitoring = "enable-cluster-monitoring" KubeAdminPassword = "kubeadmin-password" + DeveloperPassword = "developer-password" Preset = "preset" EnableSharedDirs = "enable-shared-dirs" SharedDirPassword = "shared-dir-password" // #nosec G101 @@ -134,6 +135,8 @@ func RegisterSettings(cfg *Config) { cfg.AddSetting(KubeAdminPassword, "", validateString, SuccessfullyApplied, "User defined kubeadmin password") + cfg.AddSetting(DeveloperPassword, constants.DefaultDeveloperPassword, validateString, SuccessfullyApplied, + "User defined developer password") cfg.AddSetting(IngressHTTPPort, constants.OpenShiftIngressHTTPPort, validatePort, RequiresHTTPPortChangeWarning, fmt.Sprintf("HTTP port to use for OpenShift ingress/routes on the host (1024-65535, default: %d)", constants.OpenShiftIngressHTTPPort)) cfg.AddSetting(IngressHTTPSPort, constants.OpenShiftIngressHTTPSPort, validatePort, RequiresHTTPSPortChangeWarning, diff --git a/pkg/crc/config/settings_test.go b/pkg/crc/config/settings_test.go index b8e13290b6..578e1b5d6e 100644 --- a/pkg/crc/config/settings_test.go +++ b/pkg/crc/config/settings_test.go @@ -200,6 +200,9 @@ var configDefaultValuesTestArguments = []struct { { KubeAdminPassword, "", }, + { + DeveloperPassword, "developer", + }, { CPUs, uint(4), }, @@ -286,6 +289,9 @@ var configProvidedValuesTestArguments = []struct { { KubeAdminPassword, "kubeadmin-secret-password", }, + { + DeveloperPassword, "developer-secret-password", + }, { CPUs, uint(8), }, diff --git a/pkg/crc/constants/constants.go b/pkg/crc/constants/constants.go index 383ec89c2d..7bd2db7566 100644 --- a/pkg/crc/constants/constants.go +++ b/pkg/crc/constants/constants.go @@ -30,6 +30,7 @@ const ( BackgroundLauncherURL = "https://github.com/crc-org/win32-background-launcher/releases/download/v%s/win32-background-launcher.exe" DefaultBundleURLBase = "https://mirror.openshift.com/pub/openshift-v4/clients/crc/bundles/%s/%s/%s" DefaultContext = "admin" + DefaultDeveloperPassword = "developer" DaemonHTTPEndpoint = "http://unix/api" DaemonVsockPort = 1024 DefaultPodmanNamedPipe = `\\.\pipe\crc-podman` @@ -194,6 +195,10 @@ func GetKubeAdminPasswordPath() string { return filepath.Join(MachineInstanceDir, DefaultName, "kubeadmin-password") } +func GetDeveloperPasswordPath() string { + return filepath.Join(MachineInstanceDir, DefaultName, "developer-password") +} + func GetWin32BackgroundLauncherDownloadURL() string { return fmt.Sprintf(BackgroundLauncherURL, version.GetWin32BackgroundLauncherVersion()) diff --git a/pkg/crc/machine/fakemachine/client.go b/pkg/crc/machine/fakemachine/client.go index b10146954b..6d367ab673 100644 --- a/pkg/crc/machine/fakemachine/client.go +++ b/pkg/crc/machine/fakemachine/client.go @@ -29,6 +29,7 @@ var DummyClusterConfig = types.ClusterConfig{ ClusterCACert: "MIIDODCCAiCgAwIBAgIIRVfCKNUa1wIwDQYJ", KubeConfig: "/tmp/kubeconfig", KubeAdminPass: "foobar", + DeveloperPass: "foobar", ClusterAPI: "https://foo.testing:6443", WebConsoleURL: "https://console.foo.testing:6443", ProxyConfig: nil, diff --git a/pkg/crc/machine/machine.go b/pkg/crc/machine/machine.go index 4e8389f0cc..5c38f64c36 100644 --- a/pkg/crc/machine/machine.go +++ b/pkg/crc/machine/machine.go @@ -21,10 +21,14 @@ func getClusterConfig(bundleInfo *bundle.CrcBundleInfo) (*types.ClusterConfig, e }, nil } - kubeadminPassword, err := cluster.GetKubeadminPassword() + kubeadminPassword, err := cluster.GetUserPassword(constants.GetKubeAdminPasswordPath()) if err != nil { return nil, fmt.Errorf("Error reading kubeadmin password from bundle %v", err) } + developerPassword, err := cluster.GetUserPassword(constants.GetDeveloperPasswordPath()) + if err != nil { + return nil, fmt.Errorf("error reading developer password from bundle %v", err) + } proxyConfig, err := getProxyConfig(bundleInfo) if err != nil { return nil, err @@ -38,6 +42,7 @@ func getClusterConfig(bundleInfo *bundle.CrcBundleInfo) (*types.ClusterConfig, e ClusterCACert: base64.StdEncoding.EncodeToString(clusterCACert), KubeConfig: bundleInfo.GetKubeConfigPath(), KubeAdminPass: kubeadminPassword, + DeveloperPass: developerPassword, WebConsoleURL: fmt.Sprintf("https://%s", bundleInfo.GetAppHostname("console-openshift-console")), ClusterAPI: fmt.Sprintf("https://%s:6443", bundleInfo.GetAPIHostname()), ProxyConfig: proxyConfig, diff --git a/pkg/crc/machine/start.go b/pkg/crc/machine/start.go index 17eacb2c33..5eb8182f67 100644 --- a/pkg/crc/machine/start.go +++ b/pkg/crc/machine/start.go @@ -579,7 +579,7 @@ func (client *client) Start(ctx context.Context, startConfig types.StartConfig) return nil, errors.Wrap(err, "Failed to update pull secret on the disk") } - if err := cluster.UpdateKubeAdminUserPassword(ctx, ocConfig, startConfig.KubeAdminPassword); err != nil { + if err := cluster.UpdateUserPasswords(ctx, ocConfig, startConfig.KubeAdminPassword, startConfig.DeveloperPassword); err != nil { return nil, errors.Wrap(err, "Failed to update kubeadmin user password") } @@ -686,9 +686,12 @@ func createHost(machineConfig config.MachineConfig, preset crcPreset.Preset) err return fmt.Errorf("Error generating ssh key pair: %v", err) } if preset == crcPreset.OpenShift || preset == crcPreset.OKD { - if err := cluster.GenerateKubeAdminUserPassword(); err != nil { + if err := cluster.GenerateUserPassword(constants.GetKubeAdminPasswordPath(), "kubeadmin"); err != nil { return errors.Wrap(err, "Error generating new kubeadmin password") } + if err = os.WriteFile(constants.GetDeveloperPasswordPath(), []byte(constants.DefaultDeveloperPassword), 0600); err != nil { + return errors.Wrap(err, "Error writing developer password") + } } if err := api.SetExists(vm.Name); err != nil { return fmt.Errorf("Failed to record VM existence: %s", err) diff --git a/pkg/crc/machine/types/types.go b/pkg/crc/machine/types/types.go index 3b5e5442ec..1e8c1e48ea 100644 --- a/pkg/crc/machine/types/types.go +++ b/pkg/crc/machine/types/types.go @@ -26,6 +26,9 @@ type StartConfig struct { // User defined kubeadmin password KubeAdminPassword string + // User defined developer password + DeveloperPassword string + // Preset Preset crcpreset.Preset @@ -53,6 +56,7 @@ type ClusterConfig struct { ClusterCACert string KubeConfig string KubeAdminPass string + DeveloperPass string ClusterAPI string WebConsoleURL string ProxyConfig *httpproxy.ProxyConfig diff --git a/test/e2e/features/basic.feature b/test/e2e/features/basic.feature index b728163530..48fc075ad6 100644 --- a/test/e2e/features/basic.feature +++ b/test/e2e/features/basic.feature @@ -31,12 +31,18 @@ Feature: Basic test @darwin @linux @windows @cleanup Scenario: CRC start usecase Given executing "crc setup --check-only" fails + And unsetting config property "developer-password" succeeds # Request start with monitoring stack * setting config property "enable-cluster-monitoring" to value "true" succeeds * setting config property "memory" to value "16000" succeeds Given executing single crc setup command succeeds When starting CRC with default bundle succeeds Then stdout should contain "Started the OpenShift cluster" + And stdout should contain "Log in as administrator:" + And stdout should contain " Username: kubeadmin" + And stdout should contain "Log in as user:" + And stdout should contain " Username: developer" + And stdout should contain " Password: developer" # Check if user can copy-paste login details for developer and kubeadmin users * stdout should match "(?s)(.*)oc login -u developer https:\/\/api\.crc\.testing:6443(.*)$" * stdout should match "(?s)(.*)https:\/\/console-openshift-console\.apps-crc\.testing(.*)$" diff --git a/test/e2e/features/config.feature b/test/e2e/features/config.feature index 2128c4dbf5..2b39a9e03a 100644 --- a/test/e2e/features/config.feature +++ b/test/e2e/features/config.feature @@ -17,27 +17,30 @@ Feature: Test configuration settings # always return to default values @darwin Examples: Config settings on Mac - | property | value1 | value2 | - | cpus | 5 | 3 | - | memory | 10753 | 4096 | - | nameserver | 120.0.0.1 | 999.999.999.999 | - | pull-secret-file | /etc | /nonexistent-file | + | property | value1 | value2 | + | cpus | 5 | 3 | + | memory | 10753 | 4096 | + | nameserver | 120.0.0.1 | 999.999.999.999 | + | pull-secret-file | /etc | /nonexistent-file | + | developer-password | secret1 | | @linux Examples: Config settings on Linux - | property | value1 | value2 | - | cpus | 5 | 3 | - | memory | 10753 | 4096 | - | nameserver | 120.0.0.1 | 999.999.999.999 | - | pull-secret-file | /etc | /nonexistent-file | + | property | value1 | value2 | + | cpus | 5 | 3 | + | memory | 10753 | 4096 | + | nameserver | 120.0.0.1 | 999.999.999.999 | + | pull-secret-file | /etc | /nonexistent-file | + | developer-password | secret1 | | @windows Examples: Config settings on Windows - | property | value1 | value2 | - | cpus | 5 | 3 | - | memory | 10753 | 4096 | - | nameserver | 120.0.0.1 | 999.999.999.999 | - | pull-secret-file | /Users | /nonexistent-file | + | property | value1 | value2 | + | cpus | 5 | 3 | + | memory | 10753 | 4096 | + | nameserver | 120.0.0.1 | 999.999.999.999 | + | pull-secret-file | /Users | /nonexistent-file | + | developer-password | secret1 | | @linux @darwin @windows Scenario: CRC config checks (bundle version) @@ -172,14 +175,14 @@ Feature: Test configuration settings When unsetting config property "preset" succeeds And stdout should contain "Successfully unset configuration property 'preset'" And "JSON" config file "crc.json" in CRC home folder does not contain key "preset" - - @linux @darwin @windows + + @linux @darwin @windows Examples: Config property preset setting positive | preset-value | | microshift | | okd | - Scenario: CRC config set preset (negtive cases) + Scenario: CRC config set preset (negative cases) When setting config property "preset" to value "" fails And stderr should contain "reason: Unknown preset" diff --git a/test/e2e/features/custom_developer_password.feature b/test/e2e/features/custom_developer_password.feature new file mode 100644 index 0000000000..8514a9a11b --- /dev/null +++ b/test/e2e/features/custom_developer_password.feature @@ -0,0 +1,19 @@ +@story_custom_developer_password +Feature: Custom Developer Password Test + + User provides configuration property to override default developer user password + + Background: + Given ensuring CRC cluster is running + + @linux @windows @darwin @cleanup + Scenario: Override default developer password should be reflected during crc start + Given executing "crc stop" succeeds + And setting config property "developer-password" to value "secret-dev" succeeds + When starting CRC with default bundle succeeds + Then stdout should contain "Started the OpenShift cluster" + And stdout should contain "Log in as administrator:" + And stdout should contain " Username: kubeadmin" + And stdout should contain "Log in as user:" + And stdout should contain " Username: developer" + And stdout should contain " Password: secret-dev"