Skip to content

Commit

Permalink
feat: add admin-console reset-password command (#1345)
Browse files Browse the repository at this point in the history
* feat: add admin-console reset-password command

add a new command to reset the admin console password.

* chore: add step to reset admin console password

adds a step to our single node installation test to reset the admin
console password and to login once more.
  • Loading branch information
ricardomaraschini authored Oct 21, 2024
1 parent dc8f153 commit a581c4f
Show file tree
Hide file tree
Showing 9 changed files with 112 additions and 2 deletions.
56 changes: 56 additions & 0 deletions cmd/embedded-cluster/admin_console.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
package main

import (
"fmt"
"os"

"github.com/sirupsen/logrus"
"github.com/urfave/cli/v2"

"github.com/replicatedhq/embedded-cluster/pkg/defaults"
"github.com/replicatedhq/embedded-cluster/pkg/kotscli"
)

func adminConsoleCommand() *cli.Command {
return &cli.Command{
Name: "admin-console",
Usage: fmt.Sprintf("Manage the %s Admin Console", defaults.BinaryName()),
Subcommands: []*cli.Command{
adminConsoleResetPassswordCommand(),
},
}
}

func adminConsoleResetPassswordCommand() *cli.Command {
return &cli.Command{
Name: "reset-password",
Usage: "Reset the Admin Console password",
Before: func(c *cli.Context) error {
if os.Getuid() != 0 {
return fmt.Errorf("reset-password command must be run as root")
}
if len(c.Args().Slice()) != 1 {
return fmt.Errorf("expected admin console password as argument")
}
return nil
},
Action: func(c *cli.Context) error {
provider, err := getProviderFromCluster(c.Context)
if err != nil {
return err
}

password := c.Args().Get(0)
if !validateAdminConsolePassword(password, password) {
return ErrNothingElseToAdd
}

if err := kotscli.ResetPassword(provider, password); err != nil {
return err
}

logrus.Info("Admin Console password reset successfully")
return nil
},
}
}
7 changes: 7 additions & 0 deletions cmd/embedded-cluster/k0s.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,12 @@ package main
import (
"context"
"encoding/json"
"fmt"
"os"
"os/exec"

k0sv1beta1 "github.com/k0sproject/k0s/pkg/apis/k0s/v1beta1"
"github.com/replicatedhq/embedded-cluster/pkg/defaults"
)

var (
Expand All @@ -27,6 +30,10 @@ type k0sVars struct {

// getK0sStatus returns the status of the k0s service.
func getK0sStatus(ctx context.Context) (*k0sStatus, error) {
if _, err := os.Stat(k0s); err != nil {
return nil, fmt.Errorf("%s does not seem to be installed on this node", defaults.BinaryName())
}

// get k0s status json
out, err := exec.CommandContext(ctx, k0s, "status", "-o", "json").Output()
if err != nil {
Expand Down
1 change: 1 addition & 0 deletions cmd/embedded-cluster/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ func main() {
materializeCommand(),
updateCommand(),
restoreCommand(),
adminConsoleCommand(),
},
}
if err := app.RunContext(ctx, os.Args); err != nil {
Expand Down
10 changes: 10 additions & 0 deletions e2e/install_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,16 @@ func TestSingleNodeInstallation(t *testing.T) {
t.Fatalf("fail to check postupgrade state: %v: %s: %s", err, stdout, stderr)
}

t.Logf("%s: resetting admin console password", time.Now().Format(time.RFC3339))
newPassword := "newpass"
line = []string{"embedded-cluster", "admin-console", "reset-password", newPassword}
_, _, err := tc.RunCommandOnNode(0, line)
require.NoError(t, err, "unable to reset admin console password")

t.Logf("%s: logging in with the new password", time.Now().Format(time.RFC3339))
_, _, err = tc.RunPlaywrightTest("login-with-custom-password", newPassword)
require.NoError(t, err, "unable to login with the new password")

t.Logf("%s: test complete", time.Now().Format(time.RFC3339))
}

Expand Down
8 changes: 8 additions & 0 deletions e2e/playwright/tests/login-with-custom-password/test.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import { test, expect } from '@playwright/test';
import { login } from '../shared';

test('login with custom password', async ({ page }) => {
test.setTimeout(30 * 1000); // 30 seconds
await login(page, process.env.ADMIN_CONSOLE_PASSWORD);
await expect(page.locator('.NavItem').getByText('Cluster Management')).toBeVisible();
});
4 changes: 2 additions & 2 deletions e2e/playwright/tests/shared/login.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
export const login = async (page) => {
export const login = async (page, password = 'password') => {
await page.goto('/');
await page.getByPlaceholder('password').click();
await page.getByPlaceholder('password').fill('password');
await page.getByPlaceholder('password').fill(password);
await page.getByRole('button', { name: 'Log in' }).click();
};
2 changes: 2 additions & 0 deletions e2e/scripts/playwright.sh
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@ main() {
elif [ "$test_name" == "deploy-upgrade" ]; then
export APP_UPGRADE_VERSION="$2"
export SKIP_CLUSTER_UPGRADE_CHECK="${3:-}"
elif [ "$test_name" == "login-with-custom-password" ]; then
export ADMIN_CONSOLE_PASSWORD="$2"
fi

export BASE_URL="${BASE_URL:-http://10.0.0.2:30003}"
Expand Down
5 changes: 5 additions & 0 deletions pkg/helpers/command.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@ type RunCommandOptions struct {
Writer io.Writer
// Env is a map of additional environment variables to set for the command.
Env map[string]string
// Stdin is the standard input to be used when running the command.
Stdin io.Reader
}

// RunCommandWithOptions runs a the provided command with the options specified.
Expand All @@ -28,6 +30,9 @@ func RunCommandWithOptions(opts RunCommandOptions, bin string, args ...string) e
if opts.Writer != nil {
cmd.Stdout = io.MultiWriter(opts.Writer, stdout)
}
if opts.Stdin != nil {
cmd.Stdin = opts.Stdin
}
cmd.Stderr = stderr
cmdEnv := cmd.Environ()
for k, v := range opts.Env {
Expand Down
21 changes: 21 additions & 0 deletions pkg/kotscli/kotscli.go
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,27 @@ func Install(provider *defaults.Provider, opts InstallOptions, msg *spinner.Mess
return nil
}

func ResetPassword(provider *defaults.Provider, password string) error {
materializer := goods.NewMaterializer(provider)
kotsBinPath, err := materializer.InternalBinary("kubectl-kots")
if err != nil {
return fmt.Errorf("unable to materialize kubectl-kots binary: %w", err)
}
defer os.Remove(kotsBinPath)

runCommandOptions := helpers.RunCommandOptions{
Env: map[string]string{"KUBECONFIG": provider.PathToKubeConfig()},
Stdin: strings.NewReader(fmt.Sprintf("%s\n", password)),
}

resetArgs := []string{"reset-password", "kotsadm"}
if err := helpers.RunCommandWithOptions(runCommandOptions, kotsBinPath, resetArgs...); err != nil {
return fmt.Errorf("unable to reset admin console password: %w", err)
}

return nil
}

type AirgapUpdateOptions struct {
AppSlug string
Namespace string
Expand Down

0 comments on commit a581c4f

Please sign in to comment.