The new virsh commands are: get-user-sshkeys set-user-sshkeys Signed-off-by: Michal Privoznik <mprivozn@xxxxxxxxxx> --- docs/manpages/virsh.rst | 37 +++++++++ tools/virsh-domain.c | 167 ++++++++++++++++++++++++++++++++++++++++ 2 files changed, 204 insertions(+) diff --git a/docs/manpages/virsh.rst b/docs/manpages/virsh.rst index bfd26e3120..18cee358fd 100644 --- a/docs/manpages/virsh.rst +++ b/docs/manpages/virsh.rst @@ -2636,6 +2636,21 @@ When *--timestamp* is used, a human-readable timestamp will be printed before the event. +get-user-sshkeys +---------------- + +**Syntax:** + +:: + + get-user-sshkeys domain user + +Print SSH authorized keys for given *user* in the guest *domain*. Please note, +that an entry in the file has internal structure as defined by *sshd(8)* and +virsh/libvirt does handle keys as opaque strings, i.e. does not interpret +them. + + guest-agent-timeout ------------------- @@ -4004,6 +4019,28 @@ For QEMU/KVM, this requires the guest agent to be configured and running. +set-user-sshkeys +---------------- + +**Syntax:** + +:: + + set-user-sshkeys domain user [--file FILE] [{--append | --remove}] + +Replace the contents of *user*'s SSH authorized keys file in the guest *domain* +with keys read from optional *FILE*. In the *FILE* keys must be on separate +lines and each line must follow authorized keys format as defined by *sshd(8)*. +If no *FILE* is specified then the guest authorized keys file is cleared out. + +If *--append* is specified, then the guest authorized keys file content is not +replaced and new keys from *FILE* are just appended. + +If *--remove* is specified, then instead of adding any new keys then keys read +from *FILE* are removed from the authorized keys file. It is not considered an +error if the key does not exist in the file. + + setmaxmem --------- diff --git a/tools/virsh-domain.c b/tools/virsh-domain.c index 12b35c037d..2a775fd277 100644 --- a/tools/virsh-domain.c +++ b/tools/virsh-domain.c @@ -14263,6 +14263,161 @@ cmdGuestInfo(vshControl *ctl, const vshCmd *cmd) return ret; } +/* + * "get-user-sshkeys" command + */ +static const vshCmdInfo info_get_user_sshkeys[] = { + {.name = "help", + .data = N_("list authorized SSH keys for given user (via agent)") + }, + {.name = "desc", + .data = N_("Use the guest agent to query authorized SSH keys for given " + "user") + }, + {.name = NULL} +}; + +static const vshCmdOptDef opts_get_user_sshkeys[] = { + VIRSH_COMMON_OPT_DOMAIN_FULL(VIR_CONNECT_LIST_DOMAINS_ACTIVE), + {.name = "user", + .type = VSH_OT_DATA, + .flags = VSH_OFLAG_REQ, + .help = N_("user to list authorized keys for"), + }, + {.name = NULL} +}; + +static bool +cmdGetUserSSHKeys(vshControl *ctl, const vshCmd *cmd) +{ + virDomainPtr dom = NULL; + const char *user; + char **keys = NULL; + int nkeys = 0; + size_t i; + const unsigned int flags = 0; + vshTablePtr table = NULL; + bool ret = false; + + if (!(dom = virshCommandOptDomain(ctl, cmd, NULL))) + return false; + + if (vshCommandOptStringReq(ctl, cmd, "user", &user) < 0) + goto cleanup; + + nkeys = virDomainAuthorizedSSHKeysGet(dom, user, &keys, flags); + if (nkeys < 0) + goto cleanup; + + if (!(table = vshTableNew(_("key"), NULL))) + goto cleanup; + + for (i = 0; i < nkeys; i++) { + if (vshTableRowAppend(table, keys[i], NULL) < 0) + goto cleanup; + } + + vshTablePrintToStdout(table, ctl); + + ret = true; + cleanup: + vshTableFree(table); + if (nkeys > 0) + virStringListFreeCount(keys, nkeys); + virshDomainFree(dom); + return ret; +} + + +/* + * "set-user-sshkeys" command + */ +static const vshCmdInfo info_set_user_sshkeys[] = { + {.name = "help", + .data = N_("manipulate authorized SSH keys file for given user (via agent)") + }, + {.name = "desc", + .data = N_("Append, reset or remove specified key from the authorized " + "keys file for given user") + }, + {.name = NULL} +}; + +static const vshCmdOptDef opts_set_user_sshkeys[] = { + VIRSH_COMMON_OPT_DOMAIN_FULL(VIR_CONNECT_LIST_DOMAINS_ACTIVE), + {.name = "user", + .type = VSH_OT_DATA, + .flags = VSH_OFLAG_REQ, + .help = N_("user to set authorized keys for"), + }, + {.name = "file", + .type = VSH_OT_STRING, + .help = N_("optional file to read keys from"), + }, + {.name = "append", + .type = VSH_OT_BOOL, + .help = N_("append keys to the authorized keys file"), + }, + {.name = "remove", + .type = VSH_OT_BOOL, + .help = N_("remove keys from the authorized keys file"), + }, + {.name = NULL} +}; + +static bool +cmdSetUserSSHKeys(vshControl *ctl, const vshCmd *cmd) +{ + virDomainPtr dom = NULL; + const char *user; + const char *from; + g_autofree char *buffer = NULL; + VIR_AUTOSTRINGLIST keys = NULL; + int nkeys = 0; + unsigned int flags = 0; + bool ret = false; + + VSH_REQUIRE_OPTION("append", "file"); + VSH_REQUIRE_OPTION("remove", "file"); + + if (!(dom = virshCommandOptDomain(ctl, cmd, NULL))) + return false; + + if (vshCommandOptStringReq(ctl, cmd, "user", &user) < 0) + goto cleanup; + + if (vshCommandOptStringReq(ctl, cmd, "file", &from) < 0) + goto cleanup; + + if (vshCommandOptBool(cmd, "append")) + flags |= VIR_DOMAIN_AUTHORIZED_SSH_KEYS_SET_APPEND; + if (vshCommandOptBool(cmd, "remove")) + flags |= VIR_DOMAIN_AUTHORIZED_SSH_KEYS_SET_REMOVE; + + if (from) { + if (virFileReadAll(from, VSH_MAX_XML_FILE, &buffer) < 0) { + vshReportError(ctl); + goto cleanup; + } + + if (!(keys = virStringSplit(buffer, "\n", -1))) + goto cleanup; + + nkeys = virStringListLength((const char **) keys); + } + + if (virDomainAuthorizedSSHKeysSet(dom, user, + (const char **) keys, nkeys, flags) < 0) { + goto cleanup; + } + + ret = true; + cleanup: + virshDomainFree(dom); + return ret; +} + + const vshCmdDef domManagementCmds[] = { {.name = "attach-device", .handler = cmdAttachDevice, @@ -14530,6 +14685,12 @@ const vshCmdDef domManagementCmds[] = { .info = info_event, .flags = 0 }, + {.name = "get-user-sshkeys", + .handler = cmdGetUserSSHKeys, + .opts = opts_get_user_sshkeys, + .info = info_get_user_sshkeys, + .flags = 0 + }, {.name = "inject-nmi", .handler = cmdInjectNMI, .opts = opts_inject_nmi, @@ -14776,6 +14937,12 @@ const vshCmdDef domManagementCmds[] = { .info = info_setLifecycleAction, .flags = 0 }, + {.name = "set-user-sshkeys", + .handler = cmdSetUserSSHKeys, + .opts = opts_set_user_sshkeys, + .info = info_set_user_sshkeys, + .flags = 0 + }, {.name = "set-user-password", .handler = cmdSetUserPassword, .opts = opts_set_user_password, -- 2.26.2