From nobody Sun Feb 8 19:48:58 2026 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=1605706497; cv=none; d=zohomail.com; s=zohoarc; b=jDDNdcWs9EPJOMyyBmRlsOolc+kXdiggte2gfgkFTuewwRZ4Y7jYFldv6Z8bqWakSRlY/UFdJQ9P8V0WyNzTyEZpKb7iJGbyk9W18IAWOOkl5QfCd4Zp5DKB3UmczuZ7kxuHiqmwAcXEZvEcs7yW37Ou3xhK5xuvcPL0H4/y8oo= ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=zohomail.com; s=zohoarc; t=1605706497; h=Content-Type:Content-Transfer-Encoding:Date:From:In-Reply-To:List-Subscribe:List-Post:List-Id:List-Archive:List-Help:List-Unsubscribe:MIME-Version:Message-ID:References:Sender:Subject:To; bh=NcxR1+B84Nl1wY3TeI6zVS7vlkrL4ofK9//EWzuywvE=; b=BAlDf8wJIyFEg+ML5oHeJDL9CLyDMJ/6+IK2UDeqUmagO99x1cVIm3Ti0qdNhDwmM+CObMvc4OWJMU2TQEjR8sx2EVvau/qwcoMfkld8kLOOd16aCl38Ddez3w56ZqVlijWWk7R73qS3mvTYuqkl7nBgNPRPhR4QHqbDRlE6CHA= 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 1605706497341824.6551771836199; Wed, 18 Nov 2020 05:34:57 -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-476-khBae3v3PSCvnxRjA0vuFQ-1; Wed, 18 Nov 2020 08:34:53 -0500 Received: from smtp.corp.redhat.com (int-mx03.intmail.prod.int.phx2.redhat.com [10.5.11.13]) (using TLSv1.2 with cipher AECDH-AES256-SHA (256/256 bits)) (No client certificate requested) by mimecast-mx01.redhat.com (Postfix) with ESMTPS id 786811028D53; Wed, 18 Nov 2020 13:34:47 +0000 (UTC) Received: from colo-mx.corp.redhat.com (colo-mx01.intmail.prod.int.phx2.redhat.com [10.5.11.20]) by smtp.corp.redhat.com (Postfix) with ESMTPS id 539756EF4F; Wed, 18 Nov 2020 13:34:47 +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 178AD183D025; Wed, 18 Nov 2020 13:34:47 +0000 (UTC) Received: from smtp.corp.redhat.com (int-mx03.intmail.prod.int.phx2.redhat.com [10.5.11.13]) by lists01.pubmisc.prod.ext.phx2.redhat.com (8.13.8/8.13.8) with ESMTP id 0AIDYbZg003930 for ; Wed, 18 Nov 2020 08:34:37 -0500 Received: by smtp.corp.redhat.com (Postfix) id 5ABB96EF4F; Wed, 18 Nov 2020 13:34:37 +0000 (UTC) Received: from localhost.localdomain (unknown [10.40.194.11]) by smtp.corp.redhat.com (Postfix) with ESMTP id C678673667 for ; Wed, 18 Nov 2020 13:34:36 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=redhat.com; s=mimecast20190719; t=1605706495; h=from:from:sender:sender:reply-to:subject:subject:date:date: message-id:message-id:to:to:cc:mime-version:mime-version: content-type:content-type: content-transfer-encoding:content-transfer-encoding: in-reply-to:in-reply-to:references:references:list-id:list-help: list-unsubscribe:list-subscribe:list-post; bh=NcxR1+B84Nl1wY3TeI6zVS7vlkrL4ofK9//EWzuywvE=; b=VVOt4xFvXio/o6FnnrnjPonDu1JLp+Hh5afnojW8Ci91J0LIk7UwezbS8AG4Ue21sz6EtH Te7wQwBWf+8PB75pq7isuDj196zqdAR0VFMDiiqtxJnPA1mwUAqrRx5nrUP41UTUwrNeK+ uzk88npDDhHAMlFhEn9CydgxZWGygME= X-MC-Unique: khBae3v3PSCvnxRjA0vuFQ-1 From: Michal Privoznik To: libvir-list@redhat.com Subject: [PATCH v3 3/6] virsh: Expose OpenSSH authorized key file mgmt APIs Date: Wed, 18 Nov 2020 14:34:21 +0100 Message-Id: In-Reply-To: References: MIME-Version: 1.0 X-Scanned-By: MIMEDefang 2.79 on 10.5.11.13 X-loop: libvir-list@redhat.com 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.13 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-Transfer-Encoding: quoted-printable X-ZohoMail-DKIM: pass (identity @redhat.com) Content-Type: text/plain; charset="utf-8" The new virsh commands are: get-user-sshkeys set-user-sshkeys Signed-off-by: Michal Privoznik Reviewed-by: Peter Krempa --- docs/manpages/virsh.rst | 38 ++++++++++ tools/virsh-domain.c | 164 ++++++++++++++++++++++++++++++++++++++++ 2 files changed, 202 insertions(+) diff --git a/docs/manpages/virsh.rst b/docs/manpages/virsh.rst index bfd26e3120..543f62d429 100644 --- a/docs/manpages/virsh.rst +++ b/docs/manpages/virsh.rst @@ -2636,6 +2636,21 @@ When *--timestamp* is used, a human-readable timesta= mp will be printed before the event. =20 =20 +get-user-sshkeys +---------------- + +**Syntax:** + +:: + + get-user-sshkeys domain user + +Print SSH authorized keys for given *user* in the guest *domain*. Please n= ote, +that an entry in the file has internal structure as defined by *sshd(8)* a= nd +virsh/libvirt does handle keys as opaque strings, i.e. does not interpret +them. + + guest-agent-timeout ------------------- =20 @@ -4004,6 +4019,29 @@ For QEMU/KVM, this requires the guest agent to be co= nfigured and running. =20 =20 +set-user-sshkeys +---------------- + +**Syntax:** + +:: + + set-user-sshkeys domain user [--file FILE] [{--reset | --remove}] + +Append keys read from *FILE* into *user*'sSSH 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 prov= ided +and no *FILE* was provided then no new keys are added and the authorized k= eys +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 considere= d an +error if the key does not exist in the file. + + setmaxmem --------- =20 diff --git a/tools/virsh-domain.c b/tools/virsh-domain.c index 12b35c037d..c999458d72 100644 --- a/tools/virsh-domain.c +++ b/tools/virsh-domain.c @@ -14263,6 +14263,158 @@ cmdGuestInfo(vshControl *ctl, const vshCmd *cmd) return ret; } =20 +/* + * "get-user-sshkeys" command + */ +static const vshCmdInfo info_get_user_sshkeys[] =3D { + {.name =3D "help", + .data =3D N_("list authorized SSH keys for given user (via agent)") + }, + {.name =3D "desc", + .data =3D N_("Use the guest agent to query authorized SSH keys for gi= ven " + "user") + }, + {.name =3D NULL} +}; + +static const vshCmdOptDef opts_get_user_sshkeys[] =3D { + VIRSH_COMMON_OPT_DOMAIN_FULL(VIR_CONNECT_LIST_DOMAINS_ACTIVE), + {.name =3D "user", + .type =3D VSH_OT_DATA, + .flags =3D VSH_OFLAG_REQ, + .help =3D N_("user to list authorized keys for"), + }, + {.name =3D NULL} +}; + +static bool +cmdGetUserSSHKeys(vshControl *ctl, const vshCmd *cmd) +{ + virDomainPtr dom =3D NULL; + const char *user; + VIR_AUTOSTRINGLIST keys =3D NULL; + int nkeys =3D 0; + size_t i; + const unsigned int flags =3D 0; + bool ret =3D false; + + if (!(dom =3D virshCommandOptDomain(ctl, cmd, NULL))) + return false; + + if (vshCommandOptStringReq(ctl, cmd, "user", &user) < 0) + goto cleanup; + + nkeys =3D virDomainAuthorizedSSHKeysGet(dom, user, &keys, flags); + if (nkeys < 0) + goto cleanup; + + for (i =3D 0; i < nkeys; i++) { + vshPrint(ctl, "%s", keys[i]); + } + + ret =3D true; + cleanup: + virshDomainFree(dom); + return ret; +} + + +/* + * "set-user-sshkeys" command + */ +static const vshCmdInfo info_set_user_sshkeys[] =3D { + {.name =3D "help", + .data =3D N_("manipulate authorized SSH keys file for given user (via= agent)") + }, + {.name =3D "desc", + .data =3D N_("Append, reset or remove specified key from the authoriz= ed " + "keys file for given user") + }, + {.name =3D NULL} +}; + +static const vshCmdOptDef opts_set_user_sshkeys[] =3D { + VIRSH_COMMON_OPT_DOMAIN_FULL(VIR_CONNECT_LIST_DOMAINS_ACTIVE), + {.name =3D "user", + .type =3D VSH_OT_DATA, + .flags =3D VSH_OFLAG_REQ, + .help =3D N_("user to set authorized keys for"), + }, + {.name =3D "file", + .type =3D VSH_OT_STRING, + .help =3D N_("optional file to read keys from"), + }, + {.name =3D "reset", + .type =3D VSH_OT_BOOL, + .help =3D N_("clear out authorized keys file before adding new keys"), + }, + {.name =3D "remove", + .type =3D VSH_OT_BOOL, + .help =3D N_("remove keys from the authorized keys file"), + }, + {.name =3D NULL} +}; + +static bool +cmdSetUserSSHKeys(vshControl *ctl, const vshCmd *cmd) +{ + virDomainPtr dom =3D NULL; + const char *user; + const char *from; + g_autofree char *buffer =3D NULL; + VIR_AUTOSTRINGLIST keys =3D NULL; + int nkeys =3D 0; + unsigned int flags =3D 0; + bool ret =3D false; + + VSH_REQUIRE_OPTION("remove", "file"); + VSH_EXCLUSIVE_OPTIONS("reset", "remove"); + + if (!(dom =3D 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 |=3D VIR_DOMAIN_AUTHORIZED_SSH_KEYS_SET_APPEND; + + if (!from) { + vshError(ctl, _("Option --file is required")); + goto cleanup; + } + } + + if (vshCommandOptBool(cmd, "remove")) + flags |=3D VIR_DOMAIN_AUTHORIZED_SSH_KEYS_SET_REMOVE; + + if (from) { + if (virFileReadAll(from, VSH_MAX_XML_FILE, &buffer) < 0) { + vshSaveLibvirtError(); + goto cleanup; + } + + if (!(keys =3D virStringSplit(buffer, "\n", -1))) + goto cleanup; + + nkeys =3D virStringListLength((const char **) keys); + } + + if (virDomainAuthorizedSSHKeysSet(dom, user, + (const char **) keys, nkeys, flags) = < 0) { + goto cleanup; + } + + ret =3D true; + cleanup: + virshDomainFree(dom); + return ret; +} + + const vshCmdDef domManagementCmds[] =3D { {.name =3D "attach-device", .handler =3D cmdAttachDevice, @@ -14530,6 +14682,12 @@ const vshCmdDef domManagementCmds[] =3D { .info =3D info_event, .flags =3D 0 }, + {.name =3D "get-user-sshkeys", + .handler =3D cmdGetUserSSHKeys, + .opts =3D opts_get_user_sshkeys, + .info =3D info_get_user_sshkeys, + .flags =3D 0 + }, {.name =3D "inject-nmi", .handler =3D cmdInjectNMI, .opts =3D opts_inject_nmi, @@ -14776,6 +14934,12 @@ const vshCmdDef domManagementCmds[] =3D { .info =3D info_setLifecycleAction, .flags =3D 0 }, + {.name =3D "set-user-sshkeys", + .handler =3D cmdSetUserSSHKeys, + .opts =3D opts_set_user_sshkeys, + .info =3D info_set_user_sshkeys, + .flags =3D 0 + }, {.name =3D "set-user-password", .handler =3D cmdSetUserPassword, .opts =3D opts_set_user_password, --=20 2.26.2