unofficial mirror of guix-patches@gnu.org 
 help / color / mirror / code / Atom feed
* [bug#55912] [PATCH] home: Add OpenSSH service.
@ 2022-06-11 16:49 Ludovic Courtès
  2022-06-11 19:51 ` Maxime Devos
  0 siblings, 1 reply; 20+ messages in thread
From: Ludovic Courtès @ 2022-06-11 16:49 UTC (permalink / raw)
  To: 55912; +Cc: Ludovic Courtès

* gnu/home/services/ssh.scm: New file.
* gnu/local.mk (GNU_SYSTEM_MODULES): Add it.
* po/guix/POTFILES.in: Add it.
* doc/guix.texi (Secure Shell): New section.
---
 doc/guix.texi             | 183 +++++++++++++++++++++++++++-
 gnu/home/services/ssh.scm | 250 ++++++++++++++++++++++++++++++++++++++
 gnu/local.mk              |   1 +
 po/guix/POTFILES.in       |   1 +
 4 files changed, 434 insertions(+), 1 deletion(-)
 create mode 100644 gnu/home/services/ssh.scm

Hi!

Here’s an OpenSSH Home service, loosely inspired by what Julien had
implemented at:

  https://framagit.org/tyreunom/guix-home-manager/-/blob/master/home/ssh.scm

One thing I wasn’t sure about was how to handle ~/.ssh/known_hosts.
To lower the barrier to entry, I added an option to keep handling it
in a stateful way (with ‘ssh’ updating the file as it sees fit), and
I made that the default.

I toyed with other approaches.  In particular, just like Julien’s
module had <openssh-known-host>, I tried doing that and going further
so one could write:

  (openssh-host-key ssh-rsa "AAAAE2VjZHNhLX…")

and arrange so that (1) the host key algorithm is validated (a typo
would be reported at macro-expansion time), and (2) the string is
base64-decoded, similar to what is done for origins.

But then, while this is perhaps The Right Thing, I though it could be
too inconvenient to use: users would have to convert what ‘ssh’ gives
them into this format.  Sure, that’d give them data validation in
return, but that’s probably too little for too high a cost.

So I sticked to something simpler that allows users to pass files
as-is in ‘known-hosts’ and ‘authorized-keys’ (note that ‘authorized-keys’
in <openssh-configuration> also works that way, so it’s consistent).

Thoughts?

Thanks,
Ludo’.

diff --git a/doc/guix.texi b/doc/guix.texi
index ea133d519a..831b8fa7c0 100644
--- a/doc/guix.texi
+++ b/doc/guix.texi
@@ -38899,6 +38899,7 @@ services)}.
 * Shells: Shells Home Services.          POSIX shells, Bash, Zsh.
 * Mcron: Mcron Home Service.             Scheduled User's Job Execution.
 * Shepherd: Shepherd Home Service.       Managing User's Daemons.
+* SSH: Secure Shell.                     Setting up the secure shell client.
 * Desktop: Desktop Home Services.        Services for graphical environments.
 @end menu
 @c In addition to that Home Services can provide
@@ -39219,7 +39220,7 @@ GNU@tie{}mcron, a daemon to run jobs at scheduled times (@pxref{Top,,,
 mcron, GNU@tie{}mcron}).  The information about system's mcron is
 applicable here (@pxref{Scheduled Job Execution}), the only difference
 for home services is that they have to be declared in a
-@code{home-envirnoment} record instead of an @code{operating-system}
+@code{home-environment} record instead of an @code{operating-system}
 record.
 
 @defvr {Scheme Variable} home-mcron-service-type
@@ -39287,6 +39288,186 @@ mechanism instead (@pxref{Shepherd Services}).
 @end table
 @end deftp
 
+@node Secure Shell
+@subsection Secure Shell
+
+@cindex secure shell client, configuration
+@cindex SSH client, configuration
+The @uref{https://www.openssh.com, OpenSSH package} includes a client,
+the @command{ssh} command, that allows you to connect to remote machines
+using the @acronym{SSH, secure shell} protocol.  With the @code{(gnu
+home services ssh)} module, you can set up OpenSSH so that it works in a
+predictable fashion, almost independently of state on the local machine.
+To do that, you instantiate @code{home-openssh-service-type} in your
+Home configuration, as explained below.
+
+@defvr {Scheme Variable} home-openssh-service-type
+This is the type of the service to set up the OpenSSH client.  It takes
+care of several things:
+
+@itemize
+@item
+adding the @code{openssh} package to your profile so the @command{ssh}
+command is readily available;
+
+@item
+providing a @file{~/.ssh/config} file based on your configuration so
+that @command{ssh} knows about hosts you regularly connect to and their
+associated parameters;
+
+@item
+providing a @file{~/.ssh/authorized_keys}, which lists public keys that
+the local SSH server, @command{sshd}, may accept to connect to this user
+account;
+
+@item
+optionally providing a @file{~/.ssh/known_hosts} file so that @file{ssh}
+can authenticate hosts you connect to.
+@end itemize
+
+Here is a sample configuration you could add to the @code{services}
+field of your @code{home-environment}:
+
+@lisp
+(home-openssh-configuration
+ (hosts (list (openssh-host (name "ci.guix.gnu.org")
+                            (user "charlie"))
+              (openssh-host (name "chbouib")
+                            (host-name "chbouib.example.org")
+                            (user "supercharlie")
+                            (port 10022))))
+ (authorized-keys (list (local-file "alice.pub"))))
+@end lisp
+
+The example above lists two hosts and their parameters.  For instance,
+running @command{ssh chbouib} will automatically connect to
+@code{chbouib.example.org} on port 10022, logging in as user
+@samp{supercharlie}.  Further, it marks the public key in
+@file{alice.pub} as authorized for incoming connections.
+
+The value associated with a @code{home-openssh-service-type} instance
+must be a @code{home-openssh-configuration} record, as describe below.
+@end defvr
+
+@deftp {Data Type} home-openssh-configuration
+This is the datatype representing the OpenSSH client and server
+configuration in one's home environment.  It contains the following
+fields:
+
+@table @asis
+@item @code{openssh} (default: @code{openssh})
+The OpenSSH package to add to the environment's profile.
+
+@item @code{hosts} (default: @code{'()})
+A list of @code{openssh-host} records specifying host names and
+associated connection parameters (see below).  This host list goes into
+@file{~/.ssh/config}, which @command{ssh} reads at startup.
+
+@item @code{known-hosts} (default: @code{*unspecified*})
+This must be either:
+
+@itemize
+@item
+@code{*unspecified*}, in which case @code{home-openssh-service-type}
+leaves it up to @command{ssh} and to the user to maintain the list of
+known hosts at @file{~/.ssh/known_hosts}, or
+
+@item
+a list of file-like objects, in which case those are concatenated and
+emitted as @file{~/.ssh/known_hosts}.
+@end itemize
+
+The @file{~/.ssh/known_hosts} contains a list of host name/host key
+pairs that allow @command{ssh} to authenticate hosts you connect to and
+to detect possible impersonation attacks.  By default, @command{ssh}
+updates it in a @dfn{TOFU, trust-on-first-use} fashion, meaning that it
+records the host's key in that file the first time you connect to it.
+This behavior is preserved when @code{known-hosts} is set to
+@code{*unspecified*}.
+
+If you instead provide a list of host keys upfront in the
+@code{known-hosts} field, your configuration becomes self-contained and
+stateless: it can be replicated elsewhere or at another point in time.
+Preparing this list can be relatively tedious though, which is why
+@code{*unspecified*} is kept as a default.
+
+@item @code{authorized-keys} (default: @code{'()})
+This must be a list of file-like objects, each of which containing an
+SSH public key that should be authorized to connect to this machine.
+
+Concretely, these files are concatenated and made available as
+@file{~/.ssh/authorized_keys}.  If an OpenSSH server, @command{sshd}, is
+running on this machine, then it @emph{may} take this file into account:
+this is what @command{sshd} does by default, but be aware that it can
+also be configured to ignore it.
+@end table
+@end deftp
+
+@c %start of fragment
+
+@deftp {Data Type} openssh-host
+Available @code{openssh-host} fields are:
+
+@table @asis
+@item @code{name} (type: string)
+Name of this host declaration.
+
+@item @code{host-name} (default: @code{disabled}) (type: maybe-string)
+Host name---e.g., @code{"foo.example.org"} or @code{"192.168.1.2"}.
+
+@item @code{address-family} (type: address-family)
+Address family to use when connecting to this host: one of
+@code{AF_INET} (for IPv4 only), @code{AF_INET6} (for IPv6 only), or
+@code{*unspecified*} (allowing any address family).
+
+@item @code{identity-file} (default: @code{disabled}) (type: maybe-string)
+The identity file to use---e.g., @code{"/home/charlie/.ssh/id_ed25519"}.
+
+@item @code{port} (default: @code{disabled}) (type: maybe-integer)
+TCP port number to connect to.
+
+@item @code{user} (default: @code{disabled}) (type: maybe-string)
+User name on the remote host.
+
+@item @code{forward-x11?} (default: @code{#f}) (type: boolean)
+Whether to forward remote client connections to the local X11 graphical
+display.
+
+@item @code{forward-x11-trusted?} (default: @code{#f}) (type: boolean)
+Whether remote X11 clients have full access to the original X11
+graphical display.
+
+@item @code{forward-agent?} (default: @code{#f}) (type: boolean)
+Whether the authentication agent (if any) is forwarded to the remote
+machine.
+
+@item @code{compression?} (default: @code{#f}) (type: boolean)
+Whether to compress data in transit.
+
+@item @code{proxy-command} (default: @code{disabled}) (type: maybe-string)
+The command to use to connect to the server.  As an example, a command
+to connect via an HTTP proxy at 192.0.2.0 would be: @code{"nc -X connect
+-x 192.0.2.0:8080 %h %p"}.
+
+@item @code{host-key-algorithms} (default: @code{disabled}) (type: maybe-string-list)
+The list of accepted host key algorithms---e.g.,
+@code{'("ssh-ed25519")}.
+
+@item @code{accepted-key-types} (default: @code{disabled}) (type: maybe-string-list)
+The list of accepted user public key types.
+
+@item @code{extra-content} (default: @code{""}) (type: raw-configuration-string)
+Extra content appended as-is to this @code{Host} block in
+@file{~/.ssh/config}.
+
+@end table
+
+@end deftp
+
+
+@c %end of fragment
+
+
 @node Desktop Home Services
 @subsection Desktop Home Services
 
diff --git a/gnu/home/services/ssh.scm b/gnu/home/services/ssh.scm
new file mode 100644
index 0000000000..162d7df960
--- /dev/null
+++ b/gnu/home/services/ssh.scm
@@ -0,0 +1,250 @@
+;;; GNU Guix --- Functional package management for GNU
+;;; Copyright © 2022 Ludovic Courtès <ludo@gnu.org>
+;;;
+;;; This file is part of GNU Guix.
+;;;
+;;; GNU Guix 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.
+;;;
+;;; GNU Guix 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 GNU Guix.  If not, see <http://www.gnu.org/licenses/>.
+
+(define-module (gnu home services ssh)
+  #:use-module (guix gexp)
+  #:use-module (guix records)
+  #:use-module (guix diagnostics)
+  #:use-module (guix i18n)
+  #:use-module (gnu services)
+  #:use-module (gnu services configuration)
+  #:use-module (guix modules)
+  #:use-module (gnu home services)
+  #:use-module ((gnu home services utils)
+                #:select (object->camel-case-string))
+  #:autoload   (gnu packages ssh) (openssh)
+  #:use-module (srfi srfi-1)
+  #:use-module (srfi srfi-34)
+  #:use-module (ice-9 match)
+  #:export (home-openssh-configuration
+            home-openssh-configuration-authorized-keys
+            home-openssh-configuration-known-hosts
+            home-openssh-configuration-hosts
+
+            openssh-host
+            openssh-host-host-name
+            openssh-host-identity-file
+            openssh-host-name
+            openssh-host-port
+            openssh-host-user
+            openssh-host-forward-x11?
+            openssh-host-forward-x11-trusted?
+            openssh-host-forward-agent?
+            openssh-host-compression?
+            openssh-host-proxy-command
+            openssh-host-host-key-algorithms
+            openssh-host-accepted-key-types
+            openssh-host-extra-content
+
+            home-openssh-service-type))
+
+(define (serialize-field-name name)
+  (match name
+    ('accepted-key-types "PubkeyAcceptedKeyTypes")
+    (_
+     (let ((name (let ((str (symbol->string name)))
+                   (if (string-suffix? "?" str)
+                       (string->symbol (string-drop-right str 1))
+                       name))))
+       (object->camel-case-string name 'upper)))))
+
+(define (serialize-string field value)
+  (string-append "  " (serialize-field-name field)
+                 " " value "\n"))
+
+(define (address-family? obj)
+  (memv obj (list *unspecified* AF_INET AF_INET6)))
+
+(define (serialize-address-family field family)
+  (if (unspecified? family)
+      ""
+      (string-append "  " (serialize-field-name field) " "
+                     (cond ((= family AF_INET) "inet")
+                           ((= family AF_INET6) "inet6")
+                           (else
+                            (raise
+                             (formatted-message
+                              (G_ "~s: unsupported address family")
+                              family))))
+                     "\n")))
+
+(define (serialize-integer field value)
+  (string-append "  " (serialize-field-name field) " "
+                 (number->string value) "\n"))
+
+(define (serialize-boolean field value)
+  (string-append "  " (serialize-field-name field) " "
+                 (if value "yes" "no") "\n"))
+
+(define-maybe string)
+(define-maybe integer)
+
+(define (serialize-raw-configuration-string field value)
+  (string-append value "\n"))
+(define raw-configuration-string? string?)
+
+(define (string-list? lst)
+  (and (pair? lst) (every string? lst)))
+(define (serialize-string-list field lst)
+  (string-append "  " (serialize-field-name field) " "
+                 (string-join lst ",") "\n"))
+
+(define-maybe string-list)
+
+(define-configuration openssh-host
+  (name
+   (string)
+   "Name of this host declaration.")
+  (host-name
+   (maybe-string 'disabled)
+   "Host name---e.g., @code{\"foo.example.org\"} or @code{\"192.168.1.2\"}.")
+  (address-family
+   (address-family *unspecified*)
+   "Address family to use when connecting to this host: one of
+@code{AF_INET} (for IPv4 only), @code{AF_INET6} (for IPv6 only), or
+@code{*unspecified*} (allowing any address family).")
+  (identity-file
+   (maybe-string 'disabled)
+   "The identity file to use---e.g.,
+@code{\"/home/charlie/.ssh/id_ed25519\"}.")
+  (port
+   (maybe-integer 'disabled)
+   "TCP port number to connect to.")
+  (user
+   (maybe-string 'disabled)
+   "User name on the remote host.")
+  (forward-x11?
+   (boolean #f)
+   "Whether to forward remote client connections to the local X11 graphical
+display.")
+  (forward-x11-trusted?
+   (boolean #f)
+   "Whether remote X11 clients have full access to the original X11 graphical
+display.")
+  (forward-agent?
+   (boolean #f)
+   "Whether the authentication agent (if any) is forwarded to the remote
+machine.")
+  (compression?
+   (boolean #f)
+   "Whether to compress data in transit.")
+  (proxy-command
+   (maybe-string 'disabled)
+   "The command to use to connect to the server.  As an example, a command
+to connect via an HTTP proxy at 192.0.2.0 would be: @code{\"nc -X
+connect -x 192.0.2.0:8080 %h %p\"}.")
+  (host-key-algorithms
+   (maybe-string-list 'disabled)
+   "The list of accepted host key algorithms---e.g.,
+@code{'(\"ssh-ed25519\")}.")
+  (accepted-key-types
+   (maybe-string-list 'disabled)
+   "The list of accepted user public key types.")
+  (extra-content
+   (raw-configuration-string "")
+   "Extra content appended as-is to this @code{Host} block in
+@file{~/.ssh/config}."))
+
+(define (serialize-openssh-host config)
+  (define (openssh-host-name-field? field)
+    (eq? (configuration-field-name field) 'name))
+
+  (string-append
+   "Host " (openssh-host-name config) "\n"
+   (string-concatenate
+    (map (lambda (field)
+           ((configuration-field-serializer field)
+            (configuration-field-name field)
+            ((configuration-field-getter field) config)))
+         (remove openssh-host-name-field?
+                 openssh-host-fields)))))
+
+(define-record-type* <home-openssh-configuration>
+  home-openssh-configuration make-home-openssh-configuration
+  home-openssh-configuration?
+  (openssh         home-openssh-configuration-openssh  ;file-like
+                   (default openssh))
+  (authorized-keys home-openssh-configuration-authorized-keys ;list of file-like
+                   (default '()))
+  (known-hosts     home-openssh-configuration-known-hosts ;unspec | list of file-like
+                   (default *unspecified*))
+  (hosts           home-openssh-configuration-hosts   ;list of <openssh-host>
+                   (default '())))
+
+(define (openssh-configuration->string config)
+  (string-join (map serialize-openssh-host
+                    (home-openssh-configuration-hosts config))
+               "\n"))
+
+(define* (file-join name files #:optional (delimiter " "))
+  "Return a file in the store called @var{name} that is the concatenation
+of all the file-like objects listed in @var{files}, with @var{delimited}
+inserted after each of them."
+  (computed-file name
+                 (with-imported-modules '((guix build utils))
+                   #~(begin
+                       (use-modules (guix build utils))
+                       (call-with-output-file #$output
+                         (lambda (output)
+                           (for-each (lambda (file)
+                                       (call-with-input-file file
+                                         (lambda (input)
+                                           (dump-port input output)))
+                                       (display #$delimiter output))
+                                     '#$files)))))))
+
+(define (openssh-configuration-files config)
+  (let ((config (plain-file "config" (openssh-configuration->string config)))
+        (known-hosts (home-openssh-configuration-known-hosts config))
+        (authorized-keys (file-join
+                          "authorized_keys"
+                          (home-openssh-configuration-authorized-keys config)
+                          "\n")))
+    `((".ssh/authorized_keys" ,authorized-keys)
+      ,@(if (unspecified? known-hosts)
+            '()
+            `((".ssh/known_hosts"
+               ,(file-join "known_hosts" known-hosts "\n"))))
+      (".ssh/config" ,config))))
+
+(define openssh-activation
+  (with-imported-modules (source-module-closure
+                          '((gnu build activation)))
+    #~(begin
+        (use-modules (gnu build activation))
+
+        ;; Make sure ~/.ssh is #o700.
+        (let* ((home (getenv "HOME"))
+               (dot-ssh (string-append home "/.ssh")))
+          (mkdir-p/perms dot-ssh (getpw (getuid)) #o700)))))
+
+(define home-openssh-service-type
+  (service-type
+   (name 'home-openssh)
+   (extensions
+    (list (service-extension home-files-service-type
+                             openssh-configuration-files)
+          (service-extension home-profile-service-type
+                             (compose
+                              list
+                              home-openssh-configuration-openssh))
+          (service-extension home-activation-service-type
+                             (const openssh-activation))))
+   (description "Configure the OpenSSH @acronym{SSH, secure shell}
+client and add it to the user profile.")
+   (default-value (home-openssh-configuration))))
diff --git a/gnu/local.mk b/gnu/local.mk
index d49af0d898..f3b08ffdab 100644
--- a/gnu/local.mk
+++ b/gnu/local.mk
@@ -85,6 +85,7 @@ GNU_SYSTEM_MODULES =				\
   %D%/home/services/fontutils.scm		\
   %D%/home/services/shells.scm			\
   %D%/home/services/shepherd.scm		\
+  %D%/home/services/ssh.scm			\
   %D%/home/services/mcron.scm			\
   %D%/home/services/utils.scm			\
   %D%/home/services/xdg.scm			\
diff --git a/po/guix/POTFILES.in b/po/guix/POTFILES.in
index 6b8bd92bb7..201e5dcc87 100644
--- a/po/guix/POTFILES.in
+++ b/po/guix/POTFILES.in
@@ -6,6 +6,7 @@ gnu/services.scm
 gnu/system.scm
 gnu/services/shepherd.scm
 gnu/home/services.scm
+gnu/home/services/ssh.scm
 gnu/home/services/symlink-manager.scm
 gnu/system/file-systems.scm
 gnu/system/image.scm

base-commit: 010426e2c34428d69573cdfef88239303edcab2d
-- 
2.36.1





^ permalink raw reply related	[flat|nested] 20+ messages in thread

* [bug#55912] [PATCH] home: Add OpenSSH service.
  2022-06-11 16:49 [bug#55912] [PATCH] home: Add OpenSSH service Ludovic Courtès
@ 2022-06-11 19:51 ` Maxime Devos
  2022-06-11 22:13   ` Maxime Devos
                     ` (2 more replies)
  0 siblings, 3 replies; 20+ messages in thread
From: Maxime Devos @ 2022-06-11 19:51 UTC (permalink / raw)
  To: Ludovic Courtès, 55912

[-- Attachment #1: Type: text/plain, Size: 2940 bytes --]

Hi,

Some comments on the code.

Ludovic Courtès schreef op za 11-06-2022 om 18:49 [+0200]:
> +  (port
> +   (maybe-integer 'disabled)
> +   "TCP port number to connect to.")

TCP only allows natural numbers up to some bound, and in practice
implementations only support non-zero natural numbers, so maybe the
predicate can be refined a bit?

> +                             (formatted-message
> +                              (G_ "~s: unsupported address family")

Maybe a hint:

  hint: AF_INET and AF_INET6 are supported.


> + (define (serialize-string field value)
> +   (string-append "  " (serialize-field-name field)
> +                  " " value "\n"))

> +  (name
> +   (string)
> +   "Name of this host declaration.")
> [...]
> +  (proxy-command
> +   (maybe-string 'disabled)

Attila Lendvai has a patch series at 54674 that changes 'disabled' ->
*unspecified* -- I think it would be better to apply that patch series
first.

Wouldn't the value need to be escaped?  Or at least a check that it
doesn't contain special characters like \n or whatever special
charaters an OpenSSH configuration has.


>+ (define* (file-join name files #:optional (delimiter " "))
>+  "Return a file in the store called @var{name} that is the
>+ concatenation
>+ of all the file-like objects listed in @var{files}, with
@var{delimited}
>+ inserted after each of them."

Does this work for files with non-ASCII characters and for file names
that contain non-ASCII characters?

>+          (service-extension home-profile-service-type
>+                             (compose
>+                              list
>+                              home-openssh-configuration-openssh))
>+          (service-extension home-activation-service-type
>+                             (const openssh-activation))))
>+   (description "Configure the OpenSSH @acronym{SSH, secure shell}
>+client and _add it to the user profile_.")

(emphasis added).  Why is it automagically added to the user profile? 
This is considered bad practice for system services.  Maybe the user
keeps all their remote communication things in a single profile, maybe
the user only uses openssh things via other tools like 'guix deploy' or
'gnome-shell-extension-gsconnect' and hence has no need for 'openssh'
in their home profile.   Maybe the user never ssh's _from_ the computer
that has the openssh home configuration and only connects _to_ the
computer and hence the 'openssh' in the profile isn't necessary.

Now there are two ways to add 'openssh' to the environment: the Guix
Home equivalent of a 'packages' field and the openssh home service,
with AFAICT no mechanism for deciding which one ‘wins’ and no mechanism
for a proper error message like ‘only add the openssh package to the
profile or use the openssh home service, not both!’, which doesn't seem
ideal to me.

reetings,
Maxime.

[-- Attachment #2: This is a digitally signed message part --]
[-- Type: application/pgp-signature, Size: 260 bytes --]

^ permalink raw reply	[flat|nested] 20+ messages in thread

* [bug#55912] [PATCH] home: Add OpenSSH service.
  2022-06-11 19:51 ` Maxime Devos
@ 2022-06-11 22:13   ` Maxime Devos
  2022-06-13  9:41   ` Ludovic Courtès
  2022-06-15 20:29   ` [bug#55912] [PATCH v2] " Ludovic Courtès
  2 siblings, 0 replies; 20+ messages in thread
From: Maxime Devos @ 2022-06-11 22:13 UTC (permalink / raw)
  To: Ludovic Courtès, 55912

[-- Attachment #1: Type: text/plain, Size: 616 bytes --]

Maxime Devos schreef op za 11-06-2022 om 21:51 [+0200]:

> [remarks about automatically adding openssh the profile]

To be clear, automatically adding relevant packages to the profile from
a home (or system) service is an option (the downsides seem mild to
me), but currently things are rather inconsistent -- there are some
services that add the package, some that don't and in the past some of
us have asked contributors of services to not do the automagical adding
(at least, for System services, though I think (not 100% sure) I've
asked this for the git Home service as well?).

Greetings,
Maxime

[-- Attachment #2: This is a digitally signed message part --]
[-- Type: application/pgp-signature, Size: 260 bytes --]

^ permalink raw reply	[flat|nested] 20+ messages in thread

* [bug#55912] [PATCH] home: Add OpenSSH service.
  2022-06-11 19:51 ` Maxime Devos
  2022-06-11 22:13   ` Maxime Devos
@ 2022-06-13  9:41   ` Ludovic Courtès
  2022-06-13 10:51     ` Maxime Devos
  2022-06-13 21:58     ` Maxime Devos
  2022-06-15 20:29   ` [bug#55912] [PATCH v2] " Ludovic Courtès
  2 siblings, 2 replies; 20+ messages in thread
From: Ludovic Courtès @ 2022-06-13  9:41 UTC (permalink / raw)
  To: Maxime Devos; +Cc: 55912

Hi,

Maxime Devos <maximedevos@telenet.be> skribis:

> Ludovic Courtès schreef op za 11-06-2022 om 18:49 [+0200]:
>> +  (port
>> +   (maybe-integer 'disabled)
>> +   "TCP port number to connect to.")
>
> TCP only allows natural numbers up to some bound, and in practice
> implementations only support non-zero natural numbers, so maybe the
> predicate can be refined a bit?

We could do that, though that’s more code for little in return…

>> +                             (formatted-message
>> +                              (G_ "~s: unsupported address family")
>
> Maybe a hint:
>
>   hint: AF_INET and AF_INET6 are supported.

Sure, that makes sense.

>> +  (proxy-command
>> +   (maybe-string 'disabled)
>
> Attila Lendvai has a patch series at 54674 that changes 'disabled' ->
> *unspecified* -- I think it would be better to apply that patch series
> first.

I’ll take a look.

> Wouldn't the value need to be escaped?  Or at least a check that it
> doesn't contain special characters like \n or whatever special
> charaters an OpenSSH configuration has.

Oh right, it needs to be somewhat escaped; I’ll do that.  I think
‘object->string’ will be a good-enough escaping mechanism, and it’ll
take care of newlines.  (Doing things The Right Way would require
detailed knowledge about the grammar that OpenSSH’s parser expects.)

>
>>+ (define* (file-join name files #:optional (delimiter " "))
>>+  "Return a file in the store called @var{name} that is the
>>+ concatenation
>>+ of all the file-like objects listed in @var{files}, with
> @var{delimited}
>>+ inserted after each of them."
>
> Does this work for files with non-ASCII characters and for file names
> that contain non-ASCII characters?

‘files’ is a list of “file-like objects”, which, by definition, have
names acceptable for the stores (so ASCII names).

That’s not a user-visible limitation since store file names are hints
more than anything else.  You could have a local file, say
“courtès.pub”, and you’d do:

  (local-file "courtès.pub" "that-guy.pub")

This service doesn’t change that.

>>+   (description "Configure the OpenSSH @acronym{SSH, secure shell}
>>+client and _add it to the user profile_.")
>
> (emphasis added).  Why is it automagically added to the user profile? 
> This is considered bad practice for system services.  Maybe the user
> keeps all their remote communication things in a single profile, maybe
> the user only uses openssh things via other tools like 'guix deploy' or
> 'gnome-shell-extension-gsconnect' and hence has no need for 'openssh'
> in their home profile.   Maybe the user never ssh's _from_ the computer
> that has the openssh home configuration and only connects _to_ the
> computer and hence the 'openssh' in the profile isn't necessary.
>
> Now there are two ways to add 'openssh' to the environment: the Guix
> Home equivalent of a 'packages' field and the openssh home service,
> with AFAICT no mechanism for deciding which one ‘wins’ and no mechanism
> for a proper error message like ‘only add the openssh package to the
> profile or use the openssh home service, not both!’, which doesn't seem
> ideal to me.

All good points!  I’m usually against magically extending the profile
with new packages.  In this case, my reasoning was: if you’re going to
set up OpenSSH config files, that’s probably because you’re going to
need OpenSSH, so why not bring it while we’re at it?  (This rationale
usually doesn’t hold for system services: just because I run ntpd
doesn’t mean I need to have it in the system profile.)

But you’re right here, so I guess I’ll just remove it.

v2 coming soon!

Thanks,
Ludo’.




^ permalink raw reply	[flat|nested] 20+ messages in thread

* [bug#55912] [PATCH] home: Add OpenSSH service.
  2022-06-13  9:41   ` Ludovic Courtès
@ 2022-06-13 10:51     ` Maxime Devos
  2022-06-13 12:02       ` Ludovic Courtès
  2022-06-13 21:58     ` Maxime Devos
  1 sibling, 1 reply; 20+ messages in thread
From: Maxime Devos @ 2022-06-13 10:51 UTC (permalink / raw)
  To: Ludovic Courtès; +Cc: 55912

[-- Attachment #1: Type: text/plain, Size: 477 bytes --]

Ludovic Courtès schreef op ma 13-06-2022 om 11:41 [+0200]:
> ‘files’ is a list of “file-like objects”, which, by definition, have
> names acceptable for the stores (so ASCII names).

What about
(file-append (local-file "foo" #:recursive? #true) "/éclipse")?
Seems like a file-like object with a non-ASCII name, which is
acceptable to the store (the store only cares about /gnu/store/STORE-
ITEM-NAME, anything inside can be whatever).

Greetings,
Maxime.

[-- Attachment #2: This is a digitally signed message part --]
[-- Type: application/pgp-signature, Size: 260 bytes --]

^ permalink raw reply	[flat|nested] 20+ messages in thread

* [bug#55912] [PATCH] home: Add OpenSSH service.
  2022-06-13 10:51     ` Maxime Devos
@ 2022-06-13 12:02       ` Ludovic Courtès
  2022-06-13 12:38         ` Maxime Devos
  0 siblings, 1 reply; 20+ messages in thread
From: Ludovic Courtès @ 2022-06-13 12:02 UTC (permalink / raw)
  To: Maxime Devos; +Cc: 55912

Maxime Devos <maximedevos@telenet.be> skribis:

> Ludovic Courtès schreef op ma 13-06-2022 om 11:41 [+0200]:
>> ‘files’ is a list of “file-like objects”, which, by definition, have
>> names acceptable for the stores (so ASCII names).
>
> What about
> (file-append (local-file "foo" #:recursive? #true) "/éclipse")?

That’s fine AFAICS.

Ludo’.




^ permalink raw reply	[flat|nested] 20+ messages in thread

* [bug#55912] [PATCH] home: Add OpenSSH service.
  2022-06-13 12:02       ` Ludovic Courtès
@ 2022-06-13 12:38         ` Maxime Devos
  0 siblings, 0 replies; 20+ messages in thread
From: Maxime Devos @ 2022-06-13 12:38 UTC (permalink / raw)
  To: Ludovic Courtès; +Cc: 55912

[-- Attachment #1: Type: text/plain, Size: 377 bytes --]

Ludovic Courtès schreef op ma 13-06-2022 om 14:02 [+0200]:
> That’s fine AFAICS.

AFACIT it isn't, because we are not setting locale things and aren't
using glibc-locales:

$ ls $(guix build -e '((@ (guix gexp) computed-file) "foo" #~(begin (mkdir #$output) (call-with-output-file (string-append #$output "/éclipse") identity))))')
'?clipse'

Greetings,
Maxime.

[-- Attachment #2: This is a digitally signed message part --]
[-- Type: application/pgp-signature, Size: 260 bytes --]

^ permalink raw reply	[flat|nested] 20+ messages in thread

* [bug#55912] [PATCH] home: Add OpenSSH service.
  2022-06-13  9:41   ` Ludovic Courtès
  2022-06-13 10:51     ` Maxime Devos
@ 2022-06-13 21:58     ` Maxime Devos
  2022-06-14  8:08       ` Ludovic Courtès
  1 sibling, 1 reply; 20+ messages in thread
From: Maxime Devos @ 2022-06-13 21:58 UTC (permalink / raw)
  To: Ludovic Courtès; +Cc: 55912

[-- Attachment #1: Type: text/plain, Size: 1489 bytes --]

Ludovic Courtès schreef op ma 13-06-2022 om 11:41 [+0200]:
> > TCP only allows natural numbers up to some bound, and in practice
> > implementations only support non-zero natural numbers, so maybe the
> > predicate can be refined a bit?
> 
> We could do that, though that’s more code for little in return…

Input validation is generally considered good practice.  This has
recently been rediscovered in, say,
<https://lists.gnu.org/archive/html/guix-devel/2022-02/msg00140.html>.

The little extra code is trivial (just an new predicate doing some
bounds checks and exact-integer?) and:

  * I believe that simply implementing the tiny procedure is less
    expensive than doing a proper cost-benefit analysis

  * the cost is only once, it's not a recurring cost

  * the cost is trivial

  * the new predicate can benefit _all_ services handling network ports

  * it would benefit _all_ users of OpenSSH that might make a typo
    or such.  Likewise for other network services.

  * the benefit is not only once, it's recurring

  * cost of not doing checks:
    error messages that don't appear during "guix home reconfigure"
    inside Guix, and instead appear later during using the new Home
    from within external software even though the error was in the
    Guix Home.  Becomes rather complicated.

(Slight benefit, multiplied over many uses & much time -> large benefit
compared to the tiny one-time investment.)

Greetings,
Maxime.

[-- Attachment #2: This is a digitally signed message part --]
[-- Type: application/pgp-signature, Size: 260 bytes --]

^ permalink raw reply	[flat|nested] 20+ messages in thread

* [bug#55912] [PATCH] home: Add OpenSSH service.
  2022-06-13 21:58     ` Maxime Devos
@ 2022-06-14  8:08       ` Ludovic Courtès
  0 siblings, 0 replies; 20+ messages in thread
From: Ludovic Courtès @ 2022-06-14  8:08 UTC (permalink / raw)
  To: Maxime Devos; +Cc: 55912

Maxime Devos <maximedevos@telenet.be> skribis:

> Ludovic Courtès schreef op ma 13-06-2022 om 11:41 [+0200]:
>> > TCP only allows natural numbers up to some bound, and in practice
>> > implementations only support non-zero natural numbers, so maybe the
>> > predicate can be refined a bit?
>> 
>> We could do that, though that’s more code for little in return…
>
> Input validation is generally considered good practice.

I agree with this general statement of course (as I mentioned, I toyed
with input validation for things that are much more error-prone: public
key and host key algorithms.)

Ludo’.




^ permalink raw reply	[flat|nested] 20+ messages in thread

* [bug#55912] [PATCH v2] home: Add OpenSSH service.
  2022-06-11 19:51 ` Maxime Devos
  2022-06-11 22:13   ` Maxime Devos
  2022-06-13  9:41   ` Ludovic Courtès
@ 2022-06-15 20:29   ` Ludovic Courtès
  2022-06-15 20:47     ` Maxime Devos
  2022-06-18 21:41     ` bug#55912: " Ludovic Courtès
  2 siblings, 2 replies; 20+ messages in thread
From: Ludovic Courtès @ 2022-06-15 20:29 UTC (permalink / raw)
  To: 55912; +Cc: Ludovic Courtès, Maxime Devos

* gnu/home/services/ssh.scm: New file.
* gnu/local.mk (GNU_SYSTEM_MODULES): Add it.
* po/guix/POTFILES.in: Add it.
* doc/guix.texi (Secure Shell): New section.
---
 doc/guix.texi             | 176 +++++++++++++++++++++++++-
 gnu/home/services/ssh.scm | 254 ++++++++++++++++++++++++++++++++++++++
 gnu/local.mk              |   1 +
 po/guix/POTFILES.in       |   1 +
 4 files changed, 431 insertions(+), 1 deletion(-)
 create mode 100644 gnu/home/services/ssh.scm

Hello!

Changes compared to v1:

  • Use *unspecified* instead of 'disabled for unspecified field
    values, relying on the new semantics that Attila introduced.

  • No longer add the ‘openssh’ package to the profile.

  • Support non-ASCII file names in ‘file-join’.

  • Use a “natural number” type for the ‘port’ field.

Thoughts?

Ludo’.

diff --git a/doc/guix.texi b/doc/guix.texi
index 143bf36403..35a70ba56d 100644
--- a/doc/guix.texi
+++ b/doc/guix.texi
@@ -39043,6 +39043,7 @@ services)}.
 * Shells: Shells Home Services.          POSIX shells, Bash, Zsh.
 * Mcron: Mcron Home Service.             Scheduled User's Job Execution.
 * Shepherd: Shepherd Home Service.       Managing User's Daemons.
+* SSH: Secure Shell.                     Setting up the secure shell client.
 * Desktop: Desktop Home Services.        Services for graphical environments.
 @end menu
 @c In addition to that Home Services can provide
@@ -39363,7 +39364,7 @@ GNU@tie{}mcron, a daemon to run jobs at scheduled times (@pxref{Top,,,
 mcron, GNU@tie{}mcron}).  The information about system's mcron is
 applicable here (@pxref{Scheduled Job Execution}), the only difference
 for home services is that they have to be declared in a
-@code{home-envirnoment} record instead of an @code{operating-system}
+@code{home-environment} record instead of an @code{operating-system}
 record.
 
 @defvr {Scheme Variable} home-mcron-service-type
@@ -39431,6 +39432,179 @@ mechanism instead (@pxref{Shepherd Services}).
 @end table
 @end deftp
 
+@node Secure Shell
+@subsection Secure Shell
+
+@cindex secure shell client, configuration
+@cindex SSH client, configuration
+The @uref{https://www.openssh.com, OpenSSH package} includes a client,
+the @command{ssh} command, that allows you to connect to remote machines
+using the @acronym{SSH, secure shell} protocol.  With the @code{(gnu
+home services ssh)} module, you can set up OpenSSH so that it works in a
+predictable fashion, almost independently of state on the local machine.
+To do that, you instantiate @code{home-openssh-service-type} in your
+Home configuration, as explained below.
+
+@defvr {Scheme Variable} home-openssh-service-type
+This is the type of the service to set up the OpenSSH client.  It takes
+care of several things:
+
+@itemize
+@item
+providing a @file{~/.ssh/config} file based on your configuration so
+that @command{ssh} knows about hosts you regularly connect to and their
+associated parameters;
+
+@item
+providing a @file{~/.ssh/authorized_keys}, which lists public keys that
+the local SSH server, @command{sshd}, may accept to connect to this user
+account;
+
+@item
+optionally providing a @file{~/.ssh/known_hosts} file so that @file{ssh}
+can authenticate hosts you connect to.
+@end itemize
+
+Here is a sample configuration you could add to the @code{services}
+field of your @code{home-environment}:
+
+@lisp
+(home-openssh-configuration
+ (hosts (list (openssh-host (name "ci.guix.gnu.org")
+                            (user "charlie"))
+              (openssh-host (name "chbouib")
+                            (host-name "chbouib.example.org")
+                            (user "supercharlie")
+                            (port 10022))))
+ (authorized-keys (list (local-file "alice.pub"))))
+@end lisp
+
+The example above lists two hosts and their parameters.  For instance,
+running @command{ssh chbouib} will automatically connect to
+@code{chbouib.example.org} on port 10022, logging in as user
+@samp{supercharlie}.  Further, it marks the public key in
+@file{alice.pub} as authorized for incoming connections.
+
+The value associated with a @code{home-openssh-service-type} instance
+must be a @code{home-openssh-configuration} record, as describe below.
+@end defvr
+
+@deftp {Data Type} home-openssh-configuration
+This is the datatype representing the OpenSSH client and server
+configuration in one's home environment.  It contains the following
+fields:
+
+@table @asis
+@item @code{hosts} (default: @code{'()})
+A list of @code{openssh-host} records specifying host names and
+associated connection parameters (see below).  This host list goes into
+@file{~/.ssh/config}, which @command{ssh} reads at startup.
+
+@item @code{known-hosts} (default: @code{*unspecified*})
+This must be either:
+
+@itemize
+@item
+@code{*unspecified*}, in which case @code{home-openssh-service-type}
+leaves it up to @command{ssh} and to the user to maintain the list of
+known hosts at @file{~/.ssh/known_hosts}, or
+
+@item
+a list of file-like objects, in which case those are concatenated and
+emitted as @file{~/.ssh/known_hosts}.
+@end itemize
+
+The @file{~/.ssh/known_hosts} contains a list of host name/host key
+pairs that allow @command{ssh} to authenticate hosts you connect to and
+to detect possible impersonation attacks.  By default, @command{ssh}
+updates it in a @dfn{TOFU, trust-on-first-use} fashion, meaning that it
+records the host's key in that file the first time you connect to it.
+This behavior is preserved when @code{known-hosts} is set to
+@code{*unspecified*}.
+
+If you instead provide a list of host keys upfront in the
+@code{known-hosts} field, your configuration becomes self-contained and
+stateless: it can be replicated elsewhere or at another point in time.
+Preparing this list can be relatively tedious though, which is why
+@code{*unspecified*} is kept as a default.
+
+@item @code{authorized-keys} (default: @code{'()})
+This must be a list of file-like objects, each of which containing an
+SSH public key that should be authorized to connect to this machine.
+
+Concretely, these files are concatenated and made available as
+@file{~/.ssh/authorized_keys}.  If an OpenSSH server, @command{sshd}, is
+running on this machine, then it @emph{may} take this file into account:
+this is what @command{sshd} does by default, but be aware that it can
+also be configured to ignore it.
+@end table
+@end deftp
+
+@c %start of fragment
+
+@deftp {Data Type} openssh-host
+Available @code{openssh-host} fields are:
+
+@table @asis
+@item @code{name} (type: string)
+Name of this host declaration.
+
+@item @code{host-name} (type: maybe-string)
+Host name---e.g., @code{"foo.example.org"} or @code{"192.168.1.2"}.
+
+@item @code{address-family} (type: address-family)
+Address family to use when connecting to this host: one of
+@code{AF_INET} (for IPv4 only), @code{AF_INET6} (for IPv6 only), or
+@code{*unspecified*} (allowing any address family).
+
+@item @code{identity-file} (type: maybe-string)
+The identity file to use---e.g., @code{"/home/charlie/.ssh/id_ed25519"}.
+
+@item @code{port} (type: maybe-natural-number)
+TCP port number to connect to.
+
+@item @code{user} (type: maybe-string)
+User name on the remote host.
+
+@item @code{forward-x11?} (default: @code{#f}) (type: boolean)
+Whether to forward remote client connections to the local X11 graphical
+display.
+
+@item @code{forward-x11-trusted?} (default: @code{#f}) (type: boolean)
+Whether remote X11 clients have full access to the original X11
+graphical display.
+
+@item @code{forward-agent?} (default: @code{#f}) (type: boolean)
+Whether the authentication agent (if any) is forwarded to the remote
+machine.
+
+@item @code{compression?} (default: @code{#f}) (type: boolean)
+Whether to compress data in transit.
+
+@item @code{proxy-command} (type: maybe-string)
+The command to use to connect to the server.  As an example, a command
+to connect via an HTTP proxy at 192.0.2.0 would be: @code{"nc -X connect
+-x 192.0.2.0:8080 %h %p"}.
+
+@item @code{host-key-algorithms} (type: maybe-string-list)
+The list of accepted host key algorithms---e.g.,
+@code{'("ssh-ed25519")}.
+
+@item @code{accepted-key-types} (type: maybe-string-list)
+The list of accepted user public key types.
+
+@item @code{extra-content} (default: @code{""}) (type: raw-configuration-string)
+Extra content appended as-is to this @code{Host} block in
+@file{~/.ssh/config}.
+
+@end table
+
+@end deftp
+
+
+@c %end of fragment
+
+
 @node Desktop Home Services
 @subsection Desktop Home Services
 
diff --git a/gnu/home/services/ssh.scm b/gnu/home/services/ssh.scm
new file mode 100644
index 0000000000..ff2992766c
--- /dev/null
+++ b/gnu/home/services/ssh.scm
@@ -0,0 +1,254 @@
+;;; GNU Guix --- Functional package management for GNU
+;;; Copyright © 2022 Ludovic Courtès <ludo@gnu.org>
+;;;
+;;; This file is part of GNU Guix.
+;;;
+;;; GNU Guix 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.
+;;;
+;;; GNU Guix 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 GNU Guix.  If not, see <http://www.gnu.org/licenses/>.
+
+(define-module (gnu home services ssh)
+  #:use-module (guix gexp)
+  #:use-module (guix records)
+  #:use-module (guix diagnostics)
+  #:use-module (guix i18n)
+  #:use-module (gnu services)
+  #:use-module (gnu services configuration)
+  #:use-module (guix modules)
+  #:use-module (gnu home services)
+  #:use-module ((gnu home services utils)
+                #:select (object->camel-case-string))
+  #:autoload   (gnu packages base) (glibc-utf8-locales)
+  #:use-module (srfi srfi-1)
+  #:use-module (srfi srfi-34)
+  #:use-module (srfi srfi-35)
+  #:use-module (ice-9 match)
+  #:export (home-openssh-configuration
+            home-openssh-configuration-authorized-keys
+            home-openssh-configuration-known-hosts
+            home-openssh-configuration-hosts
+
+            openssh-host
+            openssh-host-host-name
+            openssh-host-identity-file
+            openssh-host-name
+            openssh-host-port
+            openssh-host-user
+            openssh-host-forward-x11?
+            openssh-host-forward-x11-trusted?
+            openssh-host-forward-agent?
+            openssh-host-compression?
+            openssh-host-proxy-command
+            openssh-host-host-key-algorithms
+            openssh-host-accepted-key-types
+            openssh-host-extra-content
+
+            home-openssh-service-type))
+
+(define (serialize-field-name name)
+  (match name
+    ('accepted-key-types "PubkeyAcceptedKeyTypes")
+    (_
+     (let ((name (let ((str (symbol->string name)))
+                   (if (string-suffix? "?" str)
+                       (string->symbol (string-drop-right str 1))
+                       name))))
+       (object->camel-case-string name 'upper)))))
+
+(define (serialize-string field value)
+  (string-append "  " (serialize-field-name field)
+                 " " value "\n"))
+
+(define (address-family? obj)
+  (memv obj (list *unspecified* AF_INET AF_INET6)))
+
+(define (serialize-address-family field family)
+  (if (unspecified? family)
+      ""
+      (string-append "  " (serialize-field-name field) " "
+                     (cond ((= family AF_INET) "inet")
+                           ((= family AF_INET6) "inet6")
+                           ;; The 'else' branch is unreachable.
+                           (else (raise (condition (&error)))))
+                     "\n")))
+
+(define (natural-number? obj)
+  (and (integer? obj) (exact? obj) (> obj 0)))
+
+(define (serialize-natural-number field value)
+  (string-append "  " (serialize-field-name field) " "
+                 (number->string value) "\n"))
+
+(define (serialize-boolean field value)
+  (string-append "  " (serialize-field-name field) " "
+                 (if value "yes" "no") "\n"))
+
+(define-maybe string)
+(define-maybe natural-number)
+
+(define (serialize-raw-configuration-string field value)
+  (string-append value "\n"))
+(define raw-configuration-string? string?)
+
+(define (string-list? lst)
+  (and (pair? lst) (every string? lst)))
+(define (serialize-string-list field lst)
+  (string-append "  " (serialize-field-name field) " "
+                 (string-join lst ",") "\n"))
+
+(define-maybe string-list)
+
+(define-configuration openssh-host
+  (name
+   (string)
+   "Name of this host declaration.")
+  (host-name
+   maybe-string
+   "Host name---e.g., @code{\"foo.example.org\"} or @code{\"192.168.1.2\"}.")
+  (address-family
+   address-family
+   "Address family to use when connecting to this host: one of
+@code{AF_INET} (for IPv4 only), @code{AF_INET6} (for IPv6 only), or
+@code{*unspecified*} (allowing any address family).")
+  (identity-file
+   maybe-string
+   "The identity file to use---e.g.,
+@code{\"/home/charlie/.ssh/id_ed25519\"}.")
+  (port
+   maybe-natural-number
+   "TCP port number to connect to.")
+  (user
+   maybe-string
+   "User name on the remote host.")
+  (forward-x11?
+   (boolean #f)
+   "Whether to forward remote client connections to the local X11 graphical
+display.")
+  (forward-x11-trusted?
+   (boolean #f)
+   "Whether remote X11 clients have full access to the original X11 graphical
+display.")
+  (forward-agent?
+   (boolean #f)
+   "Whether the authentication agent (if any) is forwarded to the remote
+machine.")
+  (compression?
+   (boolean #f)
+   "Whether to compress data in transit.")
+  (proxy-command
+   maybe-string
+   "The command to use to connect to the server.  As an example, a command
+to connect via an HTTP proxy at 192.0.2.0 would be: @code{\"nc -X
+connect -x 192.0.2.0:8080 %h %p\"}.")
+  (host-key-algorithms
+   maybe-string-list
+   "The list of accepted host key algorithms---e.g.,
+@code{'(\"ssh-ed25519\")}.")
+  (accepted-key-types
+   maybe-string-list
+   "The list of accepted user public key types.")
+  (extra-content
+   (raw-configuration-string "")
+   "Extra content appended as-is to this @code{Host} block in
+@file{~/.ssh/config}."))
+
+(define (serialize-openssh-host config)
+  (define (openssh-host-name-field? field)
+    (eq? (configuration-field-name field) 'name))
+
+  (string-append
+   "Host " (openssh-host-name config) "\n"
+   (string-concatenate
+    (map (lambda (field)
+           ((configuration-field-serializer field)
+            (configuration-field-name field)
+            ((configuration-field-getter field) config)))
+         (remove openssh-host-name-field?
+                 openssh-host-fields)))))
+
+(define-record-type* <home-openssh-configuration>
+  home-openssh-configuration make-home-openssh-configuration
+  home-openssh-configuration?
+  (authorized-keys home-openssh-configuration-authorized-keys ;list of file-like
+                   (default '()))
+  (known-hosts     home-openssh-configuration-known-hosts ;unspec | list of file-like
+                   (default *unspecified*))
+  (hosts           home-openssh-configuration-hosts   ;list of <openssh-host>
+                   (default '())))
+
+(define (openssh-configuration->string config)
+  (string-join (map serialize-openssh-host
+                    (home-openssh-configuration-hosts config))
+               "\n"))
+
+(define* (file-join name files #:optional (delimiter " "))
+  "Return a file in the store called @var{name} that is the concatenation
+of all the file-like objects listed in @var{files}, with @var{delimited}
+inserted after each of them."
+  (computed-file name
+                 (with-imported-modules '((guix build utils))
+                   #~(begin
+                       (use-modules (guix build utils))
+
+                       ;; Support non-ASCII file names.
+                       (setenv "GUIX_LOCPATH"
+                               #+(file-append glibc-utf8-locales
+                                              "/lib/locale"))
+                       (setlocale LC_ALL "en_US.utf8")
+
+                       (call-with-output-file #$output
+                         (lambda (output)
+                           (for-each (lambda (file)
+                                       (call-with-input-file file
+                                         (lambda (input)
+                                           (dump-port input output)))
+                                       (display #$delimiter output))
+                                     '#$files)))))))
+
+(define (openssh-configuration-files config)
+  (let ((config (plain-file "ssh.conf"
+                            (openssh-configuration->string config)))
+        (known-hosts (home-openssh-configuration-known-hosts config))
+        (authorized-keys (file-join
+                          "authorized_keys"
+                          (home-openssh-configuration-authorized-keys config)
+                          "\n")))
+    `((".ssh/authorized_keys" ,authorized-keys)
+      ,@(if (unspecified? known-hosts)
+            '()
+            `((".ssh/known_hosts"
+               ,(file-join "known_hosts" known-hosts "\n"))))
+      (".ssh/config" ,config))))
+
+(define openssh-activation
+  (with-imported-modules (source-module-closure
+                          '((gnu build activation)))
+    #~(begin
+        (use-modules (gnu build activation))
+
+        ;; Make sure ~/.ssh is #o700.
+        (let* ((home (getenv "HOME"))
+               (dot-ssh (string-append home "/.ssh")))
+          (mkdir-p/perms dot-ssh (getpw (getuid)) #o700)))))
+
+(define home-openssh-service-type
+  (service-type
+   (name 'home-openssh)
+   (extensions
+    (list (service-extension home-files-service-type
+                             openssh-configuration-files)
+          (service-extension home-activation-service-type
+                             (const openssh-activation))))
+   (description "Configure the OpenSSH @acronym{SSH, secure shell} client
+by providing a @file{~/.ssh/config} file, which is honored by the OpenSSH
+client,@command{ssh}, and by other tools such as @command{guix deploy}.")
+   (default-value (home-openssh-configuration))))
diff --git a/gnu/local.mk b/gnu/local.mk
index 5a9edc16bb..372573d3c4 100644
--- a/gnu/local.mk
+++ b/gnu/local.mk
@@ -85,6 +85,7 @@ GNU_SYSTEM_MODULES =				\
   %D%/home/services/fontutils.scm		\
   %D%/home/services/shells.scm			\
   %D%/home/services/shepherd.scm		\
+  %D%/home/services/ssh.scm			\
   %D%/home/services/mcron.scm			\
   %D%/home/services/utils.scm			\
   %D%/home/services/xdg.scm			\
diff --git a/po/guix/POTFILES.in b/po/guix/POTFILES.in
index 6b8bd92bb7..201e5dcc87 100644
--- a/po/guix/POTFILES.in
+++ b/po/guix/POTFILES.in
@@ -6,6 +6,7 @@ gnu/services.scm
 gnu/system.scm
 gnu/services/shepherd.scm
 gnu/home/services.scm
+gnu/home/services/ssh.scm
 gnu/home/services/symlink-manager.scm
 gnu/system/file-systems.scm
 gnu/system/image.scm

base-commit: 8a04ac4b2f5d356719d896536dabc95a9520c938
-- 
2.36.1





^ permalink raw reply related	[flat|nested] 20+ messages in thread

* [bug#55912] [PATCH v2] home: Add OpenSSH service.
  2022-06-15 20:29   ` [bug#55912] [PATCH v2] " Ludovic Courtès
@ 2022-06-15 20:47     ` Maxime Devos
  2022-06-16 10:47       ` Ludovic Courtès
  2022-06-18 21:41     ` bug#55912: " Ludovic Courtès
  1 sibling, 1 reply; 20+ messages in thread
From: Maxime Devos @ 2022-06-15 20:47 UTC (permalink / raw)
  To: Ludovic Courtès, 55912

[-- Attachment #1: Type: text/plain, Size: 1155 bytes --]

Ludovic Courtès schreef op wo 15-06-2022 om 22:29 [+0200]:
> +  (computed-file name
> +                 (with-imported-modules '((guix build utils))
> +                   #~(begin
> +                       (use-modules (guix build utils))
> +
> +                       ;; Support non-ASCII file names.
> +                       (setenv "GUIX_LOCPATH"
> +                               #+(file-append glibc-utf8-locales
> +                                              "/lib/locale"))
> +                       (setlocale LC_ALL "en_US.utf8")

For robustness, I think it would be best to move this locale
initialisation code to the implementation of 'computed-file' itself, to
eliminate this potential pitfall entirely.

Except for 'racket' and package transformations, this does not seem to
used by any package definition (except via meson when cross-compiling),
so it doesn't seem like this would entail a world-rebuild
(unverified!).

Greetings,
Maxime.

[-- Attachment #2: This is a digitally signed message part --]
[-- Type: application/pgp-signature, Size: 260 bytes --]

^ permalink raw reply	[flat|nested] 20+ messages in thread

* [bug#55912] [PATCH v2] home: Add OpenSSH service.
  2022-06-15 20:47     ` Maxime Devos
@ 2022-06-16 10:47       ` Ludovic Courtès
  2022-06-16 12:16         ` Maxime Devos
  0 siblings, 1 reply; 20+ messages in thread
From: Ludovic Courtès @ 2022-06-16 10:47 UTC (permalink / raw)
  To: Maxime Devos; +Cc: 55912

Maxime Devos <maximedevos@telenet.be> skribis:

> Ludovic Courtès schreef op wo 15-06-2022 om 22:29 [+0200]:
>> +  (computed-file name
>> +                 (with-imported-modules '((guix build utils))
>> +                   #~(begin
>> +                       (use-modules (guix build utils))
>> +
>> +                       ;; Support non-ASCII file names.
>> +                       (setenv "GUIX_LOCPATH"
>> +                               #+(file-append glibc-utf8-locales
>> +                                              "/lib/locale"))
>> +                       (setlocale LC_ALL "en_US.utf8")
>
> For robustness, I think it would be best to move this locale
> initialisation code to the implementation of 'computed-file' itself, to
> eliminate this potential pitfall entirely.

I’d rather have ‘computed-file’ do just what it’s documented to do; I
don’t think this kind of thing belongs there.  (It’s beyond the scope of
this patch set too.)

Ideally Guile would just do the right thing without us fiddling with
locales.  That is, it would default to UTF-8 rather than ASCII.

Thanks,
Ludo’.

^ permalink raw reply	[flat|nested] 20+ messages in thread

* [bug#55912] [PATCH v2] home: Add OpenSSH service.
  2022-06-16 10:47       ` Ludovic Courtès
@ 2022-06-16 12:16         ` Maxime Devos
  2022-06-16 12:31           ` Maxime Devos
  2022-06-16 16:18           ` [bug#55912] [PATCH] " Ludovic Courtès
  0 siblings, 2 replies; 20+ messages in thread
From: Maxime Devos @ 2022-06-16 12:16 UTC (permalink / raw)
  To: Ludovic Courtès; +Cc: 55912

[-- Attachment #1: Type: text/plain, Size: 962 bytes --]

> I’d rather have ‘computed-file’ do just what it’s documented to do;
> I don’t think this kind of thing belongs there (It’s beyond the scope
> of this patch set too.)

The documentation of ‘computed-file’ can be modified to document it
uses a non-broken file name encoding instead of the broken default. 
Though something for a separate patch I suppose.

Ludovic Courtès schreef op do 16-06-2022 om 12:47 [+0200]:
> Ideally Guile would just do the right thing without us fiddling with
> locales.  That is, it would default to UTF-8 rather than ASCII.

I did a quick test, and apparently Guile calls nl_langinfo to determine
the encoding, which returns ANSI_X3.4-1968, because glibc defaults to
the C locale.  So unless you want to change the encoding of the C
locale or change the default locale or override glibc's choice of
default locale in Guile, I don't think there's anything to change in
Guile?

Greetins,
Maxime.

[-- Attachment #2: This is a digitally signed message part --]
[-- Type: application/pgp-signature, Size: 260 bytes --]

^ permalink raw reply	[flat|nested] 20+ messages in thread

* [bug#55912] [PATCH v2] home: Add OpenSSH service.
  2022-06-16 12:16         ` Maxime Devos
@ 2022-06-16 12:31           ` Maxime Devos
  2022-06-16 16:18           ` [bug#55912] [PATCH] " Ludovic Courtès
  1 sibling, 0 replies; 20+ messages in thread
From: Maxime Devos @ 2022-06-16 12:31 UTC (permalink / raw)
  To: Ludovic Courtès; +Cc: 55912

[-- Attachment #1: Type: text/plain, Size: 501 bytes --]

Maxime Devos schreef op do 16-06-2022 om 14:16 [+0200]:
> Ludovic Courtès schreef op do 16-06-2022 om 12:47 [+0200]:
> > Ideally Guile would just do the right thing without us fiddling
> > with locales.  That is, it would default to UTF-8 rather than
> > ASCII.

Somewhat related, I could look into separating locales from the file
name encoding in Guile (with a parameter object or something) later? 
Not a solution as-is, but would be convenient in many places ...

Greetings,
Maxime.

[-- Attachment #2: This is a digitally signed message part --]
[-- Type: application/pgp-signature, Size: 260 bytes --]

^ permalink raw reply	[flat|nested] 20+ messages in thread

* [bug#55912] [PATCH] home: Add OpenSSH service.
  2022-06-16 12:16         ` Maxime Devos
  2022-06-16 12:31           ` Maxime Devos
@ 2022-06-16 16:18           ` Ludovic Courtès
  2022-06-16 16:45             ` Maxime Devos
  1 sibling, 1 reply; 20+ messages in thread
From: Ludovic Courtès @ 2022-06-16 16:18 UTC (permalink / raw)
  To: Maxime Devos; +Cc: 55912

Maxime Devos <maximedevos@telenet.be> skribis:

> I did a quick test, and apparently Guile calls nl_langinfo to determine
> the encoding, which returns ANSI_X3.4-1968, because glibc defaults to
> the C locale.  So unless you want to change the encoding of the C
> locale or change the default locale or override glibc's choice of
> default locale in Guile, I don't think there's anything to change in
> Guile?

Glibc 2.35 includes the “C.UTF-8” locale; I don’t know if it’s the
default, but it will likely help.

> Somewhat related, I could look into separating locales from the file
> name encoding in Guile (with a parameter object or something) later? 
> Not a solution as-is, but would be convenient in many places ...

Yes, that too.  In (guix build syscalls), there’s a variant of ‘scandir’
for instance that is locale-independent and decodes file names as UTF-8.
Nowadays that’s probably the most sensible option.

In Guile proper, it would be nice if there were a ‘%file-name-encoding’
fluid.

Thanks,
Ludo’.




^ permalink raw reply	[flat|nested] 20+ messages in thread

* [bug#55912] [PATCH] home: Add OpenSSH service.
  2022-06-16 16:18           ` [bug#55912] [PATCH] " Ludovic Courtès
@ 2022-06-16 16:45             ` Maxime Devos
  2022-06-17 12:32               ` Ludovic Courtès
  2022-06-17 12:42               ` Philip McGrath
  0 siblings, 2 replies; 20+ messages in thread
From: Maxime Devos @ 2022-06-16 16:45 UTC (permalink / raw)
  To: Ludovic Courtès; +Cc: 55912

[-- Attachment #1: Type: text/plain, Size: 285 bytes --]

Ludovic Courtès schreef op do 16-06-2022 om 18:18 [+0200]:
> In Guile proper, it would be nice if there were a ‘%file-name-encoding’
> fluid.

I was more thinking of a %file-name-encoding parameter (instead of a
fluid), but that's what I had in mind.

Greetings,
Maxime.

[-- Attachment #2: This is a digitally signed message part --]
[-- Type: application/pgp-signature, Size: 260 bytes --]

^ permalink raw reply	[flat|nested] 20+ messages in thread

* [bug#55912] [PATCH] home: Add OpenSSH service.
  2022-06-16 16:45             ` Maxime Devos
@ 2022-06-17 12:32               ` Ludovic Courtès
  2022-06-17 12:42               ` Philip McGrath
  1 sibling, 0 replies; 20+ messages in thread
From: Ludovic Courtès @ 2022-06-17 12:32 UTC (permalink / raw)
  To: Maxime Devos; +Cc: 55912

Maxime Devos <maximedevos@telenet.be> skribis:

> Ludovic Courtès schreef op do 16-06-2022 om 18:18 [+0200]:
>> In Guile proper, it would be nice if there were a ‘%file-name-encoding’
>> fluid.
>
> I was more thinking of a %file-name-encoding parameter (instead of a
> fluid), but that's what I had in mind.

Yeah, could be (for “historical reasons”, similar interfaces such as
‘%default-port-encoding’ were fluids, that’s why I mentioned that).

Ludo’.




^ permalink raw reply	[flat|nested] 20+ messages in thread

* [bug#55912] [PATCH] home: Add OpenSSH service.
  2022-06-16 16:45             ` Maxime Devos
  2022-06-17 12:32               ` Ludovic Courtès
@ 2022-06-17 12:42               ` Philip McGrath
  2022-06-17 20:56                 ` Maxime Devos
  1 sibling, 1 reply; 20+ messages in thread
From: Philip McGrath @ 2022-06-17 12:42 UTC (permalink / raw)
  To: ludo, 55912; +Cc: maximedevos

[-- Attachment #1: Type: text/plain, Size: 1513 bytes --]

On Thursday, June 16, 2022 12:45:30 PM EDT Maxime Devos wrote:
> Ludovic Courtès schreef op do 16-06-2022 om 18:18 [+0200]:
> > In Guile proper, it would be nice if there were a ‘%file-name-encoding’
> > fluid.
> 
> I was more thinking of a %file-name-encoding parameter (instead of a
> fluid), but that's what I had in mind.
> 

I think the problem goes deeper than that: an R6RS string is a fixed-length 
sequence of Unicode scalar values, but a path on a Unix-like system is a 
sequence of non-null bytes, and on Windows is a sequence of UTF-16 code units 
with possibly unpaired surrogates (aka WTF-16 [1]). That is, there are some 
valid paths that can not be represented as Scheme strings.

Racket has a really nice path datatype that handles these subtleties (there 
are many bad headaches if you want to be portable to Windows) while allowing 
an ergonomic use of strings for the common case. [2]

Zuo has a more minimal path API which takes advantage of the fact that a Zuo 
string is like a Scheme bytevector. [3] It doesn't handle all of the 
complexity managed by the Racket path type, but focuses on the subset of paths 
applicable to a build environment. The implementation of all of Zuo is a 
single C file. [4]

-Philip

[1]: https://simonsapin.github.io/wtf-8/
[2]: https://docs.racket-lang.org/reference/pathutils.html
[3]: https://docs.racket-lang.org/zuo/zuo-base.html#%28part._.Paths%29
[4]: https://github.com/racket/racket/blob/master/racket/src/zuo/zuo.c

[-- Attachment #2: This is a digitally signed message part. --]
[-- Type: application/pgp-signature, Size: 833 bytes --]

^ permalink raw reply	[flat|nested] 20+ messages in thread

* [bug#55912] [PATCH] home: Add OpenSSH service.
  2022-06-17 12:42               ` Philip McGrath
@ 2022-06-17 20:56                 ` Maxime Devos
  0 siblings, 0 replies; 20+ messages in thread
From: Maxime Devos @ 2022-06-17 20:56 UTC (permalink / raw)
  To: philip, ludo, 55912

[-- Attachment #1: Type: text/plain, Size: 1374 bytes --]

Philip McGrath schreef op vr 17-06-2022 om 08:42 [-0400]:
> I think the problem goes deeper than that: an R6RS string is a fixed-
> length sequence of Unicode scalar values, but a path on a Unix-like
> system is a sequence of non-null bytes,

That's one of the possibilities I know about.  Treating that case as
‘ISO-88591-1’ would be sufficient for Guix, albeit conceptually
incorrect.

> and on Windows is a sequence of UTF-16 code units 
> with possibly unpaired surrogates (aka WTF-16 [1]).  
> That is, there are some
> valid paths that can not be represented as Scheme strings.

Nasty. Didn't know about that.  I'll skip that one for now though
(I don't think Guile uses the right Windows APIs for that, it just
calls 'open' and 'stat' and the like).

> Racket has a really nice path datatype that handles these subtleties
> (there  are many bad headaches if you want to be portable to Windows)
> while allowing an ergonomic use of strings for the common case. [2]

For now(later), I'll just stick to support overriding the file name
encoding, a proper separate path datatype can be added later.  E.g.,
open-file can be changed to support both filenames as strings (to be
encoded by the file name encoding) or as an instance of the path
datatype.  Except for rebase conflicts, this seems rather orthogonal to
me.

Greetings,
Maxime.

[-- Attachment #2: This is a digitally signed message part --]
[-- Type: application/pgp-signature, Size: 260 bytes --]

^ permalink raw reply	[flat|nested] 20+ messages in thread

* bug#55912: [PATCH] home: Add OpenSSH service.
  2022-06-15 20:29   ` [bug#55912] [PATCH v2] " Ludovic Courtès
  2022-06-15 20:47     ` Maxime Devos
@ 2022-06-18 21:41     ` Ludovic Courtès
  1 sibling, 0 replies; 20+ messages in thread
From: Ludovic Courtès @ 2022-06-18 21:41 UTC (permalink / raw)
  To: 55912-done; +Cc: Maxime Devos

Ludovic Courtès <ludo@gnu.org> skribis:

> * gnu/home/services/ssh.scm: New file.
> * gnu/local.mk (GNU_SYSTEM_MODULES): Add it.
> * po/guix/POTFILES.in: Add it.
> * doc/guix.texi (Secure Shell): New section.

Pushed yesterday as 7f208f68dea828fe02718ca8ce81d5975136cff8.

Thanks, Maxime!

Ludo’.




^ permalink raw reply	[flat|nested] 20+ messages in thread

end of thread, other threads:[~2022-06-18 21:42 UTC | newest]

Thread overview: 20+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2022-06-11 16:49 [bug#55912] [PATCH] home: Add OpenSSH service Ludovic Courtès
2022-06-11 19:51 ` Maxime Devos
2022-06-11 22:13   ` Maxime Devos
2022-06-13  9:41   ` Ludovic Courtès
2022-06-13 10:51     ` Maxime Devos
2022-06-13 12:02       ` Ludovic Courtès
2022-06-13 12:38         ` Maxime Devos
2022-06-13 21:58     ` Maxime Devos
2022-06-14  8:08       ` Ludovic Courtès
2022-06-15 20:29   ` [bug#55912] [PATCH v2] " Ludovic Courtès
2022-06-15 20:47     ` Maxime Devos
2022-06-16 10:47       ` Ludovic Courtès
2022-06-16 12:16         ` Maxime Devos
2022-06-16 12:31           ` Maxime Devos
2022-06-16 16:18           ` [bug#55912] [PATCH] " Ludovic Courtès
2022-06-16 16:45             ` Maxime Devos
2022-06-17 12:32               ` Ludovic Courtès
2022-06-17 12:42               ` Philip McGrath
2022-06-17 20:56                 ` Maxime Devos
2022-06-18 21:41     ` bug#55912: " Ludovic Courtès

Code repositories for project(s) associated with this public inbox

	https://git.savannah.gnu.org/cgit/guix.git

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for read-only IMAP folder(s) and NNTP newsgroup(s).