[PATCH v2 3/6] virsh: Expose OpenSSH authorized key file mgmt APIs

Michal Privoznik mprivozn at redhat.com
Mon Nov 16 12:21:00 UTC 2020


The new virsh commands are:

  get-user-sshkeys
  set-user-sshkeys

Signed-off-by: Michal Privoznik <mprivozn at redhat.com>
---
 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




More information about the libvir-list mailing list