From da6aec32cf13cf06cfda909c45f7f35625c4e3fa Mon Sep 17 00:00:00 2001 From: Oleg Pykhalov Date: Mon, 28 Sep 2020 23:52:51 +0300 Subject: [PATCH] gnu: Add webssh service. * gnu/services/ssh.scm: (): New record type. (%webssh-configuration-nginx, webssh-service-type): New variables. (webssh-account, webssh-activation, webssh-shepherd-service): New procedures. * doc/guix.texi: Document this. --- doc/guix.texi | 75 +++++++++++++++++++++++++ gnu/services/ssh.scm | 131 ++++++++++++++++++++++++++++++++++++++++++- 2 files changed, 205 insertions(+), 1 deletion(-) diff --git a/doc/guix.texi b/doc/guix.texi index e8458ad8d8..79c79b6a96 100644 --- a/doc/guix.texi +++ b/doc/guix.texi @@ -15547,6 +15547,81 @@ may cause undefined behaviour. @end table @end deftp +@cindex WebSSH +@deffn {Scheme Variable} webssh-service-type +This is the type for the @uref{https://webssh.huashengdun.org/, WebSSH} +program that runs a web SSH client. WebSSH can be run manually from the +command-line by passing arguments to the binary @command{wssh} from the +package @code{webssh}, but it can also be run as a Guix service. This +latter use case is documented here. + +For example, to specify a service running WebSSH on loopback interface +on port @code{8888} with reject policy with a list of allowed to +connection hosts, and NGINX as a reverse-proxy to this service listening +for HTTPS connection, add this call to the operating system's +@code{services} field: + +@lisp +(service webssh-service-type + (webssh-configuration (address "127.0.0.1") + (port 8888) + (policy 'reject) + (known-hosts '("localhost ecdsa-sha2-nistp256 AAAA…" + "127.0.0.1 ecdsa-sha2-nistp256 AAAA…")))) + +(service nginx-service-type + (nginx-configuration + (server-blocks + (list + (nginx-server-configuration + (inherit %webssh-configuration-nginx) + (server-name '("webssh.example.com")) + (listen '("443 ssl")) + (ssl-certificate (letsencrypt-certificate "webssh.example.com")) + (ssl-certificate-key (letsencrypt-key "webssh.example.com")) + (locations + (cons (nginx-location-configuration + (uri "/.well-known") + (body '("root /var/www;"))) + (nginx-server-configuration-locations %webssh-configuration-nginx)))))))) +@end lisp +@end deffn + +@deftp {Data Type} webssh-configuration +Data type representing the configuration for @code{webssh-service}. + +@table @asis +@item @code{package} (default: @var{webssh}) +@code{webssh} package to use. + +@item @code{user-name} (default: @var{"webssh"}) +User name or user ID that file transfers to and from that module should take +place. + +@item @code{group-name} (default: @var{"webssh"}) +Group name or group ID that will be used when accessing the module. + +@item @code{address} (default: @var{#f}) +IP address on which @command{webssh} listens for incoming connections. + +@item @code{port} (default: @var{8888}) +TCP port on which @command{webssh} listens for incoming connections. + +@item @code{policy} (default: @var{#f}) +Connection policy. @var{reject} policy requires to specify @var{known-hosts}. + +@item @code{known-hosts} (default: @var{'()}) +List of hosts which allowed for SSH connection from @command{webssh}. + +@item @code{log-file} (default: @file{"/var/log/webssh.log"}) +Name of the file where @command{rsync} writes its log file. + +@item @code{log-level} (default: @var{#f}) +Logging level. + +@end table +@end deftp + @defvr {Scheme Variable} %facebook-host-aliases This variable contains a string for use in @file{/etc/hosts} (@pxref{Host Names,,, libc, The GNU C Library Reference Manual}). Each diff --git a/gnu/services/ssh.scm b/gnu/services/ssh.scm index ced21c0742..1891db0487 100644 --- a/gnu/services/ssh.scm +++ b/gnu/services/ssh.scm @@ -5,6 +5,7 @@ ;;; Copyright © 2017 Clément Lassieur ;;; Copyright © 2019 Ricardo Wurmus ;;; Copyright © 2020 pinoaffe +;;; Copyright © 2020 Oleg Pykhalov ;;; ;;; This file is part of GNU Guix. ;;; @@ -26,6 +27,7 @@ (define-module (gnu services ssh) #:use-module (gnu packages admin) #:use-module (gnu services) #:use-module (gnu services shepherd) + #:use-module (gnu services web) #:use-module (gnu system pam) #:use-module (gnu system shadow) #:use-module (guix gexp) @@ -50,7 +52,12 @@ (define-module (gnu services ssh) autossh-configuration autossh-configuration? - autossh-service-type)) + autossh-service-type + + webssh-configuration + webssh-configuration? + webssh-service-type + %webssh-configuration-nginx)) ;;; Commentary: ;;; @@ -732,4 +739,126 @@ (define autossh-service-type autossh-service-activation))) (default-value (autossh-configuration)))) + +;;; +;;; WebSSH +;;; + +(define-record-type* + webssh-configuration make-webssh-configuration + webssh-configuration? + (package webssh-configuration-package ;package + (default webssh)) + (user-name webssh-configuration-user-name ;string + (default "webssh")) + (group-name webssh-configuration-group-name ;string + (default "webssh")) + (policy webssh-configuration-policy ;symbol + (default #f)) + (known-hosts webssh-configuration-known-hosts ;list of strings + (default #f)) + (port webssh-configuration-port ;number + (default #f)) + (address webssh-configuration-address ;string + (default #f)) + (log-file webssh-configuration-log-file ;string + (default "/var/log/webssh.log")) + (log-level webssh-configuration-log-level ;symbol + (default #f))) + +(define %webssh-configuration-nginx + (nginx-server-configuration + (listen '("80")) + (locations + (list (nginx-location-configuration + (uri "/") + (body '("proxy_pass http://127.0.0.1:8888;" + "proxy_http_version 1.1;" + "proxy_read_timeout 300;" + "proxy_set_header Upgrade $http_upgrade;" + "proxy_set_header Connection \"upgrade\";" + "proxy_set_header Host $http_host;" + "proxy_set_header X-Real-IP $remote_addr;" + "proxy_set_header X-Real-PORT $remote_port;"))))))) + +(define webssh-account + ;; Return the user accounts and user groups for CONFIG. + (match-lambda + (($ _ user-name group-name _ _ _ _ _ _) + (list (user-group + (name group-name)) + (user-account + (name user-name) + (group group-name) + (comment "webssh privilege separation user") + (home-directory (string-append "/var/run/" user-name)) + (shell #~(string-append #$shadow "/sbin/nologin"))))))) + +(define webssh-activation + ;; Return the activation GEXP for CONFIG. + (match-lambda + (($ _ user-name group-name policy known-hosts _ _ + log-file _) + (with-imported-modules '((guix build utils)) + #~(begin + (let* ((home-dir (string-append "/var/run/" #$user-name)) + (ssh-dir (string-append home-dir "/.ssh")) + (known-hosts-file (string-append ssh-dir "/known_hosts"))) + (call-with-output-file #$log-file (const #t)) + (mkdir-p ssh-dir) + (case '#$policy + ((reject) + (if '#$known-hosts + (call-with-output-file known-hosts-file + (lambda (port) + (for-each (lambda (host) (display host port) (newline port)) + '#$known-hosts))) + (display-hint (G_ "webssh: reject policy requires `known-hosts'."))))) + (for-each (lambda (file) + (chown file + (passwd:uid (getpw #$user-name)) + (group:gid (getpw #$group-name)))) + (list #$log-file ssh-dir known-hosts-file)) + (chmod ssh-dir #o700))))))) + +(define webssh-shepherd-service + (match-lambda + (($ package user-name group-name policy _ port + address log-file log-level) + (list (shepherd-service + (provision '(webssh)) + (documentation "Run webssh daemon.") + (start #~(make-forkexec-constructor + `(,(string-append #$webssh "/bin/wssh") + ,(string-append "--log-file-prefix=" #$log-file) + ,@(case '#$log-level + ((debug) '("--logging=debug")) + (else '())) + ,@(case '#$policy + ((reject) '("--policy=reject")) + (else '())) + ,@(if #$port + (list (string-append "--port=" (number->string #$port))) + '()) + ,@(if #$address + (list (string-append "--address=" #$address)) + '())) + #:user #$user-name + #:group #$group-name)) + (stop #~(make-kill-destructor))))))) + +(define webssh-service-type + (service-type + (name 'webssh) + (extensions + (list (service-extension shepherd-root-service-type + webssh-shepherd-service) + (service-extension account-service-type + webssh-account) + (service-extension activation-service-type + webssh-activation))) + (default-value (webssh-configuration)) + (description + "Run the webssh."))) + ;;; ssh.scm ends here