From e0f8895e9a6da18c27bf235247f7604e7ed10486 Mon Sep 17 00:00:00 2001 From: florian Date: Mon, 13 Jun 2022 13:06:03 +0200 Subject: [PATCH] init support for auth_request and add authentik example --- .../confs/server-http/reverse-proxy.conf | 58 +++++-- core/reverseproxy/plugin.json | 42 ++++- examples/authentik/.env | 3 + examples/authentik/docker-compose.yml | 154 ++++++++++++++++++ examples/authentik/js-app/index.js | 13 ++ examples/authentik/js-app/package.json | 15 ++ helpers/entrypoint.sh | 4 + 7 files changed, 270 insertions(+), 19 deletions(-) create mode 100644 examples/authentik/.env create mode 100644 examples/authentik/docker-compose.yml create mode 100644 examples/authentik/js-app/index.js create mode 100644 examples/authentik/js-app/package.json diff --git a/core/reverseproxy/confs/server-http/reverse-proxy.conf b/core/reverseproxy/confs/server-http/reverse-proxy.conf index 0077409..7bbcc15 100644 --- a/core/reverseproxy/confs/server-http/reverse-proxy.conf +++ b/core/reverseproxy/confs/server-http/reverse-proxy.conf @@ -6,59 +6,81 @@ proxy_intercept_errors on; proxy_intercept_errors off; {% endif +%} - {% if USE_PROXY_CACHE == "yes" +%} + {% if USE_PROXY_CACHE == "yes" +%} proxy_cache proxycache; proxy_cache_methods {{ PROXY_CACHE_METHODS }}; proxy_cache_min_uses {{ PROXY_CACHE_MIN_USES }}; proxy_cache_key {{ PROXY_CACHE_KEY }}; proxy_no_cache {{ PROXY_NO_CACHE }}; proxy_cache_bypass {{ PROXY_CACHE_BYPASS }}; - {% if PROXY_CACHE_VALID != "" +%} - {% for element in PROXY_CACHE_VALID.split(" ") +%} + {% if PROXY_CACHE_VALID != "" +%} + {% for element in PROXY_CACHE_VALID.split(" ") +%} proxy_cache_valid {{ element.split("=")[0] }} {{ element.split("=")[1] }}; - {% endfor %} + {% endfor %} add_header X-Proxy-Cache $upstream_cache_status; - {% endif %} + {% endif %} - {% endif %} + {% endif %} + {% set counter = namespace(value=1) %} {% for k, v in all.items() %} {% if k.startswith("REVERSE_PROXY_URL") and v != "" +%} {% set url = v %} {% set host = all[k.replace("URL", "HOST")] if k.replace("URL", "HOST") in all else "" %} {% set ws = all[k.replace("URL", "WS")] if k.replace("URL", "WS") in all else "" %} {% set headers = all[k.replace("URL", "HEADERS")] if k.replace("URL", "HEADERS") in all else "" %} + {% set headers_client = all[k.replace("URL", "HEADERS_CLIENT")] if k.replace("URL", "HEADERS_CLIENT") in all else "" %} {% set buffering = all[k.replace("URL", "BUFFERING")] if k.replace("URL", "BUFFERING") in all else "yes" %} {% set keepalive = all[k.replace("URL", "KEEPALIVE")] if k.replace("URL", "KEEPALIVE") in all else "yes" %} + {% set auth_request = all[k.replace("URL", "AUTH_REQUEST")] if k.replace("URL", "AUTH_REQUEST") in all else "" %} + {% set auth_request_signin_url = all[k.replace("URL", "AUTH_REQUEST_SIGNIN_URL")] if k.replace("URL", "AUTH_REQUEST_SIGNIN_URL") in all else "" %} + {% set auth_request_sets = all[k.replace("URL", "AUTH_REQUEST_SET")] if k.replace("URL", "AUTH_REQUEST_SET") in all else "" %} location {{ url }} {% raw %}{{% endraw +%} etag off; - set $backend "{{ host }}"; - proxy_pass $backend; + set $backend{{ counter.value }} "{{ host }}"; + proxy_pass $backend{{ counter.value }}; proxy_set_header Host $host; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-Proto $scheme; proxy_set_header X-Forwarded-Protocol $scheme; proxy_set_header X-Forwarded-Host $http_host; - {% if buffering == "yes" +%} + {% if buffering == "yes" +%} proxy_buffering on; - {% else +%} + {% else +%} proxy_buffering off; - {% endif %} - {% if ws == "yes" +%} + {% endif %} + {% if ws == "yes" +%} proxy_http_version 1.1; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection $connection_upgrade; - {% elif keepalive == "yes" +%} + {% elif keepalive == "yes" +%} proxy_http_version 1.1; proxy_set_header Connection ""; - {% endif %} - {% if headers != "" %} - {% for header in headers.split(";") +%} + {% endif %} + {% if auth_request != "" +%} + auth_request {{ auth_request }}; + {% endif +%} + {% if auth_request_signin_url != "" +%} + error_page 401 =302 {{ auth_request_signin_url }}; + {% endif +%} + {% if auth_request_sets != "" +%} + {% for auth_request_set in auth_request_sets.split(";") +%} + auth_request_set {{ auth_request_set }}; + {% endfor +%} + {% endif +%} + {% if headers != "" +%} + {% for header in headers.split(";") +%} proxy_set_header {{ header }}; - {% endfor %} - {% endif %} + {% endfor +%} + {% endif +%} + {% if headers_client != "" +%} + {% for header_client in headers_client.split(";") +%} + add_header {{ header_client }}; + {% endfor +%} + {% endif +%} {% raw %}}{% endraw %} {% endif %} + {% set counter.value = counter.value + 1 %} {% endfor %} {% endif %} \ No newline at end of file diff --git a/core/reverseproxy/plugin.json b/core/reverseproxy/plugin.json index f00592c..0202e77 100644 --- a/core/reverseproxy/plugin.json +++ b/core/reverseproxy/plugin.json @@ -56,13 +56,23 @@ "REVERSE_PROXY_HEADERS": { "context": "multisite", "default": "", - "help": "List of HTTP headers to send to proxied resource.", + "help": "List of HTTP headers to send to proxied resource separated with ; (values for proxy_set_header directive).", "id": "reverse-proxy-headers", "label": "Reverse proxy headers", "regex": "^.*$", "type": "text", "multiple": "reverse-proxy" }, + "REVERSE_PROXY_HEADERS_CLIENT": { + "context": "multisite", + "default": "", + "help": "List of HTTP headers to send to client separated with ; (values for add_header directive).", + "id": "reverse-proxy-headers-client", + "label": "Reverse proxy headers-client", + "regex": "^.*$", + "type": "text", + "multiple": "reverse-proxy" + }, "REVERSE_PROXY_BUFFERING": { "context": "multisite", "default": "yes", @@ -83,6 +93,36 @@ "type": "check", "multiple": "reverse-proxy" }, + "REVERSE_PROXY_AUTH_REQUEST": { + "context": "multisite", + "default": "", + "help": "Enable authentication using an external provider (value of auth_request directive).", + "id": "reverse-proxy-auth-request", + "label": "Reverse proxy auth request", + "regex": "^/.*$", + "type": "text", + "multiple": "reverse-proxy" + }, + "REVERSE_PROXY_AUTH_REQUEST_SIGNIN_URL": { + "context": "multisite", + "default": "", + "help": "Redirect clients to signin URL when using REVERSE_PROXY_AUTH_REQUEST (used when auth_request call returned 401).", + "id": "reverse-proxy-auth-request-signin-url", + "label": "Auth request signin URL", + "regex": "^http.*$", + "type": "text", + "multiple": "reverse-proxy" + }, + "REVERSE_PROXY_AUTH_REQUEST_SET": { + "context": "multisite", + "default": "", + "help": "List of variables to set from the authentication provider, separated with ; (values of auth_request_set directives).", + "id": "reverse-proxy-auth-request-set", + "label": "Reverse proxy auth request set", + "regex": "^.*$", + "type": "text", + "multiple": "reverse-proxy" + }, "USE_PROXY_CACHE": { "context": "multisite", "default": "no", diff --git a/examples/authentik/.env b/examples/authentik/.env new file mode 100644 index 0000000..e63a9c9 --- /dev/null +++ b/examples/authentik/.env @@ -0,0 +1,3 @@ +PG_PASS=changeme +AUTHENTIK_SECRET_KEY=changeme +AUTHENTIK_COOKIE_DOMAIN=example.com diff --git a/examples/authentik/docker-compose.yml b/examples/authentik/docker-compose.yml new file mode 100644 index 0000000..19367d5 --- /dev/null +++ b/examples/authentik/docker-compose.yml @@ -0,0 +1,154 @@ +version: '3.4' + +services: + + mybunker: + image: bunkerity/bunkerweb:1.4.0 + ports: + - 80:8080 + - 443:8443 + # ⚠️ read this if you use local folders for volumes ⚠️ + # bunkerweb runs as an unprivileged user with UID/GID 101 + # don't forget to edit the permissions of the files and folders accordingly + # example if you need to create a directory : mkdir folder && chown root:101 folder && chmod 770 folder + # or for an existing one : chown -R root:101 folder && chmod -R 770 folder + # more info at https://docs.bunkerweb.io + volumes: + - bw_data:/data + environment: + - MULTISITE=yes + - SERVER_NAME=auth.example.com app1.example.com app2.example.com # replace with your domains + - SERVE_FILES=no + - DISABLE_DEFAULT_SERVER=yes + - AUTO_LETS_ENCRYPT=yes + - USE_CLIENT_CACHE=yes + - USE_GZIP=yes + - USE_REVERSE_PROXY=yes + # Proxy to outpost + - REVERSE_PROXY_URL_999=/outpost.goauthentik.io + - REVERSE_PROXY_HOST_999=http://server:9000 + - REVERSE_PROXY_HEADERS_999=X-Original-URL $$scheme://$$http_host$$request_uri;Content-Length "" + - REVERSE_PROXY_HEADERS_CLIENT_999=Set-Cookie $$auth_cookie + - REVERSE_PROXY_AUTH_REQUEST_SET_999=$$auth_cookie $$upstream_http_set_cookie + # Authentik + - auth.example.com_REVERSE_PROXY_URL=/ + - auth.example.com_REVERSE_PROXY_HOST=http://server:9000 + - auth.example.com_REVERSE_PROXY_WS=yes + - auth.example.com_LIMIT_REQ_URL_1=^/api/ + - auth.example.com_LIMIT_REQ_RATE_1=5r/s + - auth.example.com_REVERSE_PROXY_INTERCEPT_ERRORS=no + - auth.example.com_ALLOWED_METHODS=GET|POST|HEAD|PUT|DELETE|PATCH + - auth.example.com_COOKIE_FLAGS=* SameSite=Lax + # Applications + - app1.example.com_REVERSE_PROXY_URL=/ + - app1.example.com_REVERSE_PROXY_HOST=http://app1:3000 + - app1.example.com_REVERSE_PROXY_AUTH_REQUEST=/outpost.goauthentik.io/auth/nginx + - app1.example.com_REVERSE_PROXY_AUTH_REQUEST_SIGNIN_URL=http://auth.example.com/outpost.goauthentik.io/start?rd=$$scheme%3A%2F%2F$$host$$request_uri + - app1.example.com_REVERSE_PROXY_AUTH_REQUEST_SET=$$auth_cookie $$upstream_http_set_cookie;$$authentik_username $$upstream_http_x_authentik_username;$$authentik_groups $$upstream_http_x_authentik_groups;$$authentik_email $$upstream_http_x_authentik_email;$$authentik_name $$upstream_http_x_authentik_name;$$authentik_uid $$upstream_http_x_authentik_uid + - app1.example.com_REVERSE_PROXY_HEADERS_CLIENT=Set-Cookie $$auth_cookie + - app1.example.com_REVERSE_PROXY_HEADERS=X-authentik-username $$authentik_username;X-authentik-groups $$authentik_groups;X-authentik-email $$authentik_email;X-authentik-name $$authentik_name;X-authentik-uid $$authentik_uid + - app2.example.com_REVERSE_PROXY_URL=/ + - app2.example.com_REVERSE_PROXY_HOST=http://app2 + - app2.example.com_REVERSE_PROXY_AUTH_REQUEST=/outpost.goauthentik.io/auth/nginx + - app2.example.com_REVERSE_PROXY_AUTH_REQUEST_SIGNIN_URL=http://auth.example.com/outpost.goauthentik.io/start?rd=$$scheme%3A%2F%2F$$host$$request_uri + - app2.example.com_REVERSE_PROXY_AUTH_REQUEST_SET=$$auth_cookie $$upstream_http_set_cookie;$$authentik_username $$upstream_http_x_authentik_username;$$authentik_groups $$upstream_http_x_authentik_groups;$$authentik_email $$upstream_http_x_authentik_email;$$authentik_name $$upstream_http_x_authentik_name;$$authentik_uid $$upstream_http_x_authentik_uid + - app2.example.com_REVERSE_PROXY_HEADERS_CLIENT=Set-Cookie $$auth_cookie + - app2.example.com_REVERSE_PROXY_HEADERS=X-authentik-username $$authentik_username;X-authentik-groups $$authentik_groups;X-authentik-email $$authentik_email;X-authentik-name $$authentik_name;X-authentik-uid $$authentik_uid + + # APPLICATIONS + app1: + image: node + working_dir: /home/node/app + volumes: + - ./js-app:/home/node/app + environment: + - NODE_ENV=production + command: bash -c "npm install express && node index.js" + app2: + image: tutum/hello-world + + # AUTHENTIK SERVICES + postgresql: + image: postgres:12-alpine + restart: unless-stopped + healthcheck: + test: ["CMD", "pg_isready"] + start_period: 20s + interval: 30s + retries: 5 + timeout: 5s + volumes: + - database:/var/lib/postgresql/data + environment: + - POSTGRES_PASSWORD=${PG_PASS:?database password required} + - POSTGRES_USER=${PG_USER:-authentik} + - POSTGRES_DB=${PG_DB:-authentik} + env_file: + - .env + redis: + image: redis:alpine + restart: unless-stopped + healthcheck: + test: ["CMD-SHELL", "redis-cli ping | grep PONG"] + start_period: 20s + interval: 30s + retries: 5 + timeout: 3s + server: + image: ${AUTHENTIK_IMAGE:-ghcr.io/goauthentik/server}:${AUTHENTIK_TAG:-2022.6.2} + restart: unless-stopped + command: server + environment: + AUTHENTIK_REDIS__HOST: redis + AUTHENTIK_POSTGRESQL__HOST: postgresql + AUTHENTIK_POSTGRESQL__USER: ${PG_USER:-authentik} + AUTHENTIK_POSTGRESQL__NAME: ${PG_DB:-authentik} + AUTHENTIK_POSTGRESQL__PASSWORD: ${PG_PASS} + # AUTHENTIK_ERROR_REPORTING__ENABLED: "true" + # WORKERS: 2 + volumes: + - ./media:/media + - ./custom-templates:/templates + - geoip:/geoip + env_file: + - .env + # ports: + # - "0.0.0.0:${AUTHENTIK_PORT_HTTP:-9000}:9000" + # - "0.0.0.0:${AUTHENTIK_PORT_HTTPS:-9443}:9443" + worker: + image: ${AUTHENTIK_IMAGE:-ghcr.io/goauthentik/server}:${AUTHENTIK_TAG:-2022.6.2} + restart: unless-stopped + command: worker + environment: + AUTHENTIK_REDIS__HOST: redis + AUTHENTIK_POSTGRESQL__HOST: postgresql + AUTHENTIK_POSTGRESQL__USER: ${PG_USER:-authentik} + AUTHENTIK_POSTGRESQL__NAME: ${PG_DB:-authentik} + AUTHENTIK_POSTGRESQL__PASSWORD: ${PG_PASS} + # AUTHENTIK_ERROR_REPORTING__ENABLED: "true" + # This is optional, and can be removed. If you remove this, the following will happen + # - The permissions for the /media folders aren't fixed, so make sure they are 1000:1000 + # - The docker socket can't be accessed anymore + user: root + volumes: + - ./media:/media + - ./certs:/certs + - /var/run/docker.sock:/var/run/docker.sock + - ./custom-templates:/templates + - geoip:/geoip + env_file: + - .env + geoipupdate: + image: "maxmindinc/geoipupdate:latest" + volumes: + - "geoip:/usr/share/GeoIP" + environment: + GEOIPUPDATE_EDITION_IDS: "GeoLite2-City" + GEOIPUPDATE_FREQUENCY: "8" + env_file: + - .env + +volumes: + bw_data: + database: + geoip: diff --git a/examples/authentik/js-app/index.js b/examples/authentik/js-app/index.js new file mode 100644 index 0000000..c623166 --- /dev/null +++ b/examples/authentik/js-app/index.js @@ -0,0 +1,13 @@ +const express = require('express') +const app = express() +const port = 3000 + +app.get('/', (req, res) => { + res.send('Hello World from app1!') +}) + +app.listen(port, () => { + console.log(`Example app listening at http://localhost:${port}`) +}) + + diff --git a/examples/authentik/js-app/package.json b/examples/authentik/js-app/package.json new file mode 100644 index 0000000..2e08d1e --- /dev/null +++ b/examples/authentik/js-app/package.json @@ -0,0 +1,15 @@ +{ + "name": "js-app", + "version": "1.0.0", + "description": "demo", + "main": "index.js", + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1" + }, + "author": "", + "license": "ISC", + "dependencies": { + "express": "^4.17.1" + } +} + diff --git a/helpers/entrypoint.sh b/helpers/entrypoint.sh index e54ab1c..e6705e1 100644 --- a/helpers/entrypoint.sh +++ b/helpers/entrypoint.sh @@ -40,6 +40,10 @@ function trap_reload() { } trap "trap_reload" HUP +if [ -f /opt/bunkerweb/tmp/scheduler.pid ] ; then + rm -f /opt/bunkerweb/tmp/scheduler.pid +fi + if [ "$SWARM_MODE" != "yes" ] && [ "$KUBERNETES_MODE" != "yes" ] && [ "$AUTOCONF_MODE" != "yes" ] ; then # execute temp nginx with no server export TEMP_NGINX="yes"