From 1fbc55dd28556b9a5ea723384cd4c4e230266d02 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 | 7 +- doc/manual.html | 51 +++++++ guix/vkraus/packages/webid-oidc.scm | 15 +- 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 | 254 +++++++++++++++++++++++++++++++ src/webid-oidc-reverse-proxy | 7 + 12 files changed, 658 insertions(+), 150 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 e58da85..7621b38 100755 --- a/bootstrap +++ b/bootstrap @@ -11,7 +11,12 @@ sed -i 's|SHELL = /bin/sh|SHELL = @SHELL@|g' po/Makefile.in.in || exit 1 mkdir -p .native || exit 1 cd .native || exit 1 bash ../configure SHELL=$(which sh) || exit 1 -sed -i "s|/usr/local/bin/guile|$(which guile)|g" ../src/webid-oidc-issuer || exit 1 + +for file in ../src/webid-oidc-issuer ../src/webid-oidc-reverse-proxy +do + 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 cd .. || exit 1 diff --git a/doc/manual.html b/doc/manual.html index d010685..f0535c2 100644 --- a/doc/manual.html +++ b/doc/manual.html @@ -987,6 +987,57 @@ check that the proofs of possession are correct, and the possessed key is signed by the identity provider.

+

Running webid-oidc-reverse-proxy

+

+ The distribution comes with a reverse proxy, aptly named +

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:

+ +

+ You can localize the interface by setting the + LANG environment variable. +

The authenticator

In (webid-oidc jws), the following function diff --git a/guix/vkraus/packages/webid-oidc.scm b/guix/vkraus/packages/webid-oidc.scm index a8251e0..b195c33 100644 --- a/guix/vkraus/packages/webid-oidc.scm +++ b/guix/vkraus/packages/webid-oidc.scm @@ -61,14 +61,13 @@ (format #f "~a/lib/guile/~a/site-ccache" prop-input effective-version)) guile-propagated-inputs))) - (wrap-program - (format #f "~a/bin/webid-oidc-issuer" out) - `("GUILE_LOAD_PATH" ":" = ,mod-paths) - `("GUILE_LOAD_COMPILED_PATH" ":" = ,go-paths)) - (wrap-program - (format #f "~a/bin/webid-oidc-hello" out) - `("GUILE_LOAD_PATH" ":" = ,mod-paths) - `("GUILE_LOAD_COMPILED_PATH" ":" = ,go-paths)))))))) + (for-each + (lambda (program) + (wrap-program + (format #f "~a/bin/webid-oidc-~a" out program) + `("GUILE_LOAD_PATH" ":" = ,mod-paths) + `("GUILE_LOAD_COMPILED_PATH" ":" = ,go-paths))) + '(issuer reverse-proxy hello)))))))) (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 0f923de..e49261c 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-05-10 22:59+0200\n" +"POT-Creation-Date: 2021-05-10 23:00+0200\n" "PO-Revision-Date: 2021-05-10 14:31+0200\n" "Last-Translator: Vivien Kraus \n" "Language-Team: French \n" @@ -705,6 +705,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:147 +#: src/scm/webid-oidc/reverse-proxy.scm:124 #: src/scm/webid-oidc/hello-world.scm:32 msgid "command-line|version" msgstr "version" @@ -748,10 +749,12 @@ msgid "comand-line|port" msgstr "port" #: src/scm/webid-oidc/identity-provider.scm:167 +#: src/scm/webid-oidc/reverse-proxy.scm:136 msgid "comand-line|log-file" msgstr "fichier-journal" #: src/scm/webid-oidc/identity-provider.scm:169 +#: src/scm/webid-oidc/reverse-proxy.scm:138 msgid "comand-line|error-file" msgstr "fichier-erreur" @@ -902,6 +905,7 @@ msgstr "" "Si vous trouvez une erreur dans le programme, envoyez-en un rapport à ~a.\n" #: src/scm/webid-oidc/identity-provider.scm:262 +#: src/scm/webid-oidc/reverse-proxy.scm:207 #: src/scm/webid-oidc/hello-world.scm:60 #, scheme-format msgid "~a version ~a\n" @@ -936,6 +940,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:322 +#: src/scm/webid-oidc/reverse-proxy.scm:235 #: src/scm/webid-oidc/hello-world.scm:71 msgid "The port should be a number between 0 and 65535.\n" msgstr "Le port doit être un nombre entre 0 et 65535.\n" @@ -1097,6 +1102,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, --help:\n" +" display this help message and exit.\n" +" -v, --version:\n" +" display the version information (~a) and exit.\n" +" -p PORT, --port=8080:\n" +" set the port to bind.\n" +" -i URI, --inbound-uri=URI: \n" +" set the public URI of the reverse proxy.\n" +" -o URI, --outbound-uri=URI:\n" +" pass the requests to the server running at URI.\n" +" -H HEADER, --header=HEADER:\n" +" pass request with optional HEADER set to the webid, XXX-Agent by " +"default.\n" +" -l FILE.log, --log-file=FILE.log:\n" +" dump the standard output to that file.\n" +" -e FILE.err, --error-file=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, --aide :\n" +" affiche ce message d’aide et quitte.\n" +" -v, --version :\n" +" affiche le numéro de version (~a) et quitte.\n" +" -p PORT, --port=PORT :\n" +" définit le port à lier.\n" +" -i URI, --uri-entrant=URI :\n" +" définit le nom public du proxy inversé.\n" +" -o URI, --uri-sortant=URI :\n" +" passe les requêtes au serveur exécuté à URI.\n" +" -h ENTÊTE, --en-tête=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, --fichier-journal=FICHIER.log :\n" +" déverser la sortie standard vers ce fichier.\n" +" -e FICHIER.err, --fichier-erreur=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:240 +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:245 +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:45 #, scheme-format msgid "" @@ -1153,141 +1300,6 @@ msgstr "" #~ msgid ", " #~ msgstr ", " -#~ 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, --help:\n" -#~ " display this help message and exit.\n" -#~ " -v, --version:\n" -#~ " display the version information (~a) and exit.\n" -#~ " -p PORT, --port=8080:\n" -#~ " set the port to bind.\n" -#~ " -i URI, --inbound-uri=URI: \n" -#~ " set the public URI of the reverse proxy.\n" -#~ " -o URI, --outbound-uri=URI:\n" -#~ " pass the requests to the server running at URI.\n" -#~ " -H HEADER, --header=HEADER:\n" -#~ " pass request with optional HEADER set to the webid, XXX-Agent by " -#~ "default.\n" -#~ " -l FILE.log, --log-file=FILE.log:\n" -#~ " dump the standard output to that file.\n" -#~ " -e FILE.err, --error-file=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, --aide :\n" -#~ " affiche ce message d’aide et quitte.\n" -#~ " -v, --version :\n" -#~ " affiche le numéro de version (~a) et quitte.\n" -#~ " -p PORT, --port=PORT :\n" -#~ " définit le port à lier.\n" -#~ " -i URI, --uri-entrant=URI :\n" -#~ " définit le nom public du proxy inversé.\n" -#~ " -o URI, --uri-sortant=URI :\n" -#~ " passe les requêtes au serveur exécuté à URI.\n" -#~ " -h ENTÊTE, --en-tête=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, --fichier-journal=FICHIER.log :\n" -#~ " déverser la sortie standard vers ce fichier.\n" -#~ " -e FICHIER.err, --fichier-erreur=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 a53ac36..337bd05 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-05-10 22:59+0200\n" +"POT-Creation-Date: 2021-05-10 23:00+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:147 +#: src/scm/webid-oidc/reverse-proxy.scm:124 #: src/scm/webid-oidc/hello-world.scm:32 msgid "command-line|version" msgstr "" @@ -733,10 +734,12 @@ msgid "comand-line|port" msgstr "" #: src/scm/webid-oidc/identity-provider.scm:167 +#: src/scm/webid-oidc/reverse-proxy.scm:136 msgid "comand-line|log-file" msgstr "" #: src/scm/webid-oidc/identity-provider.scm:169 +#: 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:262 +#: src/scm/webid-oidc/reverse-proxy.scm:207 #: src/scm/webid-oidc/hello-world.scm:60 #, 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:322 +#: src/scm/webid-oidc/reverse-proxy.scm:235 #: src/scm/webid-oidc/hello-world.scm:71 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, --help:\n" +" display this help message and exit.\n" +" -v, --version:\n" +" display the version information (~a) and exit.\n" +" -p PORT, --port=8080:\n" +" set the port to bind.\n" +" -i URI, --inbound-uri=URI: \n" +" set the public URI of the reverse proxy.\n" +" -o URI, --outbound-uri=URI:\n" +" pass the requests to the server running at URI.\n" +" -H HEADER, --header=HEADER:\n" +" pass request with optional HEADER set to the webid, XXX-Agent by " +"default.\n" +" -l FILE.log, --log-file=FILE.log:\n" +" dump the standard output to that file.\n" +" -e FILE.err, --error-file=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:240 +msgid "" +"The public name of the server must be present (with scheme) as --inbound-" +"uri.\n" +msgstr "" + +#: src/scm/webid-oidc/reverse-proxy.scm:245 +msgid "" +"The address of the proxy must be present (with scheme) as --outbound-uri.\n" +msgstr "" + #: src/scm/webid-oidc/hello-world.scm:45 #, 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..c2db62e --- /dev/null +++ b/src/scm/webid-oidc/reverse-proxy.scm @@ -0,0 +1,254 @@ +(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, --help: + display this help message and exit. + -v, --version: + display the version information (~a) and exit. + -p PORT, --port=8080: + set the port to bind. + -i URI, --inbound-uri=URI: + set the public URI of the reverse proxy. + -o URI, --outbound-uri=URI: + pass the requests to the server running at URI. + -H HEADER, --header=HEADER: + pass request with optional HEADER set to the webid, XXX-Agent by default. + -l FILE.log, --log-file=FILE.log: + dump the standard output to that file. + -e FILE.err, --error-file=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)) + cfg:version + (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