summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorVivien Kraus <vivien@planete-kraus.eu>2021-03-30 20:25:01 +0200
committerVivien Kraus <vivien@planete-kraus.eu>2021-06-05 16:59:42 +0200
commit4c5e746792dbee40252bb728c1f8debc516210f0 (patch)
treed87c4f69f79206d081fdaac9c65bc8c84e8e0e8a
parentc07743ad2d3a018ca3585badc99b5f0a58d07d8e (diff)
Implement a reverse proxy
-rwxr-xr-xbootstrap20
-rw-r--r--doc/webid-oidc.texi33
-rw-r--r--guix/vkraus/packages/webid-oidc.scm15
-rw-r--r--guix/vkraus/services/webid-oidc.scm82
-rw-r--r--man/Makefile.am6
-rw-r--r--po/POTFILES.in1
-rw-r--r--po/fr.po284
-rw-r--r--po/webid-oidc.pot93
-rw-r--r--src/Makefile.am2
-rw-r--r--src/scm/webid-oidc/Makefile.am6
-rw-r--r--src/scm/webid-oidc/reverse-proxy.scm257
-rwxr-xr-xsrc/webid-oidc-reverse-proxy7
12 files changed, 653 insertions, 153 deletions
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 2f53487..0148dbb 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>
+ 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>
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>
+ 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>
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-reverse-proxy-configuration>
+ 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
(($ <webid-oidc-hello-configuration>
@@ -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 <vivien@planete-kraus.eu>\n"
"Language-Team: French <vivien@planete-kraus.eu>\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 <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\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)