mirror of
https://github.com/systemd/systemd.git
synced 2025-02-03 17:47:28 +03:00
Merge pull request #30777 from poettering/ssh-generator
ssh-generator which makes VMs and containers accessible to ssh via AF_UNIX and AF_VSOCK
This commit is contained in:
commit
28795e4dca
16
TODO
16
TODO
@ -137,6 +137,13 @@ Features:
|
||||
to read them from. This way the data doesn't remain in the SMBIOS blob during
|
||||
runtime, but only in the credentials fs.
|
||||
|
||||
* machined: make machine registration available via varlink to simplify
|
||||
nspawn/vmspawn, and to have an extensible way to register VM/machine metadata
|
||||
|
||||
* ssh-proxy: add support for "ssh machine/foobar" to automatically connect to
|
||||
machined registered machine "foobar". Requires updating machined to track CID
|
||||
and unix-export dir of containers.
|
||||
|
||||
* add a new ExecStart= flag that inserts the configured user's shell as first
|
||||
word in the command line. (maybe use character '.'). Usecase: tool such as
|
||||
uid0 can use that to spawn the target user's default shell.
|
||||
@ -301,15 +308,6 @@ Features:
|
||||
the realized cgroup, to pin it (and later execute all cgroup operations over,
|
||||
once we drop cgroupv1 compat).
|
||||
|
||||
* add new "systemd-ssh-generator", which allows basic ssh config via
|
||||
credentials (host key). It generates sshd.socket for IP, but also
|
||||
sshd-vsock.socket for listening on AF_VSOCK when running in a VM, and
|
||||
sshd-unix.socket on AF_UNIX when running in a container. It also generates a
|
||||
matching sshd.service file with a host key passed in on the cmdline via
|
||||
credentials. Then, add a ssh_config drop-in that matches some suitable
|
||||
hostname pattern and has a ProxyCommand set that allows connecting to any
|
||||
local VM/container that way without any networking configured.
|
||||
|
||||
* Varlinkification of the following command line tools, to open them up to
|
||||
other programs via IPC:
|
||||
- bootctl
|
||||
|
@ -273,6 +273,30 @@ care should be taken to avoid naming conflicts. `systemd` (and in particular
|
||||
7. The `/run/host/credentials/` directory is a good place to pass credentials
|
||||
into the container, using the `$CREDENTIALS_DIRECTORY` protocol, see above.
|
||||
|
||||
8. The `/run/host/unix-export/` directory shall be writable from the container
|
||||
payload, and is where container payload can bind `AF_UNIX` sockets in that
|
||||
shall be *exported* to the host, so that the host can connect to them. The
|
||||
container manager should bind mount this directory on the host side
|
||||
(read-only ideally), so that the host can connect to contained sockets. This
|
||||
is most prominently used by `systemd-ssh-generator` when run in such a
|
||||
container to automatically bind an SSH socket into that directory, which
|
||||
then can be used to connect to the container.
|
||||
|
||||
9. The `/run/host/unix-export/ssh` `AF_UNIX` socket will be automatically bound
|
||||
by `systemd-ssh-generator` in the container if possible, and can be used to
|
||||
connect to the container.
|
||||
|
||||
10. The `/run/host/userdb/` directory may be used to drop-in additional JSON
|
||||
user records that `nss-systemd` inside the container shall include in the
|
||||
system's user database. This is useful to make host users and their home
|
||||
directories automatically accessible to containers in transitive
|
||||
fashion. See `nss-systemd(8)` for details.
|
||||
|
||||
11. The `/run/host/home/` directory may be used to bind mount host home
|
||||
directories of users that shall be made available in the container to. This
|
||||
may be used in combination with `/run/host/userdb/` above: one defines the
|
||||
user record, the other contains the user's home directory.
|
||||
|
||||
## What You Shouldn't Do
|
||||
|
||||
1. Do not drop `CAP_MKNOD` from the container. `PrivateDevices=` is a commonly
|
||||
|
@ -138,6 +138,18 @@
|
||||
</listitem>
|
||||
</varlistentry>
|
||||
|
||||
<varlistentry>
|
||||
<term><varname>systemd.ssh_auto=</varname></term>
|
||||
<term><varname>systemd.ssh_listen=</varname></term>
|
||||
<listitem>
|
||||
<para>These parameters are interpreted by
|
||||
<citerefentry><refentrytitle>systemd-ssh-generator</refentrytitle><manvolnum>8</manvolnum></citerefentry>
|
||||
and may be used to control SSH sockets the system shall be reachable on.</para>
|
||||
|
||||
<xi:include href="version-info.xml" xpointer="v256"/>
|
||||
</listitem>
|
||||
</varlistentry>
|
||||
|
||||
<varlistentry>
|
||||
<term><varname>systemd.volatile=</varname></term>
|
||||
<listitem>
|
||||
|
@ -1054,6 +1054,8 @@ manpages = [
|
||||
['systemd-socket-activate', '1', [], ''],
|
||||
['systemd-socket-proxyd', '8', [], ''],
|
||||
['systemd-soft-reboot.service', '8', [], ''],
|
||||
['systemd-ssh-generator', '8', [], ''],
|
||||
['systemd-ssh-proxy', '1', [], ''],
|
||||
['systemd-stdio-bridge', '1', [], ''],
|
||||
['systemd-storagetm.service', '8', ['systemd-storagetm'], 'ENABLE_STORAGETM'],
|
||||
['systemd-stub',
|
||||
|
141
man/systemd-ssh-generator.xml
Normal file
141
man/systemd-ssh-generator.xml
Normal file
@ -0,0 +1,141 @@
|
||||
<?xml version="1.0"?>
|
||||
<!--*-nxml-*-->
|
||||
<!DOCTYPE refentry PUBLIC "-//OASIS//DTD DocBook XML V4.5//EN"
|
||||
"http://www.oasis-open.org/docbook/xml/4.2/docbookx.dtd" [
|
||||
<!ENTITY % entities SYSTEM "custom-entities.ent" >
|
||||
%entities;
|
||||
]>
|
||||
<!-- SPDX-License-Identifier: LGPL-2.1-or-later -->
|
||||
<refentry id="systemd-ssh-generator"
|
||||
xmlns:xi="http://www.w3.org/2001/XInclude">
|
||||
|
||||
<refentryinfo>
|
||||
<title>systemd-ssh-generator</title>
|
||||
<productname>systemd</productname>
|
||||
</refentryinfo>
|
||||
|
||||
<refmeta>
|
||||
<refentrytitle>systemd-ssh-generator</refentrytitle>
|
||||
<manvolnum>8</manvolnum>
|
||||
</refmeta>
|
||||
|
||||
<refnamediv>
|
||||
<refname>systemd-ssh-generator</refname>
|
||||
<refpurpose>Generator for binding a socket-activated SSH server to local <constant>AV_VSOCK</constant>
|
||||
and <constant>AF_UNIX</constant> sockets</refpurpose>
|
||||
</refnamediv>
|
||||
|
||||
<refsynopsisdiv>
|
||||
<para><filename>/usr/lib/systemd/system-generators/systemd-ssh-generator</filename></para>
|
||||
</refsynopsisdiv>
|
||||
|
||||
<refsect1>
|
||||
<title>Description</title>
|
||||
|
||||
<para><command>systemd-ssh-generator</command> binds a socket-activated SSH server to local
|
||||
<constant>AV_VSOCK</constant> and <constant>AF_UNIX</constant> sockets under certain conditions. It only
|
||||
has an effect if the <citerefentry
|
||||
project="man-pages"><refentrytitle>sshd</refentrytitle><manvolnum>8</manvolnum></citerefentry> binary is
|
||||
installed. Specifically, it does the following:</para>
|
||||
|
||||
<itemizedlist>
|
||||
<listitem><para>If invoked in a VM with <constant>AF_VSOCK</constant> support, a socket-activated SSH
|
||||
per-connection service is bound to <constant>AF_VSOCK</constant> port 22.</para></listitem>
|
||||
|
||||
<listitem><para>If invoked in a container environment with a writable directory
|
||||
<filename>/run/host/unix-export/</filename> pre-mounted it binds SSH to an <constant>AF_UNIX</constant>
|
||||
socket <filename>/run/host/unix-export/ssh</filename>. The assumption is that this directory is bind
|
||||
mounted to the host side as well, and can be used to connect to the container from there. See <ulink
|
||||
url="https://systemd.io/CONTAINER_INTERFACE">Container Interface</ulink> for more information about
|
||||
this interface.</para></listitem>
|
||||
|
||||
<listitem><para>A local <constant>AF_UNIX</constant> socket
|
||||
<filename>/run/ssh-unix-local/socket</filename> is also bound, unconditionally. This may be used for
|
||||
SSH communication from the host to itself, without involving networking, for example to traverse
|
||||
security boundaries safely and with secure authentication.</para></listitem>
|
||||
|
||||
<listitem><para>Additional <constant>AF_UNIX</constant> and <constant>AF_VSOCK</constant> sockets are
|
||||
optionally bound, based on the <varname>systemd.ssh_listen=</varname> kernel command line option or the
|
||||
<filename>ssh.listen</filename> system credential (see below).</para></listitem>
|
||||
</itemizedlist>
|
||||
|
||||
<para>See
|
||||
<citerefentry><refentrytitle>systemd-ssh-proxy</refentrytitle><manvolnum>1</manvolnum></citerefentry> for
|
||||
details on how to connect to these sockets via the <command>ssh</command> client.</para>
|
||||
|
||||
<para>The generator will use a packaged <filename>sshd@.service</filename> service template file if one
|
||||
exists, and otherwise generate a suitable service template file.</para>
|
||||
|
||||
<para><filename>systemd-ssh-generator</filename> implements
|
||||
<citerefentry><refentrytitle>systemd.generator</refentrytitle><manvolnum>7</manvolnum></citerefentry>.</para>
|
||||
</refsect1>
|
||||
|
||||
<refsect1>
|
||||
<title>Kernel Command Line</title>
|
||||
|
||||
<para><filename>systemd-ssh-generator</filename> understands the following
|
||||
<citerefentry><refentrytitle>kernel-command-line</refentrytitle><manvolnum>7</manvolnum></citerefentry>
|
||||
parameters:</para>
|
||||
|
||||
<variablelist class='kernel-commandline-options'>
|
||||
<varlistentry>
|
||||
<term><varname>systemd.ssh_auto=</varname></term>
|
||||
|
||||
<listitem><para>This option takes an optional boolean argument, and defaults to yes. If enabled, the
|
||||
automatic binding to the <constant>AF_VSOCK</constant> and <constant>AF_UNIX</constant> sockets
|
||||
listed above is done. If disable, this is not done, except for those explicitly requested via
|
||||
<varname>systemd.ssh_listen=</varname> on the kernel command line or via the
|
||||
<varname>ssh.listen</varname> system credential.</para>
|
||||
|
||||
<xi:include href="version-info.xml" xpointer="v256"/></listitem>
|
||||
</varlistentry>
|
||||
|
||||
<varlistentry>
|
||||
<term><varname>systemd.ssh_listen=</varname></term>
|
||||
|
||||
<listitem><para>This option configures an additional socket to bind SSH to. It may be used multiple
|
||||
times to bind multiple sockets. The syntax should follow the one of <varname>ListenStream=</varname>,
|
||||
see
|
||||
<citerefentry><refentrytitle>systemd.socket</refentrytitle><manvolnum>5</manvolnum></citerefentry>
|
||||
for details. This functionality supports all socket families systemd supports, including
|
||||
<constant>AF_INET</constant> and <constant>AF_INET6</constant>.</para>
|
||||
|
||||
<xi:include href="version-info.xml" xpointer="v256"/></listitem>
|
||||
</varlistentry>
|
||||
</variablelist>
|
||||
</refsect1>
|
||||
|
||||
<refsect1>
|
||||
<title>Credentials</title>
|
||||
|
||||
<para><command>systemd-ssh-generator</command> supports the system credentials logic. The following
|
||||
credentials are used when passed in:</para>
|
||||
|
||||
<variablelist class='system-credentials'>
|
||||
<varlistentry>
|
||||
<term><varname>ssh.listen</varname></term>
|
||||
|
||||
<listitem><para>This credential should be a text file, with each line referencing one additional
|
||||
socket to bind SSH to. The syntax should follow the one of <varname>ListenStream=</varname>, see
|
||||
<citerefentry><refentrytitle>systemd.socket</refentrytitle><manvolnum>5</manvolnum></citerefentry>
|
||||
for details. This functionality supports all socket families systemd supports, including
|
||||
<constant>AF_INET</constant> and <constant>AF_INET6</constant>.</para>
|
||||
|
||||
<xi:include href="version-info.xml" xpointer="v256"/></listitem>
|
||||
</varlistentry>
|
||||
</variablelist>
|
||||
</refsect1>
|
||||
|
||||
<refsect1>
|
||||
<title>See Also</title>
|
||||
<para><simplelist type="inline">
|
||||
<member><citerefentry><refentrytitle>systemd</refentrytitle><manvolnum>1</manvolnum></citerefentry></member>
|
||||
<member><citerefentry><refentrytitle>kernel-command-line</refentrytitle><manvolnum>7</manvolnum></citerefentry></member>
|
||||
<member><citerefentry><refentrytitle>systemd.system-credentials</refentrytitle><manvolnum>7</manvolnum></citerefentry></member>
|
||||
<member><citerefentry project="man-pages"><refentrytitle>vsock</refentrytitle><manvolnum>7</manvolnum></citerefentry></member>
|
||||
<member><citerefentry project="man-pages"><refentrytitle>unix</refentrytitle><manvolnum>7</manvolnum></citerefentry></member>
|
||||
<member><citerefentry project="man-pages"><refentrytitle>ssh</refentrytitle><manvolnum>1</manvolnum></citerefentry></member>
|
||||
<member><citerefentry project="man-pages"><refentrytitle>sshd</refentrytitle><manvolnum>8</manvolnum></citerefentry></member>
|
||||
</simplelist></para>
|
||||
</refsect1>
|
||||
</refentry>
|
116
man/systemd-ssh-proxy.xml
Normal file
116
man/systemd-ssh-proxy.xml
Normal file
@ -0,0 +1,116 @@
|
||||
<?xml version='1.0'?> <!--*-nxml-*-->
|
||||
<!DOCTYPE refentry PUBLIC "-//OASIS//DTD DocBook XML V4.5//EN"
|
||||
"http://www.oasis-open.org/docbook/xml/4.5/docbookx.dtd">
|
||||
<!-- SPDX-License-Identifier: LGPL-2.1-or-later -->
|
||||
|
||||
<refentry id="systemd-ssh-proxy"
|
||||
xmlns:xi="http://www.w3.org/2001/XInclude">
|
||||
|
||||
<refentryinfo>
|
||||
<title>systemd-ssh-proxy</title>
|
||||
<productname>systemd</productname>
|
||||
</refentryinfo>
|
||||
|
||||
<refmeta>
|
||||
<refentrytitle>systemd-ssh-proxy</refentrytitle>
|
||||
<manvolnum>1</manvolnum>
|
||||
</refmeta>
|
||||
|
||||
<refnamediv>
|
||||
<refname>systemd-ssh-proxy</refname>
|
||||
<refpurpose>SSH client plugin for connecting to <constant>AF_VSOCK</constant> and
|
||||
<constant>AF_UNIX</constant> sockets</refpurpose>
|
||||
</refnamediv>
|
||||
|
||||
<refsynopsisdiv>
|
||||
<programlisting>
|
||||
Host unix/* vsock/*
|
||||
ProxyCommand /usr/lib/systemd/systemd-ssh-proxy %h %p
|
||||
ProxyUseFdpass yes
|
||||
</programlisting>
|
||||
<cmdsynopsis>
|
||||
<command>/usr/lib/systemd/systemd-ssh-proxy</command> <arg>ADDRESS</arg> <arg>PORT</arg>
|
||||
</cmdsynopsis>
|
||||
</refsynopsisdiv>
|
||||
|
||||
|
||||
<refsect1>
|
||||
<title>Description</title>
|
||||
|
||||
<para><command>systemd-ssh-proxy</command> is a small "proxy" plugin for the <citerefentry
|
||||
project="man-pages"><refentrytitle>ssh</refentrytitle><manvolnum>1</manvolnum></citerefentry>
|
||||
tool that allows connecting to <constant>AF_UNIX</constant> and <constant>AF_VSOCK</constant> sockets. It
|
||||
implements the interface defined by <filename>ssh</filename>'s <varname>ProxyCommand</varname>
|
||||
configuration option. It's supposed to be used with an <citerefentry
|
||||
project="man-pages"><refentrytitle>ssh_config</refentrytitle><manvolnum>5</manvolnum></citerefentry>
|
||||
configuration fragment like the following:</para>
|
||||
|
||||
<programlisting>
|
||||
Host unix/* vsock/*
|
||||
ProxyCommand /usr/lib/systemd/systemd-ssh-proxy %h %p
|
||||
ProxyUseFdpass yes
|
||||
CheckHostIP no
|
||||
|
||||
Host .host
|
||||
ProxyCommand /usr/lib/systemd/systemd-ssh-proxy unix/run/ssh-unix-local/socket %p
|
||||
ProxyUseFdpass yes
|
||||
CheckHostIP no
|
||||
</programlisting>
|
||||
|
||||
<para>A configuration fragment along these lines is by default installed into
|
||||
<filename>/etc/ssh/ssh_config.d/20-systemd-ssh-proxy.conf.in</filename>.</para>
|
||||
|
||||
<para>With this in place, SSH connections to host string <literal>unix/</literal> followed by an absolute
|
||||
<constant>AF_UNIX</constant> file system path to a socket will be directed to the specified socket, which
|
||||
must be of type <constant>SOCK_STREAM</constant>. Similar, SSH connections to <literal>vsock/</literal>
|
||||
followed by an <constant>AF_VSOCK</constant> CID will result in an SSH connection made to that
|
||||
CID. Moreover connecting to <literal>.host</literal> will connect to the local host via SSH, without
|
||||
involving networking.</para>
|
||||
|
||||
<para>This tool is supposed to be used together with
|
||||
<citerefentry><refentrytitle>systemd-ssh-generator</refentrytitle><manvolnum>8</manvolnum></citerefentry>
|
||||
which when run inside a VM or container will bind SSH to suitable
|
||||
addresses. <command>systemd-ssh-generator</command> is supposed to run in the container of VM guest, and
|
||||
<command>systemd-ssh-proxy</command> is run on the host, in order to connect to the container or VM
|
||||
guest.</para>
|
||||
</refsect1>
|
||||
|
||||
<refsect1>
|
||||
<title>Exit status</title>
|
||||
|
||||
<para>On success, 0 is returned, a non-zero failure code
|
||||
otherwise.</para>
|
||||
</refsect1>
|
||||
|
||||
<refsect1>
|
||||
<title>Examples</title>
|
||||
|
||||
<example>
|
||||
<title>Talk to a local VM with CID 4711</title>
|
||||
|
||||
<programlisting>ssh vsock/4711</programlisting>
|
||||
</example>
|
||||
|
||||
<example>
|
||||
<title>Talk to the local host via ssh</title>
|
||||
|
||||
<programlisting>ssh .host</programlisting>
|
||||
|
||||
<para>or equivalent:</para>
|
||||
|
||||
<programlisting>ssh unix/run/ssh-unix-local/socket</programlisting>
|
||||
</example>
|
||||
</refsect1>
|
||||
|
||||
<refsect1>
|
||||
<title>See Also</title>
|
||||
<para><simplelist type="inline">
|
||||
<member><citerefentry><refentrytitle>systemd</refentrytitle><manvolnum>1</manvolnum></citerefentry></member>
|
||||
<member><citerefentry><refentrytitle>systemd-ssh-generator</refentrytitle><manvolnum>8</manvolnum></citerefentry></member>
|
||||
<member><citerefentry project="man-pages"><refentrytitle>vsock</refentrytitle><manvolnum>7</manvolnum></citerefentry></member>
|
||||
<member><citerefentry project="man-pages"><refentrytitle>unix</refentrytitle><manvolnum>7</manvolnum></citerefentry></member>
|
||||
<member><citerefentry project="man-pages"><refentrytitle>ssh</refentrytitle><manvolnum>1</manvolnum></citerefentry></member>
|
||||
<member><citerefentry project="man-pages"><refentrytitle>sshd</refentrytitle><manvolnum>8</manvolnum></citerefentry></member>
|
||||
</simplelist></para>
|
||||
</refsect1>
|
||||
</refentry>
|
@ -217,6 +217,17 @@
|
||||
</listitem>
|
||||
</varlistentry>
|
||||
|
||||
<varlistentry>
|
||||
<term><varname>ssh.listen</varname></term>
|
||||
<listitem>
|
||||
<para>May be used to configure SSH sockets the system shall be reachable on. See
|
||||
<citerefentry><refentrytitle>systemd-ssh-generator</refentrytitle><manvolnum>8</manvolnum></citerefentry>
|
||||
for details.</para>
|
||||
|
||||
<xi:include href="version-info.xml" xpointer="v256"/>
|
||||
</listitem>
|
||||
</varlistentry>
|
||||
|
||||
<varlistentry>
|
||||
<term><varname>sysusers.extra</varname></term>
|
||||
<listitem>
|
||||
|
10
meson.build
10
meson.build
@ -199,6 +199,11 @@ if pamconfdir == ''
|
||||
pamconfdir = prefixdir / 'lib/pam.d'
|
||||
endif
|
||||
|
||||
sshconfdir = get_option('sshconfdir')
|
||||
if sshconfdir == ''
|
||||
sshconfdir = sysconfdir / 'ssh/ssh_config.d'
|
||||
endif
|
||||
|
||||
sshdconfdir = get_option('sshdconfdir')
|
||||
if sshdconfdir == ''
|
||||
sshdconfdir = sysconfdir / 'ssh/sshd_config.d'
|
||||
@ -235,6 +240,7 @@ conf.set_quoted('PREFIX_NOSLASH', prefixdir_noslash)
|
||||
conf.set_quoted('RANDOM_SEED', randomseeddir / 'random-seed')
|
||||
conf.set_quoted('RANDOM_SEED_DIR', randomseeddir)
|
||||
conf.set_quoted('RC_LOCAL_PATH', get_option('rc-local'))
|
||||
conf.set_quoted('SSHCONFDIR', sshconfdir)
|
||||
conf.set_quoted('SSHDCONFDIR', sshdconfdir)
|
||||
conf.set_quoted('SYSCONF_DIR', sysconfdir)
|
||||
conf.set_quoted('SYSCTL_DIR', sysctldir)
|
||||
@ -2206,6 +2212,7 @@ subdir('src/shutdown')
|
||||
subdir('src/sleep')
|
||||
subdir('src/socket-activate')
|
||||
subdir('src/socket-proxy')
|
||||
subdir('src/ssh-generator')
|
||||
subdir('src/stdio-bridge')
|
||||
subdir('src/sulogin-shell')
|
||||
subdir('src/sysctl')
|
||||
@ -2688,7 +2695,8 @@ summary({
|
||||
'SysV rc?.d directories' : sysvrcnd_path,
|
||||
'PAM modules directory' : pamlibdir,
|
||||
'PAM configuration directory' : pamconfdir,
|
||||
'ssh configuration directory' : sshdconfdir,
|
||||
'ssh server configuration directory' : sshdconfdir,
|
||||
'ssh client configuration directory' : sshconfdir,
|
||||
'libcryptsetup plugins directory' : libcryptsetup_plugins_dir,
|
||||
'RPM macros directory' : rpmmacrosdir,
|
||||
'modprobe.d directory' : modprobedir,
|
||||
|
@ -211,6 +211,8 @@ option('pamlibdir', type : 'string',
|
||||
description : 'directory for PAM modules')
|
||||
option('pamconfdir', type : 'string',
|
||||
description : 'directory for PAM configuration ["no" disables]')
|
||||
option('sshconfdir', type : 'string',
|
||||
description : 'directory for SSH client configuration ["no" disables]')
|
||||
option('sshdconfdir', type : 'string',
|
||||
description : 'directory for SSH server configuration ["no" disables]')
|
||||
option('libcryptsetup-plugins-dir', type : 'string',
|
||||
|
@ -18,6 +18,7 @@ Packages=
|
||||
libcap-ng-utils
|
||||
netcat
|
||||
openssh-server
|
||||
openssh-clients
|
||||
p11-kit
|
||||
pam
|
||||
passwd
|
||||
|
@ -18,6 +18,7 @@ Packages=
|
||||
libcap-ng-utils
|
||||
netcat-openbsd
|
||||
openssh-server
|
||||
openssh-client
|
||||
passwd
|
||||
policykit-1
|
||||
procps
|
||||
|
@ -14,6 +14,7 @@ Packages=
|
||||
kernel-kvmsmall
|
||||
libcap-ng-utils
|
||||
openssh-server
|
||||
openssh-clients
|
||||
python3
|
||||
python3-pefile
|
||||
python3-psutil
|
||||
|
@ -8,6 +8,12 @@
|
||||
#include "alloc-util.h"
|
||||
#include "macro.h"
|
||||
|
||||
/* An iovec pointing to a single NUL byte */
|
||||
#define IOVEC_NUL_BYTE (const struct iovec) { \
|
||||
.iov_base = (void*) (const uint8_t[1]) { 0 }, \
|
||||
.iov_len = 1, \
|
||||
}
|
||||
|
||||
size_t iovec_total_size(const struct iovec *iovec, size_t n);
|
||||
|
||||
bool iovec_increment(struct iovec *iovec, size_t n, size_t k);
|
||||
|
@ -28,7 +28,13 @@ static int network_save(Network *network, const char *dest_dir) {
|
||||
|
||||
assert(network);
|
||||
|
||||
r = generator_open_unit_file_full(dest_dir, NULL, NULL, &f, &temp_path);
|
||||
r = generator_open_unit_file_full(
|
||||
dest_dir,
|
||||
/* source= */ NULL,
|
||||
/* name= */ NULL,
|
||||
&f,
|
||||
/* ret_final_path= */ NULL,
|
||||
&temp_path);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
@ -56,7 +62,13 @@ static int netdev_save(NetDev *netdev, const char *dest_dir) {
|
||||
|
||||
assert(netdev);
|
||||
|
||||
r = generator_open_unit_file_full(dest_dir, NULL, NULL, &f, &temp_path);
|
||||
r = generator_open_unit_file_full(
|
||||
dest_dir,
|
||||
/* source= */ NULL,
|
||||
/* name= */ NULL,
|
||||
&f,
|
||||
/* ret_final_path= */ NULL,
|
||||
&temp_path);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
@ -81,7 +93,13 @@ static int link_save(Link *link, const char *dest_dir) {
|
||||
|
||||
assert(link);
|
||||
|
||||
r = generator_open_unit_file_full(dest_dir, NULL, NULL, &f, &temp_path);
|
||||
r = generator_open_unit_file_full(
|
||||
dest_dir,
|
||||
/* source= */ NULL,
|
||||
/* name= */ NULL,
|
||||
&f,
|
||||
/* ret_final_path= */ NULL,
|
||||
&temp_path);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
|
@ -2,7 +2,6 @@
|
||||
|
||||
#include <errno.h>
|
||||
#include <getopt.h>
|
||||
#include <linux/fs.h>
|
||||
#include <linux/loop.h>
|
||||
#if HAVE_SELINUX
|
||||
#include <selinux/selinux.h>
|
||||
@ -10,6 +9,7 @@
|
||||
#include <stdlib.h>
|
||||
#include <sys/file.h>
|
||||
#include <sys/ioctl.h>
|
||||
#include <sys/mount.h>
|
||||
#include <sys/personality.h>
|
||||
#include <sys/prctl.h>
|
||||
#include <sys/types.h>
|
||||
@ -17,6 +17,8 @@
|
||||
#include <termios.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include <linux/fs.h> /* Must be included after <sys/mount.h> */
|
||||
|
||||
#include "sd-bus.h"
|
||||
#include "sd-daemon.h"
|
||||
#include "sd-id128.h"
|
||||
@ -3608,6 +3610,102 @@ static int setup_notify_child(void) {
|
||||
return TAKE_FD(fd);
|
||||
}
|
||||
|
||||
static int setup_unix_export_dir_outside(char **ret) {
|
||||
int r;
|
||||
|
||||
assert(ret);
|
||||
|
||||
_cleanup_free_ char *p = NULL;
|
||||
p = path_join("/run/systemd/nspawn/unix-export", arg_machine);
|
||||
if (!p)
|
||||
return log_oom();
|
||||
|
||||
r = path_is_mount_point(p, /* root= */ NULL, 0);
|
||||
if (r > 0)
|
||||
return log_error_errno(SYNTHETIC_ERRNO(EEXIST), "Mount point '%s' exists already, refusing.", p);
|
||||
if (r < 0 && r != -ENOENT)
|
||||
return log_error_errno(r, "Failed to detect if '%s' is a mount point: %m", p);
|
||||
|
||||
r = mkdir_p(p, 0755);
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to create '%s': %m", p);
|
||||
|
||||
_cleanup_(rmdir_and_freep) char *q = TAKE_PTR(p);
|
||||
|
||||
/* Mount the "unix export" directory really tiny, just 64 inodes. We mark the superblock writable
|
||||
* (since the container shall bind sockets into it). */
|
||||
r = mount_nofollow_verbose(
|
||||
LOG_ERR,
|
||||
"tmpfs",
|
||||
q,
|
||||
"tmpfs",
|
||||
MS_NODEV|MS_NOEXEC|MS_NOSUID|ms_nosymfollow_supported(),
|
||||
"size=4M,nr_inodes=64,mode=0755");
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
_cleanup_(umount_and_rmdir_and_freep) char *w = TAKE_PTR(q);
|
||||
|
||||
/* After creating the superblock we change the bind mount to be read-only. This means that the fs
|
||||
* itself is writable, but not through the mount accessible from the host. */
|
||||
r = mount_nofollow_verbose(
|
||||
LOG_ERR,
|
||||
/* source= */ NULL,
|
||||
w,
|
||||
/* fstype= */ NULL,
|
||||
MS_BIND|MS_REMOUNT|MS_RDONLY|MS_NODEV|MS_NOEXEC|MS_NOSUID|ms_nosymfollow_supported(),
|
||||
/* options= */ NULL);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
*ret = TAKE_PTR(w);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int setup_unix_export_host_inside(const char *directory, const char *unix_export_path) {
|
||||
int r;
|
||||
|
||||
assert(directory);
|
||||
assert(unix_export_path);
|
||||
|
||||
r = make_run_host(directory);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
_cleanup_free_ char *p = path_join(directory, "run/host/unix-export");
|
||||
if (!p)
|
||||
return log_oom();
|
||||
|
||||
if (mkdir(p, 0755) < 0)
|
||||
return log_error_errno(errno, "Failed to create '%s': %m", p);
|
||||
|
||||
r = mount_nofollow_verbose(
|
||||
LOG_ERR,
|
||||
unix_export_path,
|
||||
p,
|
||||
/* fstype= */ NULL,
|
||||
MS_BIND,
|
||||
/* options= */ NULL);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
r = mount_nofollow_verbose(
|
||||
LOG_ERR,
|
||||
/* source= */ NULL,
|
||||
p,
|
||||
/* fstype= */ NULL,
|
||||
MS_BIND|MS_REMOUNT|MS_NODEV|MS_NOEXEC|MS_NOSUID|ms_nosymfollow_supported(),
|
||||
/* options= */ NULL);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
r = userns_lchown(p, 0, 0);
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to chown '%s': %m", p);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int outer_child(
|
||||
Barrier *barrier,
|
||||
const char *directory,
|
||||
@ -3615,7 +3713,8 @@ static int outer_child(
|
||||
int fd_outer_socket,
|
||||
int fd_inner_socket,
|
||||
FDSet *fds,
|
||||
int netns_fd) {
|
||||
int netns_fd,
|
||||
const char *unix_export_path) {
|
||||
|
||||
_cleanup_(bind_user_context_freep) BindUserContext *bind_user_context = NULL;
|
||||
_cleanup_strv_free_ char **os_release_pairs = NULL;
|
||||
@ -3909,6 +4008,10 @@ static int outer_child(
|
||||
p = prefix_roota(directory, "/run/host");
|
||||
(void) make_inaccessible_nodes(p, arg_uid_shift, arg_uid_shift);
|
||||
|
||||
r = setup_unix_export_host_inside(directory, unix_export_path);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
r = setup_pts(directory);
|
||||
if (r < 0)
|
||||
return r;
|
||||
@ -4760,6 +4863,7 @@ static int run_container(
|
||||
_cleanup_close_ int notify_socket = -EBADF, mntns_fd = -EBADF, fd_kmsg_fifo = -EBADF;
|
||||
_cleanup_(barrier_destroy) Barrier barrier = BARRIER_NULL;
|
||||
_cleanup_(sd_event_source_unrefp) sd_event_source *notify_event_source = NULL;
|
||||
_cleanup_(umount_and_rmdir_and_freep) char *unix_export_host_dir = NULL;
|
||||
_cleanup_(sd_event_unrefp) sd_event *event = NULL;
|
||||
_cleanup_(pty_forward_freep) PTYForward *forward = NULL;
|
||||
_cleanup_(sd_netlink_unrefp) sd_netlink *rtnl = NULL;
|
||||
@ -4775,6 +4879,11 @@ static int run_container(
|
||||
assert_se(sigemptyset(&mask_chld) == 0);
|
||||
assert_se(sigaddset(&mask_chld, SIGCHLD) == 0);
|
||||
|
||||
/* Set up the unix export host directory on the host first */
|
||||
r = setup_unix_export_dir_outside(&unix_export_host_dir);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
if (arg_userns_mode == USER_NAMESPACE_PICK) {
|
||||
/* When we shall pick the UID/GID range, let's first lock /etc/passwd, so that we can safely
|
||||
* check with getpwuid() if the specific user already exists. Note that /etc might be
|
||||
@ -4845,7 +4954,8 @@ static int run_container(
|
||||
fd_outer_socket_pair[1],
|
||||
fd_inner_socket_pair[1],
|
||||
fds,
|
||||
child_netns_fd);
|
||||
child_netns_fd,
|
||||
unix_export_host_dir);
|
||||
if (r < 0)
|
||||
_exit(EXIT_FAILURE);
|
||||
|
||||
@ -5919,6 +6029,10 @@ finish:
|
||||
|
||||
p = strjoina("/run/systemd/nspawn/propagate/", arg_machine);
|
||||
(void) rm_rf(p, REMOVE_ROOT);
|
||||
|
||||
p = strjoina("/run/systemd/nspawn/unix-export/", arg_machine);
|
||||
(void) umount2(p, MNT_DETACH|UMOUNT_NOFOLLOW);
|
||||
(void) rmdir(p);
|
||||
}
|
||||
|
||||
expose_port_flush(&fw_ctx, arg_expose_ports, AF_INET, &expose_args.address4);
|
||||
|
@ -29,6 +29,7 @@ int generator_open_unit_file_full(
|
||||
const char *source,
|
||||
const char *fn,
|
||||
FILE **ret_file,
|
||||
char **ret_final_path,
|
||||
char **ret_temp_path) {
|
||||
|
||||
_cleanup_free_ char *p = NULL;
|
||||
@ -72,10 +73,13 @@ int generator_open_unit_file_full(
|
||||
program_invocation_short_name);
|
||||
|
||||
*ret_file = f;
|
||||
|
||||
if (ret_final_path)
|
||||
*ret_final_path = TAKE_PTR(p);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
int generator_add_symlink_full(
|
||||
const char *dir,
|
||||
const char *dst,
|
||||
@ -88,11 +92,13 @@ int generator_add_symlink_full(
|
||||
|
||||
assert(dir);
|
||||
assert(dst);
|
||||
assert(dep_type);
|
||||
assert(src);
|
||||
|
||||
/* Adds a symlink from <dst>.<dep_type>/ to <src> (if src is absolute) or ../<src> (otherwise). If
|
||||
* <instance> is specified, then <src> must be a template unit name, and we'll instantiate it. */
|
||||
/* If 'dep_type' is specified adds a symlink from <dst>.<dep_type>/ to <src> (if src is absolute) or ../<src> (otherwise).
|
||||
*
|
||||
* If 'dep_type' is NULL, it will create a symlink to <src> (i.e. create an alias.
|
||||
*
|
||||
* If <instance> is specified, then <src> must be a template unit name, and we'll instantiate it. */
|
||||
|
||||
r = path_extract_directory(src, &dn);
|
||||
if (r < 0 && r != -EDESTADDRREQ) /* EDESTADDRREQ → just a file name was passed */
|
||||
@ -110,11 +116,19 @@ int generator_add_symlink_full(
|
||||
return log_error_errno(r, "Failed to instantiate '%s' for '%s': %m", fn, instance);
|
||||
}
|
||||
|
||||
from = path_join(dn ?: "..", fn);
|
||||
if (!from)
|
||||
return log_oom();
|
||||
if (dep_type) { /* Create a .wants/ style dep */
|
||||
from = path_join(dn ?: "..", fn);
|
||||
if (!from)
|
||||
return log_oom();
|
||||
|
||||
to = strjoin(dir, "/", dst, ".", dep_type, "/", instantiated ?: fn);
|
||||
to = strjoin(dir, "/", dst, ".", dep_type, "/", instantiated ?: fn);
|
||||
} else { /* or create an alias */
|
||||
from = dn ? path_join(dn, fn) : strdup(fn);
|
||||
if (!from)
|
||||
return log_oom();
|
||||
|
||||
to = strjoin(dir, "/", dst);
|
||||
}
|
||||
if (!to)
|
||||
return log_oom();
|
||||
|
||||
|
@ -6,10 +6,10 @@
|
||||
#include "macro.h"
|
||||
#include "main-func.h"
|
||||
|
||||
int generator_open_unit_file_full(const char *dest, const char *source, const char *name, FILE **ret_file, char **ret_temp_path);
|
||||
int generator_open_unit_file_full(const char *dest, const char *source, const char *name, FILE **ret_file, char **ret_final_path, char **ret_temp_path);
|
||||
|
||||
static inline int generator_open_unit_file(const char *dest, const char *source, const char *name, FILE **ret_file) {
|
||||
return generator_open_unit_file_full(dest, source, name, ret_file, NULL);
|
||||
return generator_open_unit_file_full(dest, source, name, ret_file, NULL, NULL);
|
||||
}
|
||||
|
||||
int generator_add_symlink_full(const char *dir, const char *dst, const char *dep_type, const char *src, const char *instance);
|
||||
|
@ -3142,8 +3142,10 @@ int unit_file_get_state(
|
||||
return unit_file_lookup_state(scope, &lp, name, ret);
|
||||
}
|
||||
|
||||
int unit_file_exists(RuntimeScope scope, const LookupPaths *lp, const char *name) {
|
||||
_cleanup_(install_context_done) InstallContext c = { .scope = scope };
|
||||
int unit_file_exists_full(RuntimeScope scope, const LookupPaths *lp, const char *name, char **ret_path) {
|
||||
_cleanup_(install_context_done) InstallContext c = {
|
||||
.scope = scope,
|
||||
};
|
||||
int r;
|
||||
|
||||
assert(lp);
|
||||
@ -3152,12 +3154,33 @@ int unit_file_exists(RuntimeScope scope, const LookupPaths *lp, const char *name
|
||||
if (!unit_name_is_valid(name, UNIT_NAME_ANY))
|
||||
return -EINVAL;
|
||||
|
||||
r = install_info_discover(&c, lp, name, 0, NULL, NULL, NULL);
|
||||
if (r == -ENOENT)
|
||||
InstallInfo *info = NULL;
|
||||
r = install_info_discover(
|
||||
&c,
|
||||
lp,
|
||||
name,
|
||||
/* flags= */ 0,
|
||||
ret_path ? &info : NULL,
|
||||
/* changes= */ NULL,
|
||||
/* n_changes= */ NULL);
|
||||
if (r == -ENOENT) {
|
||||
if (ret_path)
|
||||
*ret_path = NULL;
|
||||
return 0;
|
||||
}
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
if (ret_path) {
|
||||
assert(info);
|
||||
|
||||
_cleanup_free_ char *p = strdup(info->path);
|
||||
if (!p)
|
||||
return -ENOMEM;
|
||||
|
||||
*ret_path = TAKE_PTR(p);
|
||||
}
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
|
@ -193,7 +193,11 @@ int unit_file_lookup_state(
|
||||
UnitFileState *ret);
|
||||
|
||||
int unit_file_get_state(RuntimeScope scope, const char *root_dir, const char *filename, UnitFileState *ret);
|
||||
int unit_file_exists(RuntimeScope scope, const LookupPaths *paths, const char *name);
|
||||
|
||||
int unit_file_exists_full(RuntimeScope scope, const LookupPaths *paths, const char *name, char **ret_path);
|
||||
static inline int unit_file_exists(RuntimeScope scope, const LookupPaths *paths, const char *name) {
|
||||
return unit_file_exists_full(scope, paths, name, NULL);
|
||||
}
|
||||
|
||||
int unit_file_get_list(RuntimeScope scope, const char *root_dir, Hashmap *h, char **states, char **patterns);
|
||||
|
||||
|
18
src/ssh-generator/20-systemd-ssh-proxy.conf.in
Normal file
18
src/ssh-generator/20-systemd-ssh-proxy.conf.in
Normal file
@ -0,0 +1,18 @@
|
||||
# SPDX-License-Identifier: LGPL-2.1-or-later
|
||||
#
|
||||
# Make sure unix/* and vsock/* can be used to connect to AF_UNIX and AF_VSOCK paths
|
||||
#
|
||||
Host unix/* vsock/*
|
||||
ProxyCommand {{LIBEXECDIR}}/systemd-ssh-proxy %h %p
|
||||
ProxyUseFdpass yes
|
||||
CheckHostIP no
|
||||
|
||||
# Disable all kinds of host identity checks, since these addresses are generally ephemeral.
|
||||
StrictHostKeyChecking no
|
||||
UserKnownHostsFile /dev/null
|
||||
|
||||
# Allow connecting to the local host directly via ".host"
|
||||
Host .host
|
||||
ProxyCommand {{LIBEXECDIR}}/systemd-ssh-proxy unix/run/ssh-unix-local/socket %p
|
||||
ProxyUseFdpass yes
|
||||
CheckHostIP no
|
25
src/ssh-generator/meson.build
Normal file
25
src/ssh-generator/meson.build
Normal file
@ -0,0 +1,25 @@
|
||||
# SPDX-License-Identifier: LGPL-2.1-or-later
|
||||
|
||||
executables += [
|
||||
generator_template + {
|
||||
'name' : 'systemd-ssh-generator',
|
||||
'sources' : files('ssh-generator.c'),
|
||||
},
|
||||
libexec_template + {
|
||||
'name' : 'systemd-ssh-proxy',
|
||||
'sources' : files('ssh-proxy.c'),
|
||||
},
|
||||
]
|
||||
|
||||
custom_target(
|
||||
'20-systemd-ssh-proxy.conf',
|
||||
input : '20-systemd-ssh-proxy.conf.in',
|
||||
output : '20-systemd-ssh-proxy.conf',
|
||||
command : [jinja2_cmdline, '@INPUT@', '@OUTPUT@'],
|
||||
install : true,
|
||||
install_dir : libexecdir / 'ssh_config.d')
|
||||
|
||||
install_emptydir(sshconfdir)
|
||||
|
||||
meson.add_install_script(sh, '-c',
|
||||
ln_s.format(libexecdir / 'ssh_config.d' / '20-systemd-ssh-proxy.conf', sshconfdir / '20-systemd-ssh-proxy.conf'))
|
476
src/ssh-generator/ssh-generator.c
Normal file
476
src/ssh-generator/ssh-generator.c
Normal file
@ -0,0 +1,476 @@
|
||||
/* SPDX-License-Identifier: LGPL-2.1-or-later */
|
||||
|
||||
#include <fcntl.h>
|
||||
#include <sys/ioctl.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include "creds-util.h"
|
||||
#include "fd-util.h"
|
||||
#include "fileio.h"
|
||||
#include "generator.h"
|
||||
#include "install.h"
|
||||
#include "missing_socket.h"
|
||||
#include "parse-util.h"
|
||||
#include "path-util.h"
|
||||
#include "proc-cmdline.h"
|
||||
#include "socket-netlink.h"
|
||||
#include "socket-util.h"
|
||||
#include "special.h"
|
||||
#include "virt.h"
|
||||
|
||||
/* A small generator binding potentially five or more SSH sockets:
|
||||
*
|
||||
* 1. Listen on AF_VSOCK port 22 if we run in a VM with AF_VSOCK enabled
|
||||
* 2. Listen on AF_UNIX socket /run/host/unix-export/ssh if we run in a container with /run/host/ support
|
||||
* 3. Listen on AF_UNIX socket /run/ssh-unix-local/socket (always)
|
||||
* 4. Listen on any socket specified via kernel command line option systemd.ssh_listen=
|
||||
* 5. Similar, but from system credential ssh.listen
|
||||
*
|
||||
* The first two provide a nice way for hosts to connect to containers and VMs they invoke via the usual SSH
|
||||
* logic, but without waiting for networking or suchlike. The third allows the same for local clients. */
|
||||
|
||||
static const char *arg_dest = NULL;
|
||||
static bool arg_auto = true;
|
||||
static char **arg_listen_extra = NULL;
|
||||
|
||||
static int parse_proc_cmdline_item(const char *key, const char *value, void *data) {
|
||||
int r;
|
||||
|
||||
assert(key);
|
||||
|
||||
if (proc_cmdline_key_streq(key, "systemd.ssh_auto")) {
|
||||
r = value ? parse_boolean(value) : 1;
|
||||
if (r < 0)
|
||||
log_warning_errno(r, "Failed to parse systemd.ssh_auto switch \"%s\", ignoring: %m", value);
|
||||
else
|
||||
arg_auto = r;
|
||||
|
||||
} else if (proc_cmdline_key_streq(key, "systemd.ssh_listen")) {
|
||||
|
||||
if (proc_cmdline_value_missing(key, value))
|
||||
return 0;
|
||||
|
||||
SocketAddress sa;
|
||||
r = socket_address_parse(&sa, value);
|
||||
if (r < 0)
|
||||
log_warning_errno(r, "Failed to parse systemd.ssh_listen= expression, ignoring: %s", value);
|
||||
else {
|
||||
_cleanup_free_ char *s = NULL;
|
||||
r = socket_address_print(&sa, &s);
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to format socket address: %m");
|
||||
|
||||
if (strv_consume(&arg_listen_extra, TAKE_PTR(s)) < 0)
|
||||
return log_oom();
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int make_sshd_template_unit(
|
||||
const char *dest,
|
||||
const char *template,
|
||||
const char *sshd_binary,
|
||||
const char *found_sshd_template_service,
|
||||
char **generated_sshd_template_unit) {
|
||||
|
||||
int r;
|
||||
|
||||
assert(dest);
|
||||
assert(template);
|
||||
assert(sshd_binary);
|
||||
assert(generated_sshd_template_unit);
|
||||
|
||||
/* If the system has a suitable template already, symlink it to the name we want to reuse it */
|
||||
if (found_sshd_template_service)
|
||||
return generator_add_symlink(
|
||||
dest,
|
||||
template,
|
||||
/* dep_type= */ NULL,
|
||||
found_sshd_template_service);
|
||||
|
||||
if (!*generated_sshd_template_unit) {
|
||||
_cleanup_fclose_ FILE *f = NULL;
|
||||
|
||||
r = generator_open_unit_file_full(
|
||||
dest,
|
||||
/* source= */ NULL,
|
||||
"sshd-generated@.service", /* Give this generated unit a generic name, since we want to use it for both AF_UNIX and AF_VSOCK */
|
||||
&f,
|
||||
generated_sshd_template_unit,
|
||||
/* ret_temp_path= */ NULL);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
fprintf(f,
|
||||
"[Unit]\n"
|
||||
"Description=OpenSSH Per-Connection Server Daemon\n"
|
||||
"Documentation=man:systemd-ssh-generator(8) man:sshd(8)\n"
|
||||
"[Service]\n"
|
||||
"ExecStart=-%s -i\n"
|
||||
"StandardInput=socket",
|
||||
sshd_binary);
|
||||
|
||||
r = fflush_and_check(f);
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to write sshd template: %m");
|
||||
}
|
||||
|
||||
return generator_add_symlink(
|
||||
dest,
|
||||
template,
|
||||
/* dep_type= */ NULL,
|
||||
*generated_sshd_template_unit);
|
||||
}
|
||||
|
||||
static int write_socket_unit(
|
||||
const char *dest,
|
||||
const char *unit,
|
||||
const char *listen_stream,
|
||||
const char *comment) {
|
||||
|
||||
int r;
|
||||
|
||||
assert(dest);
|
||||
assert(unit);
|
||||
assert(listen_stream);
|
||||
assert(comment);
|
||||
|
||||
_cleanup_fclose_ FILE *f = NULL;
|
||||
r = generator_open_unit_file(
|
||||
dest,
|
||||
/* source= */ NULL,
|
||||
unit,
|
||||
&f);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
fprintf(f,
|
||||
"[Unit]\n"
|
||||
"Description=OpenSSH Server Socket (systemd-ssh-generator, %s)\n"
|
||||
"Documentation=man:systemd-ssh-generator(8)\n"
|
||||
"\n[Socket]\n"
|
||||
"ListenStream=%s\n"
|
||||
"Accept=yes\n"
|
||||
"PollLimitIntervalSec=30s\n"
|
||||
"PollLimitBurst=50\n",
|
||||
comment,
|
||||
listen_stream);
|
||||
|
||||
r = fflush_and_check(f);
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to write %s SSH socket unit: %m", comment);
|
||||
|
||||
r = generator_add_symlink(
|
||||
dest,
|
||||
SPECIAL_SOCKETS_TARGET,
|
||||
"wants",
|
||||
unit);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int add_vsock_socket(
|
||||
const char *dest,
|
||||
const char *sshd_binary,
|
||||
const char *found_sshd_template_unit,
|
||||
char **generated_sshd_template_unit) {
|
||||
|
||||
int r;
|
||||
|
||||
assert(dest);
|
||||
assert(generated_sshd_template_unit);
|
||||
|
||||
Virtualization v = detect_vm();
|
||||
if (v < 0)
|
||||
return log_error_errno(v, "Failed to detect if we run in a VM: %m");
|
||||
if (v == VIRTUALIZATION_NONE) {
|
||||
log_debug("Not running in a VM, not listening on AF_VSOCK.");
|
||||
return 0;
|
||||
}
|
||||
|
||||
_cleanup_close_ int vsock_fd = socket(AF_VSOCK, SOCK_STREAM|SOCK_CLOEXEC, 0);
|
||||
if (vsock_fd < 0) {
|
||||
if (ERRNO_IS_NOT_SUPPORTED(errno)) {
|
||||
log_debug("Not creating AF_VSOCK ssh listener, since AF_VSOCK is not available.");
|
||||
return 0;
|
||||
}
|
||||
|
||||
return log_error_errno(errno, "Unable to test if AF_VSOCK is available: %m");
|
||||
}
|
||||
|
||||
vsock_fd = safe_close(vsock_fd);
|
||||
|
||||
/* Determine the local CID so that we can log it to help users to connect to this VM */
|
||||
unsigned local_cid;
|
||||
r = vsock_get_local_cid(&local_cid);
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to query local AF_VSOCK CID: %m");
|
||||
|
||||
r = make_sshd_template_unit(
|
||||
dest,
|
||||
"sshd-vsock@.service",
|
||||
sshd_binary,
|
||||
found_sshd_template_unit,
|
||||
generated_sshd_template_unit);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
r = write_socket_unit(
|
||||
dest,
|
||||
"sshd-vsock.socket",
|
||||
"vsock::22",
|
||||
"AF_VSOCK");
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
log_info("Binding SSH to AF_VSOCK vsock::22.\n"
|
||||
"→ connect via 'ssh vsock/%u' from host", local_cid);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int add_local_unix_socket(
|
||||
const char *dest,
|
||||
const char *sshd_binary,
|
||||
const char *found_sshd_template_unit,
|
||||
char **generated_sshd_template_unit) {
|
||||
|
||||
int r;
|
||||
|
||||
assert(dest);
|
||||
assert(sshd_binary);
|
||||
assert(generated_sshd_template_unit);
|
||||
|
||||
r = make_sshd_template_unit(
|
||||
dest,
|
||||
"sshd-unix-local@.service",
|
||||
sshd_binary,
|
||||
found_sshd_template_unit,
|
||||
generated_sshd_template_unit);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
r = write_socket_unit(
|
||||
dest,
|
||||
"sshd-unix-local.socket",
|
||||
"/run/ssh-unix-local/socket",
|
||||
"AF_UNIX Local");
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
|
||||
log_info("Binding SSH to AF_UNIX socket /run/ssh-unix-local/socket.\n"
|
||||
"→ connect via 'ssh .host' locally");
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int add_export_unix_socket(
|
||||
const char *dest,
|
||||
const char *sshd_binary,
|
||||
const char *found_sshd_template_unit,
|
||||
char **generated_sshd_template_unit) {
|
||||
|
||||
int r;
|
||||
|
||||
assert(dest);
|
||||
assert(sshd_binary);
|
||||
assert(generated_sshd_template_unit);
|
||||
|
||||
Virtualization v = detect_container();
|
||||
if (v < 0)
|
||||
return log_error_errno(v, "Failed to detect if we run in a container: %m");
|
||||
if (v == VIRTUALIZATION_NONE) {
|
||||
log_debug("Not running in container, not listening on /run/host/unix-export/ssh");
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (access("/run/host/unix-export/", W_OK) < 0) {
|
||||
if (errno == ENOENT) {
|
||||
log_debug("Container manager does not provide /run/host/unix-export/ mount, not binding AF_UNIX socket there.");
|
||||
return 0;
|
||||
}
|
||||
if (errno == EROFS || ERRNO_IS_PRIVILEGE(errno)) {
|
||||
log_debug("Container manager does not provide write access to /run/host/unix-export/, not binding AF_UNIX socket there.");
|
||||
return 0;
|
||||
}
|
||||
|
||||
return log_debug_errno(errno, "Unable to check if /run/host/unix-export exists: %m");
|
||||
}
|
||||
|
||||
r = make_sshd_template_unit(
|
||||
dest,
|
||||
"sshd-unix-export@.service",
|
||||
sshd_binary,
|
||||
found_sshd_template_unit,
|
||||
generated_sshd_template_unit);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
r = write_socket_unit(
|
||||
dest,
|
||||
"sshd-unix-export.socket",
|
||||
"/run/host/unix-export/ssh",
|
||||
"AF_UNIX Export");
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
log_info("Binding SSH to AF_UNIX socket /run/host/unix-export/ssh\n"
|
||||
"→ connect via 'ssh unix/run/systemd/nspawn/unix-export/\?\?\?/ssh' from host");
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int add_extra_sockets(
|
||||
const char *dest,
|
||||
const char *sshd_binary,
|
||||
const char *found_sshd_template_unit,
|
||||
char **generated_sshd_template_unit) {
|
||||
|
||||
unsigned n = 1;
|
||||
int r;
|
||||
|
||||
assert(dest);
|
||||
assert(sshd_binary);
|
||||
assert(generated_sshd_template_unit);
|
||||
|
||||
if (strv_isempty(arg_listen_extra))
|
||||
return 0;
|
||||
|
||||
STRV_FOREACH(i, arg_listen_extra) {
|
||||
_cleanup_free_ char *service = NULL, *socket = NULL;
|
||||
|
||||
if (n > 1) {
|
||||
if (asprintf(&service, "sshd-extra-%u@.service", n) < 0)
|
||||
return log_oom();
|
||||
|
||||
if (asprintf(&socket, "sshd-extra-%u.socket", n) < 0)
|
||||
return log_oom();
|
||||
}
|
||||
|
||||
r = make_sshd_template_unit(
|
||||
dest,
|
||||
service ?: "sshd-extra@.service",
|
||||
sshd_binary,
|
||||
found_sshd_template_unit,
|
||||
generated_sshd_template_unit);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
r = write_socket_unit(
|
||||
dest,
|
||||
socket ?: "sshd-extra.socket",
|
||||
*i,
|
||||
*i);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
log_info("Binding SSH to socket %s.", *i);
|
||||
n++;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int parse_credentials(void) {
|
||||
_cleanup_free_ char *b = NULL;
|
||||
size_t sz = 0;
|
||||
int r;
|
||||
|
||||
r = read_credential_with_decryption("ssh.listen", (void*) &b, &sz);
|
||||
if (r < 0)
|
||||
return r;
|
||||
if (r == 0)
|
||||
return 0;
|
||||
|
||||
_cleanup_fclose_ FILE *f = NULL;
|
||||
f = fmemopen_unlocked(b, sz, "r");
|
||||
if (!f)
|
||||
return log_oom();
|
||||
|
||||
for (;;) {
|
||||
_cleanup_free_ char *item = NULL;
|
||||
|
||||
r = read_stripped_line(f, LINE_MAX, &item);
|
||||
if (r == 0)
|
||||
break;
|
||||
if (r < 0) {
|
||||
log_error_errno(r, "Failed to parse credential 'ssh.listen': %m");
|
||||
break;
|
||||
}
|
||||
|
||||
if (startswith(item, "#"))
|
||||
continue;
|
||||
|
||||
SocketAddress sa;
|
||||
r = socket_address_parse(&sa, item);
|
||||
if (r < 0) {
|
||||
log_warning_errno(r, "Failed to parse systemd.ssh_listen= expression, ignoring: %s", item);
|
||||
continue;
|
||||
}
|
||||
|
||||
_cleanup_free_ char *s = NULL;
|
||||
r = socket_address_print(&sa, &s);
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to format socket address: %m");
|
||||
|
||||
if (strv_consume(&arg_listen_extra, TAKE_PTR(s)) < 0)
|
||||
return log_oom();
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int run(const char *dest, const char *dest_early, const char *dest_late) {
|
||||
int r;
|
||||
|
||||
assert_se(arg_dest = dest);
|
||||
|
||||
r = proc_cmdline_parse(parse_proc_cmdline_item, /* userdata= */ NULL, /* flags= */ 0);
|
||||
if (r < 0)
|
||||
log_warning_errno(r, "Failed to parse kernel command line, ignoring: %m");
|
||||
|
||||
(void) parse_credentials();
|
||||
|
||||
strv_sort(arg_listen_extra);
|
||||
strv_uniq(arg_listen_extra);
|
||||
|
||||
if (!arg_auto && strv_isempty(arg_listen_extra)) {
|
||||
log_debug("Disabling SSH generator logic, because as it has been turned off explicitly.");
|
||||
return 0;
|
||||
}
|
||||
|
||||
_cleanup_free_ char *sshd_binary = NULL;
|
||||
r = find_executable("sshd", &sshd_binary);
|
||||
if (r == -ENOENT) {
|
||||
log_info("Disabling SSH generator logic, since sshd is not installed.");
|
||||
return 0;
|
||||
}
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to determine if sshd is installed: %m");
|
||||
|
||||
_cleanup_(lookup_paths_free) LookupPaths lp = {};
|
||||
r = lookup_paths_init_or_warn(&lp, RUNTIME_SCOPE_SYSTEM, LOOKUP_PATHS_EXCLUDE_GENERATED, /* root_dir= */ NULL);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
_cleanup_free_ char *found_sshd_template_unit = NULL;
|
||||
r = unit_file_exists_full(RUNTIME_SCOPE_SYSTEM, &lp, "sshd@.service", &found_sshd_template_unit);
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Unable to detect if sshd@.service exists: %m");
|
||||
|
||||
_cleanup_free_ char *generated_sshd_template_unit = NULL;
|
||||
RET_GATHER(r, add_extra_sockets(dest, sshd_binary, found_sshd_template_unit, &generated_sshd_template_unit));
|
||||
|
||||
if (arg_auto) {
|
||||
RET_GATHER(r, add_vsock_socket(dest, sshd_binary, found_sshd_template_unit, &generated_sshd_template_unit));
|
||||
RET_GATHER(r, add_local_unix_socket(dest, sshd_binary, found_sshd_template_unit, &generated_sshd_template_unit));
|
||||
RET_GATHER(r, add_export_unix_socket(dest, sshd_binary, found_sshd_template_unit, &generated_sshd_template_unit));
|
||||
}
|
||||
|
||||
return r;
|
||||
}
|
||||
|
||||
DEFINE_MAIN_GENERATOR_FUNCTION(run);
|
102
src/ssh-generator/ssh-proxy.c
Normal file
102
src/ssh-generator/ssh-proxy.c
Normal file
@ -0,0 +1,102 @@
|
||||
/* SPDX-License-Identifier: LGPL-2.1-or-later */
|
||||
|
||||
#include <net/if_arp.h>
|
||||
#include <stdio.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include "fd-util.h"
|
||||
#include "iovec-util.h"
|
||||
#include "log.h"
|
||||
#include "main-func.h"
|
||||
#include "missing_socket.h"
|
||||
#include "parse-util.h"
|
||||
#include "socket-util.h"
|
||||
#include "string-util.h"
|
||||
#include "strv.h"
|
||||
|
||||
static int process_vsock(const char *host, const char *port) {
|
||||
int r;
|
||||
|
||||
assert(host);
|
||||
assert(port);
|
||||
|
||||
union sockaddr_union sa = {
|
||||
.vm.svm_family = AF_VSOCK,
|
||||
};
|
||||
|
||||
r = vsock_parse_cid(host, &sa.vm.svm_cid);
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to parse vsock cid: %s", host);
|
||||
|
||||
r = vsock_parse_port(port, &sa.vm.svm_port);
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to parse vsock port: %s", port);
|
||||
|
||||
_cleanup_close_ int fd = socket(AF_VSOCK, SOCK_STREAM|SOCK_CLOEXEC, 0);
|
||||
if (fd < 0)
|
||||
return log_error_errno(errno, "Failed to allocate AF_VSOCK socket: %m");
|
||||
|
||||
if (connect(fd, &sa.sa, SOCKADDR_LEN(sa)) < 0)
|
||||
return log_error_errno(errno, "Failed to connect to vsock:%u:%u: %m", sa.vm.svm_cid, sa.vm.svm_port);
|
||||
|
||||
/* OpenSSH wants us to send a single byte along with the file descriptor, hence do so */
|
||||
r = send_one_fd_iov(STDOUT_FILENO, fd, &IOVEC_NUL_BYTE, /* n_iovec= */ 1, /* flags= */ 0);
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to send socket via STDOUT: %m");
|
||||
|
||||
log_debug("Successfully sent AF_VSOCK socket via STDOUT.");
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int process_unix(const char *path) {
|
||||
int r;
|
||||
|
||||
assert(path);
|
||||
|
||||
/* We assume the path is absolute unless it starts with a dot (or is already explicitly absolute) */
|
||||
_cleanup_free_ char *prefixed = NULL;
|
||||
if (!STARTSWITH_SET(path, "/", "./")) {
|
||||
prefixed = strjoin("/", path);
|
||||
if (!prefixed)
|
||||
return log_oom();
|
||||
|
||||
path = prefixed;
|
||||
}
|
||||
|
||||
_cleanup_close_ int fd = socket(AF_UNIX, SOCK_STREAM|SOCK_CLOEXEC, 0);
|
||||
if (fd < 0)
|
||||
return log_error_errno(errno, "Failed to allocate AF_UNIX socket: %m");
|
||||
|
||||
r = connect_unix_path(fd, AT_FDCWD, path);
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to connect to AF_UNIX socket %s: %m", path);
|
||||
|
||||
r = send_one_fd_iov(STDOUT_FILENO, fd, &IOVEC_NUL_BYTE, /* n_iovec= */ 1, /* flags= */ 0);
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to send socket via STDOUT: %m");
|
||||
|
||||
log_debug("Successfully sent AF_UNIX socket via STDOUT.");
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int run(int argc, char* argv[]) {
|
||||
|
||||
log_setup();
|
||||
|
||||
if (argc != 3)
|
||||
return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Expected two arguments: host and port.");
|
||||
|
||||
const char *host = argv[1], *port = argv[2];
|
||||
|
||||
const char *p = startswith(host, "vsock/");
|
||||
if (p)
|
||||
return process_vsock(p, port);
|
||||
|
||||
p = startswith(host, "unix/");
|
||||
if (p)
|
||||
return process_unix(p);
|
||||
|
||||
return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Don't know how to parse host name specification: %s", host);
|
||||
}
|
||||
|
||||
DEFINE_MAIN_FUNCTION(run);
|
@ -11,6 +11,10 @@ NSPAWN_ARGUMENTS="--private-network"
|
||||
# (Hopefully) a temporary workaround for https://github.com/systemd/systemd/issues/30573
|
||||
KERNEL_APPEND="${KERNEL_APPEND:-} SYSTEMD_DEFAULT_MOUNT_RATE_LIMIT_BURST=100"
|
||||
|
||||
# Make sure vsock is available in the VM
|
||||
CID=$((RANDOM + 3))
|
||||
QEMU_OPTIONS+=" -device vhost-vsock-pci,guest-cid=$CID"
|
||||
|
||||
test_append_files() {
|
||||
local workspace="${1:?}"
|
||||
|
||||
@ -26,7 +30,15 @@ test_append_files() {
|
||||
generate_module_dependencies
|
||||
fi
|
||||
|
||||
image_install socat
|
||||
inst_binary socat
|
||||
inst_binary ssh
|
||||
inst_binary sshd
|
||||
inst_binary ssh-keygen
|
||||
inst_binary usermod
|
||||
instmods vmw_vsock_virtio_transport
|
||||
instmods vsock_loopback
|
||||
instmods vmw_vsock_vmci_transport
|
||||
generate_module_dependencies
|
||||
}
|
||||
|
||||
do_test "$@"
|
||||
|
58
test/units/testsuite-74.ssh.sh
Executable file
58
test/units/testsuite-74.ssh.sh
Executable file
@ -0,0 +1,58 @@
|
||||
#!/usr/bin/env bash
|
||||
# SPDX-License-Identifier: LGPL-2.1-or-later
|
||||
set -eux
|
||||
set -o pipefail
|
||||
|
||||
if ! command -v ssh &> /dev/null || ! command -v sshd &> /dev/null ; then
|
||||
echo "ssh/sshd not found, skipping test." >&2
|
||||
exit 0
|
||||
fi
|
||||
|
||||
systemctl -q is-active sshd-unix-local.socket
|
||||
|
||||
if test -e /dev/vsock ; then
|
||||
systemctl -q is-active sshd-vsock.socket
|
||||
fi
|
||||
|
||||
if test -d /run/host/unix-export ; then
|
||||
systemctl -q is-active sshd-unix-export.socket
|
||||
fi
|
||||
|
||||
# FIXME: sshd seems to crash inside asan currently, skip the actual ssh test hence
|
||||
if [[ -v ASAN_OPTIONS ]] ; then
|
||||
exit 0
|
||||
fi
|
||||
|
||||
ROOTID=$(mktemp -u)
|
||||
|
||||
removesshid() {
|
||||
rm -f "$ROOTID" "$ROOTID".pub
|
||||
}
|
||||
|
||||
ssh-keygen -N '' -C '' -t rsa -f "$ROOTID"
|
||||
|
||||
mkdir -p 0700 /root/.ssh
|
||||
cat "$ROOTID".pub >> /root/.ssh/authorized_keys
|
||||
|
||||
# set root pw to "foo", just to set it to something valid
|
||||
# shellcheck disable=SC2016
|
||||
usermod -p '$5$AAy6BYJ6rzz.QELv$6LpVEU3/RQmVz.svHu/33qoJWWWzZuJ3DM2fo9JgcUD' root
|
||||
usermod -U root
|
||||
|
||||
mkdir -p /etc/ssh
|
||||
test -f /etc/ssh/ssh_host_rsa_key || ssh-keygen -t rsa -C '' -N '' -f /etc/ssh/ssh_host_rsa_key
|
||||
echo "PermitRootLogin yes" >> /etc/ssh/sshd_config
|
||||
echo "LogLevel DEBUG3" >> /etc/ssh/sshd_config
|
||||
|
||||
test -f /etc/ssh/ssh_config || echo 'Include /etc/ssh/ssh_config.d/*.conf' > /etc/ssh/ssh_config
|
||||
|
||||
# ssh wants this dir around, but distros cannot agree on a common name for it, let's just create all that are aware of distros use
|
||||
mkdir -p /usr/share/empty.sshd /var/empty /var/empty/sshd
|
||||
|
||||
ssh -o StrictHostKeyChecking=no -v -i "$ROOTID" .host cat /etc/machine-id | cmp - /etc/machine-id
|
||||
ssh -o StrictHostKeyChecking=no -v -i "$ROOTID" unix/run/ssh-unix-local/socket cat /etc/machine-id | cmp - /etc/machine-id
|
||||
|
||||
modprobe vsock_loopback ||:
|
||||
if test -e /dev/vsock -a -d /sys/module/vsock_loopback ; then
|
||||
ssh -o StrictHostKeyChecking=no -v -i "$ROOTID" vsock/1 cat /etc/machine-id | cmp - /etc/machine-id
|
||||
fi
|
10
tmpfiles.d/20-systemd-ssh-generator.conf.in
Normal file
10
tmpfiles.d/20-systemd-ssh-generator.conf.in
Normal file
@ -0,0 +1,10 @@
|
||||
# This file is part of systemd.
|
||||
#
|
||||
# systemd is free software; you can redistribute it and/or modify it
|
||||
# under the terms of the GNU Lesser General Public License as published by
|
||||
# the Free Software Foundation; either version 2.1 of the License, or
|
||||
# (at your option) any later version.
|
||||
|
||||
# See tmpfiles.d(5) for details
|
||||
|
||||
L {{SSHCONFDIR}}/20-systemd-ssh-proxy.conf - - - - {{LIBEXECDIR}}/ssh_config.d/20-systemd-ssh-proxy.conf
|
@ -35,6 +35,7 @@ in_files = [['etc.conf', ''],
|
||||
['systemd.conf', ''],
|
||||
['var.conf', ''],
|
||||
['20-systemd-userdb.conf', 'ENABLE_USERDB'],
|
||||
['20-systemd-ssh-generator.conf', ''],
|
||||
]
|
||||
|
||||
foreach pair : in_files
|
||||
|
Loading…
x
Reference in New Issue
Block a user