From 1c2c188dc3544bd4df571ce06d24784640db43d5 Mon Sep 17 00:00:00 2001 From: Vivien Kraus Date: Tue, 30 Mar 2021 20:25:01 +0200 Subject: Implement a reverse proxy --- bootstrap | 20 ++- doc/webid-oidc.texi | 33 ++++ guix/vkraus/packages/webid-oidc.scm | 2 +- guix/vkraus/services/webid-oidc.scm | 82 ++++++++++ man/Makefile.am | 6 +- po/POTFILES.in | 1 + po/fr.po | 284 ++++++++++++++++++----------------- po/webid-oidc.pot | 93 +++++++++++- src/Makefile.am | 2 +- src/scm/webid-oidc/Makefile.am | 6 +- src/scm/webid-oidc/reverse-proxy.scm | 257 +++++++++++++++++++++++++++++++ src/webid-oidc-reverse-proxy | 7 + 12 files changed, 647 insertions(+), 146 deletions(-) create mode 100644 src/scm/webid-oidc/reverse-proxy.scm create mode 100755 src/webid-oidc-reverse-proxy diff --git a/bootstrap b/bootstrap index 878bc5c..ea8a192 100755 --- a/bootstrap +++ b/bootstrap @@ -4,14 +4,26 @@ autoreconf -vif || exit 1 sed -i 's|SHELL = /bin/sh|SHELL = @SHELL@|g' po/Makefile.in.in || exit 1 ## Prepare the man pages +SCRIPTS_THAT_GET_EXECUTED="../src/webid-oidc-issuer ../src/webid-oidc-reverse-proxy" + mkdir -p .native || exit 1 cd .native || exit 1 bash ../configure SHELL=$(which sh) || exit 1 -cp ../src/webid-oidc-issuer ../src/webid-oidc-issuer.bak || exit 1 -sed -i "s|/usr/local/bin/guile|$(which guile)|g" ../src/webid-oidc-issuer || exit 1 + +for file in $SCRIPTS_THAT_GET_EXECUTED +do + cp $file ${file}.bak + sed -i "s|/usr/local/bin/guile|$(which guile)|g" $file || exit 1 +done + make -j V=1 || exit 1 make -j dist || exit 1 -chmod u+w ../src/webid-oidc-issuer || exit 1 -cp ../src/webid-oidc-issuer.bak ../src/webid-oidc-issuer || exit 1 + +for file in $SCRIPTS_THAT_GET_EXECUTED +do + chmod u+w $file || exit 1 + cp ${file}.bak $file || exit 1 +done + cd .. || exit 1 rm -rf .native || exit 1 diff --git a/doc/webid-oidc.texi b/doc/webid-oidc.texi index 6a0e633..dda97bd 100644 --- a/doc/webid-oidc.texi +++ b/doc/webid-oidc.texi @@ -438,6 +438,7 @@ the seed. @chapter Running a Resource Server @menu +* Running webid-oidc-reverse-proxy:: * The authenticator:: @end menu @@ -445,6 +446,38 @@ A Solid server is the server that manages your data. It needs to check that the proofs of possession are correct, and the possessed key is signed by the identity provider. +@node Running webid-oidc-reverse-proxy +@section Running webid-oidc-reverse-proxy + +The distribution comes with a reverse proxy, aptly named +@code{webid-oidc-reverse-proxy}, to listen to an interface, take +requests, authenticate them, and pass them to a backend with an +additional header containing the webid of the agent, if authenticated. + +The reverse proxy is invoked with the following arguments: + +@table @asis +@item @code{-p}@var{PORT}, @code{--port=}@var{PORT} +the port on which the reverse proxy listens; +@item @code{-i}@var{INBOUND}, @code{--inbound-uri=}@var{INBOUND} +the public name of the server; +@item @code{-o}@var{OUTBOUND}, @code{--outbound-uri=}@var{OUTBOUND} +the address of the backend; +@item @code{-H}@var{HEADER}, @code{--header=}@var{HEADER} +replace the name of the header that will contain the webid of the +user. Defaults to @code{XXX-Agent}. Please note that this value should +be ASCII, otherwise it’s not guaranteed that the reverse proxy will +drop other capitalizations of the header in malicious requests; +@item @code{-l @var{FILE.log}}, or @code{--log-file=@var{FILE.log}} +let the server dump all its output to @var{FILE.log}. See the identity +provider comment; +@item @code{-e @var{FILE.err}}, or @code{--error-file=@var{FILE.err}} +let the server dump all its errors to @var{FILE.err}. +@end table + +You can localize the interface by setting the @var{LANG} environment +variable. + @node The authenticator @section The authenticator diff --git a/guix/vkraus/packages/webid-oidc.scm b/guix/vkraus/packages/webid-oidc.scm index 58e5913..f5ef57f 100644 --- a/guix/vkraus/packages/webid-oidc.scm +++ b/guix/vkraus/packages/webid-oidc.scm @@ -79,7 +79,7 @@ (format #f "~a/bin/webid-oidc-~a" out program) `("GUILE_LOAD_PATH" ":" = ,mod-paths) `("GUILE_LOAD_COMPILED_PATH" ":" = ,go-paths))) - '(hello issuer)))))))) + '(hello issuer reverse-proxy)))))))) (native-inputs `(("pkg-config" ,pkg-config) ("guile" ,guile-3.0) diff --git a/guix/vkraus/services/webid-oidc.scm b/guix/vkraus/services/webid-oidc.scm index 931e96a..33b4fae 100644 --- a/guix/vkraus/services/webid-oidc.scm +++ b/guix/vkraus/services/webid-oidc.scm @@ -31,6 +31,21 @@ webid-oidc-issuer-configuration-extra-options (default '()))) +(define-record-type* + webid-oidc-reverse-proxy-configuration + make-webid-oidc-reverse-proxy-configuration + webid-oidc-reverse-proxy-configuration? + (webid-oidc webid-oidc-reverse-proxy-configuration-webid-oidc + (default webid-oidc)) + (port webid-oidc-reverse-proxy-port (default 8090)) + (inbound-uri webid-oidc-reverse-proxy-configuration-inbound-uri) + (outbound-uri webid-oidc-reverse-proxy-configuration-outbound-uri) + (header webid-oidc-reverse-proxy-configuration-header + (default "XXX-Agent")) + (extra-options + webid-oidc-reverse-proxy-extra-options + (default '()))) + (define-record-type* webid-oidc-hello-configuration make-webid-oidc-hello-configuration @@ -56,6 +71,17 @@ webid-oidc-issuer-configuration-token-endpoint-uri webid-oidc-issuer-configuration-port webid-oidc-issuer-configuration-extra-options + + webid-oidc-reverse-proxy-configuration + make-webid-oidc-reverse-proxy-configuration + webid-oidc-reverse-proxy-configuration? + webid-oidc-reverse-proxy-configuration-webid-oidc + webid-oidc-reverse-proxy-configuration-port + webid-oidc-reverse-proxy-configuration-inbound-uri + webid-oidc-reverse-proxy-configuration-outbound-uri + webid-oidc-reverse-proxy-configuration-header + webid-oidc-reverse-proxy-configuration-extra-options + webid-oidc-hello-configuration make-webid-oidc-hello-configuration webid-oidc-hello-configuration? @@ -113,6 +139,51 @@ "LANG=C")))) (stop #~(make-kill-destructor)))))))) +(define webid-oidc-reverse-proxy-shepherd-service + (match-lambda + (($ + webid-oidc port inbound-uri outbound-uri header + extra-options) + (with-imported-modules + (source-module-closure + '((gnu build shepherd) + (gnu system file-systems))) + (list (shepherd-service + (provision '(webid-oidc-reverse-proxy)) + (documentation "Run a proxy to authenticate with Solid.") + (requirement '(user-processes)) + (modules '((gnu build shepherd) + (gnu system file-systems))) + (start + #~(begin + (let* ((user (getpwnam "webid-oidc")) + (prepare-directory + (lambda (dir) + (mkdir-p dir) + (chown dir (passwd:uid user) (passwd:gid user)) + (chmod dir #o700)))) + (prepare-directory "/var/log/webid-oidc") + (prepare-directory "/var/lib/webid-oidc") + (prepare-directory "/var/cache/webid-oidc")) + (make-forkexec-constructor + (list + (string-append #$webid-oidc "/bin/webid-oidc-reverse-proxy") + "--port" (with-output-to-string (lambda () (display #$port))) + "--inbound-uri" #$inbound-uri + "--outbound-uri" #$outbound-uri + "--header" #$header + "--log-file" "reverse-proxy.log" + "--error-file" "reverse-proxy.err" + #$@extra-options) + #:user "webid-oidc" + #:group "webid-oidc" + #:directory "/var/log/webid-oidc" + #:environment-variables + `("XDG_DATA_HOME=/var/lib" + "XDG_CACHE_HOME=/var/cache" + "LANG=C")))) + (stop #~(make-kill-destructor)))))))) + (define webid-oidc-hello-shepherd-service (match-lambda (($ @@ -174,6 +245,17 @@ shepherd-root-service-type webid-oidc-issuer-shepherd-service))))) +(define-public webid-oidc-reverse-proxy-service-type + (service-type + (name 'webid-oidc-reverse-proxy) + (extensions + (list + (service-extension account-service-type + (const %webid-oidc-accounts)) + (service-extension + shepherd-root-service-type + webid-oidc-reverse-proxy-shepherd-service))))) + (define-public webid-oidc-hello-service-type (service-type (name 'webid-oidc-hello) diff --git a/man/Makefile.am b/man/Makefile.am index ac01459..100d314 100644 --- a/man/Makefile.am +++ b/man/Makefile.am @@ -1,7 +1,11 @@ -dist_man8_MANS = webid-oidc-issuer.man +dist_man8_MANS = webid-oidc-issuer.man webid-oidc-reverse-proxy.man EXTRA_DIST = ./reset-env webid-oidc-issuer.man: ../src/scm/webid-oidc/identity-provider.scm ../configure.ac $(AM_V_GEN) ../pre-inst-env ./reset-env $(HELP2MAN) $(srcdir)/../src/webid-oidc-issuer > $@-t mv $@-t $(srcdir)/$@ + +webid-oidc-reverse-proxy.man: ../src/scm/webid-oidc/reverse-proxy.scm ../configure.ac + $(AM_V_GEN) ../pre-inst-env ./reset-env $(HELP2MAN) $(srcdir)/../src/webid-oidc-reverse-proxy > $@-t + mv $@-t $(srcdir)/$@ diff --git a/po/POTFILES.in b/po/POTFILES.in index 48b1c7d..0115b93 100644 --- a/po/POTFILES.in +++ b/po/POTFILES.in @@ -10,4 +10,5 @@ src/scm/webid-oidc/errors.scm src/scm/webid-oidc/identity-provider.scm src/scm/webid-oidc/authorization-page-unsafe.scm src/scm/webid-oidc/resource-server.scm +src/scm/webid-oidc/reverse-proxy.scm src/scm/webid-oidc/hello-world.scm diff --git a/po/fr.po b/po/fr.po index 2c4a6fd..222f4cf 100644 --- a/po/fr.po +++ b/po/fr.po @@ -2,7 +2,7 @@ msgid "" msgstr "" "Project-Id-Version: webid-oidc 0.0.0\n" "Report-Msgid-Bugs-To: vivien@planete-kraus.eu\n" -"POT-Creation-Date: 2021-06-05 16:19+0200\n" +"POT-Creation-Date: 2021-06-05 16:20+0200\n" "PO-Revision-Date: 2021-06-05 11:07+0200\n" "Last-Translator: Vivien Kraus \n" "Language-Team: French \n" @@ -707,6 +707,7 @@ msgid "Warning: generating a new key pair." msgstr "Attention : génération d'une nouvelle paire de clé." #: src/scm/webid-oidc/identity-provider.scm:148 +#: src/scm/webid-oidc/reverse-proxy.scm:124 #: src/scm/webid-oidc/hello-world.scm:31 msgid "command-line|version" msgstr "version" @@ -750,10 +751,12 @@ msgid "comand-line|port" msgstr "port" #: src/scm/webid-oidc/identity-provider.scm:168 +#: src/scm/webid-oidc/reverse-proxy.scm:136 msgid "comand-line|log-file" msgstr "fichier-journal" #: src/scm/webid-oidc/identity-provider.scm:170 +#: src/scm/webid-oidc/reverse-proxy.scm:138 msgid "comand-line|error-file" msgstr "fichier-erreur" @@ -904,6 +907,7 @@ msgstr "" "Si vous trouvez une erreur dans le programme, envoyez-en un rapport à ~a.\n" #: src/scm/webid-oidc/identity-provider.scm:267 +#: src/scm/webid-oidc/reverse-proxy.scm:210 #: src/scm/webid-oidc/hello-world.scm:61 #, scheme-format msgid "~a version ~a\n" @@ -938,6 +942,7 @@ msgid "You need to set the token endpoint URI.\n" msgstr "Vous devez définir l'URI du terminal de jeton.\n" #: src/scm/webid-oidc/identity-provider.scm:327 +#: src/scm/webid-oidc/reverse-proxy.scm:238 #: src/scm/webid-oidc/hello-world.scm:72 msgid "The port should be a number between 0 and 65535.\n" msgstr "Le port doit être un nombre entre 0 et 65535.\n" @@ -1099,6 +1104,148 @@ msgstr "" msgid "~a: authentication failure: ~a\n" msgstr "~a : échec d’authentificationn : ~a\n" +#: src/scm/webid-oidc/reverse-proxy.scm:126 +msgid "command-line|help" +msgstr "aide" + +#: src/scm/webid-oidc/reverse-proxy.scm:128 +msgid "command-line|port" +msgstr "port" + +#: src/scm/webid-oidc/reverse-proxy.scm:130 +msgid "command-line|inbound-uri" +msgstr "uri-entrant" + +#: src/scm/webid-oidc/reverse-proxy.scm:132 +msgid "command-line|outbound-uri" +msgstr "uri-sortant" + +#: src/scm/webid-oidc/reverse-proxy.scm:134 +msgid "command-line|header" +msgstr "en-tête" + +#: src/scm/webid-oidc/reverse-proxy.scm:152 +#, scheme-format +msgid "" +"Usage: ~a [OPTIONS]...\n" +"\n" +"Run a reverse proxy, taking requests with webid-oidc authentication\n" +"and passing them to the outbound URI with an additional header\n" +"containing the webid of the agent.\n" +"\n" +"Options:\n" +" -h, --~a:\n" +" display this help message and exit.\n" +" -v, --~a:\n" +" display the version information (~a) and exit.\n" +" -p PORT, --~a=8080:\n" +" set the port to bind.\n" +" -i URI, --~a=URI: \n" +" set the public URI of the reverse proxy.\n" +" -o URI, --~a=URI:\n" +" pass the requests to the server running at URI.\n" +" -H HEADER, --~a=HEADER:\n" +" pass request with optional HEADER set to the webid, XXX-Agent by " +"default.\n" +" -l FILE.log, --~a=FILE.log:\n" +" dump the standard output to that file.\n" +" -e FILE.err, --~a=FILE.err:\n" +" dump the standard error to that file.\n" +"\n" +"Environment variables:\n" +"\n" +" LANG: set the locale of the sysadmin-facing interface. It is\n" +"currently ~a.\n" +"\n" +"Example:\n" +"\n" +"Suppose that you operate data.provider.com. Since everything is behind\n" +"a big global reverse proxy, the authenticated proxy listens on\n" +"http://localhost:8080. You have the data server running at\n" +"https://private.data.provider.com, set up so that only you can query\n" +"it. The private server needs the XXX-Agent header to contain the\n" +"authenticated webid of the user, if the user is authenticated. That’s\n" +"why you don’t want anyone to query it. You would run:\n" +"\n" +" export LANG=C\n" +" webid-oidc-reverse-proxy \\\n" +" --port 8080 \\\n" +" --inbound-uri https://data.provider.com \\\n" +" --outbound-uri https://private.data.provider.com \\\n" +" --header XXX-Agent \\\n" +" --log-file /var/log/proxy.log \\\n" +" --error-file /var/log/proxy.err\n" +"\n" +"If you find a bug, send a report to ~a.\n" +msgstr "" +"Utilisation : ~a [OPTIONS]...\n" +"\n" +"Exécute un proxy inversé, prenant des requêtes avec authentification\n" +"webid-oidc et les passant à un URI sortant avec un en-tête\n" +"additionel contenant le webid de l’agent.\n" +"\n" +"Options :\n" +" -h, --~a :\n" +" affiche ce message d’aide et quitte.\n" +" -v, --~a :\n" +" affiche le numéro de version (~a) et quitte.\n" +" -p PORT, --~a=PORT :\n" +" définit le port à lier.\n" +" -i URI, --~a=URI :\n" +" définit le nom public du proxy inversé.\n" +" -o URI, --~a=URI :\n" +" passe les requêtes au serveur exécuté à URI.\n" +" -h ENTÊTE, --~a=ENTÊTE :\n" +" passe les requêtes avec un ENTÊTE optionnel défini par la valeur\n" +" du webid, XXX-Agent par défaut.\n" +" -l FICHIER.log, --~a=FICHIER.log :\n" +" déverser la sortie standard vers ce fichier.\n" +" -e FICHIER.err, --~a=FICHIER.err :\n" +" déverser la sortie d’erreur vers ce fichier.\n" +"\n" +"Variables d’environnement :\n" +"\n" +" LANG : définit la locale de l’interface de l’administrateur système\n" +"Elle vaut actuellement ~a.\n" +"\n" +"Exemple :\n" +"\n" +"Supposons que vous opérez donnees.fournisseur.com. Puisque tout est\n" +"derrière un grand proxy inversé global, le proxy d’authentification\n" +"écoute sur http://localhost:8080. Vous avez le serveur de données sur\n" +"https://prive.donnees.fournisseur.com, configuré de sorte à ce que\n" +"vous seul puissiez le requêter. Le serveur privé nécessite que la\n" +"valeur de l’en-tête XXX-Agent contienne le webid de l’utilisateur\n" +"authentifié, si c’est le cas. C’est pourquoi vous ne voulez pas que\n" +"n’importe qui puisse le requêter. Vous exécuteriez :\n" +"\n" +" export LANG=fr_FR.UTF-8\n" +" webid-oidc-reverse-proxy \\\n" +" --port 8080 \\\n" +" --uri-entrant https://donnees.fournisseur.com \\\n" +" --uri-sortant https://prive.donnees.fournisseur.com \\\n" +" --en-tête XXX-Agent \\\n" +" --fichier-journal /var/log/proxy.log \\\n" +" --fichier-erreur /var/log/proxy.err\n" +"\n" +"Si vous trouvez une erreur dans le programme, envoyez-en un rapport à\n" +"~a.\n" + +#: src/scm/webid-oidc/reverse-proxy.scm:243 +msgid "" +"The public name of the server must be present (with scheme) as --inbound-" +"uri.\n" +msgstr "" +"Le nom public du serveur doit être présent (avec protocole) avec\n" +"--uri-entrant.\n" + +#: src/scm/webid-oidc/reverse-proxy.scm:248 +msgid "" +"The address of the proxy must be present (with scheme) as --outbound-uri.\n" +msgstr "" +"L’adresse du serveur doit être présent (avec protocole) avec\n" +"--uri-sortant.\n" + #: src/scm/webid-oidc/hello-world.scm:44 #, scheme-format msgid "" @@ -1209,141 +1356,6 @@ msgstr "" #~ msgid "there is an external error" #~ msgstr "il y a une erreur externe" -#~ msgid "command-line|help" -#~ msgstr "aide" - -#~ msgid "command-line|port" -#~ msgstr "port" - -#~ msgid "command-line|inbound-uri" -#~ msgstr "uri-entrant" - -#~ msgid "command-line|outbound-uri" -#~ msgstr "uri-sortant" - -#~ msgid "command-line|header" -#~ msgstr "en-tête" - -#, scheme-format -#~ msgid "" -#~ "Usage: ~a [OPTIONS]...\n" -#~ "\n" -#~ "Run a reverse proxy, taking requests with webid-oidc authentication\n" -#~ "and passing them to the outbound URI with an additional header\n" -#~ "containing the webid of the agent.\n" -#~ "\n" -#~ "Options:\n" -#~ " -h, --~a:\n" -#~ " display this help message and exit.\n" -#~ " -v, --~a:\n" -#~ " display the version information (~a) and exit.\n" -#~ " -p PORT, --~a=8080:\n" -#~ " set the port to bind.\n" -#~ " -i URI, --~a=URI: \n" -#~ " set the public URI of the reverse proxy.\n" -#~ " -o URI, --~a=URI:\n" -#~ " pass the requests to the server running at URI.\n" -#~ " -H HEADER, --~a=HEADER:\n" -#~ " pass request with optional HEADER set to the webid, XXX-Agent by " -#~ "default.\n" -#~ " -l FILE.log, --~a=FILE.log:\n" -#~ " dump the standard output to that file.\n" -#~ " -e FILE.err, --~a=FILE.err:\n" -#~ " dump the standard error to that file.\n" -#~ "\n" -#~ "Environment variables:\n" -#~ "\n" -#~ " LANG: set the locale of the sysadmin-facing interface. It is\n" -#~ "currently ~a.\n" -#~ "\n" -#~ "Example:\n" -#~ "\n" -#~ "Suppose that you operate data.provider.com. Since everything is behind\n" -#~ "a big global reverse proxy, the authenticated proxy listens on\n" -#~ "http://localhost:8080. You have the data server running at\n" -#~ "https://private.data.provider.com, set up so that only you can query\n" -#~ "it. The private server needs the XXX-Agent header to contain the\n" -#~ "authenticated webid of the user, if the user is authenticated. That’s\n" -#~ "why you don’t want anyone to query it. You would run:\n" -#~ "\n" -#~ " export LANG=C\n" -#~ " webid-oidc-reverse-proxy \\\n" -#~ " --port 8080 \\\n" -#~ " --inbound-uri https://data.provider.com \\\n" -#~ " --outbound-uri https://private.data.provider.com \\\n" -#~ " --header XXX-Agent \\\n" -#~ " --log-file /var/log/proxy.log \\\n" -#~ " --error-file /var/log/proxy.err\n" -#~ "\n" -#~ "If you find a bug, send a report to ~a.\n" -#~ msgstr "" -#~ "Utilisation : ~a [OPTIONS]...\n" -#~ "\n" -#~ "Exécute un proxy inversé, prenant des requêtes avec authentification\n" -#~ "webid-oidc et les passant à un URI sortant avec un en-tête\n" -#~ "additionel contenant le webid de l’agent.\n" -#~ "\n" -#~ "Options :\n" -#~ " -h, --~a :\n" -#~ " affiche ce message d’aide et quitte.\n" -#~ " -v, --~a :\n" -#~ " affiche le numéro de version (~a) et quitte.\n" -#~ " -p PORT, --~a=PORT :\n" -#~ " définit le port à lier.\n" -#~ " -i URI, --~a=URI :\n" -#~ " définit le nom public du proxy inversé.\n" -#~ " -o URI, --~a=URI :\n" -#~ " passe les requêtes au serveur exécuté à URI.\n" -#~ " -h ENTÊTE, --~a=ENTÊTE :\n" -#~ " passe les requêtes avec un ENTÊTE optionnel défini par la valeur\n" -#~ " du webid, XXX-Agent par défaut.\n" -#~ " -l FICHIER.log, --~a=FICHIER.log :\n" -#~ " déverser la sortie standard vers ce fichier.\n" -#~ " -e FICHIER.err, --~a=FICHIER.err :\n" -#~ " déverser la sortie d’erreur vers ce fichier.\n" -#~ "\n" -#~ "Variables d’environnement :\n" -#~ "\n" -#~ " LANG : définit la locale de l’interface de l’administrateur système\n" -#~ "Elle vaut actuellement ~a.\n" -#~ "\n" -#~ "Exemple :\n" -#~ "\n" -#~ "Supposons que vous opérez donnees.fournisseur.com. Puisque tout est\n" -#~ "derrière un grand proxy inversé global, le proxy d’authentification\n" -#~ "écoute sur http://localhost:8080. Vous avez le serveur de données sur\n" -#~ "https://prive.donnees.fournisseur.com, configuré de sorte à ce que\n" -#~ "vous seul puissiez le requêter. Le serveur privé nécessite que la\n" -#~ "valeur de l’en-tête XXX-Agent contienne le webid de l’utilisateur\n" -#~ "authentifié, si c’est le cas. C’est pourquoi vous ne voulez pas que\n" -#~ "n’importe qui puisse le requêter. Vous exécuteriez :\n" -#~ "\n" -#~ " export LANG=fr_FR.UTF-8\n" -#~ " webid-oidc-reverse-proxy \\\n" -#~ " --port 8080 \\\n" -#~ " --uri-entrant https://donnees.fournisseur.com \\\n" -#~ " --uri-sortant https://prive.donnees.fournisseur.com \\\n" -#~ " --en-tête XXX-Agent \\\n" -#~ " --fichier-journal /var/log/proxy.log \\\n" -#~ " --fichier-erreur /var/log/proxy.err\n" -#~ "\n" -#~ "Si vous trouvez une erreur dans le programme, envoyez-en un rapport à\n" -#~ "~a.\n" - -#~ msgid "" -#~ "The public name of the server must be present (with scheme) as --inbound-" -#~ "uri.\n" -#~ msgstr "" -#~ "Le nom public du serveur doit être présent (avec protocole) avec\n" -#~ "--uri-entrant.\n" - -#~ msgid "" -#~ "The address of the proxy must be present (with scheme) as --outbound-" -#~ "uri.\n" -#~ msgstr "" -#~ "L’adresse du serveur doit être présent (avec protocole) avec\n" -#~ "--uri-sortant.\n" - #~ msgid "comand-line|client-id" #~ msgstr "id-client" diff --git a/po/webid-oidc.pot b/po/webid-oidc.pot index d652b54..f3e5c51 100644 --- a/po/webid-oidc.pot +++ b/po/webid-oidc.pot @@ -8,7 +8,7 @@ msgid "" msgstr "" "Project-Id-Version: webid-oidc SNAPSHOT\n" "Report-Msgid-Bugs-To: vivien@planete-kraus.eu\n" -"POT-Creation-Date: 2021-06-05 16:19+0200\n" +"POT-Creation-Date: 2021-06-05 16:20+0200\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" @@ -690,6 +690,7 @@ msgid "Warning: generating a new key pair." msgstr "" #: src/scm/webid-oidc/identity-provider.scm:148 +#: src/scm/webid-oidc/reverse-proxy.scm:124 #: src/scm/webid-oidc/hello-world.scm:31 msgid "command-line|version" msgstr "" @@ -733,10 +734,12 @@ msgid "comand-line|port" msgstr "" #: src/scm/webid-oidc/identity-provider.scm:168 +#: src/scm/webid-oidc/reverse-proxy.scm:136 msgid "comand-line|log-file" msgstr "" #: src/scm/webid-oidc/identity-provider.scm:170 +#: src/scm/webid-oidc/reverse-proxy.scm:138 msgid "comand-line|error-file" msgstr "" @@ -814,6 +817,7 @@ msgid "" msgstr "" #: src/scm/webid-oidc/identity-provider.scm:267 +#: src/scm/webid-oidc/reverse-proxy.scm:210 #: src/scm/webid-oidc/hello-world.scm:61 #, scheme-format msgid "~a version ~a\n" @@ -848,6 +852,7 @@ msgid "You need to set the token endpoint URI.\n" msgstr "" #: src/scm/webid-oidc/identity-provider.scm:327 +#: src/scm/webid-oidc/reverse-proxy.scm:238 #: src/scm/webid-oidc/hello-world.scm:72 msgid "The port should be a number between 0 and 65535.\n" msgstr "" @@ -1001,6 +1006,92 @@ msgstr "" msgid "~a: authentication failure: ~a\n" msgstr "" +#: src/scm/webid-oidc/reverse-proxy.scm:126 +msgid "command-line|help" +msgstr "" + +#: src/scm/webid-oidc/reverse-proxy.scm:128 +msgid "command-line|port" +msgstr "" + +#: src/scm/webid-oidc/reverse-proxy.scm:130 +msgid "command-line|inbound-uri" +msgstr "" + +#: src/scm/webid-oidc/reverse-proxy.scm:132 +msgid "command-line|outbound-uri" +msgstr "" + +#: src/scm/webid-oidc/reverse-proxy.scm:134 +msgid "command-line|header" +msgstr "" + +#: src/scm/webid-oidc/reverse-proxy.scm:152 +#, scheme-format +msgid "" +"Usage: ~a [OPTIONS]...\n" +"\n" +"Run a reverse proxy, taking requests with webid-oidc authentication\n" +"and passing them to the outbound URI with an additional header\n" +"containing the webid of the agent.\n" +"\n" +"Options:\n" +" -h, --~a:\n" +" display this help message and exit.\n" +" -v, --~a:\n" +" display the version information (~a) and exit.\n" +" -p PORT, --~a=8080:\n" +" set the port to bind.\n" +" -i URI, --~a=URI: \n" +" set the public URI of the reverse proxy.\n" +" -o URI, --~a=URI:\n" +" pass the requests to the server running at URI.\n" +" -H HEADER, --~a=HEADER:\n" +" pass request with optional HEADER set to the webid, XXX-Agent by " +"default.\n" +" -l FILE.log, --~a=FILE.log:\n" +" dump the standard output to that file.\n" +" -e FILE.err, --~a=FILE.err:\n" +" dump the standard error to that file.\n" +"\n" +"Environment variables:\n" +"\n" +" LANG: set the locale of the sysadmin-facing interface. It is\n" +"currently ~a.\n" +"\n" +"Example:\n" +"\n" +"Suppose that you operate data.provider.com. Since everything is behind\n" +"a big global reverse proxy, the authenticated proxy listens on\n" +"http://localhost:8080. You have the data server running at\n" +"https://private.data.provider.com, set up so that only you can query\n" +"it. The private server needs the XXX-Agent header to contain the\n" +"authenticated webid of the user, if the user is authenticated. That’s\n" +"why you don’t want anyone to query it. You would run:\n" +"\n" +" export LANG=C\n" +" webid-oidc-reverse-proxy \\\n" +" --port 8080 \\\n" +" --inbound-uri https://data.provider.com \\\n" +" --outbound-uri https://private.data.provider.com \\\n" +" --header XXX-Agent \\\n" +" --log-file /var/log/proxy.log \\\n" +" --error-file /var/log/proxy.err\n" +"\n" +"If you find a bug, send a report to ~a.\n" +msgstr "" + +#: src/scm/webid-oidc/reverse-proxy.scm:243 +msgid "" +"The public name of the server must be present (with scheme) as --inbound-" +"uri.\n" +msgstr "" + +#: src/scm/webid-oidc/reverse-proxy.scm:248 +msgid "" +"The address of the proxy must be present (with scheme) as --outbound-uri.\n" +msgstr "" + #: src/scm/webid-oidc/hello-world.scm:44 #, scheme-format msgid "" diff --git a/src/Makefile.am b/src/Makefile.am index 79ac441..d990641 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -1,6 +1,6 @@ lib_LTLIBRARIES += %reldir%/libwebidoidc.la -dist_bin_SCRIPTS += %reldir%/webid-oidc-issuer %reldir%/webid-oidc-hello +dist_bin_SCRIPTS += %reldir%/webid-oidc-issuer %reldir%/webid-oidc-reverse-proxy %reldir%/webid-oidc-hello AM_CPPFLAGS += -I %reldir% -I $(srcdir)/%reldir% diff --git a/src/scm/webid-oidc/Makefile.am b/src/scm/webid-oidc/Makefile.am index c2d4646..8f5f105 100644 --- a/src/scm/webid-oidc/Makefile.am +++ b/src/scm/webid-oidc/Makefile.am @@ -21,7 +21,8 @@ dist_webidoidcmod_DATA += \ %reldir%/identity-provider.scm \ %reldir%/provider-confirmation.scm \ %reldir%/resource-server.scm \ - %reldir%/hello-world.scm + %reldir%/hello-world.scm \ + %reldir%/reverse-proxy.scm webidoidcgo_DATA += \ %reldir%/errors.go \ @@ -46,6 +47,7 @@ webidoidcgo_DATA += \ %reldir%/identity-provider.go \ %reldir%/provider-confirmation.go \ %reldir%/resource-server.go \ - %reldir%/hello-world.go + %reldir%/hello-world.go \ + %reldir%/reverse-proxy.go EXTRA_DIST += %reldir%/ChangeLog diff --git a/src/scm/webid-oidc/reverse-proxy.scm b/src/scm/webid-oidc/reverse-proxy.scm new file mode 100644 index 0000000..87588b9 --- /dev/null +++ b/src/scm/webid-oidc/reverse-proxy.scm @@ -0,0 +1,257 @@ +(define-module (webid-oidc reverse-proxy) + #:use-module (webid-oidc errors) + #:use-module ((webid-oidc stubs) #:prefix stubs:) + #:use-module (webid-oidc resource-server) + #:use-module (webid-oidc jti) + #:use-module ((webid-oidc config) #:prefix cfg:) + #:use-module (ice-9 optargs) + #:use-module (ice-9 receive) + #:use-module (ice-9 i18n) + #:use-module (ice-9 getopt-long) + #:use-module (ice-9 suspendable-ports) + #:use-module (srfi srfi-19) + #:use-module (rnrs bytevectors) + #:use-module (web uri) + #:use-module (web request) + #:use-module (web response) + #:use-module (web client) + #:use-module (webid-oidc cache) + #:use-module (web server)) + +(define (G_ text) + (let ((out (gettext text))) + (if (string=? out text) + ;; No translation, disambiguate + (car (reverse (string-split text #\|))) + out))) + +(define*-public (make-reverse-proxy + #:key + (jti-list #f) + (server-uri #f) + (current-time current-time) + (http-get http-get) + (endpoint #f) + (auth-header 'XXX-Agent)) + (set! auth-header + ;; We need to remove the lowercase version of auth-header from + ;; all incoming requests! + (string->symbol + (string-downcase + (symbol->string auth-header)))) + (define authenticate + (make-authenticator + (or jti-list (make-jti-list)) + #:server-uri server-uri + #:current-time current-time + #:http-get http-get)) + (unless (and endpoint (uri? endpoint)) + (error "#:endpoint argument is not present or not an URI.")) + (lambda (request request-body) + (let ((agent + (catch #t + (lambda () + (authenticate request request-body)) + (lambda (key . args) + (case key + ((invalid-access-token + invalid-proof + unconfirmed-issuer) + #f) + (else + (apply throw key args))))))) + (let ((raw-headers (request-headers request))) + (let ((modified-headers + (append + (if agent + (list (cons auth-header (uri->string agent))) + '()) + (filter + (lambda (h) + (not (eq? (car h) auth-header))) + raw-headers)))) + (let ((modified-request + (build-request + (request-uri request) + #:method (request-method request) + #:headers modified-headers))) + (let ((port (open-socket-for-uri endpoint))) + (let ((request-with-port + (write-request modified-request port))) + (when request-body + (unless (bytevector? request-body) + (set! request-body (string->utf8 request-body))) + (write-request-body request-with-port request-body)) + (force-output (request-port request-with-port)) + (let ((response (read-response port))) + (let ((response-body + (or (response-must-not-include-body? response) + (read-response-body response)))) + (let ((adapted-response + (build-response + #:code (response-code response) + #:reason-phrase (response-reason-phrase response) + #:headers + (append + (if (eqv? (response-code response) 401) + (list (cons 'www-authenticate '((DPoP)))) + '()) + (response-headers response))))) + (close-port port) + (values adapted-response response-body)))))))))))) + +(define-public (main) + (define* (http-get-with-log uri #:key (headers '())) + (define date (date->string (time-utc->date (current-time)))) + (define uri-string (if (uri? uri) (uri->string uri) uri)) + (format (current-error-port) "~a: GET ~a ~s...\n" + date uri-string headers) + (receive (response response-body) (http-get uri #:headers headers) + (format (current-error-port) "~a: GET ~a ~s: ~s ~a bytes\n" + date uri-string headers response + (if (bytevector? response-body) + (bytevector-length response-body) + (string-length response-body))) + (values response response-body))) + (define cache-http-get + (with-cache #:http-get http-get-with-log)) + (setvbuf (current-output-port) 'none) + (setvbuf (current-error-port) 'none) + (setlocale LC_ALL "") + (bindtextdomain cfg:package cfg:localedir) + (textdomain cfg:package) + (let ((version-sym + (string->symbol (G_ "command-line|version"))) + (help-sym + (string->symbol (G_ "command-line|help"))) + (port-sym + (string->symbol (G_ "command-line|port"))) + (inbound-uri-sym + (string->symbol (G_ "command-line|inbound-uri"))) + (outbound-uri-sym + (string->symbol (G_ "command-line|outbound-uri"))) + (header-sym + (string->symbol (G_ "command-line|header"))) + (log-file-sym + (string->symbol (G_ "comand-line|log-file"))) + (error-file-sym + (string->symbol (G_ "comand-line|error-file")))) + (let ((options + (let ((option-spec + `((,version-sym (single-char #\v) (value #f)) + (,help-sym (single-char #\h) (value #f)) + (,port-sym (single-char #\p) (value #t)) + (,inbound-uri-sym (single-char #\i) (value #t)) + (,outbound-uri-sym (single-char #\o) (value #t)) + (,header-sym (single-char #\H) (value #t)) + (,log-file-sym (single-char #\l) (value #t)) + (,error-file-sym (single-char #\e) (value #t))))) + (getopt-long (command-line) option-spec)))) + (cond + ((option-ref options help-sym #f) + (format #t (G_ "Usage: ~a [OPTIONS]... + +Run a reverse proxy, taking requests with webid-oidc authentication +and passing them to the outbound URI with an additional header +containing the webid of the agent. + +Options: + -h, --~a: + display this help message and exit. + -v, --~a: + display the version information (~a) and exit. + -p PORT, --~a=8080: + set the port to bind. + -i URI, --~a=URI: + set the public URI of the reverse proxy. + -o URI, --~a=URI: + pass the requests to the server running at URI. + -H HEADER, --~a=HEADER: + pass request with optional HEADER set to the webid, XXX-Agent by default. + -l FILE.log, --~a=FILE.log: + dump the standard output to that file. + -e FILE.err, --~a=FILE.err: + dump the standard error to that file. + +Environment variables: + + LANG: set the locale of the sysadmin-facing interface. It is +currently ~a. + +Example: + +Suppose that you operate data.provider.com. Since everything is behind +a big global reverse proxy, the authenticated proxy listens on +http://localhost:8080. You have the data server running at +https://private.data.provider.com, set up so that only you can query +it. The private server needs the XXX-Agent header to contain the +authenticated webid of the user, if the user is authenticated. That’s +why you don’t want anyone to query it. You would run: + + export LANG=C + webid-oidc-reverse-proxy \\ + --port 8080 \\ + --inbound-uri https://data.provider.com \\ + --outbound-uri https://private.data.provider.com \\ + --header XXX-Agent \\ + --log-file /var/log/proxy.log \\ + --error-file /var/log/proxy.err + +If you find a bug, send a report to ~a. +") + (car (command-line)) + help-sym version-sym + cfg:version + port-sym inbound-uri-sym outbound-uri-sym header-sym + log-file-sym error-file-sym + (or (getenv "LANG") "") + cfg:package-bugreport)) + ((option-ref options version-sym #f) + (format #t (G_ "~a version ~a\n") + cfg:package cfg:version)) + (else + (let ((port-string + (option-ref options port-sym "8080")) + (inbound-uri-string + (option-ref options inbound-uri-sym #f)) + (outbound-uri-string + (option-ref options outbound-uri-sym #f)) + (header-string + (option-ref options header-sym "XXX-Agent")) + (log-file-string + (option-ref options log-file-sym #f)) + (error-file-string + (option-ref options error-file-sym #f))) + (when log-file-string + (set-current-output-port (stubs:open-output-file* log-file-string)) + (setvbuf (current-output-port) 'none)) + (when error-file-string + (set-current-error-port (stubs:open-output-file* error-file-string)) + (setvbuf (current-error-port) 'none)) + (unless (and port-string + (string? port-string) + (string->number port-string) + (integer? (string->number port-string)) + (>= (string->number port-string) 0) + (<= (string->number port-string) 65535)) + (format (current-error-port) + (G_ "The port should be a number between 0 and 65535.\n")) + (exit 1)) + (unless (and inbound-uri-string + (string->uri inbound-uri-string)) + (format (current-error-port) + (G_ "The public name of the server must be present (with scheme) as --inbound-uri.\n")) + (exit 1)) + (unless (and outbound-uri-string + (string->uri outbound-uri-string)) + (format (current-error-port) + (G_ "The address of the proxy must be present (with scheme) as --outbound-uri.\n")) + (exit 1)) + (install-suspendable-ports!) + (run-server (make-reverse-proxy + #:server-uri (string->uri inbound-uri-string) + #:http-get cache-http-get + #:endpoint (string->uri outbound-uri-string) + #:auth-header (string->symbol header-string)) + 'http + (list #:port (string->number port-string))))))))) diff --git a/src/webid-oidc-reverse-proxy b/src/webid-oidc-reverse-proxy new file mode 100755 index 0000000..5b7855e --- /dev/null +++ b/src/webid-oidc-reverse-proxy @@ -0,0 +1,7 @@ +#!/usr/local/bin/guile \ +--no-auto-compile -s +!# + +(use-modules (webid-oidc reverse-proxy)) + +(main) -- cgit v1.2.3