virsh: Expose OpenSSH authorized key file mgmt APIs

The new virsh commands are:

  get-user-sshkeys
  set-user-sshkeys

Signed-off-by: Michal Privoznik <mprivozn@redhat.com>
Reviewed-by: Peter Krempa <pkrempa@redhat.com>
This commit is contained in:
Michal Privoznik 2020-11-10 09:25:27 +01:00
parent 40c35dfa1f
commit 87d12effbe
2 changed files with 202 additions and 0 deletions

View File

@ -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,29 @@ For QEMU/KVM, this requires the guest agent to be configured
and running.
set-user-sshkeys
----------------
**Syntax:**
::
set-user-sshkeys domain user [--file FILE] [{--reset | --remove}]
Append keys read from *FILE* into *user*'s SSH authorized keys file in the
guest *domain*. In the *FILE* keys must be on separate lines and each line
must follow authorized keys format as defined by *sshd(8)*.
If *--reset* is specified, then the guest authorized keys file content is
removed before appending new keys. As a special case, if *--reset* is provided
and no *FILE* was provided then no new keys are added and the authorized keys
file is cleared out.
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
---------

View File

@ -14263,6 +14263,158 @@ 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;
VIR_AUTOSTRINGLIST keys = NULL;
int nkeys = 0;
size_t i;
const unsigned int flags = 0;
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;
for (i = 0; i < nkeys; i++) {
vshPrint(ctl, "%s", keys[i]);
}
ret = true;
cleanup:
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 = "reset",
.type = VSH_OT_BOOL,
.help = N_("clear out authorized keys file before adding new keys"),
},
{.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("remove", "file");
VSH_EXCLUSIVE_OPTIONS("reset", "remove");
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, "reset")) {
flags |= VIR_DOMAIN_AUTHORIZED_SSH_KEYS_SET_APPEND;
if (!from) {
vshError(ctl, _("Option --file is required"));
goto cleanup;
}
}
if (vshCommandOptBool(cmd, "remove"))
flags |= VIR_DOMAIN_AUTHORIZED_SSH_KEYS_SET_REMOVE;
if (from) {
if (virFileReadAll(from, VSH_MAX_XML_FILE, &buffer) < 0) {
vshSaveLibvirtError();
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 +14682,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 +14934,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,