qemu_agent: add qemuAgentSSH{Add,Remove,Get}AuthorizedKeys

In QEMU 5.2, the guest agent learned to manipulate a user
~/.ssh/authorized_keys. Bind the JSON API to libvirt.

https://wiki.qemu.org/ChangeLog/5.2#Guest_agent

Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>
Signed-off-by: Michal Privoznik <mprivozn@redhat.com>
Reviewed-by: Peter Krempa <pkrempa@redhat.com>
This commit is contained in:
Marc-André Lureau 2020-11-07 13:12:53 +04:00 committed by Michal Privoznik
parent 87d12effbe
commit 9770578904
3 changed files with 235 additions and 0 deletions

View File

@ -2496,3 +2496,144 @@ qemuAgentSetResponseTimeout(qemuAgentPtr agent,
{
agent->timeout = timeout;
}
/**
* qemuAgentSSHGetAuthorizedKeys:
* @agent: agent object
* @user: user to get authorized keys for
* @keys: Array of authorized keys
*
* Fetch the public keys from @user's $HOME/.ssh/authorized_keys.
*
* Returns: number of keys returned on success,
* -1 otherwise (error is reported)
*/
int
qemuAgentSSHGetAuthorizedKeys(qemuAgentPtr agent,
const char *user,
char ***keys)
{
g_autoptr(virJSONValue) cmd = NULL;
g_autoptr(virJSONValue) reply = NULL;
virJSONValuePtr data = NULL;
size_t ndata;
size_t i;
char **keys_ret = NULL;
if (!(cmd = qemuAgentMakeCommand("guest-ssh-get-authorized-keys",
"s:username", user,
NULL)))
return -1;
if (qemuAgentCommand(agent, cmd, &reply, agent->timeout) < 0)
return -1;
if (!(data = virJSONValueObjectGetObject(reply, "return")) ||
!(data = virJSONValueObjectGetArray(data, "keys"))) {
virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
_("qemu agent didn't return an array of keys"));
return -1;
}
ndata = virJSONValueArraySize(data);
keys_ret = g_new0(char *, ndata + 1);
for (i = 0; i < ndata; i++) {
virJSONValuePtr entry = virJSONValueArrayGet(data, i);
if (!entry) {
virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
_("array element missing in guest-ssh-get-authorized-keys return value"));
goto error;
}
keys_ret[i] = g_strdup(virJSONValueGetString(entry));
}
*keys = g_steal_pointer(&keys_ret);
return ndata;
error:
virStringListFreeCount(keys_ret, ndata);
return -1;
}
/**
* qemuAgentSSHAddAuthorizedKeys:
* @agent: agent object
* @user: user to add authorized keys for
* @keys: Array of authorized keys
* @nkeys: number of items in @keys array
* @reset: whether to truncate authorized keys file before writing
*
* Append SSH @keys into the @user's authorized keys file. If
* @reset is true then the file is truncated before write and
* thus contains only newly added @keys.
*
* Returns: 0 on success,
* -1 otherwise (error is reported)
*/
int
qemuAgentSSHAddAuthorizedKeys(qemuAgentPtr agent,
const char *user,
const char **keys,
size_t nkeys,
bool reset)
{
g_autoptr(virJSONValue) cmd = NULL;
g_autoptr(virJSONValue) reply = NULL;
g_autoptr(virJSONValue) jkeys = NULL;
jkeys = qemuAgentMakeStringsArray(keys, nkeys);
if (jkeys == NULL)
return -1;
if (!(cmd = qemuAgentMakeCommand("guest-ssh-add-authorized-keys",
"s:username", user,
"a:keys", &jkeys,
"b:reset", reset,
NULL)))
return -1;
return qemuAgentCommand(agent, cmd, &reply, agent->timeout);
}
/**
* qemuAgentSSHRemoveAuthorizedKeys:
* @agent: agent object
* @user: user to remove authorized keys for
* @keys: Array of authorized keys
* @nkeys: number of items in @keys array
*
* Remove SSH @keys from the @user's authorized keys file. It's
* not considered an error when trying to remove a non-existent
* key.
*
* Returns: 0 on success,
* -1 otherwise (error is reported)
*/
int
qemuAgentSSHRemoveAuthorizedKeys(qemuAgentPtr agent,
const char *user,
const char **keys,
size_t nkeys)
{
g_autoptr(virJSONValue) cmd = NULL;
g_autoptr(virJSONValue) reply = NULL;
g_autoptr(virJSONValue) jkeys = NULL;
jkeys = qemuAgentMakeStringsArray(keys, nkeys);
if (jkeys == NULL)
return -1;
if (!(cmd = qemuAgentMakeCommand("guest-ssh-remove-authorized-keys",
"s:username", user,
"a:keys", &jkeys,
NULL)))
return -1;
return qemuAgentCommand(agent, cmd, &reply, agent->timeout);
}

View File

@ -170,3 +170,18 @@ int qemuAgentGetTimezone(qemuAgentPtr mon,
void qemuAgentSetResponseTimeout(qemuAgentPtr mon,
int timeout);
int qemuAgentSSHGetAuthorizedKeys(qemuAgentPtr agent,
const char *user,
char ***keys);
int qemuAgentSSHAddAuthorizedKeys(qemuAgentPtr agent,
const char *user,
const char **keys,
size_t nkeys,
bool reset);
int qemuAgentSSHRemoveAuthorizedKeys(qemuAgentPtr agent,
const char *user,
const char **keys,
size_t nkeys);

View File

@ -35,6 +35,84 @@
virQEMUDriver driver;
static int
testQemuAgentSSHKeys(const void *data)
{
virDomainXMLOptionPtr xmlopt = (virDomainXMLOptionPtr)data;
qemuMonitorTestPtr test = qemuMonitorTestNewAgent(xmlopt);
char **keys = NULL;
int nkeys = 0;
int ret = -1;
if (!test)
return -1;
if (qemuMonitorTestAddAgentSyncResponse(test) < 0)
goto cleanup;
if (qemuMonitorTestAddItem(test, "guest-ssh-get-authorized-keys",
"{\"return\": {"
" \"keys\": ["
" \"algo1 key1 comments1\","
" \"algo2 key2 comments2\""
" ]"
"}}") < 0)
goto cleanup;
if (qemuMonitorTestAddAgentSyncResponse(test) < 0)
goto cleanup;
if (qemuMonitorTestAddItem(test, "guest-ssh-add-authorized-keys",
"{ \"return\" : {} }") < 0)
goto cleanup;
if (qemuMonitorTestAddAgentSyncResponse(test) < 0)
goto cleanup;
if (qemuMonitorTestAddItem(test, "guest-ssh-remove-authorized-keys",
"{ \"return\" : {} }") < 0)
goto cleanup;
if ((nkeys = qemuAgentSSHGetAuthorizedKeys(qemuMonitorTestGetAgent(test),
"user",
&keys)) < 0)
goto cleanup;
if (nkeys != 2) {
virReportError(VIR_ERR_INTERNAL_ERROR,
"expected 2 keys, got %d", nkeys);
ret = -1;
goto cleanup;
}
if (STRNEQ(keys[1], "algo2 key2 comments2")) {
virReportError(VIR_ERR_INTERNAL_ERROR, "Unexpected key returned: %s", keys[1]);
ret = -1;
goto cleanup;
}
if ((ret = qemuAgentSSHAddAuthorizedKeys(qemuMonitorTestGetAgent(test),
"user",
(const char **) keys,
nkeys,
true)) < 0)
goto cleanup;
if ((ret = qemuAgentSSHRemoveAuthorizedKeys(qemuMonitorTestGetAgent(test),
"user",
(const char **) keys,
nkeys)) < 0)
goto cleanup;
ret = 0;
cleanup:
virStringListFreeCount(keys, nkeys);
qemuMonitorTestFree(test);
return ret;
}
static int
testQemuAgentFSFreeze(const void *data)
{
@ -1315,6 +1393,7 @@ mymain(void)
DO_TEST(Users);
DO_TEST(OSInfo);
DO_TEST(Timezone);
DO_TEST(SSHKeys);
DO_TEST(Timeout); /* Timeout should always be called last */