Skip to content

Commit

Permalink
virtctl: add command: credentials set-password
Browse files Browse the repository at this point in the history
The command modifies a secret to set the user's password.

Signed-off-by: Andrej Krejcir <akrejcir@redhat.com>
  • Loading branch information
akrejcir committed Jun 6, 2023
1 parent 033e89a commit f4d4cdf
Show file tree
Hide file tree
Showing 7 changed files with 492 additions and 25 deletions.
1 change: 1 addition & 0 deletions pkg/virtctl/credentials/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ go_library(
deps = [
"//pkg/virtctl/credentials/add-key:go_default_library",
"//pkg/virtctl/credentials/remove-key:go_default_library",
"//pkg/virtctl/credentials/set-password:go_default_library",
"//pkg/virtctl/templates:go_default_library",
"//vendor/github.com/spf13/cobra:go_default_library",
"//vendor/k8s.io/client-go/tools/clientcmd:go_default_library",
Expand Down
31 changes: 20 additions & 11 deletions pkg/virtctl/credentials/common/common.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,25 @@ import (
"kubevirt.io/kubevirt/pkg/apimachinery/patch"
)

type CommandFlags struct {
User string
Secret string
}

func (c *CommandFlags) AddToCommand(cmd *cobra.Command) {
const userFlag = "user"
cmd.Flags().StringVarP(&c.User, userFlag, "u", "", "Name of the user.")
err := cmd.MarkFlagRequired(userFlag)
if err != nil {
panic(err)
}

cmd.Flags().StringVar(&c.Secret, "secret", "", "Name of the secret with SSH keys.")
}

type SshCommandFlags struct {
User string
Secret string
CommandFlags

SshPubKeyFile string
SshPubKeyLiteral string
}
Expand All @@ -26,17 +42,10 @@ const (
)

func (s *SshCommandFlags) AddToCommand(cmd *cobra.Command) {
const userFlag = "user"
cmd.Flags().StringVarP(&s.User, userFlag, "u", "", "Name of the user.")
err := cmd.MarkFlagRequired(userFlag)
if err != nil {
panic(err)
}

cmd.Flags().StringVar(&s.Secret, "secret", "", "Name of the secret with SSH keys.")
s.CommandFlags.AddToCommand(cmd)

cmd.Flags().StringVarP(&s.SshPubKeyFile, keyFileFlag, "f", "", "Path to the SSH public key file.")
err = cmd.MarkFlagFilename(keyFileFlag)
err := cmd.MarkFlagFilename(keyFileFlag)
if err != nil {
panic(err)
}
Expand Down
2 changes: 2 additions & 0 deletions pkg/virtctl/credentials/credentials.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import (

add_key "kubevirt.io/kubevirt/pkg/virtctl/credentials/add-key"
remove_key "kubevirt.io/kubevirt/pkg/virtctl/credentials/remove-key"
set_password "kubevirt.io/kubevirt/pkg/virtctl/credentials/set-password"
"kubevirt.io/kubevirt/pkg/virtctl/templates"
)

Expand All @@ -21,6 +22,7 @@ func NewCommand(clientConfig clientcmd.ClientConfig) *cobra.Command {
cmd.AddCommand(
add_key.NewCommand(clientConfig),
remove_key.NewCommand(clientConfig),
set_password.SetPasswordCommand(clientConfig),
)

cmd.SetUsageTemplate(templates.UsageTemplate())
Expand Down
41 changes: 41 additions & 0 deletions pkg/virtctl/credentials/set-password/BUILD.bazel
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
load("@io_bazel_rules_go//go:def.bzl", "go_library", "go_test")

go_library(
name = "go_default_library",
srcs = ["set-password.go"],
importpath = "kubevirt.io/kubevirt/pkg/virtctl/credentials/set-password",
visibility = ["//visibility:public"],
deps = [
"//pkg/virtctl/credentials/common:go_default_library",
"//pkg/virtctl/templates:go_default_library",
"//staging/src/kubevirt.io/api/core/v1:go_default_library",
"//staging/src/kubevirt.io/client-go/kubecli:go_default_library",
"//vendor/github.com/spf13/cobra:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/types:go_default_library",
"//vendor/k8s.io/client-go/tools/clientcmd:go_default_library",
],
)

go_test(
name = "go_default_test",
srcs = ["set-password_test.go"],
deps = [
"//staging/src/kubevirt.io/api/core:go_default_library",
"//staging/src/kubevirt.io/api/core/v1:go_default_library",
"//staging/src/kubevirt.io/client-go/api:go_default_library",
"//staging/src/kubevirt.io/client-go/kubecli:go_default_library",
"//staging/src/kubevirt.io/client-go/testutils:go_default_library",
"//tests/clientcmd:go_default_library",
"//vendor/github.com/golang/mock/gomock:go_default_library",
"//vendor/github.com/onsi/ginkgo/v2:go_default_library",
"//vendor/github.com/onsi/gomega:go_default_library",
"//vendor/k8s.io/api/core/v1:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/api/errors:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/runtime/schema:go_default_library",
"//vendor/k8s.io/client-go/kubernetes:go_default_library",
"//vendor/k8s.io/client-go/kubernetes/fake:go_default_library",
"//vendor/k8s.io/utils/pointer:go_default_library",
],
)
123 changes: 123 additions & 0 deletions pkg/virtctl/credentials/set-password/set-password.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
package set_password

import (
"fmt"

"github.com/spf13/cobra"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/types"
"k8s.io/client-go/tools/clientcmd"
v1 "kubevirt.io/api/core/v1"
"kubevirt.io/client-go/kubecli"

"kubevirt.io/kubevirt/pkg/virtctl/credentials/common"
"kubevirt.io/kubevirt/pkg/virtctl/templates"
)

func SetPasswordCommand(clientConfig clientcmd.ClientConfig) *cobra.Command {
cmdFlags := &passwordCommandFlags{}
cmd := &cobra.Command{
Use: "set-password",
Short: "Set password for a user",
Args: templates.ExactArgs("set-password", 1),
RunE: func(cmd *cobra.Command, args []string) error {
return runSetPasswordCommand(clientConfig, cmdFlags, cmd, args)
},
}
cmdFlags.AddToCommand(cmd)

cmd.SetUsageTemplate(templates.UsageTemplate())
return cmd
}

type passwordCommandFlags struct {
common.CommandFlags

Password string

Force bool
}

func (p *passwordCommandFlags) AddToCommand(cmd *cobra.Command) {
p.CommandFlags.AddToCommand(cmd)

const passwordFlag = "password"
cmd.Flags().StringVarP(&p.Password, passwordFlag, "p", "", "Password for the user")
err := cmd.MarkFlagRequired(passwordFlag)
if err != nil {
panic(err)
}

cmd.Flags().BoolVar(&p.Force, "force", false, "Force update of secret, even if it's not owned by the VM.")
}

func runSetPasswordCommand(clientConfig clientcmd.ClientConfig, cmdFlags *passwordCommandFlags, cmd *cobra.Command, args []string) error {
vmName := args[0]

vmNamespace, _, err := clientConfig.Namespace()
if err != nil {
return fmt.Errorf("error getting namespace: %w", err)
}

cli, err := kubecli.GetKubevirtClientFromClientConfig(clientConfig)
if err != nil {
return fmt.Errorf("error getting kubevirt client: %w", err)
}

vm, err := cli.VirtualMachine(vmNamespace).Get(cmd.Context(), vmName, &metav1.GetOptions{})
if err != nil {
return fmt.Errorf("error getting virtual machine: %w", err)
}

secrets := getPasswordSecrets(vm.Spec.Template.Spec.AccessCredentials)
if len(secrets) == 0 {
return fmt.Errorf("no secrets assigned to UserPassword AccessCredentials")
}

secretName, err := common.FindSecretOrGetFirst(cmdFlags.Secret, secrets)
if err != nil {
return err
}

if !cmdFlags.Force {
secret, err := cli.CoreV1().Secrets(vm.Namespace).Get(cmd.Context(), secretName, metav1.GetOptions{})
if err != nil {
return fmt.Errorf("error getting secret \"%s\": %w", secretName, err)
}

// Check if secret is owned by the VM. This is useful to not accidentally update a secret that is used by multiple VMs.
if !common.IsOwnedByVm(secret, vm) {
return fmt.Errorf("secret %s does not have an owner reference pointing to VM %s", secretName, vm.Name)
}
}

addKeyPatch := common.AddKeyToSecretPatchOp(cmdFlags.User, []byte(cmdFlags.Password))

// Try patch to only add the new key.
_, err = cli.CoreV1().Secrets(vm.Namespace).Patch(cmd.Context(), secretName, types.JSONPatchType, common.MustMarshalPatch(addKeyPatch), metav1.PatchOptions{})
if err != nil {
// If it fails, it probably means that /data field is nil. Try second patch to add /data field.
fullPatch := common.MustMarshalPatch(append(common.AddDataFieldToSecretPatchOp(), addKeyPatch)...)
_, err = cli.CoreV1().Secrets(vmNamespace).Patch(cmd.Context(), secretName, types.JSONPatchType, fullPatch, metav1.PatchOptions{})
if err != nil {
return fmt.Errorf("error patching secret \"%s\": %w", secretName, err)
}
}

cmd.Printf("Successfully set password in secret \"%s\"", secretName)
return nil
}

func getPasswordSecrets(accessCredentials []v1.AccessCredential) []string {
var result []string
for i := range accessCredentials {
credential := &accessCredentials[i]
if credential.UserPassword != nil &&
credential.UserPassword.Source.Secret != nil &&
credential.UserPassword.Source.Secret.SecretName != "" &&
credential.UserPassword.PropagationMethod.QemuGuestAgent != nil {
result = append(result, credential.UserPassword.Source.Secret.SecretName)
}
}
return result
}

0 comments on commit f4d4cdf

Please sign in to comment.