From nobody Tue May 7 20:52:43 2024 Delivered-To: importer@patchew.org Received-SPF: pass (zohomail.com: domain of redhat.com designates 63.128.21.124 as permitted sender) client-ip=63.128.21.124; envelope-from=libvir-list-bounces@redhat.com; helo=us-smtp-delivery-124.mimecast.com; Authentication-Results: mx.zohomail.com; dkim=pass; spf=pass (zohomail.com: domain of redhat.com designates 63.128.21.124 as permitted sender) smtp.mailfrom=libvir-list-bounces@redhat.com; dmarc=pass(p=none dis=none) header.from=redhat.com ARC-Seal: i=1; a=rsa-sha256; t=1604740404; cv=none; d=zohomail.com; s=zohoarc; b=LW+BBK+92xY4jXn8opm13gL2yOCqQ6BUd0MA6xJcP+suRy+A3pRWlcob4zsRC5QvSOy3GHlWIgLY1iNpVDvhjMC0Xm/r+PWG+A2jckDWMKZbrAUavJjU4hErYQCbNEtqY2LM00x3LN3v9PTy/Pt7BOO9bwUT1sVBKit8oMatLR8= ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=zohomail.com; s=zohoarc; t=1604740404; h=Content-Type:Content-Transfer-Encoding:Cc:Date:From:List-Subscribe:List-Post:List-Id:List-Archive:List-Help:List-Unsubscribe:MIME-Version:Message-ID:Sender:Subject:To; bh=qSSiuacM98p4x0z06MhrJbjof2KDMWU2NwKjV1YcwTU=; b=FkE2pY3h88Wdb00brXgPN8vROQbYV8J42lTo3dFSLHfIr3kYpPgVCyO42BZ07ftq2NzegJJhB1vqzwZALgOnfoh2tyt2SSR7PM2wmlz2hP1QTvafg4FY1TOFVL45lWvkya0JZH+177tPjwTKaN2N2p7P1E/HURuuuwKIlpuG67c= ARC-Authentication-Results: i=1; mx.zohomail.com; dkim=pass; spf=pass (zohomail.com: domain of redhat.com designates 63.128.21.124 as permitted sender) smtp.mailfrom=libvir-list-bounces@redhat.com; dmarc=pass header.from= (p=none dis=none) header.from= Return-Path: Received: from us-smtp-delivery-124.mimecast.com (us-smtp-delivery-124.mimecast.com [63.128.21.124]) by mx.zohomail.com with SMTPS id 1604740404286590.2043586281732; Sat, 7 Nov 2020 01:13:24 -0800 (PST) Received: from mimecast-mx01.redhat.com (mimecast-mx01.redhat.com [209.132.183.4]) (Using TLS) by relay.mimecast.com with ESMTP id us-mta-48-kbQUjT6zP7KZdsvKNw2mgg-1; Sat, 07 Nov 2020 04:13:20 -0500 Received: from smtp.corp.redhat.com (int-mx05.intmail.prod.int.phx2.redhat.com [10.5.11.15]) (using TLSv1.2 with cipher AECDH-AES256-SHA (256/256 bits)) (No client certificate requested) by mimecast-mx01.redhat.com (Postfix) with ESMTPS id E13611842166; Sat, 7 Nov 2020 09:13:13 +0000 (UTC) Received: from colo-mx.corp.redhat.com (colo-mx02.intmail.prod.int.phx2.redhat.com [10.5.11.21]) by smtp.corp.redhat.com (Postfix) with ESMTPS id ED6697512B; Sat, 7 Nov 2020 09:13:11 +0000 (UTC) Received: from lists01.pubmisc.prod.ext.phx2.redhat.com (lists01.pubmisc.prod.ext.phx2.redhat.com [10.5.19.33]) by colo-mx.corp.redhat.com (Postfix) with ESMTP id B4C42CF42; Sat, 7 Nov 2020 09:13:05 +0000 (UTC) Received: from smtp.corp.redhat.com (int-mx04.intmail.prod.int.phx2.redhat.com [10.5.11.14]) by lists01.pubmisc.prod.ext.phx2.redhat.com (8.13.8/8.13.8) with ESMTP id 0A79D3kL017051 for ; Sat, 7 Nov 2020 04:13:03 -0500 Received: by smtp.corp.redhat.com (Postfix) id B25465D993; Sat, 7 Nov 2020 09:13:03 +0000 (UTC) Received: from localhost (unknown [10.36.110.8]) by smtp.corp.redhat.com (Postfix) with ESMTP id 731785D9CA; Sat, 7 Nov 2020 09:12:55 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=redhat.com; s=mimecast20190719; t=1604740402; h=from:from:sender:sender:reply-to:subject:subject:date:date: message-id:message-id:to:to:cc:cc:mime-version:mime-version: content-type:content-type: content-transfer-encoding:content-transfer-encoding:list-id:list-help: list-unsubscribe:list-subscribe:list-post; bh=qSSiuacM98p4x0z06MhrJbjof2KDMWU2NwKjV1YcwTU=; b=WCXlphueJJiXU7ddsbnqNHhF/LE4yuV7iUwHYmsOUEgv4AOqo3IXN3tTNxsHfUh3Nc8RNK IgJhZxwzzrou5Ln0HmlkUDtJv78506C8utuytilLI9L/773cZYZbhVGGo7DGZk6YhKshMN KPqw2immdx5b3PsodLs4biB9kGkoAsI= X-MC-Unique: kbQUjT6zP7KZdsvKNw2mgg-1 From: marcandre.lureau@redhat.com To: libvir-list@redhat.com Subject: [libvirt PATCH] qemu: add qemuAgentSSH{Add,Remove,Get}AuthorizedKeys Date: Sat, 7 Nov 2020 13:12:53 +0400 Message-Id: <20201107091253.1381157-1-marcandre.lureau@redhat.com> MIME-Version: 1.0 X-Scanned-By: MIMEDefang 2.79 on 10.5.11.14 X-loop: libvir-list@redhat.com Cc: mprivozn@redhat.com, =?UTF-8?q?Marc-Andr=C3=A9=20Lureau?= X-BeenThere: libvir-list@redhat.com X-Mailman-Version: 2.1.12 Precedence: junk List-Id: Development discussions about the libvirt library & tools List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Sender: libvir-list-bounces@redhat.com Errors-To: libvir-list-bounces@redhat.com X-Scanned-By: MIMEDefang 2.79 on 10.5.11.15 Authentication-Results: relay.mimecast.com; auth=pass smtp.auth=CUSA124A263 smtp.mailfrom=libvir-list-bounces@redhat.com X-Mimecast-Spam-Score: 0 X-Mimecast-Originator: redhat.com Content-Type: text/plain; charset="utf-8" Content-Transfer-Encoding: quoted-printable X-ZohoMail-DKIM: pass (identity @redhat.com) From: Marc-Andr=C3=A9 Lureau 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 Fixes: https://bugzilla.redhat.com/show_bug.cgi?id=3D1888537 Signed-off-by: Marc-Andr=C3=A9 Lureau --- src/qemu/qemu_agent.c | 158 ++++++++++++++++++++++++++++++++++++++++++ src/qemu/qemu_agent.h | 26 +++++++ tests/qemuagenttest.c | 80 +++++++++++++++++++++ 3 files changed, 264 insertions(+) diff --git a/src/qemu/qemu_agent.c b/src/qemu/qemu_agent.c index 7fbb4a9431..75e7fea9e4 100644 --- a/src/qemu/qemu_agent.c +++ b/src/qemu/qemu_agent.c @@ -2496,3 +2496,161 @@ qemuAgentSetResponseTimeout(qemuAgentPtr agent, { agent->timeout =3D timeout; } + +void qemuAgentSSHAuthorizedKeyFree(qemuAgentSSHAuthorizedKeyPtr key) +{ + if (!key) + return; + + g_free(key->key); + g_free(key); +} + +/* Returns: 0 on success + * -2 when agent command is not supported by the agent and + * 'report_unsupported' is false (libvirt error is not reporte= d) + * -1 otherwise (libvirt error is reported) + */ +int qemuAgentSSHGetAuthorizedKeys(qemuAgentPtr agent, + const char *user, + qemuAgentSSHAuthorizedKeyPtr **keys, + bool report_unsupported) +{ + g_autoptr(virJSONValue) cmd =3D NULL; + g_autoptr(virJSONValue) reply =3D NULL; + virJSONValuePtr data =3D NULL; + size_t ndata; + size_t i; + int rc; + qemuAgentSSHAuthorizedKeyPtr *keys_ret =3D NULL; + + if (!(cmd =3D qemuAgentMakeCommand("guest-ssh-get-authorized-keys", + "s:username", user, + NULL))) + return -1; + + if ((rc =3D qemuAgentCommandFull(agent, cmd, &reply, agent->timeout, + report_unsupported)) < 0) + return rc; + + if (!(data =3D virJSONValueObjectGetArray(reply, "return"))) { + virReportError(VIR_ERR_INTERNAL_ERROR, "%s", + _("qemu agent didn't return an array of keys")); + return -1; + } + ndata =3D virJSONValueArraySize(data); + + keys_ret =3D g_new0(qemuAgentSSHAuthorizedKeyPtr, ndata); + + for (i =3D 0; i < ndata; i++) { + virJSONValuePtr entry =3D virJSONValueArrayGet(data, i); + + if (!entry) { + virReportError(VIR_ERR_INTERNAL_ERROR, "%s", + _("array element missing in guest-ssh-get-autho= rized-keys return " + "value")); + goto cleanup; + } + + keys_ret[i] =3D g_new0(qemuAgentSSHAuthorizedKey, 1); + keys_ret[i]->key =3D g_strdup(virJSONValueGetString(entry)); + } + + *keys =3D g_steal_pointer(&keys_ret); + return ndata; + + cleanup: + if (keys_ret) { + for (i =3D 0; i < ndata; i++) + qemuAgentSSHAuthorizedKeyFree(keys_ret[i]); + g_free(keys_ret); + } + return -1; +} + +static virJSONValuePtr +makeJSONArrayFromKeys(qemuAgentSSHAuthorizedKeyPtr *keys, + size_t nkeys) +{ + g_autoptr(virJSONValue) jkeys =3D NULL; + size_t i; + + jkeys =3D virJSONValueNewArray(); + + for (i =3D 0; i < nkeys; i++) { + qemuAgentSSHAuthorizedKeyPtr k =3D keys[i]; + + if (virJSONValueArrayAppendString(jkeys, k->key) < 0) + return NULL; + } + + return g_steal_pointer(&jkeys); +} + +/* Returns: 0 on success + * -2 when agent command is not supported by the agent and + * 'report_unsupported' is false (libvirt error is not reporte= d) + * -1 otherwise (libvirt error is reported) + */ +int qemuAgentSSHAddAuthorizedKeys(qemuAgentPtr agent, + const char *user, + qemuAgentSSHAuthorizedKeyPtr *keys, + size_t nkeys, + bool reset, + bool report_unsupported) +{ + g_autoptr(virJSONValue) cmd =3D NULL; + g_autoptr(virJSONValue) reply =3D NULL; + g_autoptr(virJSONValue) jkeys =3D NULL; + int rc; + + jkeys =3D makeJSONArrayFromKeys(keys, nkeys); + if (jkeys =3D=3D NULL) + return -1; + + if (!(cmd =3D qemuAgentMakeCommand("guest-ssh-add-authorized-keys", + "s:username", user, + "a:keys", &jkeys, + "b:reset", reset, + NULL))) + return -1; + + if ((rc =3D qemuAgentCommandFull(agent, cmd, &reply, agent->timeout, + report_unsupported)) < 0) + return rc; + + return 0; +} + +/* Returns: 0 on success + * -2 when agent command is not supported by the agent and + * 'report_unsupported' is false (libvirt error is not reporte= d) + * -1 otherwise (libvirt error is reported) + */ +int qemuAgentSSHRemoveAuthorizedKeys(qemuAgentPtr agent, + const char *user, + qemuAgentSSHAuthorizedKeyPtr *keys, + size_t nkeys, + bool report_unsupported) +{ + g_autoptr(virJSONValue) cmd =3D NULL; + g_autoptr(virJSONValue) reply =3D NULL; + g_autoptr(virJSONValue) jkeys =3D NULL; + int rc; + + jkeys =3D makeJSONArrayFromKeys(keys, nkeys); + if (jkeys =3D=3D NULL) + return -1; + + if (!(cmd =3D qemuAgentMakeCommand("guest-ssh-remove-authorized-keys", + "s:username", user, + "a:keys", &jkeys, + NULL))) + return -1; + + if ((rc =3D qemuAgentCommandFull(agent, cmd, &reply, agent->timeout, + report_unsupported)) < 0) + return rc; + + return 0; +} diff --git a/src/qemu/qemu_agent.h b/src/qemu/qemu_agent.h index 2eeb376a68..3e70a55c19 100644 --- a/src/qemu/qemu_agent.h +++ b/src/qemu/qemu_agent.h @@ -67,6 +67,12 @@ typedef enum { QEMU_AGENT_SHUTDOWN_LAST, } qemuAgentShutdownMode; =20 +typedef struct _qemuAgentSSHAuthorizedKey qemuAgentSSHAuthorizedKey; +typedef qemuAgentSSHAuthorizedKey *qemuAgentSSHAuthorizedKeyPtr; +struct _qemuAgentSSHAuthorizedKey { + char *key; +}; + typedef struct _qemuAgentDiskInfo qemuAgentDiskInfo; typedef qemuAgentDiskInfo *qemuAgentDiskInfoPtr; struct _qemuAgentDiskInfo { @@ -170,3 +176,23 @@ int qemuAgentGetTimezone(qemuAgentPtr mon, =20 void qemuAgentSetResponseTimeout(qemuAgentPtr mon, int timeout); + +void qemuAgentSSHAuthorizedKeyFree(qemuAgentSSHAuthorizedKeyPtr key); + +int qemuAgentSSHGetAuthorizedKeys(qemuAgentPtr agent, + const char *user, + qemuAgentSSHAuthorizedKeyPtr **keys, + bool report_unsupported); + +int qemuAgentSSHAddAuthorizedKeys(qemuAgentPtr agent, + const char *user, + qemuAgentSSHAuthorizedKeyPtr *keys, + size_t nkeys, + bool reset, + bool report_unsupported); + +int qemuAgentSSHRemoveAuthorizedKeys(qemuAgentPtr agent, + const char *user, + qemuAgentSSHAuthorizedKeyPtr *keys, + size_t nkeys, + bool report_unsupported); diff --git a/tests/qemuagenttest.c b/tests/qemuagenttest.c index 607bd97b5c..eda688850f 100644 --- a/tests/qemuagenttest.c +++ b/tests/qemuagenttest.c @@ -35,6 +35,85 @@ virQEMUDriver driver; =20 =20 +static int +testQemuAgentSSHKeys(const void *data) +{ + virDomainXMLOptionPtr xmlopt =3D (virDomainXMLOptionPtr)data; + qemuMonitorTestPtr test =3D qemuMonitorTestNewAgent(xmlopt); + qemuAgentSSHAuthorizedKeyPtr *keys =3D NULL; + int nkeys =3D 0, i; + int ret =3D -1; + + if (!test) + return -1; + + if (qemuMonitorTestAddAgentSyncResponse(test) < 0) + goto cleanup; + + if (qemuMonitorTestAddItem(test, "guest-ssh-get-authorized-keys", + "{ \"return\" : [\"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 =3D qemuAgentSSHGetAuthorizedKeys(qemuMonitorTestGetAgent(t= est), + "user", + &keys, + true)) < 0) + goto cleanup; + + if (nkeys !=3D 2) { + virReportError(VIR_ERR_INTERNAL_ERROR, + "expected 2 keys, got %d", nkeys); + ret =3D -1; + goto cleanup; + } + + if (STRNEQ(keys[1]->key, "algo2 key2 comments2")) { + virReportError(VIR_ERR_INTERNAL_ERROR, "Unexpected key returned: %= s", keys[1]->key); + ret =3D -1; + goto cleanup; + } + + if ((ret =3D qemuAgentSSHAddAuthorizedKeys(qemuMonitorTestGetAgent(tes= t), + "user", + keys, + nkeys, + true, + true)) < 0) + goto cleanup; + + if ((ret =3D qemuAgentSSHRemoveAuthorizedKeys(qemuMonitorTestGetAgent(= test), + "user", + keys, + nkeys, + true)) < 0) + goto cleanup; + + ret =3D 0; + + cleanup: + for (i =3D 0; i < nkeys; i++) + qemuAgentSSHAuthorizedKeyFree(keys[i]); + VIR_FREE(keys); + qemuMonitorTestFree(test); + return ret; +} + + static int testQemuAgentFSFreeze(const void *data) { @@ -1315,6 +1394,7 @@ mymain(void) DO_TEST(Users); DO_TEST(OSInfo); DO_TEST(Timezone); + DO_TEST(SSHKeys); =20 DO_TEST(Timeout); /* Timeout should always be called last */ =20 --=20 2.29.0