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 May 29, 2023
1 parent 6c2a3b7 commit c745e24
Show file tree
Hide file tree
Showing 3 changed files with 346 additions and 38 deletions.
128 changes: 115 additions & 13 deletions pkg/virtctl/credentials/credentials.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ func NewCommand(clientConfig clientcmd.ClientConfig) *cobra.Command {
cmd.AddCommand(
newAddKeyCommand(clientConfig),
newRemoveKeyCommand(clientConfig),
setPasswordCommand(clientConfig),
)

return cmd
Expand Down Expand Up @@ -100,7 +101,7 @@ func newAddKeyCommand(clientConfig clientcmd.ClientConfig) *cobra.Command {
}

randomFilename := "ssh-key-" + rand.String(6)
patchData := createPatchToAddKeyToSecret(randomFilename, []byte(sshKey))
patchData := patchToAddDataToSecret(randomFilename, []byte(sshKey))

_, err = cli.CoreV1().Secrets(vmNamespace).Patch(cmd.Context(), secretName, types.JSONPatchType, patchData, metav1.PatchOptions{})
if err != nil {
Expand Down Expand Up @@ -181,9 +182,85 @@ func newRemoveKeyCommand(clientConfig clientcmd.ClientConfig) *cobra.Command {
return cmd
}

func setPasswordCommand(clientConfig clientcmd.ClientConfig) *cobra.Command {
cmdFlags := &passwordCommandFlags{}
cmd := &cobra.Command{
Use: "set-password",
Short: "Set password for a user",
RunE: func(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)
}

accessCredentials, err := getAccessCredentialsForVm(cmd.Context(), cli, vmNamespace, vmName)
if err != nil {
return fmt.Errorf("error getting access credentials: %w", err)
}

secrets := getPasswordSecrets(accessCredentials)
if len(secrets) == 0 {
return fmt.Errorf("no secrets assigned to UserPassword AccessCredentials")
}
secretName := cmdFlags.secret
if secretName == "" {
if len(secrets) > 1 {
return fmt.Errorf("multiple secrets specified, use option --secret to specify which secret to modify")
}
secretName = secrets[0]
} else {
var found bool
for _, secret := range secrets {
if secretName == secret {
found = true
break
}
}
if !found {
return fmt.Errorf("secret %s is not defined as a source for passwords", cmdFlags.secret)
}
}

patchData := patchToAddDataToSecret(cmdFlags.user, []byte(cmdFlags.password))
_, err = cli.CoreV1().Secrets(vmNamespace).Patch(cmd.Context(), secretName, types.JSONPatchType, patchData, 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
},
}
cmdFlags.AddToCommand(cmd)

return cmd
}

type commandFlags struct {
user string
secret string
}

func (c *commandFlags) AddToCommand(cmd *cobra.Command) {
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 @@ -192,19 +269,14 @@ const (
userFlag = "user"
keyFileFlag = "file"
keyValueFlag = "value"
passwordFlag = "password"
)

func (s *sshCommandFlags) AddToCommand(cmd *cobra.Command) {
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 All @@ -213,6 +285,22 @@ func (s *sshCommandFlags) AddToCommand(cmd *cobra.Command) {
cmd.MarkFlagsMutuallyExclusive(keyFileFlag, keyValueFlag)
}

type passwordCommandFlags struct {
commandFlags

password string
}

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

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

func getSshKey(flags *sshCommandFlags) (string, error) {
if flags.sshPubKeyLiteral != "" {
return flags.sshPubKeyLiteral, nil
Expand Down Expand Up @@ -273,6 +361,20 @@ func getSshSecretsForUser(accessCredentials []v1.AccessCredential, user string)
return result
}

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
}

func secretContainsKey(secretData map[string][]byte, key string) bool {
for _, data := range secretData {
lines := strings.Split(string(data), "\n")
Expand All @@ -285,12 +387,12 @@ func secretContainsKey(secretData map[string][]byte, key string) bool {
return false
}

func createPatchToAddKeyToSecret(keyName string, key []byte) []byte {
func patchToAddDataToSecret(key string, value []byte) []byte {
payload, err := patch.GeneratePatchPayload(
patch.PatchOperation{
Op: patch.PatchAddOp,
Path: "/data",
Value: map[string][]byte{keyName: key},
Value: map[string][]byte{key: value},
},
)
if err != nil {
Expand Down

0 comments on commit c745e24

Please sign in to comment.