Skip to content

Commit

Permalink
feat : Write developer password to file just like kubeadmin password (#…
Browse files Browse the repository at this point in the history
…2539)

Signed-off-by: Rohan Kumar <[email protected]>
  • Loading branch information
rohanKanojia committed Dec 20, 2024
1 parent 130a608 commit c2dd806
Show file tree
Hide file tree
Showing 13 changed files with 147 additions and 59 deletions.
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
2 changes: 1 addition & 1 deletion cmd/crc/cmd/start.go
Original file line number Diff line number Diff line change
Expand Up @@ -140,7 +140,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
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)
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)
}
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)
})
}
}
4 changes: 4 additions & 0 deletions pkg/crc/constants/constants.go
Original file line number Diff line number Diff line change
Expand Up @@ -194,6 +194,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
1 change: 1 addition & 0 deletions pkg/crc/machine/fakemachine/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
7 changes: 6 additions & 1 deletion pkg/crc/machine/machine.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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,
Expand Down
7 changes: 5 additions & 2 deletions pkg/crc/machine/start.go
Original file line number Diff line number Diff line change
Expand Up @@ -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")
}

Expand Down Expand Up @@ -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)
Expand Down
Loading

0 comments on commit c2dd806

Please sign in to comment.