Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat : Add support to customize the developer account password (#2359) #4451

Open
wants to merge 3 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion cmd/crc/cmd/console.go
Original file line number Diff line number Diff line change
Expand Up @@ -123,7 +123,7 @@ func toConsoleClusterConfig(result *client.ConsoleResult) *clusterConfig {
},
DeveloperCredentials: credentials{
Username: "developer",
Password: "developer",
Password: result.ClusterConfig.DeveloperPass,
},
}
}
41 changes: 21 additions & 20 deletions cmd/crc/cmd/console_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -60,42 +61,42 @@ 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())
}

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())
}

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())
Expand Down
3 changes: 2 additions & 1 deletion cmd/crc/cmd/start.go
Original file line number Diff line number Diff line change
Expand Up @@ -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(),
Expand Down Expand Up @@ -140,7 +141,7 @@ func toClusterConfig(result *types.StartResult) *clusterConfig {
},
DeveloperCredentials: credentials{
Username: "developer",
Password: "developer",
Password: result.ClusterConfig.DeveloperPass,
},
}
}
Expand Down
8 changes: 4 additions & 4 deletions cmd/crc/cmd/start_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ func TestRenderActionPlainSuccess(t *testing.T) {
},
DeveloperCredentials: credentials{
Username: "developer",
Password: "developer",
Password: "secret",
},
},
}, out, ""))
Expand Down Expand Up @@ -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)
Expand All @@ -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
Expand All @@ -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
Expand Down
1 change: 1 addition & 0 deletions pkg/crc/api/api_client_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
6 changes: 3 additions & 3 deletions pkg/crc/api/api_http_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down
1 change: 1 addition & 0 deletions pkg/crc/api/handlers.go
Original file line number Diff line number Diff line change
Expand Up @@ -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),
Expand Down
69 changes: 42 additions & 27 deletions pkg/crc/cluster/kubeadmin_password.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand All @@ -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)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Did this belong in the previous commit?

if err != nil {
return err
Expand All @@ -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)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same question?

}
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
}
Expand Down Expand Up @@ -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
}
57 changes: 57 additions & 0 deletions pkg/crc/cluster/kubeadmin_password_test.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
package cluster

import (
"os"
"path/filepath"
"testing"

"github.com/stretchr/testify/assert"
Expand Down Expand Up @@ -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)
})
}
}
3 changes: 3 additions & 0 deletions pkg/crc/config/settings.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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,
Expand Down
6 changes: 6 additions & 0 deletions pkg/crc/config/settings_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -200,6 +200,9 @@ var configDefaultValuesTestArguments = []struct {
{
KubeAdminPassword, "",
},
{
DeveloperPassword, "developer",
},
{
CPUs, uint(4),
},
Expand Down Expand Up @@ -286,6 +289,9 @@ var configProvidedValuesTestArguments = []struct {
{
KubeAdminPassword, "kubeadmin-secret-password",
},
{
DeveloperPassword, "developer-secret-password",
},
{
CPUs, uint(8),
},
Expand Down
5 changes: 5 additions & 0 deletions pkg/crc/constants/constants.go
Original file line number Diff line number Diff line change
Expand Up @@ -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`
Expand Down Expand Up @@ -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())
Expand Down
Loading
Loading