From nobody Tue Oct 29 21:31:11 2024 Delivered-To: importer@patchew.org Received-SPF: none (zohomail.com: 8.43.85.245 is neither permitted nor denied by domain of lists.libvirt.org) client-ip=8.43.85.245; envelope-from=devel-bounces@lists.libvirt.org; helo=lists.libvirt.org; Authentication-Results: mx.zohomail.com; spf=none (zohomail.com: 8.43.85.245 is neither permitted nor denied by domain of lists.libvirt.org) smtp.mailfrom=devel-bounces@lists.libvirt.org; dmarc=fail(p=none dis=none) header.from=redhat.com Return-Path: Received: from lists.libvirt.org (lists.libvirt.org [8.43.85.245]) by mx.zohomail.com with SMTPS id 1715080215657516.7104911904439; Tue, 7 May 2024 04:10:15 -0700 (PDT) Received: by lists.libvirt.org (Postfix, from userid 996) id 3313F1D2E; Tue, 7 May 2024 07:10:14 -0400 (EDT) Received: from lists.libvirt.org (localhost [IPv6:::1]) by lists.libvirt.org (Postfix) with ESMTP id F25681ADF; Tue, 7 May 2024 07:08:12 -0400 (EDT) Received: by lists.libvirt.org (Postfix, from userid 996) id AD2491A02; Tue, 7 May 2024 07:08:09 -0400 (EDT) Received: from us-smtp-delivery-124.mimecast.com (us-smtp-delivery-124.mimecast.com [170.10.129.124]) (using TLSv1.2 with cipher ECDHE-RSA-AES128-GCM-SHA256 (128/128 bits)) (No client certificate requested) by lists.libvirt.org (Postfix) with ESMTPS id 876D01AC6 for ; Tue, 7 May 2024 07:08:08 -0400 (EDT) Received: from mimecast-mx02.redhat.com (mimecast-mx02.redhat.com [66.187.233.88]) by relay.mimecast.com with ESMTP with STARTTLS (version=TLSv1.3, cipher=TLS_AES_256_GCM_SHA384) id us-mta-561-pfcXIZ48N7uwpu2rMwpQsw-1; Tue, 07 May 2024 07:08:06 -0400 Received: from smtp.corp.redhat.com (int-mx07.intmail.prod.int.rdu2.redhat.com [10.11.54.7]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange X25519 server-signature RSA-PSS (2048 bits) server-digest SHA256) (No client certificate requested) by mimecast-mx02.redhat.com (Postfix) with ESMTPS id 48654101A54F for ; Tue, 7 May 2024 11:08:06 +0000 (UTC) Received: from maggie.brq.redhat.com (unknown [10.43.3.102]) by smtp.corp.redhat.com (Postfix) with ESMTP id E71BA1C060AE for ; Tue, 7 May 2024 11:08:05 +0000 (UTC) X-Spam-Checker-Version: SpamAssassin 3.4.4 (2020-01-24) on lists.libvirt.org X-Spam-Level: X-Spam-Status: No, score=-0.7 required=5.0 tests=HEADER_FROM_DIFFERENT_DOMAINS, MAILING_LIST_MULTI,RCVD_IN_DNSWL_NONE,RCVD_IN_MSPIKE_H4, RCVD_IN_MSPIKE_WL,SPF_HELO_NONE autolearn=unavailable autolearn_force=no version=3.4.4 X-MC-Unique: pfcXIZ48N7uwpu2rMwpQsw-1 From: Michal Privoznik To: devel@lists.libvirt.org Subject: [PATCH v2 1/3] tools: Introduce SSH proxy Date: Tue, 7 May 2024 13:08:00 +0200 Message-ID: In-Reply-To: References: MIME-Version: 1.0 X-Scanned-By: MIMEDefang 3.4.1 on 10.11.54.7 X-Mimecast-Spam-Score: 0 X-Mimecast-Originator: redhat.com Message-ID-Hash: ZD6OOXT3ZETGDR73MFRJL6YRGQ424FHO X-Message-ID-Hash: ZD6OOXT3ZETGDR73MFRJL6YRGQ424FHO X-MailFrom: mprivozn@redhat.com X-Mailman-Rule-Misses: dmarc-mitigation; no-senders; approved; emergency; loop; banned-address; member-moderation; header-match-config-1; header-match-config-2; header-match-config-3; header-match-devel.lists.libvirt.org-0; nonmember-moderation; administrivia; implicit-dest; max-recipients; max-size; news-moderation; no-subject; suspicious-header X-Mailman-Version: 3.2.2 Precedence: list List-Id: Development discussions about the libvirt library & tools Archived-At: List-Archive: List-Help: List-Post: List-Subscribe: List-Unsubscribe: Content-Type: text/plain; charset="utf-8"; x-default="true" Content-Transfer-Encoding: quoted-printable X-ZM-MESSAGEID: 1715080216220100001 This allows users to SSH into a domain with a VSOCK device: ssh user@qemu/machineName So far, only QEMU domains are supported AND qemu:///system is looked for the first for 'machineName' followed by qemu:///session. I took an inspiration from SystemD's ssh proxy [1] [2]. To just work out of the box, it requires (yet unreleased) systemd to be running inside the guest to set up a socket activated SSHD on the VSOCK. Alternatively, users can set up the socket activation themselves, or just run a socat that'll forward vsock <-> TCP communication. 1: https://github.com/systemd/systemd/blob/main/src/ssh-generator/ssh-proxy= .c 2: https://github.com/systemd/systemd/blob/main/src/ssh-generator/20-system= d-ssh-proxy.conf.in Resolves: https://gitlab.com/libvirt/libvirt/-/issues/579 Signed-off-by: Michal Privoznik --- libvirt.spec.in | 33 +++ meson.build | 16 +- meson_options.txt | 2 + po/POTFILES | 1 + tools/meson.build | 2 + tools/ssh-proxy/30-libvirt-ssh-proxy.conf.in | 6 + tools/ssh-proxy/meson.build | 25 ++ tools/ssh-proxy/ssh-proxy.c | 239 +++++++++++++++++++ 8 files changed, 323 insertions(+), 1 deletion(-) create mode 100644 tools/ssh-proxy/30-libvirt-ssh-proxy.conf.in create mode 100644 tools/ssh-proxy/meson.build create mode 100644 tools/ssh-proxy/ssh-proxy.c diff --git a/libvirt.spec.in b/libvirt.spec.in index 88c62f6d92..521ecebf05 100644 --- a/libvirt.spec.in +++ b/libvirt.spec.in @@ -91,6 +91,7 @@ # Other optional features %define with_numactl 0%{!?_without_numactl:1} %define with_userfaultfd_sysctl 0%{!?_without_userfaultfd_sysctl:1} +%define with_ssh_proxy 0%{!?_without_ssh_proxy:1} =20 # A few optional bits off by default, we enable later %define with_fuse 0 @@ -903,6 +904,9 @@ Requires: libvirt-daemon-driver-nodedev =3D %{version}-= %{release} Requires: libvirt-daemon-driver-nwfilter =3D %{version}-%{release} Requires: libvirt-daemon-driver-secret =3D %{version}-%{release} Requires: libvirt-daemon-driver-storage =3D %{version}-%{release} + %if %{with_ssh_proxy} +Requires: libvirt-ssh-proxy =3D %{version}-%{release} + %endif Requires: qemu =20 %description daemon-qemu @@ -931,6 +935,9 @@ Requires: libvirt-daemon-driver-nodedev =3D %{version}-= %{release} Requires: libvirt-daemon-driver-nwfilter =3D %{version}-%{release} Requires: libvirt-daemon-driver-secret =3D %{version}-%{release} Requires: libvirt-daemon-driver-storage =3D %{version}-%{release} + %if %{with_ssh_proxy} +Requires: libvirt-ssh-proxy =3D %{version}-%{release} + %endif Requires: qemu-kvm =20 %description daemon-kvm @@ -1018,6 +1025,9 @@ Summary: Client side utilities of the libvirt library Requires: libvirt-libs =3D %{version}-%{release} # Needed by virt-pki-validate script. Requires: gnutls-utils + %if %{with_ssh_proxy} +Recommends: libvirt-ssh-proxy =3D %{version}-%{release} + %endif =20 # Ensure smooth upgrades Obsoletes: libvirt-bash-completion < 7.3.0 @@ -1100,6 +1110,15 @@ Requires: libvirt-daemon-driver-network =3D %{versio= n}-%{release} Libvirt plugin for NSS for translating domain names into IP addresses. %endif =20 +%if %{with_ssh_proxy} +%package ssh-proxy +Summary: Libvirt SSH proxy +Requires: libvirt-libs =3D %{version}-%{release} + +%description ssh-proxy +Allows SSH into domains via VSOCK without need for network. +%endif + %if %{with_mingw32} %package -n mingw32-libvirt Summary: %{summary} @@ -1291,6 +1310,12 @@ exit 1 %define arg_userfaultfd_sysctl -Duserfaultfd_sysctl=3Ddisabled %endif =20 +%if %{with_ssh_proxy} + %define arg_ssh_proxy -Dssh_proxy=3Denabled +%else + %define arg_ssh_proxy -Dssh_proxy=3Ddisabled +%endif + %define when %(date +"%%F-%%T") %define where %(hostname) %define who %{?packager}%{!?packager:Unknown} @@ -1372,6 +1397,7 @@ export SOURCE_DATE_EPOCH=3D$(stat --printf=3D'%Y' %{_= specdir}/libvirt.spec) -Dtls_priority=3D%{tls_priority} \ -Dsysctl_config=3Denabled \ %{?arg_userfaultfd_sysctl} \ + %{?arg_ssh_proxy} \ %{?enable_werror} \ -Dexpensive_tests=3Denabled \ -Dinit_script=3Dsystemd \ @@ -1456,6 +1482,7 @@ export SOURCE_DATE_EPOCH=3D$(stat --printf=3D'%Y' %{_= specdir}/libvirt.spec) -Dstorage_zfs=3Ddisabled \ -Dsysctl_config=3Ddisabled \ -Duserfaultfd_sysctl=3Ddisabled \ + -Dssh_proxy=3Ddisabled \ -Dtests=3Ddisabled \ -Dudev=3Ddisabled \ -Dwireshark_dissector=3Ddisabled \ @@ -2426,6 +2453,12 @@ exit 0 %{_libdir}/libnss_libvirt.so.2 %{_libdir}/libnss_libvirt_guest.so.2 =20 + %if %{with_ssh_proxy} +%files ssh-proxy +%config(noreplace) %{_sysconfdir}/ssh/ssh_config.d/30-libvirt-ssh-proxy.co= nf +%{_libexecdir}/libvirt-ssh-proxy + %endif + %if %{with_lxc} %files login-shell %attr(4750, root, virtlogin) %{_bindir}/virt-login-shell diff --git a/meson.build b/meson.build index e8b0094b91..f642247794 100644 --- a/meson.build +++ b/meson.build @@ -113,6 +113,11 @@ endif confdir =3D sysconfdir / meson.project_name() pkgdatadir =3D datadir / meson.project_name() =20 +sshconfdir =3D get_option('sshconfdir') +if sshconfdir =3D=3D '' + sshconfdir =3D sysconfdir / 'ssh' / 'ssh_config.d' +endif + =20 # generate configmake.h header =20 @@ -690,12 +695,14 @@ if host_machine.system() =3D=3D 'linux' symbols +=3D [ # process management [ 'sys/syscall.h', 'SYS_pidfd_open' ], + # vsock + [ 'linux/vm_sockets.h', 'struct sockaddr_vm', '#include = ' ], ] endif =20 foreach symbol : symbols if cc.has_header_symbol(symbol[0], symbol[1], args: '-D_GNU_SOURCE', pre= fix: symbol.get(2, '')) - conf.set('WITH_DECL_@0@'.format(symbol[1].to_upper()), 1) + conf.set('WITH_DECL_@0@'.format(symbol[1].underscorify().to_upper()), = 1) endif endforeach =20 @@ -2033,6 +2040,12 @@ if not get_option('pm_utils').disabled() endif endif =20 +if not get_option('ssh_proxy').disabled() and conf.has('WITH_DECL_STRUCT_S= OCKADDR_VM') + conf.set('WITH_SSH_PROXY', 1) +elif get_option('ssh_proxy').enabled() + error('ssh proxy requires vm_sockets.h which wasn\'t found') +endif + if not get_option('sysctl_config').disabled() and host_machine.system() = =3D=3D 'linux' conf.set('WITH_SYSCTL', 1) elif get_option('sysctl_config').enabled() @@ -2344,6 +2357,7 @@ misc_summary =3D { 'virt-login-shell': conf.has('WITH_LOGIN_SHELL'), 'virt-host-validate': conf.has('WITH_HOST_VALIDATE'), 'TLS priority': conf.get_unquoted('TLS_PRIORITY'), + 'SSH proxy': conf.has('WITH_SSH_PROXY'), 'sysctl config': conf.has('WITH_SYSCTL'), 'userfaultfd sysctl': conf.has('WITH_USERFAULTFD_SYSCTL'), } diff --git a/meson_options.txt b/meson_options.txt index ed91d97abf..35af27306d 100644 --- a/meson_options.txt +++ b/meson_options.txt @@ -40,6 +40,7 @@ option('sanlock', type: 'feature', value: 'auto', descrip= tion: 'sanlock support' option('sasl', type: 'feature', value: 'auto', description: 'sasl support') option('selinux', type: 'feature', value: 'auto', description: 'selinux su= pport') option('selinux_mount', type: 'string', value: '', description: 'set SELin= ux mount point') +option('sshconfdir', type: 'string', value: '', description: 'directory fo= r SSH client configuration') option('udev', type: 'feature', value: 'auto', description: 'udev support') option('wireshark_dissector', type: 'feature', value: 'auto', description:= 'wireshark support') option('wireshark_plugindir', type: 'string', value: '', description: 'wir= eshark plugins directory for use when installing wireshark plugin') @@ -107,6 +108,7 @@ option('numad', type: 'feature', value: 'auto', descrip= tion: 'use numad to manag option('nbdkit', type: 'feature', value: 'auto', description: 'Build nbdki= t storage backend') option('nbdkit_config_default', type: 'feature', value: 'auto', descriptio= n: 'Whether to use nbdkit storage backend for network disks by default (con= figurable)') option('pm_utils', type: 'feature', value: 'auto', description: 'use pm-ut= ils for power management') +option('ssh_proxy', type: 'feature', value: 'auto', description: 'Build ss= h-proxy for ssh over vsock') option('sysctl_config', type: 'feature', value: 'auto', description: 'Whet= her to install sysctl configs') option('userfaultfd_sysctl', type: 'feature', value: 'auto', description: = 'Whether to install sysctl config for enabling unprivileged userfaultfd') option('tls_priority', type: 'string', value: 'NORMAL', description: 'set = the default TLS session priority string') diff --git a/po/POTFILES b/po/POTFILES index 6fbff4bef2..cec7e4abf4 100644 --- a/po/POTFILES +++ b/po/POTFILES @@ -359,6 +359,7 @@ src/vz/vz_utils.c src/vz/vz_utils.h tests/virpolkittest.c tools/libvirt-guests.sh.in +tools/ssh-proxy/ssh-proxy.c tools/virsh-backup.c tools/virsh-checkpoint.c tools/virsh-completer-host.c diff --git a/tools/meson.build b/tools/meson.build index 15be557dfe..1bb84be0be 100644 --- a/tools/meson.build +++ b/tools/meson.build @@ -343,3 +343,5 @@ endif if wireshark_dep.found() subdir('wireshark') endif + +subdir('ssh-proxy') diff --git a/tools/ssh-proxy/30-libvirt-ssh-proxy.conf.in b/tools/ssh-proxy= /30-libvirt-ssh-proxy.conf.in new file mode 100644 index 0000000000..cd19bdbc95 --- /dev/null +++ b/tools/ssh-proxy/30-libvirt-ssh-proxy.conf.in @@ -0,0 +1,6 @@ +# SPDX-License-Identifier: LGPL-2.1-or-later + +Host qemu/* + ProxyCommand @libexecdir@/libvirt-ssh-proxy %h %p + ProxyUseFdpass yes + CheckHostIP no diff --git a/tools/ssh-proxy/meson.build b/tools/ssh-proxy/meson.build new file mode 100644 index 0000000000..e9f312fa25 --- /dev/null +++ b/tools/ssh-proxy/meson.build @@ -0,0 +1,25 @@ +if conf.has('WITH_SSH_PROXY') + executable( + 'libvirt-ssh-proxy', + [ + 'ssh-proxy.c' + ], + dependencies: [ + src_dep, + ], + link_with: [ + libvirt_lib, + ], + install: true, + install_dir: libexecdir, + install_rpath: libvirt_rpath, + ) + + configure_file( + input : '30-libvirt-ssh-proxy.conf.in', + output: '@BASENAME@', + configuration: tools_conf, + install: true, + install_dir : sshconfdir, + ) +endif diff --git a/tools/ssh-proxy/ssh-proxy.c b/tools/ssh-proxy/ssh-proxy.c new file mode 100644 index 0000000000..207d0488fb --- /dev/null +++ b/tools/ssh-proxy/ssh-proxy.c @@ -0,0 +1,239 @@ +/* + * SPDX-License-Identifier: LGPL-2.1-or-later + * + * For given domain and port create a VSOCK socket and pass it onto STDOUT. + */ + +#include + +#include +#include +#include +#include +#include +#include +#include + +#include "internal.h" +#include "virsocket.h" +#include "virstring.h" +#include "virfile.h" +#include "datatypes.h" +#include "virgettext.h" +#include "virxml.h" + +#define VIR_FROM_THIS VIR_FROM_NONE + +#define SYS_ERROR(...) \ +do { \ + int err =3D errno; \ + fprintf(stderr, "ERROR %s:%d : ", __FUNCTION__, __LINE__); \ + fprintf(stderr, __VA_ARGS__); \ + fprintf(stderr, " : %s\n", g_strerror(err)); \ + fprintf(stderr, "\n"); \ +} while (0) + +#define ERROR(...) \ +do { \ + fprintf(stderr, "ERROR %s:%d : ", __FUNCTION__, __LINE__); \ + fprintf(stderr, __VA_ARGS__); \ + fprintf(stderr, "\n"); \ +} while (0) + +#define HOSTNAME_PREFIX "qemu/" + +static void +dummyErrorHandler(void *opaque G_GNUC_UNUSED, + virErrorPtr error G_GNUC_UNUSED) +{ + +} + +static void +printUsage(const char *argv0) +{ + const char *progname; + + if (!(progname =3D strrchr(argv0, '/'))) + progname =3D argv0; + else + progname++; + + printf(_("\n" + "Usage:\n" + "%1$s hostname port\n" + "\n" + "Hostname should be in form '%2$s$domname'\n"), + progname, HOSTNAME_PREFIX); +} + +static int +parseArgs(int argc, + char *argv[], + const char **domname, + unsigned int *port) +{ + if (argc !=3D 3 || + !(*domname =3D STRSKIP(argv[1], HOSTNAME_PREFIX))) { + ERROR(_("Bad usage")); + printUsage(argv[0]); + return -1; + } + + if (virStrToLong_ui(argv[2], NULL, 10, port) < 0) { + ERROR(_("Unable to parse port: %1$s"), argv[2]); + printUsage(argv[0]); + return -1; + } + + return 0; +} + +static virDomainPtr +lookupDomain(const char *domname, + const char *uri, + virConnectPtr *connRet) +{ + g_autoptr(virConnect) conn =3D NULL; + virDomainPtr dom =3D NULL; + + if (!(conn =3D virConnectOpenReadOnly(uri))) + return NULL; + + dom =3D virDomainLookupByName(conn, domname); + if (!dom) + dom =3D virDomainLookupByUUIDString(conn, domname); + if (!dom) { + int id; + + if (virStrToLong_i(domname, NULL, 10, &id) >=3D 0) + dom =3D virDomainLookupByID(conn, id); + } + if (!dom) + return NULL; + + *connRet =3D g_steal_pointer(&conn); + return dom; +} + + +#define VSOCK_CID_XPATH "/domain/devices/vsock/cid" + +static int +extractCID(virDomainPtr dom, + unsigned long long *cidRet) +{ + g_autofree char *domxml =3D NULL; + g_autoptr(xmlDoc) doc =3D NULL; + g_autoptr(xmlXPathContext) ctxt =3D NULL; + g_autofree xmlNodePtr *nodes =3D NULL; + int nnodes =3D 0; + size_t i; + + if (!(domxml =3D virDomainGetXMLDesc(dom, 0))) + return -1; + + doc =3D virXMLParseStringCtxtWithIndent(domxml, "domain", &ctxt); + if (!doc) + return -1; + + if ((nnodes =3D virXPathNodeSet(VSOCK_CID_XPATH, ctxt, &nodes)) < 0) { + return -1; + } + + for (i =3D 0; i < nnodes; i++) { + unsigned long long cid; + + if (virXMLPropULongLong(nodes[i], "address", 10, 0, &cid) > 0) { + *cidRet =3D cid; + return 0; + } + } + + return -1; +} + +#undef VSOCK_CID_XPATH + +static int +processVsock(const char *domname, + unsigned int port) +{ + const char *uris[] =3D {"qemu:///system", "qemu:///session"}; + struct sockaddr_vm sa =3D { + .svm_family =3D AF_VSOCK, + .svm_port =3D port, + }; + VIR_AUTOCLOSE fd =3D -1; + const uid_t userid =3D geteuid(); + unsigned long long cid =3D -1; + size_t i; + + for (i =3D 0; i < G_N_ELEMENTS(uris); i++) { + g_autoptr(virConnect) conn =3D NULL; + g_autoptr(virDomain) dom =3D NULL; + + if (userid =3D=3D 0 && + STREQ(uris[i], "qemu:///session")) { + continue; + } + + if (!(dom =3D lookupDomain(domname, uris[i], &conn))) + continue; + + if (extractCID(dom, &cid) >=3D 0) + break; + } + + if (cid =3D=3D -1) { + ERROR(_("No usable vsock found")); + return -1; + } + + sa.svm_cid =3D cid; + + fd =3D socket(AF_VSOCK, SOCK_STREAM | SOCK_CLOEXEC, 0); + if (fd < 0) { + SYS_ERROR(_("Failed to allocate AF_VSOCK socket")); + return -1; + } + + if (connect(fd, (const struct sockaddr *)&sa, sizeof(sa)) < 0) { + SYS_ERROR(_("Failed to connect to vsock (cid=3D%1$llu port=3D%2$u)= "), + cid, port); + return -1; + } + + /* OpenSSH wants us to send a single byte along with the file descript= or, + * hence do so. */ + if (virSocketSendFD(STDOUT_FILENO, fd) < 0) { + SYS_ERROR(_("Failed to send file descriptor %1$d"), fd); + return -1; + } + + return 0; +} + +int main(int argc, char *argv[]) +{ + const char *domname =3D NULL; + unsigned int port; + + if (virGettextInitialize() < 0) + return EXIT_FAILURE; + + if (virInitialize() < 0) { + ERROR(_("Failed to initialize libvirt")); + return EXIT_FAILURE; + } + + virSetErrorFunc(NULL, dummyErrorHandler); + + if (parseArgs(argc, argv, &domname, &port) < 0) + return EXIT_FAILURE; + + if (processVsock(domname, port) < 0) + return EXIT_FAILURE; + + return EXIT_SUCCESS; +} --=20 2.43.2 _______________________________________________ Devel mailing list -- devel@lists.libvirt.org To unsubscribe send an email to devel-leave@lists.libvirt.org