From mboxrd@z Thu Jan 1 00:00:00 1970 Return-Path: Received: from mp12.migadu.com ([2001:41d0:403:58f0::]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits)) by ms8.migadu.com with LMTPS id yGVSJYEfXWX9bAEAauVa8A:P1 (envelope-from ) for ; Tue, 21 Nov 2023 22:22:09 +0100 Received: from aspmx1.migadu.com ([2001:41d0:403:58f0::]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits)) by mp12.migadu.com with LMTPS id yGVSJYEfXWX9bAEAauVa8A (envelope-from ) for ; Tue, 21 Nov 2023 22:22:09 +0100 Received: from lists.gnu.org (lists.gnu.org [209.51.188.17]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by aspmx1.migadu.com (Postfix) with ESMTPS id BB1B03CB73 for ; Tue, 21 Nov 2023 22:22:08 +0100 (CET) Authentication-Results: aspmx1.migadu.com; dkim=none; dmarc=none; spf=pass (aspmx1.migadu.com: domain of "guix-patches-bounces+larch=yhetil.org@gnu.org" designates 209.51.188.17 as permitted sender) smtp.mailfrom="guix-patches-bounces+larch=yhetil.org@gnu.org" ARC-Seal: i=1; s=key1; d=yhetil.org; t=1700601729; a=rsa-sha256; cv=none; b=NN3BEEXw4X3EoVTc7pDGGwK4FX1o4AADZe8zRFMfFGYKNM70qeuOxO1O8FuymeUskQnCae 2dcvDY8SWLRL4YSId4w5mC5XTlY+VoavfxIPefBpboib6KcmsyzpfOrna72ynOTwtJipwu wotW8d8DueBg4W9XfU5C3Yfn1ztsxekN22QxI/K53prSwqnMELgqvlRcgaEtGtLfOwFXAX DEEIj5fLBHy+9MHGs58N2qPdpHLnFlIG19/xzOLxl4NEk4EY/RPGpRGmRE6Oul1oFFkdUC sNgbz6Lg7flUiXSQlDlW0tB0P3zRLMtRKdD8An1AWRhR0Cu9gz/JIbI4/VwL5A== ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=yhetil.org; s=key1; t=1700601729; 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:resent-cc: resent-from:resent-sender:resent-message-id:in-reply-to:in-reply-to: references:references:list-id:list-help:list-unsubscribe: list-subscribe:list-post; bh=hnzZC6SPX0Bpw01pQvAILLUAU9GEEgicZmwFaUPqC+s=; b=rxIpmwIvTcTmk0Qp8tL0x3fOvC6TPv+/aAw4095XIVVNsftvtA4eHq211znCTOfNWlOE/F GFqNZ4MwNliuBOXCCydmEONF/g/mvHoM/+338h0XQXnCG+KftBTEVtye3KwyaFb275vYxK EyNM3l8Qyaqi2j9B3yaI7HnId7cJbQn02HcUf5/l03iiMgKAAY35qlva0EDdSZZ4RT9t7V iYMC0I3WcNyyHh9AzX5qpxtiS3vq6JDaqCoRSuFDfxvepyFw6fmu8XJz3w+wW31qV9fVNx SzgILagP7YXFWMBYKe510fZSw+yX1gEdwK428fu7FwgTf56rVZ8nig0dtXK3rg== ARC-Authentication-Results: i=1; aspmx1.migadu.com; dkim=none; dmarc=none; spf=pass (aspmx1.migadu.com: domain of "guix-patches-bounces+larch=yhetil.org@gnu.org" designates 209.51.188.17 as permitted sender) smtp.mailfrom="guix-patches-bounces+larch=yhetil.org@gnu.org" Received: from localhost ([::1] helo=lists1p.gnu.org) by lists.gnu.org with esmtp (Exim 4.90_1) (envelope-from ) id 1r5YBy-0001Ru-Ky; Tue, 21 Nov 2023 16:21:58 -0500 Received: from eggs.gnu.org ([2001:470:142:3::10]) by lists.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_256_GCM_SHA384:256) (Exim 4.90_1) (envelope-from ) id 1r5Vk4-00075b-IP for guix-patches@gnu.org; Tue, 21 Nov 2023 13:45:00 -0500 Received: from debbugs.gnu.org ([2001:470:142:5::43]) by eggs.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_128_GCM_SHA256:128) (Exim 4.90_1) (envelope-from ) id 1r5Vk4-00069u-51 for guix-patches@gnu.org; Tue, 21 Nov 2023 13:45:00 -0500 Received: from Debian-debbugs by debbugs.gnu.org with local (Exim 4.84_2) (envelope-from ) id 1r5Vk6-00033t-Le for guix-patches@gnu.org; Tue, 21 Nov 2023 13:45:02 -0500 X-Loop: help-debbugs@gnu.org Subject: [bug#64349] [PATH] Guix service for robust and flexible persistent ssh forwarding Resent-From: Runciter Original-Sender: "Debbugs-submit" Resent-CC: guix-patches@gnu.org Resent-Date: Tue, 21 Nov 2023 18:45:02 +0000 Resent-Message-ID: Resent-Sender: help-debbugs@gnu.org X-GNU-PR-Message: followup 64349 X-GNU-PR-Package: guix-patches X-GNU-PR-Keywords: patch moreinfo To: Bruno Victal , 64349@debbugs.gnu.org Received: via spool by 64349-submit@debbugs.gnu.org id=B64349.170059229811737 (code B ref 64349); Tue, 21 Nov 2023 18:45:02 +0000 Received: (at 64349) by debbugs.gnu.org; 21 Nov 2023 18:44:58 +0000 Received: from localhost ([127.0.0.1]:57417 helo=debbugs.gnu.org) by debbugs.gnu.org with esmtp (Exim 4.84_2) (envelope-from ) id 1r5Vjy-00033C-5A for submit@debbugs.gnu.org; Tue, 21 Nov 2023 13:44:58 -0500 Received: from mx1.polytechnique.org ([129.104.30.34]:60548) by debbugs.gnu.org with esmtp (Exim 4.84_2) (envelope-from ) id 1r5TIO-0007Gk-56 for 64349@debbugs.gnu.org; Tue, 21 Nov 2023 11:08:20 -0500 Received: from ubik (unknown [36.106.156.121]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by ssl.polytechnique.org (Postfix) with ESMTPSA id 636255646E5; Tue, 21 Nov 2023 17:08:08 +0100 (CET) From: Runciter In-Reply-To: <87cyxj7vyu.fsf@pkbd.org> (runciter@pkbd.org's message of "Thu, 12 Oct 2023 22:32:09 +0800") References: <87352a4541.fsf@pkbd.org> <54efe1c6-6a81-497d-8b8b-0b499cfc2acb@makinata.eu> <87cyxj7vyu.fsf@pkbd.org> Date: Wed, 22 Nov 2023 00:08:04 +0800 Message-ID: <87y1erks2j.fsf@whispers-vpn.org> User-Agent: Gnus/5.13 (Gnus v5.13) MIME-Version: 1.0 Content-Type: text/plain; charset=utf-8 Content-Transfer-Encoding: quoted-printable X-AV-Checked: ClamAV using ClamSMTP at svoboda.polytechnique.org (Tue Nov 21 17:08:11 2023 +0100 (CET)) X-Mailman-Approved-At: Tue, 21 Nov 2023 13:44:52 -0500 X-BeenThere: debbugs-submit@debbugs.gnu.org X-Mailman-Version: 2.1.18 Precedence: list X-Mailman-Approved-At: Tue, 21 Nov 2023 16:21:57 -0500 X-BeenThere: guix-patches@gnu.org List-Id: List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: guix-patches-bounces+larch=yhetil.org@gnu.org Sender: guix-patches-bounces+larch=yhetil.org@gnu.org X-Migadu-Flow: FLOW_IN X-Migadu-Country: US X-Migadu-Scanner: mx11.migadu.com X-Spam-Score: -6.32 X-Migadu-Queue-Id: BB1B03CB73 X-Migadu-Spam-Score: -6.32 X-TUID: 6rXaFH697oZw Runciter writes: Hi, I've been busy with the job sending me on trips all the time, making it difficult to progress as I'd have liked to. I only just finished writing a documentation for the module (gnu services ssh-tunneler). It comes as a standalone manual. I will now try to add a test for the module with a marionette, as I said before. The following patch add doc/ssh-tunneler.texi to the source tree as well as a minimal stance to compile it into doc/ssh-tunneler.info, and making git ignore the compile info manual. Also in the patch, some very minor changes to gnu/services/ssh-tunneler.scm, which probably and hopefully shouldn't break the module. --- .gitignore | 1 + doc/local.mk | 3 +- doc/ssh-tunneler.texi | 979 ++++++++++++++++++++++++++++++++++ gnu/services/ssh-tunneler.scm | 837 +++++++++++++++++++++++++++++ 4 files changed, 1819 insertions(+), 1 deletion(-) create mode 100644 doc/ssh-tunneler.texi create mode 100644 gnu/services/ssh-tunneler.scm diff --git a/.gitignore b/.gitignore index 0f74b5da3d..57086aac7b 100644 --- a/.gitignore +++ b/.gitignore @@ -67,6 +67,7 @@ /doc/stamp-vti /doc/version.texi /doc/version-*.texi +/doc/ssh-tunneler.info /etc/committer.scm /etc/gnu-store.mount /etc/guix-daemon.cil diff --git a/doc/local.mk b/doc/local.mk index 97f0c3a92a..94c12456cd 100644 --- a/doc/local.mk +++ b/doc/local.mk @@ -42,7 +42,8 @@ info_TEXINFOS =3D %D%/guix.texi \ %D%/guix-cookbook.de.texi \ %D%/guix-cookbook.fr.texi \ %D%/guix-cookbook.ko.texi \ - %D%/guix-cookbook.sk.texi + %D%/guix-cookbook.sk.texi \ + %D%/ssh-tunneler.texi =20 %C%_guix_TEXINFOS =3D \ %D%/contributing.texi \ diff --git a/doc/ssh-tunneler.texi b/doc/ssh-tunneler.texi new file mode 100644 index 0000000000..fad6a5ec34 --- /dev/null +++ b/doc/ssh-tunneler.texi @@ -0,0 +1,979 @@ +\input texinfo +@c -*-texinfo-*- + +@c %**start of header +@setfilename ssh-tunneler.info +@documentencoding UTF-8 +@settitle SSH Tunneler Reference Manual +@c %**end of header + +@set UPDATED 31 October 2023 +@set UPDATED-MONTH October 2023 +@set EDITION 0.1.0 +@set VERSION 0.1.0 + +@copying +Copyright @copyright{} 2023 Runciter@* + +Permission is granted to copy, distribute and/or modify this document +under the terms of the GNU Free Documentation License, Version 1.3 or +any later version published by the Free Software Foundation; with no +Invariant Sections, no Front-Cover Texts, and no Back-Cover Texts. A +copy of the license is included in the section entitled ``GNU Free +Documentation License''. +@end copying + +@dircategory System administration +@direntry +* SSH tunneler: (ssh-tunneler). Daemonized SSH forwardings for GNU Guix +@end direntry + +@titlepage +@title SSH Tunneler Reference Manual +@subtitle Daemonized SSH Forwardings for GNU Guix +@author Runciter + +@page +@vskip 0pt plus 1filll +Edition @value{EDITION} @* +@value{UPDATED} @* + +@insertcopying +@end titlepage + +@contents + +@c ********************************************************************* +@node Top +@top SSH Tunneler + +This document describes SSH Tunneler version @value{VERSION}, a ssh +forwarding service written for GNU Guix. + +The @code{(gnu packages ssh-tunneler)} module provides Guix services +extending a root or home shepherd with daemonized client ssh connections +establishing all types of ssh forwardings: + +@table @code + +@cindex port forwarding +@item Port forwarding +Port forwardings, which can be established using the @command{-L} switch +of the ssh command, forward connections to a port or socket of the ssh +client to a port or socket of the sshd, or to a port of another remote +host whose port becomes reachable through client host's port, +transported through the sshd host. + +@cindex reverse port forwarding +@item Reverse port forwarding +Reverse port forwardings, which can be established using the +@command{-R} switch of the ssh command, forward connections to a port or +socket of the sshd to a port or socket of the ssh client, or to a port +of another remote host whose port becomes reachable through the sshd +host's port, transported through the client host. + +@cindex dynamic forwarding +@item Dynamic forwarding +Dynamic forwardings, which can be established using the @command{-D} +switch of the ssh command, expose the sshd as a SOCKS proxy that a +network program which supports this type of proxying can reach through a +port of the ssh client. + +@cindex TUN device forwarding +@item TUN device forwarding +Tun device forwardings, a.k.a. ``ssh tunnels'' in the vernacular, which +can be established using the @command{-w} switch of the ssh command, +create a TUN software network device on the ssh client and another such +device on the sshd, such that all network packets routed through either +of these TUN device are encrypted and can be de-encrypted by the TUN +device on the other end of the tunnel, should they be transported there +through other (ultimately physical) network devices. + +@end table + +@menu +* Purpose:: +* Configuration:: +* Shepherd actions:: +* GNU Free Documentation License:: +* Concept Index:: +* Programming Index:: +@end menu + +@c ********************************************************************* +@node Purpose +@chapter Purpose + +Apart from the proverbial ease with which its adepts are empowered to +work-around the firewalls of their place of employment, ssh forwarding +has several other useful applications, non-exhaustively listed +here. The services that the @code{(gnu packages ssh-tunneler)} module +extends are an attempt to make these available to Guix users, easily +configurable, robustly daemonized, and when needed in their most +unstoppable form. + +@table @code + +@cindex remote shell access +@item Remote shell access +Through reverse port forwarding, the sshd of a home computer stuck +behind a dynamic IP router can be made permanently available through a +chosen port of a VPS. On the shepherd side, ssh-tunneler provides +features to @command{resurrect} the reverse forwarding in case the +connection to the VPS is unreliable. @xref{Remote shell access} and +@ref{Resurrected remote shell access} for example configurations. + +@cindex censorship-resistant web browsing +@item Censorship-resistant web browsing +Many web browsers support SOCKS v4 or v5 proxies. Any sshd can act as +such a proxy, nearly out-of-the-box (@pxref{sshd configuration}). The +ssh-tunneler module can be used to turn a remote sshd into a SOCKS proxy +reachable through a chosen port of localhost. The proxy host can be a +VPS with a sshd under the user's full control, or a server from a +company offering a simple commercial SOCKS proxying service. When the +proxy host is located outside the area where a local censorship IP +blacklist is enforced, such censorship is effectively nullified for +purpose of web browsing. At the time of writing, proxy hosts reached +exclusively in this way seem to be immune to detection by advanced +packet-scanning techniques... or at the very least, spared from +automatic blacklisting. @xref{Dynamic forwarding to a SOCKS v5 proxy} +for an example configuration. + +@cindex VPN +@item VPN +When augmented with appropriate network addressing, routing and +@code{iptables} stances on the client and server side, ssh tunnels can +support the operation of a VPN. At the time of writing, such +augmentations probably have to be setup by the user manually or using +shell scripts, since the ssh-tunneler module only supports the creation +of the ssh tunnel proper. @xref{ssh tunnel for a VPN} for an example +configuration of a service extending a ssh tunnel. There are plans in +the works to create other services that will enable a set of computers +all running Guix to unite into a small dynamically addressed VPN. + +@cindex stealth VPN +@item Stealth VPN +It is possible, albeit in a pretty hackish way, to establish a ssh +tunnel through an intermediate SOCKS proxy. At the time of writing, +similar to what is mentioned above, when the proxy host is located +outside an area where packets are being scanned for VPN connection +signatures, this method protects the VPN server host and the proxy host +from being blacklisted. The ssh-tunneler module supports the +establishment of such ``stealth'' tunnels through a SOCKS +proxy. @xref{Proxyed ssh tunnel for a stealth VPN} for an example +configuration. + +@end table + +@c ********************************************************************* +@node Configuration +@chapter Configuration + +@c ********************************************************************* +@menu +* Client system configuration:: +* sshd configuration:: +* Configuration examples:: +@end menu + +@node Client system configuration +@section Client system configuration + +In order to establish the persistent forwardings, the client has to +extend a service (@pxref{Services,,, guix, GNU Guix}) from its system +configuration file (@pxref{Using the Configuration System,,, guix, GNU +Guix}). + +@defvar persistent-ssh-service-type +This is the type for the service extending the shepherd with a +daemonized ssh connection. Its value must be an +@code{ssh-connection-configuration} record. + +@end defvar + +@deftp {Data Type} ssh-connection-configuration +This is the configuration record for a ssh connection daemonized by the +shepherd. + +@table @asis + +@item @code{shepherd-package} (default @code{shepherd}) +A file-like object. The shepherd package to use + +@item @code{ssh-package} (default @code{openssh}) +A file-like object. The openssh package to use. + +@item @code{netcat-package} (default @code{netcat-openbsd})) +A file-like object. The netcat-openbsd package to use. + +@item @code{sshpass-package} (default @code{sshpass}) +A file-like object. The sshpass package to use. + +@item @code{ineutils-package} (default @code{inetutils}) +A file-like object. The inetutils package to use. + +@item @code{procps-package} (default @code{procps}) +A file-like object. The procps package to use. + +@item @code{socks-proxy-config} (default @code{(socks-proxy-configuration)= }) +A guix record of type @code{socks-proxy-configuration}, configuring +proxying of the connection opened by the service. See below for the +record's documentation. + +@item @code{id-rsa-file?} (default @code{#t}) +A boolean value. Whether to authenticate to the sshd from a private key +loaded from a file. + +@item @code{id-rsa-file} (default @code{"/root/.ssh/id_rsa"}) +A string. When configured to do so, the path to the private key file to +load in order to authenticate to the sshd. + +@item @code{clear-password?} (default @code{#f}) +A boolean value. Whether to authenticate to the sshd with a clear +password. Setting this field to @code{#t} is not recommended for +security, especially on a multi-user machine, among other concerns +because a password will be written into the Guix store in clear text. + +@item @code{sshd-user-password} (default @code{"none"}) +A string. When configured to do so, the clear text password to use to +authenticate the connection. About security, see the reservations above. + +@item @code{sshd-user} (default @code{"root"}) +A string, the UNIX handle of the user to authenticate as on the sshd. + +@item @code{sshd-host} (default @code{"127.0.0.1"}) +A string defining an IP address. The IP of the sshd to connect to. + +@item @code{sshd-port} (default @code{22}) +An integer. The port used to connect to the sshd. + +@item @code{gateway-ports?} (default @code{#t}) +A boolean value. Whether to activate the GatewayPorts switch @emph{on +the client side}. This is the @emph{ssh_config} GatewayPorts, @emph{not} +the @emph{sshd_config} GatewayPorts. + +@item @code{name-prefix} (default @code{"ssh-forwards"}) +A string. The prefix of the service provision of the shepherd service +supporting the connection. To this prefix will be by default appended a +suffix computed from the characteristics of the forwarding(s) configured +for the connection, and its proxy, if any. The resulting string will be +converted to a symbol. + +@item @code{suffix-name?} (default @code{#t}) +A boolean value. Whether to append an automatically computed suffix to +the shepherd provision of the service supporting the connection. + +@item @code{special-options} (default @code{'()}) +A list of strings. A list of options to add to the ssh command of the +connection. + +@item @code{forwards} (default @code{'()}) +A list of @code{ssh-forward-configuration} records. + +@item @code{exit-forward-failure?} (default @code{#t})) +A boolean value. Whether to active the ExitOnForwardFailure +ssh configuration switch for the connection. + +@item @code{connection-attempts} (default @code{1})) +An integer. the value assigned to to the ConnectionAttempts ssh +configuration switch of the connection. + +@item @code{local-command?} +A boolean value. Its default is computed at system reconfiguration +time. Whether to execute a command locally on the client after +successfully creating the forwardings of the connection. If the shepherd +service uses a PID file, which is the default, setting this options to +@code{#f} will prevent the service from starting successfully. + +@item @code{extra-local-commands} (default @code{'()}) +A list of strings. A list of commands to execute locally on the client +after successfully creating the forwardings of the connection and +starting the shepherd service. + +@item @code{require-networking?} (default @code{#t}) +A boolean value. Whether the @code{networking} service should be +included in the requirements of the shepherd service of the connection. + +@item @code{extra-requires} (default @code{'()}) +A list of symbols. A list of extra requirements for the shepherd service +of the connection. + +@item @code{elogind?} (default @code{#f}) +A boolean value. + +@item @code{pid-file?} (default @code{#t}) +A boolean value. Whether the shepherd should use a PID file for the +service of the connection. + +@item @code{pid-folder-override?} (default @code{#f}) +A boolean value. Whether to override the shepherd's global default for +the folder of the PID file of the service. + +@item @code{pid-folder-override} (default @code{"/var/run"}) +A string. When configured to override the shepherd's global default, the +path to the folder where to store the PID file of the service. + +@item @code{timeout-override?} (default @code{#f}) +A boolean value. Whether to override the shepherd's global default for +the timeout of the service startup. + +@item @code{timeout-override} (default @code{5}) +An integer. When configured to override the shepherd's global default, +the timeout of the service startup. + +@item @code{dedicated-log-file?} (default @code{#f}) +A boolean value. Whether the service should log to a dedicated file. + +@item @code{log-rotate?} (default @code{#f}) +A boolean value. Whether the dedicated log file of the service should be +rotated by @command{rottlog}. This is an experimental feature. + +@item @code{log-folder-override?} (default @code{#f}) +A boolean value. Whether to override the shepherd's global default for +the log folder of the service. + +@item @code{log-folder-override} (default @code{"/var/run"}) +A string. When configured to override the shepherd's global default, the +folder where to store the log file of the service. + +@item @code{verbosity} (default @code{0}) +An integer between 0 and 3, both included. The verbosity level of the +ssh command of the connection, equal to the number of times the +@command{-v} switch of ssh is used. + +@item @code{command?} (default @code{#f}) +A boolean value. Whether to execute a command on the sshd host after +successfully creating the forwardings of the connection. + +@item @code{command} (default @code{'()}) +A string. When configured to do so, the command to be executed on the +sshd after successfully establishing the forwardings of the connection. + +@item @code{resurrect-time-spec} (default @code{''(next-minute '(47))}) +A quoted cron job time specification, for which the author would like to +extend his most sincere apologies to the user. See the default value for +an example of this field's format. The quoted time specification of the +cron job extended to @command{resurrect} the service, when configured to +do so. @pxref{Shepherd actions}. + +@item @code{flat-resurrect?} (default @code{#f}) +A boolean value. Whether to @emph{not} recursively @command{resurrect} +the service. + +@item @code{force-resurrect-time-spec} (default @code{''(next-hour '(3))}) +A quoted cron job time specification. Apologies repeated. The quoted +time specification of the cron job extended to @command{force-resurrect} +the service, when configured to do so. @pxref{Shepherd actions}. + +@item @code{flat-force-resurrect?} (default @code{#f}) +A boolean value. Whether to @emph{not} recursively +@command{force-resurrect} the service. + +@item @code{%cron-resurrect?} (default @code{#f}) +A boolean value. Whether to automatically @command{resurrect} the +service by means of a cron job. + +@item @code{%cron-force-resurrect?} (default @code{#f}) +A boolean value. Whether to automatically @command{force-resurrect} the +service by means of a cron job. + +@item @code{%auto-start?} (default @code{#f}) +A boolean value. Whether to automatically start the service on +boot. This feature is experimental, and unreliable. + +@end table +@end deftp + +@deftp {Data Type} ssh-forward-configuration +This is the configuration record for one of the forwardings provided by +a daemonized ssh connection. + +@table @asis + +@item @code{forward-type} (default @code{'dynamic}) +A symbol which can be @code{'dynamic}, @code{'port}, +@code{'reverse-port} or @code{'tunnel}. + +@item @code{entry-type} (default @code{'port}) +A symbol which can be @code{'preset} or @code{'any} when the +@code{'forward-type} field is @code{'tunnel}, and which can be +@code{'port} or @code{'socket} otherwise. It is ignored when the +@code{'forward-type} field is @code{'dynamic.} + +@item @code{exit-type} (default @code{'port}) +A symbol which can be @code{'preset} or @code{'any} when the +@code{forward-type} field is @code{'tunnel}, and which can be +@code{'port} or @code{'socket} otherwise. It is ignored when the +@code{forward-type} field is @code{'dynamic}. + +@item @code{entry-port} (default @code{8971}) +An integer. When the @code{forward-type} is @code{'dynamic}, +@code{'port} or @code{'reverse-port} and the @code{entry-type} is +@code{'port}, the port to forward from at the entry of the forwarding. + +@item @code{exit-port} (default @code{22}) +An integer. When the @code{forward-type} is @code{'port} or +@code{'reverse-port} and the @code{exit-type} is @code{'port}, the port +to forward to at the final destination of the forwarding. + +@item @code{entry-socket} (default @code{""})) +A string. When the @code{forward-type} is @code{'port} or +@code{'reverse-port} and the @code{entry-type} is @code{'socket}, the +path to the socket file to forward from at the entry of the +forwarding. This is an experimental feature. + +@item @code{exit-socket} (default @code{""}) +A string. When the @code{forward-type} is @code{'port} or +@code{'reverse-port} and the @code{exit-type} is @code{'socket}, the path +to the socket file to forward to at the final destination of the +forwarding. This is an experimental feature. + +@item @code{forward-host} (default @code{"127.0.0.1"}) +A string representing an IP address. The final destination host of the +forwarding, applicable when the @code{forward-type} is @code{'port} or +@code{'reverse-port}. + +@item @code{entry-tun} (default @code{0}) +An integer. When the @code{forward-type} is @code{'tunnel} and the +@code{entry-type} is @code{'preset}, the TUN interface +number (tunX) on the side of the client extending the service. + +@item @code{exit-tun} (default @code{0}))) +An integer. When the @code{forward-type} is @code{'tunnel} and the +@code{exit-type} is @code{'preset}, the TUN interface number (tunX) on +the sshd side. + +@end table +@end deftp + +@deftp {Data Type} socks-proxy-configuration +This is the configuration record for the SOCKS proxy used by a +daemonized ssh connection to connect to the sshd. + +@table @asis + +@item @code{use-proxy?} (default @code{#f}) +A boolean value. Whether to establish the ssh connection configured by +the parent record through a SOCKS proxy. For as much as the rest of this +record's documentation may be confusing to the first-time reader +(sorry), he might feel relieved to note that it is sufficient to set the +value of this field to @code{#t} to proxy a single daemonized ssh +connection through a default port of localhost. + +@item @code{extend?} +A boolean value. Whether the ssh connection connection supporting the +SOCKS proxy should be auto-magically extended as a shepherd service on +whose provision the ssh connection configured by the parent record will +depend. While, strictly speaking, the default of the @code{extend?} +field is computed at Guix system-reconfiguration time, the default +behavior is to auto-magically extend a shepherd service adequately +configured to expose the proxy on localhost whenever the user +configuring the Guix system has elected to use such a proxy through the +@code{use-proxy?} field of this record. Overriding this default behavior +is experimental, and can be achieved by explicitly setting the +@code{extend?} field to @code{#f} in your system configuration. + +@item @code{port} +An integer. Its default is computed at Guix system-reconfiguration +time. Overriding this default is experimental. The port of localhost to +use to connect to the SOCKS proxy. + +@item @code{dynamic-forward} +A value which can be @code{#f}, or a Guix record returned by a call to +@code{ssh-connection-configuration}. In the latter case, this field +defines the configuration record for the service of type +@code{persistent-ssh-service-type} that will extend the connection +supporting the proxy for the connection configured by the parent record, +if any. The value @code{#f} is probably always the most adequate when +the connection extended by the parent record will not use a SOCKS proxy, +and it does not need to be changed by the user. When the user +configuring a the system extends a SOCKS proxy, he may optionally wish +to change the value of the @code{dynamic-forward} field from its +computed default, for example if he wants to use a non-default port, one +requirement being that it must then be a configuration record for a +connection creating a dynamic port forward as the first member of its +list of @code{forwards}. + +@end table +@end deftp + +For the user's convenience, macros are provided as helpers to +instantiate @code{ssh-forward-configuration} records with sane defaults +preset for the supported types of forwardings. Field values of the +record @code{ssh-forward-configuration} returned by these macros can be +changed from their defaults exactly as if instantiating a +@code{ssh-forward-configuration} record directly. + +@defmac dynamic-forward-configuration @dots{} + +This macro (included for the sake of completeness) returns an +@code{ssh-forward-configuration} record with sane defaults set to +configure a dynamic port forwarding in the service. The defaults of +fields are actually not changed from a direct call to +@code{dynamic-forward-configuration}. @pxref{Dynamic forwarding to a +SOCKS v5 proxy} for an example usage. + +@end defmac + +@defmac port-forward-configuration @dots{} + +This macro returns an @code{ssh-forward-configuration} record with sane +defaults set to configure a port forwarding in the service. The default +of the @code{forward-type} field of the returned record is changed to +@code{'port}, and the default of the @code{entry-port} field of the +record is chanted to @code{6947}. @xref{Port forwarding example} for an +example usage. + +@end defmac + +@defmac reverse-port-forward-configuration @dots{} + +This macro returns an @code{ssh-forward-configuration} record with sane +defaults set to configure a reverse port forwarding in the service. The +default of the @code{forward-type} field of the returned record is +changed to @code{'reverse-port}, and the default of the +@code{entry-port} field of the record is chanted to +@code{6283}. @xref{Remote shell access} for an example usage. + +@end defmac + +@defmac tunnel-forward-configuration @dots{} + +This macro returns an @code{ssh-forward-configuration} record with sane +defaults set to configure a tunnel forwarding in the service. The +default of the @code{forward-type} field of the returned record is +changed to @code{'tunnel}, the default of the @code{entry-type} field of +the record is chanted to @code{'any}, and the default of the +@code{exit-type} field of the record is also changed to @code{'any} +. @xref{ssh tunnel for a VPN} for an example usage. + +@end defmac + +@c ********************************************************************* +@node sshd configuration +@section sshd configuration + +Any machine running a sshd can be the server for the connection, it +doesn't need to be running Guix System. + +By default, the client extending the service will connect to the sshd as +root. Depending on the specifics of your sshd host, you might wish to +change that in order to improve security. You can do it by setting a +non-default value for the @code{sshd-user} field of the +@code{ssh-connection-configuration} record of the client service, and +probably still enjoy functionality provided you do not want to extend a +ssh tunnel or to reverse forward a priviledged port of the sshd. + +Configuring the sshd should be easy, there are 2 items to consider: + +@table @code + +@item Authentication +The service extended by the client must authenticate its connection to +the sshd. The recommended way is through public key authentication. To +achieve it, the public key corresponding to the private key configured +by the @code{ssh-connection-configuration} record of the service must be +registered as authorized for the user of the sshd host that the +connection authenticates as. In common situations (but see above), and +with default values of the @code{id-rsa-file?}, @code{id-rsa-file} and +@code{sshd-user} fields of the client's configuration record, you should +be able to authenticate by adding the contents of the +@code{/root/.ssh/id_rsa.pub} file of the client to the +@code{/root/.ssh/authorized_keys} file of the sshd. Alternatively, when +the sshd itself is also extended as a Guix service on the server host, +Guix provides a nice facility to extend public key authorizations +(@pxref{Networking Services,,, guix, GNU Guix}). + +@item GatewayPorts sshd option +For most uses of the ssh-tunneler service, it should be either practical +or necessary to set @code{GatewayPorts=3Dyes} in the configuration of +sshd, for example by adding this option switch to the @code{sshd_config} +file. + +@end table + +@c ********************************************************************* +@node Configuration examples +@section Configuration examples + +This section provides a collection of client configuration examples for +typical uses of the @code{(gnu packages ssh-tunneler)} module, including +the applications described in @ref{Purpose}. + +Throughout this section, @code{1.2.3.4} is used as a placeholder for the +IP address of the sshd host to which the client extending a forwarding +service connects. + +@menu +* Port forwarding example:: +* Remote shell access:: +* Resurrected remote shell access:: +* Dynamic forwarding to a SOCKS v5 proxy:: +* Clear password authentication:: +* ssh tunnel for a VPN:: +* Proxyed ssh tunnel for a stealth VPN:: +@end menu + +@c ********************************************************************* +@node Port forwarding example +@subsection Port forwarding + +This example extends a port forwarding service which forwards the Guix +client's port 1357 to the port 2468 of a third host, which the sshd +at IP 1.2.3.4 reaches at IP 5.6.7.8. + +The daemonized ssh forwarding stance is @command{-L 1357:5.6.7.8:2468}. + +@cindex port forwarding, example +@lisp + (ssh-connection-configuration + (sshd-user "joe-chip") ; "root" is the default + (sshd-host "1.2.3.4") ; change to IP of sshd as string + (forwards + (list (port-forward-configuration + (entry-port 1357) ; 8971 is the default + (exit-host "5.6.7.8") ; default is the sshd's localhost + (exit-port 2468)))))) ; 22 is the default +@end lisp + +You can start the extended service with the following shell command as +root: + +@example +herd start ssh-forwards@@port,1357:5.6.7.8:2468 +@end example + +@c ********************************************************************* +@node Remote shell access +@subsection Remote shell access + +This example extends a simple reverse port forwarding service, of the +kind that can be used for remote shell access to the local machine, +should this machine be firewalled or stuck behind a dynamic IP. + +The daemonized ssh forwarding stance is @command{-R 1357:localhost:22}. + +@cindex reverse port forwarding, example +@cindex remote shell access, example +@lisp +(service persistent-ssh-service-type + (ssh-connection-configuration + (sshd-user "joe-chip") ; "root" is the default + (sshd-host "1.2.3.4") ; change to IP of sshd as string + (forwards + (list (reverse-port-forward-configuration + (entry-port 1357) ; 8971 is the default + (exit-port 22)))))) ; 22 is the default +@end lisp + +Note that if the port 1357 of the sshd is not priviledged, it is +possible to extend a connection to the sshd as a non-root user, such as +in this example. + +You can start the extended service with the following shell command as +root: + +@example +herd start ssh-forwards@@reverse-port,1357:localhost:22 +@end example + +After setting @code{GatewayPorts=3Dyes} on the sshd and starting the +extended shepherd service on the client, you can start a remote shell +session on the client through the sshd's IP on port 1357, for example by +running this command to start a remote shell as the client's UNIX handle +@code{pat-conley}: + +@example +ssh -p 1357 pat-conley@@1.2.3.4 +@end example + +@c ********************************************************************* +@node Resurrected remote shell access +@subsection Remote shell access + +This example defines exactly the same reverse port forwarding service as +the previous one, @xref{Remote shell access}. As an added feature of the +service, mcron jobs are extended to improve its robustness. Those are +especially useful and perhaps @emph{necessary} if you cannot physically +attend the machine which daemonizes the connection for prolonged periods +of time. + +The daemonized ssh forwarding stance is @command{-R 1357:localhost:22}. + +@cindex resurrected reverse port forward, ex. +@cindex resurrected remote shell access, ex. +@lisp +(service persistent-ssh-service-type + (ssh-connection-configuration + (sshd-user "joe-chip") ; "root" is the default + (sshd-host "1.2.3.4") ; change to IP of sshd as string + (%cron-resurrect? #t) ; #f is the default + (resurrect-time-spec ''(next-minute '(38))) + (%cron-force-resurrect? #t) ; #f is the default + (force-resurrect-time-spec ''(next-hour '(3))) + (forwards + (list (reverse-port-forward-configuration + (entry-port 1357) ; 8971 is the default + (exit-port 22)))))) ; 22 is the default +@end lisp + +You can start the extended service with the following shell command as +root: + +@example +herd start ssh-forwards@@reverse-port,1357:localhost:22 +@end example + +In this example, the daemonized ssh connection is resurrected at the +38th minute of every hour and forcefully resurrected at 03:00AM every +day. + +For the sake of the illustration's completeness, 2 mcron jobs are +extended by the configured example service. If your situation makes +resurrection desirable, you should probably @code{resurrect} your +tunneler service(s) with a mcron job. If you have decided to +@code{resurrect} a service, you should only then consider if you also +want to @code{force-resurrect} this service by means of a second cron +job. Forced resurrection can be useful in the event a long-running +daemonized ssh connection has stopped providing its forwardings. + +Resurrecting a started service should be completely innocuous to the +running service being resurrected and consume only a small amount of +shepherd run-time. The author considers a frequency of once per hour for +the mcron job of the @code{resurrect} action to be adequate. By +contrast, in most situations, it is expected to be counter-productive to +@code{force-resurrect} too frequently. The author recommends a maximum +frequency of once a day for forced resurrection. + +In the event that you resurrect and/or forcefully resurrect multiple +services, it might be (tbc) good practice to spread the times at which +the mcron jobs are performed by a couple of minutes or more. + +@c ********************************************************************* +@node Dynamic forwarding to a SOCKS v5 proxy +@subsection Dynamic forwarding to a SOCKS v5 proxy + +This example extends a dynamic forwarding service, making the sshd host +available as a SOCKS proxy. + +The daemonized ssh forwarding stance is @command{-D 1357}. + +@cindex dynamic forwarding, example +@cindex SOCKS proxy, example +@cindex censorship-resistant browsing, ex. +@lisp +(service persistent-ssh-service-type + (ssh-connection-configuration + (sshd-user "joe-chip") ; "root" is the default + (sshd-host "1.2.3.4") ; change to IP of sshd as string + (forwards + (list (dynamic-forward-configuration + (entry-port 1357)))))) ; 8971 is the default +@end lisp + +You can start the extended service with the following shell command as +root: + +@example +herd start ssh-forwards@@dynamic,1357 +@end example + +In graphical web browsers, proxy settings are generally accessible +through a settings dialog. You would setup a proxy of type @code{SOCKS5} +or @code{SOCKS v5} with proxy host @code{localhost} on port @code{1357}. + +For such a general web browsing use case, you definitely need +@code{GatewayPorts=3Dyes} to be set for the proxy sshd at 1.2.3.4, for +example in its @code{sshd_config} file. + +@c ********************************************************************* +@node Clear password authentication +@subsection Clear password authentication + +This example defines exactly the same dynamic forwarding as the previous +one, @xref{Dynamic forwarding to a SOCKS v5 proxy}. The difference is +that authentication is achieved with a password extended in clear text +from the Guix service's configuration record, and by wrapping the ssh +command in a call to the @command{sshpass} program. + +This type of configuration might expose a clear password to other users +of the machine, and is not recommended. In any case, it should be +reserved for situations where key pair authentication is not available, +and only when the extended clear password does not protect any +confidential data. + +When the client extending the service is a multi-user machine, this is +even worse security than merely using the sshpass program from +command-line, because the clear-text password will end up in the Guix +store. + +The daemonized ssh forwarding stance is @command{-D 1357}. + +@cindex clear password, example +@lisp +(service persistent-ssh-service-type + (ssh-connection-configuration + (sshd-user "joe-chip") ; "root" is the default + (sshd-host "1.2.3.4") ; change to IP of sshd as string + (clear-password? #t) ; what do you think you're doing? + (sshd-user-password "12345") ; here's hoping yours is better + (forwards + (list (dynamic-forward-configuration + (entry-port 1357)))))) ; 8971 is the default +@end lisp + +You can start the extended service with the following shell command as +root: + +@example +herd start ssh-forwards@@dynamic,1357 +@end example + +@c ********************************************************************* +@node ssh tunnel for a VPN +@subsection ssh tunnel for a VPN + +This example extends a ssh tunnel for purpose of supporting a connection +to the sshd as a VPN server. + +The daemonized ssh forwarding stance is @command{-w any:any}. + +@cindex TUN device forwarding, example +@cindex ssh tunnel, example +@cindex VPN, example +@lisp +(service persistent-ssh-service-type + (ssh-connection-configuration + (sshd-user "root") ; "root" is the default + (sshd-host "1.2.3.4") ; change to IP of sshd as string + (forwards + (list (tunnel-forward-configuration))))) +@end lisp + +You can start the extended service with the following shell command as +root: + +@example +herd start ssh-forwards@@tunnel,any:any +@end example + +@c ********************************************************************* +@node Proxyed ssh tunnel for a stealth VPN +@subsection Proxyed ssh tunnel for a stealth VPN + +This example extends a ssh tunnel through an inferior socks proxy of +which it also extends the dynamic forward, for purpose of supporting a +connection to the sshd as a ``stealth'' VPN server. + +Under the hood, the daemonized ssh connection uses the command +@command{nc} from the program @code{netcat-openbsd} to direct packets of +the connection to the dynamic forward providing access to the SOCKS +proxy. This dirty hack is known to work at the time of writing. + +The ssh stance directing the connection to transmit through the proxy +should be something like the following, with shell quoting added +somewhat artificially for clarity to the human reader: + +@example +-o ProxyCommand=3D'/gnu/store/...-netcat-openbsd-x.x-x/bin/nc -X 5 -x loca= lhost:1357 %h %p' +@end example + +The daemonized ssh forwarding stance is @command{-w any:any}. + +@cindex stealth TUN device forwarding, ex. +@cindex stealth ssh tunnel, example +@cindex stealth VPN, example +@lisp +(service persistent-ssh-service-type + (ssh-connection-configuration + (sshd-user "root") ; "root" is the default + (sshd-host "1.2.3.4") ; change to IP of VPN server sshd as string + (forwards + (list (tunnel-forward-configuration))) + (socks-proxy-config + (socks-proxy-configuration + (use-proxy? #t) + (dynamic-forward + (ssh-connection-configuration + (sshd-host "1.2.3.4") ; change to IP of proxy sshd as string + (forwards + (list (dynamic-forward-configuration + (entry-port 1357)))))))))) ; default is 8971 +@end lisp + +You can start the extended service with the following shell command as +root: + +@example +herd start ssh-forwards@@tunnel,any:any@@1357 +@end example + +In this example, the SOCKS proxy sshd providing stealth and the VPN +server sshd are actually one and the same host. In general, you can use +the same host or have 2 different hosts according to your +preference. While using 2 hosts might provide more privacy, in the +author's experience, using the same host as SOCKS proxy and VPN server +still grants protection from IP blacklisting to the sshd. + +Final note of caution: once a host is blacklisted, connecting to that +same host's VPN server stealthily will obviously not unblacklist +it. Your luck holds only as long as the server host is @emph{only} +connected to through a SOCKS proxy and @emph{never} directly by VPN +clients. Do not underestimate the evils of this world, we are not +@emph{defeating} censorship, merely @emph{flying under the radar} for a +little while. + +@c ********************************************************************* +@node Shepherd actions +@chapter Shepherd actions + +Shepherd services extended by Guix services of type +@code{persistent-ssh-service-type} provide 2 special shepherd actions, +@ref{Jump Start,,, shepherd, The GNU Shepherd Manual} on how to use them +from command-line. + +@table @code + +@cindex resurrect, shepherd action +@item resurrect +The @code{resurrect} action has no side effects when the service to +which is belongs is running. Otherwise, and in this order, it will +@code{enable} the service, by default recursively perform itself on a +service which provides a dynamic forward that the service uses for +proxying (if any), then @code{start} the service. + +@cindex force-resurrect, shepherd action +@item force-resurrect +The @code{force-resurrect} always has side effects which include +stopping before starting the service to which it belongs when this +service is started. It therefore @emph{always} causes an interruption of +connectivity, namely it will @code{enable} the service, @code{stop} the +service, by default recursively perform itself on a service which +provides a dynamic forward that the service uses for proxying (if any), +then @code{start} the service. + +@end table + + +@c ********************************************************************* +@node GNU Free Documentation License +@appendix GNU Free Documentation License +@cindex license, GNU Free Documentation License +@include fdl-1.3.texi + +@c ********************************************************************* +@node Concept Index +@unnumbered Concept Index +@printindex cp + +@node Programming Index +@unnumbered Programming Index +@syncodeindex tp fn +@syncodeindex vr fn +@printindex fn + +@bye + +@c Local Variables: +@c ispell-local-dictionary: "american"; +@c End: diff --git a/gnu/services/ssh-tunneler.scm b/gnu/services/ssh-tunneler.scm new file mode 100644 index 0000000000..571710e769 --- /dev/null +++ b/gnu/services/ssh-tunneler.scm @@ -0,0 +1,837 @@ +;;; Whispers --- Stealth VPN and ssh tunneler +;;; Copyright =C2=A9 2023 Runciter +;;; +;;; This file is part of Whispers. +;;; +;;; Whispers is free software; you can redistribute it and/or modify it +;;; under the terms of the GNU General Public License as published by +;;; the Free Software Foundation; either version 3 of the License, or (at +;;; your option) any later version. +;;; +;;; Whispers is distributed in the hope that it will be useful, but +;;; WITHOUT ANY WARRANTY; without even the implied warranty of +;;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +;;; GNU General Public License for more details. +;;; +;;; You should have received a copy of the GNU General Public License +;;; along with Whispers. If not, see . + +(define-module (gnu services ssh-tunneler) + #:use-module (guix gexp) + #:use-module (guix records) + #:use-module (gnu services) + #:use-module (gnu services shepherd) + #:use-module (gnu services admin) + #:use-module (gnu services mcron) + #:use-module (gnu packages base) + #:use-module (gnu packages admin) + #:use-module (gnu packages linux) + #:use-module (gnu packages ssh) + #:use-module (gnu home services) + #:use-module (gnu home services shepherd) + #:export (ssh-connection-configuration + make-ssh-connection-configuration + ssh-connection-configuration? + this-ssh-connection-configuration + ssh-connection-configuration-forwards + ssh-forward-configuration + this-ssh-forward-configuration + ssh-forward-configuration? + make-ssh-forward-configuration + ssh-forward-configuration-entry-port + socks-proxy-configuration + this-socks-proxy-configuration + socks-proxy-configuration? + make-socks-proxy-configuration + dynamic-forward-configuration + port-forward-configuration + reverse-port-forward-configuration + tunnel-forward-configuration + persistent-ssh-name + persistent-ssh-service-type + home-persistent-ssh-service-type)) + +(define-record-type* + ssh-connection-configuration make-ssh-connection-configuration + ssh-connection-configuration? + this-ssh-connection-configuration + ;; A file-like object. + (shepherd-package ssh-connection-configuration-shepherd-package + (default shepherd)) + ;; A file-like object. + (ssh-package ssh-connection-configuration-ssh-package + (default openssh)) + ;; A file-like object. + (netcat-package ssh-connection-configuration-netcat-package + (default netcat-openbsd)) + ;; A file-like object. + (sshpass-package ssh-connection-configuration-sshpass-package + (default sshpass)) + ;; A file-like object. + (ineutils-package ssh-connection-configuration-inetutils-package + (default inetutils)) + ;; A file-like object. + (procps-package ssh-connection-configuration-procps-package + (default procps)) + ;; A guix record of type . + (socks-proxy-config ssh-connection-configuration-socks-proxy-config + (default (socks-proxy-configuration))) + ;; A boolean value. + (id-rsa-file? ssh-connection-configuration-id-rsa-file? + (default #t)) + ;; A string. + (id-rsa-file ssh-connection-configuration-id-rsa-file + (default "/root/.ssh/id_rsa")) + ;; A boolean value. + (clear-password? ssh-connection-configuration-clear-password? + (default #f)) + ;; A string. + (sshd-user-password ssh-connection-configuration-sshd-user-password + (default "none")) + ;; A string. + (sshd-user ssh-connection-configuration-sshd-user + (default "root")) + ;; A string. + (sshd-host ssh-connection-configuration-sshd-host + (default "127.0.0.1")) + ;; An integer. + (sshd-port ssh-connection-configuration-sshd-port + (default 22)) + ;; A boolean value. + (gateway-ports? ssh-connection-configuration-gateway-ports? + (default #t)) + ;; A string. + (name-prefix ssh-connection-configuration-name-prefix + (default "ssh-forwards")) + ;; A boolean value. + (suffix-name? ssh-connection-configuration-suffix-name? + (default #t)) + ;; A list of strings. + (special-options ssh-connection-configuration-special-options + (default (list))) + ;; A list of records. + (forwards ssh-connection-configuration-forwards + (default '())) + ;; A boolean value. + (exit-forward-failure? ssh-connection-configuration-exit-forward-failur= e? + (default #t)) + ;; An integer. + (connection-attempts ssh-connection-configuration-connection-attempts + (default 1)) + ;; A boolean value. + (local-command? ssh-connection-configuration-local-command? + (default (ssh-connection-configuration-pid-file? + this-ssh-connection-configuration)) + (thunked)) + ;; A list of strings + (extra-local-commands ssh-connection-configuration-extra-local-commands + (default '())) + ;; A boolean value. + (require-networking? ssh-connection-configuration-require-networking? + (default #t)) + ;; A list of symbols. + (extra-requires ssh-connection-configuration-extra-requires + (default '())) + ;; A boolean value. + (elogind? ssh-connection-configuration-elogind? + (default #f)) + ;; A boolean value. + (pid-file? ssh-connection-configuration-pid-file? + (default #t)) + ;; A boolean value. + (pid-folder-override? ssh-connection-configuration-pid-folder-override? + (default #f)) + ;; A string. + (pid-folder-override ssh-connection-configuration-pid-folder-override + (default "/var/run")) + ;; A boolean value. + (timeout-override? ssh-connection-configuration-timeout-override? + (default #f)) + ;; An integer. + (timeout-override ssh-connection-configuration-timeout-override + (default 5)) + ;; A boolean value. + (dedicated-log-file? ssh-connection-configuration-dedicated-log-file? + (default #f)) + ;; A boolean value. + (log-rotate? ssh-connection-configuration-log-rotate? + (default #f)) + ;; A boolean value. + (log-folder-override? ssh-connection-configuration-log-folder-override? + (default #f)) + ;; A string. + (log-folder-override ssh-connection-configuration-log-folder-override + (default "/var/run")) + ;; An integer between 0 and 3, both included. + (verbosity ssh-connection-configuration-verbosity + (default 0)) + ;; A boolean value. + (command? ssh-connection-configuration-command? + (default #f)) + ;; A string. + (command ssh-connection-configuration-command + (default '())) + ;; A quoted cron job time specification. + (resurrect-time-spec ssh-connection-configuration-resurrect-time-spec + (default ''(next-minute '(47)))) + ;; A boolean value. + (flat-resurrect? ssh-connection-configuration-flat-resurrect? + (default #f)) + ;; A quoted cron job time specification. + (force-resurrect-time-spec + ssh-connection-configuration-force-resurrect-time-spec + (default ''(next-hour '(3)))) + ;; A boolean value. + (flat-force-resurrect? ssh-connection-configuration-flat-force-resurrec= t? + (default #f)) + ;; A boolean value. + (%cron-resurrect? ssh-connection-configuration-cron-resurrect? + (default #f)) + ;; A boolean value. + (%cron-force-resurrect? ssh-connection-configuration-cron-force-resurrec= t? + (default #f)) + ;; A boolean value. + (%auto-start? ssh-connection-configuration-auto-start? + (default #f))) + +(define-record-type* + ssh-forward-configuration make-ssh-forward-configuration + ssh-forward-configuration? + this-ssh-forward-configuration + ;; A symbol which can be 'dynamic, 'port, 'reverse-port or 'tunnel + (forward-type ssh-forward-configuration-forward-type + (default 'dynamic)) + ;; A symbol which can be 'preset or 'any when the 'forward-type field + ;; is 'tunnel, and which can be 'port or 'socket otherwise. It is + ;; ignored when the 'forward-type field is 'dynamic. + (entry-type ssh-forward-configuration-entry-type + (default 'port)) + ;; A symbol which can be 'preset or 'any when the 'forward-type field + ;; is 'tunnel, and which can be 'port or 'socket otherwise. It is + ;; ignored when the 'forward-type field evaluates to 'dynamic. + (exit-type ssh-forward-configuration-exit-type + (default 'port)) + ;; An integer + (entry-port ssh-forward-configuration-entry-port + (default 8971)) + ;; An integer + (exit-port ssh-forward-configuration-exit-port + (default 22)) + ;; A string + (entry-socket ssh-forward-configuration-entry-socket + (default "")) + ;; A string + (exit-socket ssh-forward-configuration-exit-socket + (default "")) + ;; A string + (forward-host ssh-forward-configuration-exit-host + (default "127.0.0.1")) + ;; An integer + (entry-tun ssh-forward-configuration-entry-tun + (default 0)) + ;; An integer + (exit-tun ssh-forward-configuration-exit-tun + (default 0))) + +(define-record-type* + socks-proxy-configuration make-socks-proxy-configuration + socks-proxy-configuration? + this-socks-proxy-configuration + ;; A boolean value + (use-proxy? socks-proxy-configuration-use-proxy? + (default #f)) + ;; A boolean value + (extend? socks-proxy-configuration-extend? + (default (socks-proxy-configuration-use-proxy? + this-socks-proxy-configuration)) + (thunked)) + ;; An integer + (port socks-proxy-configuration-port + (default + (if + (socks-proxy-configuration-extend? + this-socks-proxy-configuration) + (ssh-forward-configuration-entry-port + (car + (ssh-connection-configuration-forwards + (socks-proxy-configuration-dynamic-forward + this-socks-proxy-configuration)))) + 8971)) + (thunked)) + ;; #f, or a guix record returned by a call to + ;; (ssh-connection-configuration + ;; (forwards (list (dynamic-forward-configuration ...))) + ;; ...) + (dynamic-forward socks-proxy-configuration-dynamic-forward + (default (if (socks-proxy-configuration-extend? + this-socks-proxy-configuration) + (dynamic-forward-configuration) + #f)) + (thunked))) + + +(define-syntax dynamic-forward-configuration + (syntax-rules () + ((_ fields ...) + (ssh-forward-configuration + (inherit + (ssh-forward-configuration)) + fields ...)))) + +(define-syntax port-forward-configuration + (syntax-rules () + ((_ fields ...) + (ssh-forward-configuration + (inherit + (ssh-forward-configuration (forward-type 'port) + (entry-port 6947))) + fields ...)))) + +(define-syntax reverse-port-forward-configuration + (syntax-rules () + ((_ fields ...) + (ssh-forward-configuration + (inherit + (ssh-forward-configuration (forward-type 'reverse-port) + (entry-port 6283))) + fields ...)))) + +(define-syntax tunnel-forward-configuration + (syntax-rules () + ((_ fields ...) + (ssh-forward-configuration + (inherit + (ssh-forward-configuration (forward-type 'tunnel) + (entry-type 'any) + (exit-type 'any))) + fields ...)))) + +(define (persistent-ssh-socks-port config) + "Returns an integer defining the localhost port that a persistent ssh +connection can use to establish itself through a socks proxy, +configurable by CONFIG, a record of the +type." + (socks-proxy-configuration-port + (ssh-connection-configuration-socks-proxy-config config))) + +(define (persistent-ssh-forward-stance forward-conf) + "Returns a string defining one of the forwarding stances of a +persistent ssh connection, configurable by FORWARD-CONF, a record of the + type." + (let* ((forward-type (ssh-forward-configuration-forward-type forward-con= f)) + (entry-type (ssh-forward-configuration-entry-type forward-conf)) + (exit-type (ssh-forward-configuration-exit-type forward-conf)) + (entry-port (ssh-forward-configuration-entry-port forward-conf)) + (entry-port-str (number->string entry-port)) + (exit-port (ssh-forward-configuration-exit-port forward-conf)) + (exit-port-str (number->string exit-port)) + (entry-socket (ssh-forward-configuration-entry-socket forward-con= f)) + (exit-socket (ssh-forward-configuration-exit-socket forward-conf)) + (exit-host (ssh-forward-configuration-exit-host forward-conf)) + (entry-tun (ssh-forward-configuration-entry-tun forward-conf)) + (entry-tun-str (number->string entry-tun)) + (exit-tun (ssh-forward-configuration-exit-tun forward-conf)) + (exit-tun-str (number->string exit-tun))) + (cond ((equal? forward-type 'dynamic) + (number->string entry-port)) + ((or (equal? forward-type 'port) + (equal? forward-type 'reverse-port)) + (cond ((equal? entry-type 'port) (string-append entry-port-str + ":" + exit-host + ":" + exit-port-str)) + ((equal? entry-type 'socket) (string-append entry-socket + ":" + exit-socket)) + (#t #f))) + ((equal? forward-type 'tunnel) + (string-append (cond ((equal? entry-type 'preset) entry-tun-str) + ((equal? entry-type 'any) "any") + (#t #f)) + ":" + (cond ((equal? exit-type 'preset) exit-tun-str) + ((equal? exit-type 'any) "any") + (#t #f)))) + (#t + #f)))) + +(define (persistent-ssh-forward-switch forward-conf) + "Returns a string defining one of the forwarding switches of a +persistent ssh connection, configurable by FORWARD-CONF, a record of the + type." + (let ((forward-type (ssh-forward-configuration-forward-type forward-conf= ))) + (cond ((equal? forward-type 'dynamic) "-D") + ((equal? forward-type 'port) "-L") + ((equal? forward-type 'reverse-port) "-R") + ((equal? forward-type 'tunnel) "-w") + (#t #f)))) + +(define (persistent-ssh-forward forward-conf) + "Returns a list of 2 strings containing the switch and stance of one of = the +forwardings of a persistent ssh connection, configurable by +FORWARD-CONF, a record of the type." + (list (persistent-ssh-forward-switch forward-conf) + (persistent-ssh-forward-stance forward-conf))) + +(define (persistent-ssh-name-suffix config) + "Returns a string defining the suffix part of the shepherd service +provision of the shepherd service daemonizing a persistent ssh +connection, configurable by CONFIG, a record of the + type." + (let* ((forwards (ssh-connection-configuration-forwards config)) + (typer ssh-forward-configuration-forward-type) + (typer-str (lambda (forward) + (symbol->string (typer forward)))) + (stancer persistent-ssh-forward-stance) + (socks-rec (ssh-connection-configuration-socks-proxy-config confi= g)) + (use-socks? (socks-proxy-configuration-use-proxy? socks-rec)) + (socks-port (socks-proxy-configuration-port socks-rec)) + (socks-port-str (number->string socks-port)) + (flat? (ssh-connection-configuration-flat-resurrect? config))) + (string-append "@" + (string-join (map (lambda (forward) + (string-append (typer-str forward) + "," + (stancer forward))) + forwards) + "_") + (if use-socks? + (string-append "@" + socks-port-str) + "")))) + +(define (persistent-ssh-name config) + "Returns a symbol defining the shpherd service provision of the +shepherd service daemonizing a persistent ssh connection, configurable +by CONFIG, a record of the type." + (string->symbol + (string-append (ssh-connection-configuration-name-prefix config) + (if (ssh-connection-configuration-suffix-name? config) + (persistent-ssh-name-suffix config) + "")))) + +(define (persistent-ssh-pid-folder config) + "Returns a string defining the path to the folder in which the pid +file of a persistent ssh connection service is stored by default, +configurable by CONFIG, a record of the +type." + (cond ((ssh-connection-configuration-pid-folder-override? config) + (ssh-connection-configuration-pid-folder-override config)) + ((ssh-connection-configuration-elogind? config) + (string-append "/run/user/" (number->string (getuid)))) + (else "/var/run"))) + +(define (persistent-ssh-pid-file-path config) + "Returns a string defining the path to the pid file of a persistent +ssh connection service, configurable by CONFIG, configurable by CONFIG, +a record of the type." + (string-append (persistent-ssh-pid-folder config) + "/" + (symbol->string (persistent-ssh-name config)) + ".pid")) + +(define (persistent-ssh-log-folder config) + "Returns a string defining the path to the folder in which the log +file of a persistent ssh connection service is stored by default, +configurable by CONFIG, a record of the +type." + (cond ((ssh-connection-configuration-log-folder-override? config) + (ssh-connection-configuration-log-folder-override config)) + ((ssh-connection-configuration-elogind? config) + (string-append "/run/user/" (number->string (getuid)))) + (else "/var/run"))) + +(define (persistent-ssh-log-file-path config) + "Returns a string defining the path to the log file of a persistent +ssh connection service, configurable by CONFIG, a record of the + type." + (string-append (persistent-ssh-log-folder config) + "/" + (symbol->string (persistent-ssh-name config)) + ".log")) + +(define (persistent-ssh-local-command config) + "Returns a string defining command executed locally after the forwards +of a persistent ssh connection service have been succesfully created, +configurable by CONFIG, a record of the +type." + (let ((procps-package (ssh-connection-configuration-procps-package confi= g)) + (clear-password? (ssh-connection-configuration-clear-password? + config)) + (extra-local-commands + (ssh-connection-configuration-extra-local-commands + config))) + (append (list (file-append procps-package + "/bin/ps") + " --no-header --pid $PPID -o " + (if clear-password? + "ppid" + "pid") + " > " + (persistent-ssh-pid-file-path config)) + (map (lambda (command) + (string-append " && " + command)) + extra-local-commands)))) + +(define (persistent-ssh-requires config) + "Returns a list of symbols defining the other services required as +dependencies by the shepherd service of a persistent ssh connection, +configurable by CONFIG, a record of the +type." + (let* ((req-net? (ssh-connection-configuration-require-networking? confi= g)) + (extra-reqs (ssh-connection-configuration-extra-requires config)) + (socks-rec (ssh-connection-configuration-socks-proxy-config confi= g)) + (inferior? (socks-proxy-configuration-extend? socks-rec)) + (inferior-cnf (socks-proxy-configuration-dynamic-forward socks-re= c)) + (use-socks? (socks-proxy-configuration-use-proxy? socks-rec)) + (socks-port (socks-proxy-configuration-port socks-rec)) + (socks-port-str (number->string socks-port)) + (flat? (ssh-connection-configuration-flat-force-resurrect? config= ))) + (append + (if req-net? + (list 'networking) + (list)) + extra-reqs + (if inferior? + (list (persistent-ssh-name inferior-cnf)) + (if use-socks? + (list (string->symbol + ;; FIXME: this just assumes a possible + ;; default name, not always true and not + ;; even the only possible default. + (string-append "ssh-forwards@dynamic," + (number->string socks-port)))) + (list)))))) + +(define (persistent-ssh-timeout config) + "Returns an integer setting the pid file timout of the shepherd +service daemonizing a persistent ssh connection, configurable by CONFIG, +a record of the type." + (if (ssh-connection-configuration-timeout-override? config) + (ssh-connection-configuration-timeout-override config) + #~(+ #$(ssh-connection-configuration-connection-attempts config) + (default-pid-file-timeout)))) + +(define (persistent-ssh-constructor-gexp config) + "Returns G-exp to a procedure starting the ssh client process of a +persistent ssh connection, configurable by CONFIG, a record of the + type." + (let* ((sshpass-pkg (ssh-connection-configuration-sshpass-package config= )) + (password (ssh-connection-configuration-sshd-user-password config= )) + (ssh-pkg (ssh-connection-configuration-ssh-package config)) + (netcat-pkg (ssh-connection-configuration-netcat-package config)) + (verbosity (ssh-connection-configuration-verbosity config)) + (eff? (ssh-connection-configuration-exit-forward-failure? config)) + (tries (ssh-connection-configuration-connection-attempts config)) + (tries-str (number->string tries)) + (local-com? (ssh-connection-configuration-local-command? config)) + (local-com (persistent-ssh-local-command config)) + (gateway? (ssh-connection-configuration-gateway-ports? config)) + (socks-rec (ssh-connection-configuration-socks-proxy-config confi= g)) + (use-socks? (socks-proxy-configuration-use-proxy? socks-rec)) + (socks-port (socks-proxy-configuration-port socks-rec)) + (socks-port-str (number->string socks-port)) + (command? (ssh-connection-configuration-command? config)) + (command (ssh-connection-configuration-command config)) + (forwards (ssh-connection-configuration-forwards config)) + (sshd-port (ssh-connection-configuration-sshd-port config)) + (sshd-port-str (number->string sshd-port)) + (id-rsa? (ssh-connection-configuration-id-rsa-file? config)) + (id-rsa (ssh-connection-configuration-id-rsa-file config)) + (sshd-user (ssh-connection-configuration-sshd-user config)) + (sshd-host (ssh-connection-configuration-sshd-host config)) + (dlf? (ssh-connection-configuration-dedicated-log-file? config)) + (log-file (persistent-ssh-log-file-path config)) + (pid-file? (ssh-connection-configuration-pid-file? config)) + (pid-file (persistent-ssh-pid-file-path config)) + (timeout (persistent-ssh-timeout config)) + (special-opt (ssh-connection-configuration-special-options config= ))) + #~(make-forkexec-constructor + (append #$(if (ssh-connection-configuration-clear-password? config) + #~(list #$(file-append sshpass-pkg "/bin/sshpass") + "-p" + #$password) + #~(list)) + (list #$(file-append ssh-pkg "/bin/ssh") + "-o" + "TCPKeepAlive=3Dno" + "-o" + "ServerAliveInterval=3D30" + "-o" + "ServerAliveCountMax=3D6" + "-o" + "UserKnownHostsFile=3D/dev/null" + "-o" + "StrictHostKeyChecking=3Dno" + ;; "-o" + ;; "Tunnel=3Dpoint-to-point" + "-o" + (string-append "ExitOnForwardFailure=3D" + #$(if eff? + "yes" + "no")) + "-o" + (string-append "ConnectionAttempts=3D" + #$tries-str)) + #$(if local-com? + #~(list "-o" + "PermitLocalCommand=3Dyes" + "-o" + (apply string-append + (append (list "LocalCommand=3D") + #$(append (list 'list) + local-com)))) + #~(list)) + #$(if gateway? + #~(list "-o" + "GatewayPorts=3Dyes") + #~(list)) + #$(if use-socks? + #~(list "-o" + (string-append "ProxyCommand=3D" + #$netcat-pkg + "/bin/nc" + " -X 5 -x localhost:" + #$socks-port-str + " %h %p")) + #~(list)) + #$(append (list 'list) + special-opt) + (list "-p" + #$sshd-port-str) + #$(if id-rsa? + #~(list "-i" + #$id-rsa) + #~(list)) + #$(cond ((=3D verbosity 0) #~(list)) + ((=3D verbosity 1) #~(list "-v")) + ((=3D verbosity 2) #~(list "-v" "-v")) + ((=3D verbosity 3) #~(list "-v" "-v" "-v")) + (#t #f)) + #$(if command? + #~(list) + #~(list "-N")) + #$(append (list 'list) + (apply append + (map persistent-ssh-forward + forwards))) + (list (string-append #$sshd-user + "@" + #$sshd-host)) + #$(if command? + #~(list #$command) + #~(list))) + #:log-file + #$(if dlf? + log-file + #f) + #:pid-file + #$(if pid-file? + pid-file + #f) + #:pid-file-timeout + #$timeout))) + +(define (persistent-ssh-resurrect-action config) + "Returns a G-exp to a procedure used as the procedure of the +'resurrect action of the shepherd service supporting a persistent ssh +connection , configurable by CONFIG, a record of the + type." + (let* ((name (persistent-ssh-name config)) + (socks-rec (ssh-connection-configuration-socks-proxy-config confi= g)) + (inferior? (socks-proxy-configuration-extend? socks-rec)) + (inferior-cnf (socks-proxy-configuration-dynamic-forward socks-re= c)) + (use-socks? (socks-proxy-configuration-use-proxy? socks-rec)) + (socks-port (socks-proxy-configuration-port socks-rec)) + (socks-port-str (number->string socks-port)) + (flat? (ssh-connection-configuration-flat-resurrect? config))) + #~(lambda (running) + (unless (service-running? (lookup-service '#$name)) + (perform-service-action (lookup-service '#$name) + 'enable) + (unless (or #$flat? + (and (not #$inferior?) + (not #$use-socks?))) + (let ((inferior-name + '#$(if inferior? + (persistent-ssh-name inferior-cnf) + (if use-socks? + (string->symbol + ;; FIXME: this just assumes a possible + ;; default name, not always true and not + ;; even the only possible default. + (string-append "ssh-forwards@dynamic," + socks-port-str)) + 'not-a-service)))) + (perform-service-action (lookup-service inferior-name) + 'resurrect))) + (start-service (lookup-service '#$name))) + #t))) + +(define (persistent-ssh-force-resurrect-action config) + "Returns a G-exp to a procedure used as the procedure of the +'force-resurrect action of the shepherd service supporting a persistent +ssh connection , configurable by CONFIG, a record of the + type." + (let* ((name (persistent-ssh-name config)) + (socks-rec (ssh-connection-configuration-socks-proxy-config confi= g)) + (inferior? (socks-proxy-configuration-extend? socks-rec)) + (inferior-cnf (socks-proxy-configuration-dynamic-forward socks-re= c)) + (use-socks? (socks-proxy-configuration-use-proxy? socks-rec)) + (socks-port (socks-proxy-configuration-port socks-rec)) + (socks-port-str (number->string socks-port)) + (flat? (ssh-connection-configuration-flat-force-resurrect? config= ))) + #~(lambda (running) + (perform-service-action (lookup-service '#$name) + 'enable) + (stop-service (lookup-service '#$name)) + (unless (or #$flat? + (and (not #$inferior?) + (not #$use-socks?))) + (let ((inferior-name + '#$(if inferior? + (persistent-ssh-name inferior-cnf) + (if use-socks? + (string->symbol + ;; FIXME: this just assumes a possible + ;; default name, not always true and not + ;; even the only possible default. + (string-append "ssh-forwards@dynamic," + socks-port-str)) + 'not-a-service)))) + (perform-service-action (lookup-service inferior-name) + 'force-resurrect))) + (start-service (lookup-service '#$name)) + #t))) + +(define (persistent-ssh-shepherd-services config) + "Returns a list of shepherd services handling a ssh client daemon +connection, configured by CONFIG, a record of the + type." + (let* ((name (persistent-ssh-name config)) + (socks-rec (ssh-connection-configuration-socks-proxy-config confi= g)) + (inferior? (socks-proxy-configuration-extend? socks-rec)) + (inferior-cnf (socks-proxy-configuration-dynamic-forward socks-re= c)) + (use-socks? (socks-proxy-configuration-use-proxy? socks-rec)) + (socks-port (socks-proxy-configuration-port socks-rec)) + (socks-port-str (number->string socks-port)) + (reqs (persistent-ssh-requires config)) + (constructor-gexp (persistent-ssh-constructor-gexp config)) + (res-gexp (persistent-ssh-resurrect-action config)) + (force-res-gexp (persistent-ssh-force-resurrect-action config)) + (auto-start? (ssh-connection-configuration-auto-start? config))) + (append + (if inferior? + (persistent-ssh-shepherd-services inferior-cnf) + (list)) + (list + (shepherd-service + (documentation "Persistent ssh client connection") + (provision `(,name)) + (requirement reqs) + (start constructor-gexp) + (stop #~(make-kill-destructor)) + (actions + (list + (shepherd-action (name 'resurrect) + (documentation + "Resurrect this connection and its +inferiors-proxies if they are stopped or disabled by the Shepherd.") + (procedure res-gexp)) + (shepherd-action (name 'force-resurrect) + (documentation "Enable, stop and restart this +connection and its inferior-proxies , regardless of their current +status.") + (procedure force-res-gexp)))) + (auto-start? auto-start?)))))) + +(define (persistent-ssh-cron-jobs config) + "Returns a list of cron job specifications to extend the mcron service +with scheduled resurrection actions on the persistent ssh connection +port forwards configured by CONFIG, a record of the + type." + (append + (if (ssh-connection-configuration-cron-resurrect? config) + (list + #~(job #$(ssh-connection-configuration-resurrect-time-spec config) + (lambda () + (execl + (string-append + #$(ssh-connection-configuration-shepherd-package config) + "/bin/herd") + "herd" + "resurrect" + #$(symbol->string (persistent-ssh-name config)))) + (string-append + "resurrect " + #$(symbol->string (persistent-ssh-name config))))) + (list)) + (if (ssh-connection-configuration-cron-force-resurrect? config) + (list + #~(job #$(ssh-connection-configuration-force-resurrect-time-spec + config) + (lambda() + (execl + (string-append + #$(ssh-connection-configuration-shepherd-package config) + "/bin/herd") + "herd" + "force-resurrect" + #$(symbol->string (persistent-ssh-name config)))) + (string-append + "force-resurrect " + #$(symbol->string (persistent-ssh-name config))))) + (list)))) + +(define (persistent-ssh-log-rotation config) + "Returns a list of log-rotation records specifying how to rotate the +logs of a persistent ssh connection configurable by CONFIG, a record of +the type." + (if (and (ssh-connection-configuration-dedicated-log-file? config) + (ssh-connection-configuration-log-rotate? config)) + (list + (log-rotation (frequency 'daily) + (files `(,(persistent-ssh-log-file-path config))))) + (list))) + +(define persistent-ssh-service-type + (service-type + (name 'persistent-ssh) + (description "Persistent ssh connection service") + (extensions + (list (service-extension shepherd-root-service-type + persistent-ssh-shepherd-services) + (service-extension mcron-service-type + persistent-ssh-cron-jobs) + (service-extension rottlog-service-type + persistent-ssh-log-rotation) + (service-extension + profile-service-type + (lambda (config) + (list + (ssh-connection-configuration-ssh-package config) + (ssh-connection-configuration-netcat-package config) + (ssh-connection-configuration-sshpass-package config) + (ssh-connection-configuration-procps-package config) + (ssh-connection-configuration-inetutils-package config)))))) + (default-value (ssh-connection-configuration)))) + +(define home-persistent-ssh-service-type + (service-type + (name 'persistent-ssh) + (description "Persistent ssh connection normal user service") + (extensions + (list (service-extension home-shepherd-service-type + persistent-ssh-shepherd-services) + (service-extension + home-profile-service-type + (lambda (config) + (list + (ssh-connection-configuration-ssh-package config) + (ssh-connection-configuration-netcat-package config) + (ssh-connection-configuration-sshpass-package config) + (ssh-connection-configuration-procps-package config) + (ssh-connection-configuration-inetutils-package config)))))) + (default-value (ssh-connection-configuration)))) base-commit: c07a5f050f67fa9054e93479cdda2f298c567460 --=20 2.41.0