-
Notifications
You must be signed in to change notification settings - Fork 1.3k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
virtctl: add command: credentials set-password
The command modifies a secret to set the user's password. Signed-off-by: Andrej Krejcir <akrejcir@redhat.com>
- Loading branch information
Showing
7 changed files
with
492 additions
and
25 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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", | ||
], | ||
) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 | ||
} |
Oops, something went wrong.