From 0355e0161f45f1010a3bf7fdf7def8c8cf3a3a6f Mon Sep 17 00:00:00 2001 From: Ryan McGuire Date: Wed, 25 Oct 2023 10:57:23 -0600 Subject: [PATCH 1/2] Header Authorization (#120) * Adds traefik header authorization plugin. * Adds whoami example for header authorization * add header authorization to oauth2 config in makefile, .env-dist, and docker-compose.instance.yaml * updated readmes * clarified authentication/authorization in .env-dist files * Reconfigures header authorization middleware on Traefik rather than the app itself. * adding "remove users from group" function, added menus to other functions * Fix removing users from group * fixes to README * README fixes * combined all auth config questions, made authroization group configurable in docker-compose.instance.yaml,, added reconfigure_menu, updated readmes * install script-wizard * Adds reconfigure_choose * remove subshell * Adds select , but still needs a default value * reconfigure_choose and reconfigure_select load default from .env * fix select for script-wizard 0.1.8 * reconfigure_editor * Makes original ask functions use a green prompt to match script-wizard's style * updated reconfigure_auth to incorporate script-wizard * fix link * changed env_var name, fixed unset() * Re-adds the old pure bash confirm dialog * Adds confirmation before installing script-wizard * Makes WHOAMI_OAUTH= blank by default (was 'no' before) Removes confirmation in reconfigure_htpasswd * make unset _OAUTH2 env var be blank instead of "no", clean up script * rename variables for clarity * update 10 apps for reconfigure_auth * prevent reconfigure_oauth2 and reconfigure_htpasswd from being called directly * Add check if traefik-forward-auth is installed. * corrected syntax, removed security note from reconfigure_oauth2 * update readmes re: authorization groups and security * README dependencies * README * README app re-ordering * README * tfa readme * traefik readme * traefik readme * traefik readme * Traefik README env vars add TRAEFIK_HEADER_AUTHORIZATION_GROUPS and sort alphabetically * update invidious makefile for reconfigure_auth * fixed/updated readmes for reconfigure_auth, corrected spelling * removed 2nd confirmation for configuring oauth2 * check for existance of passwords.json before rewriting it. * added notes to `confirm` and `confirm.sh` * check for passwords.json * combine both confirm scripts into one. * remove old comment * colorize * set default oauth2 var to blank instead of no in .env-dist * make `reconfigure dashboard` work with `reconfigure_auth` * Revert "make `reconfigure dashboard` work with `reconfigure_auth`" This reverts commit fe52797de48d52184bd69bab4305478b8da8183b. * make `reconfigure_dashboard` work with updated `reconfigure_htpasswd` * traefik `make groups` uses script-wizard now * adds user management * fix manage user * fix var name * fix interpreting user arguments as command arguments (eg with -- in the name) * use eval * Refactors remove users to edit users * fix * Improve change detection by referencing traefik label * add failsafe for missing env vars * Adds missing audiobookshelf AUDIOBOOKSHELF_HTTP_AUTH var * OAuth2 is a big feature, put it at the top. * Add lemmy auth for private instances * refactors OAUTH2=yes to OAUTH2=true * t-f-a readme fix for OAUTH2=true * Instantiate archivebox * Instantiate baikal * Grab the correct traefik env file when configuring an app instance. * Instantiate calcpad * fix DOCKER_CONTEXT in reconfigure_oauth2 * Instantiate filestash * Instantiate freshrss * Instantiate github-actions-runner * Instantiate jupyterlab * Instantiate larynx * Instantiate maubot * Instantiate nodered * intro about auth * Instantiate pairdrop * Instantiate privatebin * Instantiated qbittorrent, but its still broken per #125 * make vars match accross .env-dist, docker-compose.yaml, and qBIttorrent.conf * Instantiated redbean * fix transmission * Instantiate s3proxy * Instantiate shaarli * Instantiate smokeping * Instantiate thttpd * fix username default * Move transmission-wireguard to _attic * remove qbittorrent username/password config Since WebUI\AuthSubnetWhitelistEnabled=true and WebUI\AuthSubnetWhitelist=0.0.0.0/0 there shouldn't be any auth needed at all, right??? * Fix qbittorrent proxy config * Update tt-rss * fix tiddlywiki - but still need to make use of reconfigure_auth * Instantiate Vaultwarden * Instantiate websocketed * Adds note about increasing pool of Docker bridge network addresses. * Instantiate xbs * Fixes traefik-forward-auth auth-host mode. * make sentry * refactor `make switch` to use script-wizard * refactor `make instance` to use suggestions * tfa: Set the gitea domain (which may be external) * fix HTTPS_PORT * fix HTTPS port again * tfa readme on logout mindset * fix vaultwarden README * Move cryptpad to the _attic * readme * script-wizard version check in .tools.lock.json * fix version_spec for jq 1.6 syntax * fix version_spec for jq 1.6 syntax * upgrade script-wizard to v0.1.22 * tfa readme * `make clean` will check for existing instances before cleaning files. * _attic --------- Co-authored-by: mcmikemn Co-authored-by: mcmikemn <46011270+mcmikemn@users.noreply.github.com> --- .gitignore | 1 + .tools.lock.json | 6 + Makefile | 9 +- README.md | 124 +++++-- _attic/README.md | 10 +- {cryptpad => _attic/cryptpad}/.env-dist | 0 {cryptpad => _attic/cryptpad}/.gitignore | 0 {cryptpad => _attic/cryptpad}/Makefile | 2 +- {cryptpad => _attic/cryptpad}/README.md | 5 + .../cryptpad}/config/Dockerfile | 0 .../cryptpad}/config/config.template.js | 0 {cryptpad => _attic/cryptpad}/config/setup.sh | 0 .../cryptpad}/cryptpad/Dockerfile | 0 .../cryptpad}/cryptpad/start_cryptpad.sh | 0 .../cryptpad}/docker-compose.yaml | 0 .../transmission-wireguard}/.env-dist | 0 .../transmission-wireguard}/Makefile | 0 .../transmission-wireguard}/README.md | 2 +- .../docker-compose.yaml | 0 .../transmission-config/Dockerfile | 0 .../transmission-config/setup.sh | 0 .../template/settings.json | 0 .../wireguard-config/Dockerfile | 0 .../wireguard-config/setup.sh | 0 .../wireguard-config/template/wg0.conf | 0 _scripts/Makefile.clean | 3 + _scripts/Makefile.docker-compose | 2 +- _scripts/Makefile.lifecycle | 2 + _scripts/clean_instance | 25 ++ _scripts/color | 1 + _scripts/confirm | 37 ++- _scripts/funcs.sh | 82 ++++- _scripts/gen_password | 1 + _scripts/install_script-wizard | 92 ++++++ _scripts/instance | 19 +- _scripts/reconfigure_ask | 2 +- _scripts/reconfigure_auth | 63 ++++ _scripts/reconfigure_choose | 24 ++ _scripts/reconfigure_dashboard | 4 +- _scripts/reconfigure_editor | 32 ++ _scripts/reconfigure_header_authorization | 303 ++++++++++++++++++ _scripts/reconfigure_htpasswd | 14 +- _scripts/reconfigure_oauth2 | 45 ++- _scripts/reconfigure_password | 2 +- _scripts/reconfigure_select | 25 ++ _scripts/version_spec | 1 + archivebox/.env-dist | 17 + archivebox/Makefile | 16 +- archivebox/docker-compose.instance.yaml | 51 +++ archivebox/docker-compose.yaml | 12 +- audiobookshelf/.env-dist | 10 +- audiobookshelf/Makefile | 4 +- audiobookshelf/README.md | 29 +- audiobookshelf/docker-compose.instance.yaml | 6 +- baikal/.env-dist | 5 + baikal/Makefile | 15 + baikal/docker-compose.instance.yaml | 48 +++ baikal/docker-compose.yaml | 7 +- calcpad/.env-dist | 17 + calcpad/Makefile | 4 +- calcpad/docker-compose.instance.yaml | 7 + drawio/.env-dist | 9 +- drawio/Makefile | 5 +- drawio/README.md | 31 +- drawio/docker-compose.instance.yaml | 6 +- filestash/.env-dist | 24 +- filestash/Makefile | 16 +- filestash/docker-compose.instance.yaml | 48 +++ filestash/docker-compose.yaml | 10 +- freshrss/.env-dist | 23 ++ freshrss/Makefile | 15 + freshrss/docker-compose.instance.yaml | 48 +++ freshrss/docker-compose.yaml | 12 +- github-actions-runner/.env-dist | 6 +- github-actions-runner/Makefile | 3 +- github-actions-runner/docker-compose.yaml | 6 +- grocy/.env-dist | 8 +- grocy/Makefile | 5 +- grocy/README.md | 30 +- grocy/docker-compose.instance.yaml | 6 +- homepage/.env-dist | 8 +- homepage/Makefile | 5 +- homepage/README.md | 34 +- homepage/docker-compose.instance.yaml | 6 +- invidious/.env-dist | 8 +- invidious/Makefile | 5 +- invidious/README.md | 30 +- invidious/docker-compose.instance.yaml | 6 +- jupyterlab/.env-dist | 21 ++ jupyterlab/Dockerfile | 4 +- jupyterlab/Makefile | 16 + jupyterlab/docker-compose.instance.yaml | 48 +++ larynx/.env-dist | 23 +- larynx/Makefile | 20 +- larynx/docker-compose.instance.yaml | 48 +++ larynx/docker-compose.yaml | 13 +- lemmy/.env-dist | 34 +- lemmy/Makefile | 5 +- lemmy/README.md | 19 +- lemmy/docker-compose.instance.yaml | 25 +- maubot/.env-dist | 20 ++ maubot/Makefile | 18 +- maubot/docker-compose.instance.yaml | 51 +++ maubot/docker-compose.yaml | 9 +- nodered/.env-dist | 25 +- nodered/Makefile | 18 +- nodered/docker-compose.instance.yaml | 48 +++ nodered/docker-compose.yaml | 12 +- pairdrop/.env-dist | 17 + pairdrop/Makefile | 4 +- pairdrop/docker-compose.instance.yaml | 7 + photoprism/.env-dist | 6 +- photoprism/Makefile | 5 +- photoprism/README.md | 31 +- photoprism/docker-compose.instance.yaml | 2 + piwigo/.env-dist | 8 +- piwigo/Makefile | 5 +- piwigo/README.md | 34 +- piwigo/docker-compose.instance.yaml | 6 +- privatebin/.env-dist | 20 ++ privatebin/Makefile | 7 +- privatebin/docker-compose.instance.yaml | 7 + qbittorrent-wireguard/.env-dist | 39 ++- qbittorrent-wireguard/Makefile | 26 +- qbittorrent-wireguard/README.md | 6 + .../docker-compose.instance.yaml | 50 +++ qbittorrent-wireguard/docker-compose.yaml | 21 +- .../template/qBittorrent.conf | 18 +- redbean/.env-dist | 18 ++ redbean/Makefile | 4 +- redbean/docker-compose.instance.yaml | 7 + s3-proxy/.env-dist | 23 +- s3-proxy/Makefile | 4 +- s3-proxy/docker-compose.instance.yaml | 9 +- shaarli/.env-dist | 24 +- shaarli/Makefile | 18 +- shaarli/docker-compose.instance.yaml | 48 +++ shaarli/docker-compose.yaml | 7 +- smokeping/.env-dist | 18 ++ smokeping/Makefile | 4 +- smokeping/docker-compose.instance.yaml | 7 + thttpd/.env-dist | 18 ++ thttpd/Makefile | 4 +- thttpd/docker-compose.instance.yaml | 7 + tiddlywiki-nodejs/Makefile | 5 +- tiddlywiki-webdav/Makefile | 6 +- traefik-forward-auth/.env-dist | 5 + traefik-forward-auth/Makefile | 12 +- traefik-forward-auth/README.md | 129 ++++++-- traefik-forward-auth/docker-compose.yaml | 2 +- traefik/.env-dist | 13 +- traefik/Dockerfile | 4 + traefik/Makefile | 9 + traefik/README.md | 171 +++++++--- .../config-template/header-authorization.yml | 22 ++ traefik/config/setup.sh | 4 +- traefik/config/traefik.yml | 4 + traefik/docker-compose.yaml | 8 +- ttrss/.env-dist | 20 ++ ttrss/Makefile | 18 +- ttrss/docker-compose.instance.yaml | 48 +++ ttrss/docker-compose.yaml | 22 +- vaultwarden/.env-dist | 23 +- vaultwarden/Makefile | 18 +- vaultwarden/README.md | 8 +- vaultwarden/docker-compose.instance.yaml | 59 ++++ vaultwarden/docker-compose.yaml | 20 +- websocketd/.env-dist | 17 + websocketd/Makefile | 4 +- websocketd/docker-compose.instance.yaml | 7 + whoami/.env-dist | 8 +- whoami/Makefile | 5 +- whoami/README.md | 29 +- whoami/docker-compose.instance.yaml | 6 +- wordpress/.env-dist | 8 +- wordpress/Makefile | 6 +- wordpress/README.md | 31 +- wordpress/docker-compose.instance.yaml | 6 +- xbs/.env-dist | 26 +- xbs/Makefile | 16 + xbs/api/Dockerfile | 3 +- xbs/docker-compose.instance.yaml | 50 +++ xbs/docker-compose.yaml | 13 +- 183 files changed, 2898 insertions(+), 582 deletions(-) create mode 100644 .tools.lock.json rename {cryptpad => _attic/cryptpad}/.env-dist (100%) rename {cryptpad => _attic/cryptpad}/.gitignore (100%) rename {cryptpad => _attic/cryptpad}/Makefile (98%) rename {cryptpad => _attic/cryptpad}/README.md (84%) rename {cryptpad => _attic/cryptpad}/config/Dockerfile (100%) rename {cryptpad => _attic/cryptpad}/config/config.template.js (100%) rename {cryptpad => _attic/cryptpad}/config/setup.sh (100%) rename {cryptpad => _attic/cryptpad}/cryptpad/Dockerfile (100%) rename {cryptpad => _attic/cryptpad}/cryptpad/start_cryptpad.sh (100%) rename {cryptpad => _attic/cryptpad}/docker-compose.yaml (100%) rename {transmission-wireguard => _attic/transmission-wireguard}/.env-dist (100%) rename {transmission-wireguard => _attic/transmission-wireguard}/Makefile (100%) rename {transmission-wireguard => _attic/transmission-wireguard}/README.md (98%) rename {transmission-wireguard => _attic/transmission-wireguard}/docker-compose.yaml (100%) rename {transmission-wireguard => _attic/transmission-wireguard}/transmission-config/Dockerfile (100%) rename {transmission-wireguard => _attic/transmission-wireguard}/transmission-config/setup.sh (100%) rename {transmission-wireguard => _attic/transmission-wireguard}/transmission-config/template/settings.json (100%) rename {transmission-wireguard => _attic/transmission-wireguard}/wireguard-config/Dockerfile (100%) rename {transmission-wireguard => _attic/transmission-wireguard}/wireguard-config/setup.sh (100%) rename {transmission-wireguard => _attic/transmission-wireguard}/wireguard-config/template/wg0.conf (100%) create mode 100755 _scripts/clean_instance create mode 120000 _scripts/color create mode 120000 _scripts/gen_password create mode 100755 _scripts/install_script-wizard create mode 100755 _scripts/reconfigure_auth create mode 100755 _scripts/reconfigure_choose create mode 100755 _scripts/reconfigure_editor create mode 100755 _scripts/reconfigure_header_authorization create mode 100755 _scripts/reconfigure_select create mode 120000 _scripts/version_spec create mode 100644 archivebox/docker-compose.instance.yaml create mode 100644 baikal/docker-compose.instance.yaml create mode 100644 filestash/docker-compose.instance.yaml create mode 100644 freshrss/docker-compose.instance.yaml create mode 100644 jupyterlab/docker-compose.instance.yaml create mode 100644 larynx/docker-compose.instance.yaml create mode 100644 maubot/docker-compose.instance.yaml create mode 100644 nodered/docker-compose.instance.yaml create mode 100644 qbittorrent-wireguard/docker-compose.instance.yaml create mode 100644 shaarli/docker-compose.instance.yaml create mode 100644 traefik/config/config-template/header-authorization.yml create mode 100644 ttrss/docker-compose.instance.yaml create mode 100644 vaultwarden/docker-compose.instance.yaml create mode 100644 xbs/docker-compose.instance.yaml diff --git a/.gitignore b/.gitignore index b0c39273..36472bf0 100644 --- a/.gitignore +++ b/.gitignore @@ -11,3 +11,4 @@ docker-compose.override*.yaml dump.* tasks.org node_modules +_scripts/script-wizard diff --git a/.tools.lock.json b/.tools.lock.json new file mode 100644 index 00000000..e9a8bf8e --- /dev/null +++ b/.tools.lock.json @@ -0,0 +1,6 @@ +{ + "Description": "This file is used to configure the required versions of external application dependencies. End users should not need to edit these, but they are set by the d.rymcg.tech authors.", + "dependencies": { + "script-wizard": "0.1.22" + } +} diff --git a/Makefile b/Makefile index 3b3a506b..e2f91cdd 100644 --- a/Makefile +++ b/Makefile @@ -8,16 +8,20 @@ help: include _scripts/Makefile.globals include _scripts/Makefile.cd +.PHONY: script-wizard +script-wizard: + _scripts/install_script-wizard + .PHONY: check-deps check-deps: - _scripts/check_deps docker sed awk xargs openssl htpasswd jq + _scripts/check_deps docker sed awk xargs openssl htpasswd jq curl .PHONY: check-docker check-docker: @docker info >/dev/null && echo "Docker is running." || (echo "Could not connect to Docker!" && false) .PHONY: config # Configure main variables -config: check-deps check-docker +config: script-wizard check-deps check-docker # @${BIN}/userns-remap check @echo "" @${BIN}/confirm yes "This will make a configuration for the current docker context (${DOCKER_CONTEXT})" @@ -119,4 +123,3 @@ docker-workstation: docker-workstation-clean: docker compose -f compose-dev.yaml kill docker compose -f compose-dev.yaml down -v - \ No newline at end of file diff --git a/README.md b/README.md index 9b87bc3f..7ad1fc8c 100644 --- a/README.md +++ b/README.md @@ -4,14 +4,20 @@ [![Chat on Matrix](_meta/img/matrix-badge.svg)](https://matrix.to/#/#d.rymcg.tech:enigmacurry.com) This is a collection of Docker Compose projects consisting of -[Traefik](https://doc.traefik.io/traefik/) as a TLS HTTP/TCP/UDP reverse -proxy and other various self-hosted applications and services behind -this proxy. Each project is in its own sub-directory containing its -own `docker-compose.yaml` and `.env-dist` sample config file. This +[Traefik](https://doc.traefik.io/traefik/) as a TLS HTTP/TCP/UDP +reverse proxy and other various self-hosted applications and services +behind this proxy. Each project is in its own sub-directory containing +its own `docker-compose.yaml` and `.env-dist` sample config file. This structure allows you to pick and choose which services you wish to enable. You may also integrate your own external Docker Compose projects into this framework. +All (http) apps are secured with automatic Lets Encrypt TLS +certificates, along with configurable self-hosted authentication +middleware (OAuth2 with Gitea and/or HTTP Basic auth), as well as user +group authorization middlewares. Even non-http apps may be secured +with the optional VPN (Wireguard) support. + Each project has a `Makefile` to simplify configuration, installation, and maintainance tasks. The setup for any sub-project is as easy as running: @@ -29,7 +35,7 @@ configuration derived from your customized `.env` file. - [All configuration comes from the environment](#all-configuration-comes-from-the-environment) - [Prerequisites](#prerequisites) -- [Setup](#setup) +- [Setup Workstation](#setup-workstation) - [Main configuration](#main-configuration) - [Install applications](#install-applications) - [Command line interaction](#command-line-interaction) @@ -75,7 +81,7 @@ all of the dependent files are fully contained by Docker itself state is managed as part of the container/volume lifecycle. ## Prerequisites -### Create a Docker host +### Create a Docker host (server) [Install Docker Server](https://docs.docker.com/engine/install/#server) on your own @@ -99,8 +105,9 @@ and [IPWhitelist](https://doc.traefik.io/traefik/middlewares/http/ipwhitelist/) middlewares (see [s3-proxy](https://github.com/EnigmaCurry/d.rymcg.tech/blob/f77aaaa5a2705eedaf29a4cdc32f91cdb65e66f7/s3-proxy/docker-compose.yaml#L35-L41) -for an example that uses both basic auth and ip whitelist) or you can -make an exclusively private Traefik service with a +for an example that uses both of these), or by turning on [Oauth2 +authentication](https://github.com/EnigmaCurry/d.rymcg.tech/tree/master/traefik-forward-auth) +,or you can make an exclusively private Traefik service with a [Wireguard](https://github.com/EnigmaCurry/d.rymcg.tech/tree/master/traefik#wireguard-vpn) VPN. @@ -215,7 +222,42 @@ find traefik and the wireguard server/client in this latter category). Each sub-project directory also has a `make status` with useful per-project information. -## Setup +### Configure Docker bridge networks (optional) + +By default, Docker will only reserve enough IP addresses for a total +of 30 user-defined networks. This means that, by default, you can only +deploy up to 30 apps per docker server. + +If you would like more than 30, you can increase the range of IP +addresses that Docker reserves. On your Docker server, edit +`/etc/docker/daemon.json` (create this file if it does not exist), and +merge the following configuration: + +``` +{ + "default-address-pools": [ + {"base": "172.17.0.0/16", "size": 24} + ] +} +``` + +and restart the docker daemon, or reboot the server. + +## Setup Workstation + +Your local "workstation" is assumed to be a Linux desktop/laptop +computer, or another Linux system that you remotely connect to via +SSH: + + * Tested workstation architectures: + * Linux x86_64 (64 bit Intel or AMD) + * Linux aarch64 (64 bit ARM) + * Arch Linux and Ubuntu have been regularly tested. + * Other operating systems and architectures have not been tested, and +may require customization (please [open an +issue](https://github.com/EnigmaCurry/d.rymcg.tech/issues)). + * Your workstation should not be the same machine as the docker + server (unless docker is in its own virtual machine). ### Install Docker CLI tools @@ -266,14 +308,15 @@ installation: docker buildx install ``` -### Install workstation tools (optional) +### Install workstation tools -There are also **optional** helper scripts and Makefiles included, -that will have some additional system package requirements (Note: -these Makefiles are just convenience wrappers for creating/modifying -your `.env` files and for running `docker compose`, so these are not -required to use if you would rather just edit your `.env` files by -hand and/or run `docker compose` manually.): +The Makefiles have extra dependencies in order to help configure and +manage your containers. These dependencies are optional, but strongly +recommended. (The Makefiles are strictly convenience wrappers for +creating/modifying your `.env` files, and for running `docker compose` +commands, so if you would rather just edit your `.env` files by hand +and/or run `docker compose` manually, these dependencies may be +skipped): * Base development tools including `bash`, `make`, `sed`, `xargs`, and `shred`. @@ -288,17 +331,19 @@ hand and/or run `docker compose` manually.): simply printing the URL that you can copy and paste.) * `wireguard` (client for connecting to the [traefik wireguard](traefik#wireguard-vpn) VPN) + * `curl` (for downloading an installing external dependencies: + [script-wizard](https://github.com/enigmacurry/script-wizard)) On Arch Linux, run this to install all these dependencies: ``` -pacman -S bash base-devel openssl apache xdg-utils jq sshfs wireguard-tools +pacman -S bash base-devel openssl apache xdg-utils jq sshfs wireguard-tools curl ``` For Debian or Ubuntu, run: ``` -apt-get install bash build-essential openssl apache2-utils xdg-utils jq sshfs wireguard +apt-get install bash build-essential openssl apache2-utils xdg-utils jq sshfs wireguard curl ``` ### Setup SSH access to the server @@ -419,10 +464,18 @@ Run the configuration wizard, and answer the questions: make config ``` -(This writes the main project level variables into a file named -`.env_${DOCKER_CONTEXT}` (eg. `.env_d.example.com`) in the root source -directory, based upon the name of the current Docker context. This -file is excluded from the git repository via `.gitignore`.) +Running `make config`, in the root project directory, writes the main +project level variables into a file named `.env_${DOCKER_CONTEXT}` +(eg. `.env_d.example.com`) in the root source directory, based upon +the name of the current Docker context. This file is excluded from the +git repository via `.gitignore`.) + +All of the Makefiles depend on a helper utility called +[script-wizard](https://github.com/enigmacurry/script-wizard), which +is automatically installed the first time you run `make config`. + +This will also check your system for the dependencies and alert you if +you need to install something. The `ROOT_DOMAIN` variable is saved in `.env_${DOCKER_CONTEXT}` and will serve as the default root domain of all of the sub-project @@ -450,21 +503,35 @@ Install these first: * [Traefik](traefik#readme) - HTTP / TLS / TCP / UDP reverse proxy * [Whoami](whoami#readme) - HTTP test service -Install these services at your leisure/preference: +Install these recommended backbone applications next: + +* [Gitea](gitea#readme) + * A git host (like self-hosted GitHub) and OAuth2 server. + * Like GitHub, it can act as an OAuth2 identity service, which + supports 2FA including hardware tokens, even if you have no need + for a git forge, install this! +* [Traefik-forward-auth](traefik-forward-auth#readme) + * Traefik OAuth2 authentication middleware. + * Required if you want OAuth2 authentication. You'll combine this + with your gitea instance (or another external Oauth provider) to + add authentication to any of your apps. +* [Homepage](homepage#readme) + * Homepage acts as a dashboard or launcher for all your other apps + (but this is not required for any other functionality, if you + don't need it.) + +Install these other services at your leisure/preference: * [ArchiveBox](archivebox#readme) - a website archiving tool * [Audiobookshelf](audiobookshelf#readme) - an audiobook and podcast server * [Autoheal](autoheal#readme) - a Docker container healthcheck monitor with auto-restart service * [Baikal](baikal#readme) - a lightweight CalDAV+CardDAV server * [CalcPad](calcpad#readme) - a different take on the caculator -* [CryptPad](cryptpad#readme) - a collaborative document and spreadsheet editor * [DrawIO](drawio#readme) - a diagram / whiteboard editor tool * [Ejabberd](ejabberd#readme) - an XMPP (Jabber) server * [Filestash](filestash#readme) - a web based file manager with customizable backend storage providers * [FreshRSS](freshrss#readme) - an RSS reader / proxy -* [Gitea](gitea#readme) - Git host (like self-hosted GitHub) and oauth server * [Grocy](grocy#readme) - a grocery & household management/chore solution -* [Homepage](homepage#readme) - an application dashboard with several integrations * [Icecast](icecast#readme) - a SHOUTcast compatible streaming multimedia server * [Invidious](invidious#readme) - a Youtube proxy * [Jitsi Meet](jitsi-meet#readme) - a video conferencing and screencasting service @@ -497,8 +564,6 @@ Install these services at your leisure/preference: * [Thttpd](thttpd#readme) - a tiny/turbo/throttling HTTP server for serving static files * [TiddlyWiki (WebDAV version)](tiddlywiki-webdav#readme) - a personal wiki stored in a single static HTML file * [TiddlyWiki (NodeJS version)](tiddlywiki-nodejs#readme) - Advanced server edition of TiddlyWiki with image CDN -* [Traefik-forward-auth](traefik-forward-auth#readme) - Traefik oauth middleware -* [Transmission-Wireguard](transmission-wireguard#readme) - An older but very popular Bittorrent (v1) client with a combined VPN client * [Tiny Tiny RSS](ttrss#readme) - an RSS reader / proxy * [Vaultward](vaultwarden#readme) - a bitwarden compatible password manager written in Rust (formerly bitwarden_rs) * [Websocketd](websocketd#readme) - a websocket / CGI server @@ -514,6 +579,9 @@ Bespoke things: automatically build and run programs in Docker containers. * [_docker_vm](_docker_vm#readme) Run Docker in a Virtual Machine (KVM) on Linux. +Also check the [_attic](_attic) directory for a collection of old and +broken things. + ## Command line interaction As alluded to earlier, this project offers multiple ways to control diff --git a/_attic/README.md b/_attic/README.md index d7804966..9d6b2b84 100644 --- a/_attic/README.md +++ b/_attic/README.md @@ -1,6 +1,12 @@ # Attic -Heres things that are half baked or don't work anymore. Caveat Emptor. +Things living in the _attic directory application that are no longer +supported, and could be broken, but at least they all worked at one +point in time or the other. With a bit of love and care these could be +fixed up, and moved back into the root directory. + +Caveat Emptor! * [Mailu](mailu) - an email service suite. Run a private mail server connected to a public relay host. -* [Tiny Tiny RSS](ttrss) - an RSS reader / proxy +* [transmission-wireguard](transmission-wireguard) - an older but very popular Bittorrent (v1) client with a combined VPN client +* [CryptPad](cryptpad) - a collaborative document and spreadsheet editor stuck on an old unsupported version. diff --git a/cryptpad/.env-dist b/_attic/cryptpad/.env-dist similarity index 100% rename from cryptpad/.env-dist rename to _attic/cryptpad/.env-dist diff --git a/cryptpad/.gitignore b/_attic/cryptpad/.gitignore similarity index 100% rename from cryptpad/.gitignore rename to _attic/cryptpad/.gitignore diff --git a/cryptpad/Makefile b/_attic/cryptpad/Makefile similarity index 98% rename from cryptpad/Makefile rename to _attic/cryptpad/Makefile index ec942ed0..1631dbb8 100644 --- a/cryptpad/Makefile +++ b/_attic/cryptpad/Makefile @@ -1,4 +1,4 @@ -ROOT_DIR = .. +ROOT_DIR = ../.. include ${ROOT_DIR}/_scripts/Makefile.projects include ${ROOT_DIR}/_scripts/Makefile.instance diff --git a/cryptpad/README.md b/_attic/cryptpad/README.md similarity index 84% rename from cryptpad/README.md rename to _attic/cryptpad/README.md index 1e8c99fa..1f6acb6f 100644 --- a/cryptpad/README.md +++ b/_attic/cryptpad/README.md @@ -3,6 +3,11 @@ [CryptPad](https://cryptpad.fr/) is an encrypted, open source office collaboration suite. +WARNING: this config has not been updated in a while, and it is no +longer using the latest version. There is an [open issue to upgrade +cryptpad](https://github.com/EnigmaCurry/d.rymcg.tech/issues/30), but +there are some difficulties that need to be overcome. + CryptPad is designed to serve its content over two domains, a `main` domain, and a `sandbox` domain (eg `pad.d.example.com` and `pad-sandbox.d.example.com`). Account passwords and cryptographic content diff --git a/cryptpad/config/Dockerfile b/_attic/cryptpad/config/Dockerfile similarity index 100% rename from cryptpad/config/Dockerfile rename to _attic/cryptpad/config/Dockerfile diff --git a/cryptpad/config/config.template.js b/_attic/cryptpad/config/config.template.js similarity index 100% rename from cryptpad/config/config.template.js rename to _attic/cryptpad/config/config.template.js diff --git a/cryptpad/config/setup.sh b/_attic/cryptpad/config/setup.sh similarity index 100% rename from cryptpad/config/setup.sh rename to _attic/cryptpad/config/setup.sh diff --git a/cryptpad/cryptpad/Dockerfile b/_attic/cryptpad/cryptpad/Dockerfile similarity index 100% rename from cryptpad/cryptpad/Dockerfile rename to _attic/cryptpad/cryptpad/Dockerfile diff --git a/cryptpad/cryptpad/start_cryptpad.sh b/_attic/cryptpad/cryptpad/start_cryptpad.sh similarity index 100% rename from cryptpad/cryptpad/start_cryptpad.sh rename to _attic/cryptpad/cryptpad/start_cryptpad.sh diff --git a/cryptpad/docker-compose.yaml b/_attic/cryptpad/docker-compose.yaml similarity index 100% rename from cryptpad/docker-compose.yaml rename to _attic/cryptpad/docker-compose.yaml diff --git a/transmission-wireguard/.env-dist b/_attic/transmission-wireguard/.env-dist similarity index 100% rename from transmission-wireguard/.env-dist rename to _attic/transmission-wireguard/.env-dist diff --git a/transmission-wireguard/Makefile b/_attic/transmission-wireguard/Makefile similarity index 100% rename from transmission-wireguard/Makefile rename to _attic/transmission-wireguard/Makefile diff --git a/transmission-wireguard/README.md b/_attic/transmission-wireguard/README.md similarity index 98% rename from transmission-wireguard/README.md rename to _attic/transmission-wireguard/README.md index e56c5782..30c2e318 100644 --- a/transmission-wireguard/README.md +++ b/_attic/transmission-wireguard/README.md @@ -5,7 +5,7 @@ client combined with the [Wireguard](https://www.wireguard.com/) VPN service. Connect wireguard to your VPN provider and anonymize your peer connections. -You might prefer [qBittorrent-wireguard](../qbittorrent-wireguard) +You might prefer [qBittorrent-wireguard](../../qbittorrent-wireguard) instead of this, because its based off libtorrent, and because transmission lags behind in modern torrent v2 support, and transmission 3.0.0 has numerous [bugs regarding how many files you can diff --git a/transmission-wireguard/docker-compose.yaml b/_attic/transmission-wireguard/docker-compose.yaml similarity index 100% rename from transmission-wireguard/docker-compose.yaml rename to _attic/transmission-wireguard/docker-compose.yaml diff --git a/transmission-wireguard/transmission-config/Dockerfile b/_attic/transmission-wireguard/transmission-config/Dockerfile similarity index 100% rename from transmission-wireguard/transmission-config/Dockerfile rename to _attic/transmission-wireguard/transmission-config/Dockerfile diff --git a/transmission-wireguard/transmission-config/setup.sh b/_attic/transmission-wireguard/transmission-config/setup.sh similarity index 100% rename from transmission-wireguard/transmission-config/setup.sh rename to _attic/transmission-wireguard/transmission-config/setup.sh diff --git a/transmission-wireguard/transmission-config/template/settings.json b/_attic/transmission-wireguard/transmission-config/template/settings.json similarity index 100% rename from transmission-wireguard/transmission-config/template/settings.json rename to _attic/transmission-wireguard/transmission-config/template/settings.json diff --git a/transmission-wireguard/wireguard-config/Dockerfile b/_attic/transmission-wireguard/wireguard-config/Dockerfile similarity index 100% rename from transmission-wireguard/wireguard-config/Dockerfile rename to _attic/transmission-wireguard/wireguard-config/Dockerfile diff --git a/transmission-wireguard/wireguard-config/setup.sh b/_attic/transmission-wireguard/wireguard-config/setup.sh similarity index 100% rename from transmission-wireguard/wireguard-config/setup.sh rename to _attic/transmission-wireguard/wireguard-config/setup.sh diff --git a/transmission-wireguard/wireguard-config/template/wg0.conf b/_attic/transmission-wireguard/wireguard-config/template/wg0.conf similarity index 100% rename from transmission-wireguard/wireguard-config/template/wg0.conf rename to _attic/transmission-wireguard/wireguard-config/template/wg0.conf diff --git a/_scripts/Makefile.clean b/_scripts/Makefile.clean index 9d7dbf2a..0036d206 100644 --- a/_scripts/Makefile.clean +++ b/_scripts/Makefile.clean @@ -1,10 +1,13 @@ .PHONY: clean # Remove current context/instance environment file and saved passwords.json clean: + @${BIN}/clean_instance # checks if instance exists and faults here @echo "ENV_FILE=${ENV_FILE}" @ENV_FILE=${ENV_FILE}; set -x && rm -f $${ENV_FILE} docker-compose.override_${CONTEXT_INSTANCE}.yaml + @echo @echo "# Removed current context/instance environment files." @[[ -f passwords.json ]] && TEMP_PWD_FILE=$$(mktemp) && cat passwords.json | jq 'del(.["'${DOCKER_CONTEXT}'"])' >$${TEMP_PWD_FILE} && mv $${TEMP_PWD_FILE} passwords.json && echo "# Removed all entries from passwords.json for context: ${DOCKER_CONTEXT}" || true @grep "^clean-hook:" "${PROJECT_MAKEFILE}" >/dev/null 2>&1 && make -e --no-print-directory clean-hook || true + @echo .PHONY: clean-all # Remove all environment files and saved passwords.json diff --git a/_scripts/Makefile.docker-compose b/_scripts/Makefile.docker-compose index da027c31..45e5acb8 100644 --- a/_scripts/Makefile.docker-compose +++ b/_scripts/Makefile.docker-compose @@ -6,7 +6,7 @@ docker-compose: check-instance-project .PHONY: docker-compose-lifecycle-cmd docker-compose-lifecycle-cmd: check-instance-project - @QUIET="${QUIET}"; DOCKER_COMPOSE_FILE_ARGS="${DOCKER_COMPOSE_FILE_ARGS}"; export COMPOSE_PROFILES="$${DOCKER_COMPOSE_PROFILES:-$$(${BIN}/dotenv -f "${ENV_FILE}" get DOCKER_COMPOSE_PROFILES)}"; COMMAND="docker compose $${DOCKER_COMPOSE_FILE_ARGS} --env-file=${ENV_FILE} --project-name="${PROJECT_NAME}" ${EXTRA_ARGS}"; test -n "$$COMPOSE_PROFILES" && echo COMPOSE_PROFILES="$${COMPOSE_PROFILES}"; test "$${QUIET}" != "true" && echo "ENV_FILE=${ENV_FILE}" && echo "# $${COMMAND}"; sh -c "exec $${COMMAND}" + @QUIET="${QUIET}"; DOCKER_COMPOSE_FILE_ARGS="${DOCKER_COMPOSE_FILE_ARGS}"; export COMPOSE_PROFILES="$${DOCKER_COMPOSE_PROFILES:-$$(${BIN}/dotenv -f "${ENV_FILE}" get DOCKER_COMPOSE_PROFILES)}"; COMMAND="docker compose $${DOCKER_COMPOSE_FILE_ARGS} --env-file=${ENV_FILE} --project-name="${PROJECT_NAME}" ${EXTRA_ARGS}"; test -n "$$COMPOSE_PROFILES" && echo COMPOSE_PROFILES="$${COMPOSE_PROFILES}" >/dev/stderr; test "$${QUIET}" != "true" && echo "ENV_FILE=${ENV_FILE}" >/dev/stderr && echo "# $${COMMAND}" >/dev/stderr; sh -c "exec $${COMMAND}" .PHONY: docker-compose-build docker-compose-build: check-instance-project diff --git a/_scripts/Makefile.lifecycle b/_scripts/Makefile.lifecycle index 37b5ff08..6d9b778f 100644 --- a/_scripts/Makefile.lifecycle +++ b/_scripts/Makefile.lifecycle @@ -2,6 +2,8 @@ config: check-instance-project @echo "Configuring environment file: ${ENV_FILE}" @make -e --no-print-directory config-hook override instance=${INSTANCE} + @echo "" + @echo "Remember to run 'make install' to deploy your changes." .PHONY: start # Start services start: diff --git a/_scripts/clean_instance b/_scripts/clean_instance new file mode 100755 index 00000000..fa2e92a5 --- /dev/null +++ b/_scripts/clean_instance @@ -0,0 +1,25 @@ +#!/bin/bash + +BIN=$(realpath $(dirname ${BASH_SOURCE})) +source ${BIN}/funcs.sh +INSTANCE=${INSTANCE:-default} + +set -eo pipefail + +get_services_json() { + make --no-print-directory docker-compose-lifecycle-cmd EXTRA_ARGS="ps -a --format json" +} + +get_services() { + make --no-print-directory docker-compose-lifecycle-cmd EXTRA_ARGS="ps -a" +} + +if [[ "$(get_services_json)" == "[]" ]]; then + exit 0 +fi + +get_services +echo +echo "## There is an existing instance that must be destroyed (\`make destroy\`) before cleaning can happen." +echo +exit 1 diff --git a/_scripts/color b/_scripts/color new file mode 120000 index 00000000..fd4f62d6 --- /dev/null +++ b/_scripts/color @@ -0,0 +1 @@ +run_func \ No newline at end of file diff --git a/_scripts/confirm b/_scripts/confirm index dbab46a4..3a3c9cee 100755 --- a/_scripts/confirm +++ b/_scripts/confirm @@ -1,21 +1,34 @@ #!/bin/bash -## Confirm with the user -## Check env for the var YES, if it equals "yes" then bypass this confirm +BIN=$(realpath $(dirname ${BASH_SOURCE})) +source ${BIN}/funcs.sh + +## Confirm with the user. +## Check env for the var YES, if it equals "yes" then bypass this confirm. +## This version depends on `script-wizard` being installed. test ${YES:-no} == "yes" && exit 0 default=$1; prompt=$2; question=${3:-". Proceed?"} -if [[ $default == "y" || $default == "yes" || $default == "ok" ]]; then - dflt="Y/n" -else - dflt="y/N" -fi -read -p "${prompt}${question} (${dflt}): " answer -answer=${answer:-${default}} +check_var default prompt question -if [[ ${answer,,} == "y" || ${answer,,} == "yes" || ${answer,,} == "ok" ]]; then - exit 0 +if [[ -f ${BIN}/script-wizard ]]; then + ## Check if script-wizard is installed, and prefer to use that: + wizard confirm "$prompt$question" "$default" else - exit 1 + ## Otherwise use a pure bash version: + if [[ $default == "y" || $default == "yes" || $default == "ok" ]]; then + dflt="Y/n" + else + dflt="y/N" + fi + + read -e -p $'\e[32m?\e[0m '"${prompt}${question} (${dflt}): " answer + answer=${answer:-${default}} + + if [[ ${answer,,} == "y" || ${answer,,} == "yes" || ${answer,,} == "ok" ]]; then + exit 0 + else + exit 1 + fi fi diff --git a/_scripts/funcs.sh b/_scripts/funcs.sh index 5c7e9f3d..fdba9842 100644 --- a/_scripts/funcs.sh +++ b/_scripts/funcs.sh @@ -31,7 +31,7 @@ check_num(){ ask() { ## Ask the user a question and set the given variable name with their answer local __prompt="${1}"; local __var="${2}"; local __default="${3}" - read -e -p "${__prompt}"$'\x0a: ' -i "${__default}" ${__var} + read -e -p "${__prompt}"$'\x0a\e[32m:\e[0m ' -i "${__default}" ${__var} export ${__var} } @@ -40,7 +40,7 @@ ask_no_blank() { ## If the answer is blank, repeat the question. local __prompt="${1}"; local __var="${2}"; local __default="${3}" while true; do - read -e -p "${__prompt}"$'\x0a: ' -i "${__default}" ${__var} + read -e -p "${__prompt}"$'\x0a\e[32m:\e[0m ' -i "${__default}" ${__var} export ${__var} [[ -z "${!__var}" ]] || break done @@ -189,3 +189,81 @@ random_port() { grep "[0-9]\{1,5\}" | sort | uniq) 2>/dev/null | \ shuf 2>/dev/null | head -n 1; true } + +wizard() { + ${BIN}/script-wizard "$@" +} + +color() { + ## Print text in ANSI color + set -e + if [[ $# -lt 2 ]]; then + fault "Not enough args: expected COLOR and TEXT arguments" + fi + local COLOR_CODE_PREFIX='\033[' + local COLOR_CODE_SUFFIX='m' + local COLOR=$1; shift + local TEXT="$*" + local LIGHT=1 + check_var COLOR TEXT + case "${COLOR}" in + "black") COLOR=30; LIGHT=0;; + "red") COLOR=31; LIGHT=0;; + "green") COLOR=32; LIGHT=0;; + "brown") COLOR=33; LIGHT=0;; + "orange") COLOR=33; LIGHT=0;; + "blue") COLOR=34; LIGHT=0;; + "purple") COLOR=35; LIGHT=0;; + "cyan") COLOR=36; LIGHT=0;; + "light gray") COLOR=37; LIGHT=0;; + "dark gray") COLOR=30; LIGHT=1;; + "light red") COLOR=31; LIGHT=1;; + "light green") COLOR=32; LIGHT=1;; + "yellow") COLOR=33; LIGHT=1;; + "light blue") COLOR=34; LIGHT=1;; + "light purple") COLOR=35; LIGHT=1;; + "light cyan") COLOR=36; LIGHT=1;; + "white") COLOR=37; LIGHT=1;; + *) fault "Unknown color" + esac + echo -en "${COLOR_CODE_PREFIX}${LIGHT};${COLOR}${COLOR_CODE_SUFFIX}${TEXT}${COLOR_CODE_PREFIX}0;0${COLOR_CODE_SUFFIX}" +} + +element_in_array () { + local e match="$1"; shift; + for e; do [[ "$e" == "$match" ]] && return 0; done + return 1 +} + +gen_password() { + set -eo pipefail + LENGTH=${1:-30} + openssl rand -base64 ${LENGTH} | tr '=' '0' | tr '+' '0' | tr '/' '0' | tr '\n' '0' | head -c ${LENGTH} +} + +version_spec() { + ## Check the lock file to see if the apps INSTALLED_VERSION is ok + # version_spec APP_NAME INSTALLED_VERSION + set -eo pipefail + # The name of the app: + local APP=$1; + check_var APP + # The installed version to check against the lock file version, could be blank: + local CHECK_VERSION=$2; + local VERSION_LOCK="${ROOT_DIR}/.tools.lock.json" + if [[ ! -f "$VERSION_LOCK" ]]; then + fault "The version lock spec file is missing: ${VERSION_LOCK}" + fi + # Grab the locked version of APP from the lock file: + set -x + local LOCKED_VERSION=$(jq -r ".dependencies.\"${APP}\"" ${ROOT_DIR}/.tools.lock.json) + (test -z "${LOCKED_VERSION}" || test "${LOCKED_VERSION}" == "null") && fault "The app '${APP}' is not listed in ${VERSION_LOCK}" + + # Return the locked version string: + echo ${LOCKED_VERSION} + + # But error if the installed version is different than the locked version: + if [[ -n "${CHECK_VERSION}" ]] && [[ "${VERSION}" != "${CHECK_VERSION}" ]]; then + fault "Installed ${APP} version ${CHECK_VERSION} does not match the locked version: ${LOCKED_VERSION}" + fi +} diff --git a/_scripts/gen_password b/_scripts/gen_password new file mode 120000 index 00000000..fd4f62d6 --- /dev/null +++ b/_scripts/gen_password @@ -0,0 +1 @@ +run_func \ No newline at end of file diff --git a/_scripts/install_script-wizard b/_scripts/install_script-wizard new file mode 100755 index 00000000..661f8704 --- /dev/null +++ b/_scripts/install_script-wizard @@ -0,0 +1,92 @@ +#!/bin/bash + +BIN=$(realpath $(dirname ${BASH_SOURCE})) +source ${BIN}/funcs.sh +set -e + +${BIN}/check_deps curl jq + +get_latest_version() { + echo "## Checking for latest version of script-wizard:" > /dev/stderr + (set -x; curl -sL https://api.github.com/repos/EnigmaCurry/script-wizard/releases/latest | jq -r ".tag_name" | sed 's/^v//') +} + +check_os() { + local SUPPORTED_OS=$@ + if ! echo "${SUPPORTED_OS[@]}" | grep -F --word-regexp "$(uname -s)" >/dev/null; then + echo -e "\nError: Your operating system ($(uname -s)) is not supported by script-wizard (yet)." + echo -e "Please file an issue at https://github.com/EnigmaCurry/script-wizard/issues\n" + echo "Supported operating systems: ${SUPPORTED_OS[@]}" + exit 1 + else + echo "Found supported OS: $(uname -s)" + fi +} + +check_os_architecture() { + local OS=$1; shift; + local SUPPORTED_ARCHITECTURES=$@ + if [[ "$(uname -s)" == "${OS}" ]]; then + if ! echo "${SUPPORTED_ARCHITECTURES[@]}" | grep -F --word-regexp "$(uname -m)" >/dev/null; then + echo -e "\nError: Your system architecture ($(uname -m)) is not supported by script-wizard (yet)." + echo -e "Please file an issue at https://github.com/EnigmaCurry/script-wizard/issues\n" + echo "Supported architectures: ${SUPPORTED_ARCHITECTURES[@]}" + exit 1 + fi + echo "Found supported architecture: $(uname -m)" + else + return 1 + fi +} + +download() { + local SCRIPT_WIZARD_TARBALL="script-wizard-$(uname -s)-$(uname -m).tar.gz" + local VERSION=${1:-latest} + local SCRIPT_WIZARD_DOWNLOAD_URL="https://github.com/Enigmacurry/script-wizard/releases/latest/download/${SCRIPT_WIZARD_TARBALL}" + if [[ "$VERSION" != "latest" ]]; then + SCRIPT_WIZARD_DOWNLOAD_URL="https://github.com/Enigmacurry/script-wizard/releases/download/v${VERSION}/${SCRIPT_WIZARD_TARBALL}" + fi + local TMP_DIR=$(mktemp -d) + cd ${TMP_DIR} + echo "## Downloading ..." + exe curl -LO "${SCRIPT_WIZARD_DOWNLOAD_URL}" + tar xfvz "${SCRIPT_WIZARD_TARBALL}" + mv ${TMP_DIR}/script-wizard ${BIN} +} + +## Check all supported OS: +check_os Linux +## Check each supported architectures per OS: +check_os_architecture Linux x86_64 aarch64 + +#LATEST_RELEASE=$(get_latest_version) +VERSION_LOCK_FILE=${BIN}/../.tools.lock.json +if [[ ! -f "${VERSION_LOCK_FILE}" ]]; then + fault "The version lock spec file is missing: ${VERSION_LOCK_FILE}" +fi +LOCKED_VERSION=$(version_spec script-wizard) +#echo "Latest released version: ${LATEST_RELEASE}" +if test -f ${BIN}/script-wizard; then + echo "script-wizard is already installed: ${BIN}/script-wizard" + INSTALLED_VERSION=$(${BIN}/script-wizard --version | cut -d ' ' -f 2) + echo "script-wizard version (installed): ${INSTALLED_VERSION}" + echo "Required locked version: ${LOCKED_VERSION}" + if [[ "$INSTALLED_VERSION" == "$LOCKED_VERSION" ]]; then + echo "You already have the required version of script-wizard installed." + exit 0 + else + echo "Re-installing required version ..." + fi +fi + +echo +echo "This utility can automatically install a required helper tool called script-wizard." +echo "See https://github.com/enigmacurry/script-wizard" +echo +${BIN}/confirm yes 'Do you wish to automatically install script-wizard into `_scripts/script-wizard`' "?" || fault "OK, then you must download/build script-wizard and put it into the _scripts directory yourself." +echo "## Running script-wizard installer ..." +echo "## See https://github.com/enigmacurry/script-wizard" + +download "${LOCKED_VERSION}" +echo "Installed new script-wizard version: $(${BIN}/script-wizard --version | cut -d ' ' -f 2) (${BIN}/script-wizard)" +echo diff --git a/_scripts/instance b/_scripts/instance index 3b8b5831..af6d7a45 100755 --- a/_scripts/instance +++ b/_scripts/instance @@ -8,17 +8,18 @@ ROOT_ENV_FILE=".env_${DOCKER_CONTEXT}" valid_regex='^[a-zA-Z][a-zA-Z0-9_-]*$' if [[ "${showmenu}" == "true" ]]; then - EXISTING_ENVS=$(ls .env_${DOCKER_CONTEXT}_* 2>/dev/null | sed "s/.env_${DOCKER_CONTEXT}_//" || true) - if [[ -n "$EXISTING_ENVS" ]]; then - echo "Existing environments:" - echo "${EXISTING_ENVS}" - echo "" - fi + readarray -t EXISTING_ENVS < <(ls .env_${DOCKER_CONTEXT}_* 2>/dev/null | sed "s/.env_${DOCKER_CONTEXT}_//" || true) + # if [[ -n "$EXISTING_ENVS" ]]; then + # echo "Existing environments:" + # echo "${EXISTING_ENVS}" + # echo "" + # fi while [[ -z "${newinstance}" ]]; do - if [[ call == "switch" ]]; then - read -p "Enter an instance name to switch to"$'\x0a: ' newinstance + if [[ "${call}" == "switch" ]]; then + newinstance=$(eval wizard choose "'Enter an instance to switch to'" ${EXISTING_ENVS[@]@Q}) else - read -p "Enter an instance name to create/edit"$'\x0a: ' newinstance + suggestions=$(jq -c -n '$ARGS.positional | unique' --args -- ${EXISTING_ENVS[@]}) + newinstance=$(wizard ask "Enter an instance name to create/edit:" --suggestions "${suggestions}") fi if ! [[ "${newinstance}" =~ $valid_regex ]]; then error "Invalid instance name. Try again." diff --git a/_scripts/reconfigure_ask b/_scripts/reconfigure_ask index 3546d004..0264250f 100755 --- a/_scripts/reconfigure_ask +++ b/_scripts/reconfigure_ask @@ -37,7 +37,7 @@ if [[ "${#example}" -ne "0" ]]; then example=" (eg. ${example})" fi while true; do - read -e -p "${var}: ${prompt}${example}"$'\x0a: ' -i "${default}" answer + read -e -p "${var}: ${prompt}${example}"$'\x0a\e[32m:\e[0m ' -i "${default}" answer if [[ -n ${answer} || -z ${default} || "${ALLOW_BLANK}" == 1 ]]; then break fi diff --git a/_scripts/reconfigure_auth b/_scripts/reconfigure_auth new file mode 100755 index 00000000..b8368141 --- /dev/null +++ b/_scripts/reconfigure_auth @@ -0,0 +1,63 @@ +#!/bin/bash + +## reconfigure_auth VAR_PREFIX +## Subsume all the authentication and authorization configurations in 1 script + +BIN=$(dirname ${BASH_SOURCE}) +source ${BIN}/funcs.sh +set -eo pipefail + +ENV_FILE=$1 +VAR_PREFIX=$2 +export __D_RY_CONFIG_ENTRY=reconfigure_auth + +## note: should we add comments to reconfigure_htpasswd and reconfigure_oauth2 indicating they were designed to be called by this script, not independently? + +unset_oauth2() { + ${BIN}/reconfigure ${ENV_FILE} "${VAR_PREFIX}_OAUTH2"= +} +unset_http_auth() { + ${BIN}/reconfigure ${ENV_FILE} "${VAR_PREFIX}_HTTP_AUTH"= + TMP_PASSWORD=$(mktemp) + if [[ -f passwords.json ]]; then + cat passwords.json | jq "del(.\"${CONTEXT_INSTANCE}\")" > ${TMP_PASSWORD} && mv ${TMP_PASSWORD} passwords.json + fi +} + +prompt="Do you want to enable sentry authentication in front of this app (effectively making the entire site private)?" +options=("No" "Yes, with HTTP Basic Authentication" "Yes, with Oauth2") +## Set default based on current auth settings in ENV_FILE + +set +e +http_auth=$(${BIN}/dotenv -f ${ENV_FILE} get ${VAR_PREFIX}_HTTP_AUTH) +if [[ "$?" != "0" ]]; then fault "could not read ${VAR_PREFIX}_HTTP_AUTH from env file"; fi +oauth2_auth=$(${BIN}/dotenv -f ${ENV_FILE} get ${VAR_PREFIX}_OAUTH2) +if [[ "$?" != "0" ]]; then fault "could not read ${VAR_PREFIX}_OAUTH2 from env file"; fi +set -e + +if [[ -n "${http_auth}" ]]; then + default="${options[1]}" +elif [[ "${oauth2_auth}" == "true" ]]; then + default="${options[2]}" +else + default="${options[0]}" +fi +## Ask user which authentication configuration they want +choice=$(eval "${BIN}/script-wizard choose ${prompt@Q} ${options[@]@Q} --default ${default@Q}") + +## Configure authentication per user's choice +case "${choice}" in + No) + unset_oauth2 + unset_http_auth + ;; + *HTTP*) + ${BIN}/reconfigure_htpasswd ${ENV_FILE} ${VAR_PREFIX}_HTTP_AUTH default=no && unset_oauth2 + ;; + *Oauth2*) + ${BIN}/reconfigure_oauth2 ${ENV_FILE} ${VAR_PREFIX} && unset_http_auth + ;; + *) + echo "Unknown option." + exit 1 +esac diff --git a/_scripts/reconfigure_choose b/_scripts/reconfigure_choose new file mode 100755 index 00000000..4fc482a0 --- /dev/null +++ b/_scripts/reconfigure_choose @@ -0,0 +1,24 @@ +#!/bin/bash + +## reconfigure_choose ENV_FILE VAR PROMPT CHOICE... +## Shows the user a list of choices and edits the config file variable VAR with the selection + +## Example: +## ${BIN}/reconfigure_choose .env THING "choose a THING" "Thing 1" "Thing 2" "Something else" "None of the above" + +ENV_FILE="$1"; shift +var="$1"; shift +prompt="$1"; shift +choices=("$@") + +BIN=$(dirname ${BASH_SOURCE}) +source ${BIN}/funcs.sh + +check_var ENV_FILE var prompt choices + +## Make new .env if it doesn't exist: +test -f ${ENV_FILE} || cp .env-dist ${ENV_FILE} + +DEFAULT=$(${BIN}/dotenv -f "${ENV_FILE}" get "${var}") +answer=$(eval "${BIN}/script-wizard choose ${prompt@Q} ${choices[@]@Q} --default '$DEFAULT'") +${BIN}/reconfigure ${ENV_FILE} ${var}="${answer}" diff --git a/_scripts/reconfigure_dashboard b/_scripts/reconfigure_dashboard index 5a96bc43..d4b94dd0 100755 --- a/_scripts/reconfigure_dashboard +++ b/_scripts/reconfigure_dashboard @@ -6,6 +6,8 @@ BIN=$(dirname ${BASH_SOURCE}) source ${BIN}/funcs.sh set -e +export __D_RY_CONFIG_ENTRY=reconfigure_auth + ENV_FILE=../traefik/${1} shift ## Make new .env if it doesn't exist: @@ -15,7 +17,7 @@ echo "" if ${BIN}/confirm $([[ $(${BIN}/dotenv -f ${ENV_FILE} get TRAEFIK_DASHBOARD_ENTRYPOINT_ENABLED) == "true" ]] && echo yes || echo no) "Do you want to enable the Traefik dashboard" "?"; then ${BIN}/reconfigure ${ENV_FILE} TRAEFIK_DASHBOARD_ENTRYPOINT_ENABLED=true echo "It's important to protect the dashboard and so a username/password is required." - ${BIN}/reconfigure_htpasswd ${ENV_FILE} TRAEFIK_DASHBOARD_AUTH + ${BIN}/reconfigure_htpasswd ${ENV_FILE} TRAEFIK_DASHBOARD_HTTP_AUTH else ${BIN}/reconfigure ${ENV_FILE} TRAEFIK_DASHBOARD_ENTRYPOINT_ENABLED=false fi diff --git a/_scripts/reconfigure_editor b/_scripts/reconfigure_editor new file mode 100755 index 00000000..19fa8030 --- /dev/null +++ b/_scripts/reconfigure_editor @@ -0,0 +1,32 @@ +#!/bin/bash + +## reconfigure_editor ENV_FILE VAR PROMPT +## Allows user to edit a variable in the .env file using a full text editor +## Output is serialized to a JSON string. + +## Example: +## ${BIN}/reconfigure_editor .env BIOGRAPHY "Tell me about yourself" + +ENV_FILE="$1"; shift +var="$1"; shift +prompt="$1"; shift + +BIN=$(dirname ${BASH_SOURCE}) +source ${BIN}/funcs.sh + +check_var ENV_FILE var prompt + +## Make new .env if it doesn't exist: +test -f ${ENV_FILE} || cp .env-dist ${ENV_FILE} + +DEFAULT=$(${BIN}/dotenv -f "${ENV_FILE}" get "${var}") +answer=$(eval "${BIN}/script-wizard editor ${prompt@Q} --json --default '$DEFAULT'") + +## Strip everything before the first double quote " of the properly +## JSON formatted string: This fixes a bug when inquire reads the text +## coming from emacsclient "Waiting for Emacs..." and it prepended +## that to the output. +answer=$(echo "${answer}" | sed 's/^[^\"]*//') + +## Set the final answer: +${BIN}/reconfigure ${ENV_FILE} ${var}="${answer}" diff --git a/_scripts/reconfigure_header_authorization b/_scripts/reconfigure_header_authorization new file mode 100755 index 00000000..9e5aab1f --- /dev/null +++ b/_scripts/reconfigure_header_authorization @@ -0,0 +1,303 @@ +#!/bin/bash + +## reconfigure_header_authorization ENV_FILE + +BIN=$(dirname ${BASH_SOURCE}) +source ${BIN}/funcs.sh + +ENV_FILE=../traefik/${1} +shift +## Make new .env if it doesn't exist: +test -f ${ENV_FILE} || cp ../traefik/.env-dist ${ENV_FILE} + +manage_groups() { + set -eo pipefail + local TRAEFIK_HEADER_AUTHORIZATION_GROUPS=$(${BIN}/dotenv -f ${ENV_FILE} get TRAEFIK_HEADER_AUTHORIZATION_GROUPS) + readarray -t existing_groups < <(echo "${TRAEFIK_HEADER_AUTHORIZATION_GROUPS}" | jq keys | jq -r ".[]") + local GROUP + GROUP=$(eval "wizard choose 'Choose a group to manage' -- ${existing_groups[@]@Q} 'Create a new group'") + if [[ "${GROUP}" == "Create a new group" ]]; then + create_group + else + manage_group "${GROUP}" + fi +} + +manage_group() { + set -eo pipefail + local GROUP=$1 + if [[ -z "${GROUP}" ]]; then return 1; fi + while : + do + set +e + local TASK + TASK=$(wizard choose -- "Managing group ${GROUP} :: select a task ::" "Add members" "Edit members" "List all members" "Delete group") + if [[ "$?" != "0" ]]; then + return 1 + fi + case "${TASK}" in + "Add members") + (add_group_members "${GROUP}");; + "Edit members") + (edit_group_users "${GROUP}");; + "List all members") + list_group_members "${GROUP}";; + "Delete group") + wizard confirm "Do you really want to delete the group ${GROUP}?" no && delete_group "${GROUP}" && break + esac + done +} + +manage_users() { + set -eo pipefail + while : + do + local TRAEFIK_HEADER_AUTHORIZATION_GROUPS=$(${BIN}/dotenv -f ${ENV_FILE} get TRAEFIK_HEADER_AUTHORIZATION_GROUPS) + readarray -t existing_groups < <(echo "${TRAEFIK_HEADER_AUTHORIZATION_GROUPS}" | jq keys | jq -r ".[]") + local GROUP + users=() + for GROUP in ${existing_groups[@]}; do + readarray -t group_users < <(echo "${TRAEFIK_HEADER_AUTHORIZATION_GROUPS}" | jq -r ".\"${GROUP}\"[]") + users+=( "${group_users[@]}" ) + done + IFS=$'\n' users=($(sort <<<"${users[*]}" | uniq)); unset IFS; + local USER_TO_EDIT + USER_TO_EDIT="$(eval "wizard choose 'Select a user by id' 'Create a new user' -- ${users[@]@Q}")" + if [[ "$USER_TO_EDIT" == "Create a new user" ]]; then + USER_TO_EDIT=$(wizard ask "Enter the user id for the new user:") + fi + manage_user "$USER_TO_EDIT" + done +} + +manage_user(){ + set -eo pipefail + local USER_TO_EDIT=$1 + local TRAEFIK_HEADER_AUTHORIZATION_GROUPS=$(${BIN}/dotenv -f ${ENV_FILE} get TRAEFIK_HEADER_AUTHORIZATION_GROUPS) + readarray -t existing_groups < <(echo "${TRAEFIK_HEADER_AUTHORIZATION_GROUPS}" | jq keys | jq -r ".[]") + local user_groups=() + for GROUP in ${existing_groups[@]}; do + readarray -t group_users < <(echo "${TRAEFIK_HEADER_AUTHORIZATION_GROUPS}" | jq -r ".\"${GROUP}\"[]") + if element_in_array "$USER_TO_EDIT" "${group_users[@]}"; then + user_groups+=("${GROUP}") + fi + done + echo "user_groups: ${user_groups[@]}" + local user_groups_json=$(jq -c -n '$ARGS.positional | unique' --args -- "${user_groups[@]}") + local GROUP_SELECTION + GROUP_SELECTION=$(eval "wizard select 'Editing user ${USER_TO_EDIT} :: Select group membership' --default '${user_groups_json}' -- ${existing_groups[@]@Q}") + readarray -t GROUP_SELECTION < <(echo "$GROUP_SELECTION") + for GROUP in ${existing_groups[@]}; do + readarray -t group_users < <(echo "${TRAEFIK_HEADER_AUTHORIZATION_GROUPS}" | jq -r ".\"${GROUP}\"[]") + if element_in_array "${GROUP}" "${GROUP_SELECTION[@]}"; then + echo "group: $GROUP" + group_users+=("${USER_TO_EDIT}") + ## Add user to selected groups: + group_users=$(jq --compact-output --null-input '$ARGS.positional | unique' --args -- "${group_users[@]}") + TRAEFIK_HEADER_AUTHORIZATION_GROUPS=$(echo "${TRAEFIK_HEADER_AUTHORIZATION_GROUPS}" | jq --compact-output "(.[\"${GROUP}\"] = ${group_users})") + else + ## Remove user from unselected groups: + TRAEFIK_HEADER_AUTHORIZATION_GROUPS=$(echo "${TRAEFIK_HEADER_AUTHORIZATION_GROUPS}" | jq --compact-output "del(.[\"${GROUP}\"][] | select(index(\"${USER_TO_EDIT}\")))") + fi + done + echo "New config: ${TRAEFIK_HEADER_AUTHORIZATION_GROUPS}" + ${BIN}/dotenv -f ${ENV_FILE} set TRAEFIK_HEADER_AUTHORIZATION_GROUPS="${TRAEFIK_HEADER_AUTHORIZATION_GROUPS}" + echo "Wrote new TRAEFIK_HEADER_AUTHORIZATION_GROUPS to ${ENV_FILE}" +} + +list_group_members() { + set -eo pipefail + local GROUP=$1 + local TRAEFIK_HEADER_AUTHORIZATION_GROUPS=$(${BIN}/dotenv -f ${ENV_FILE} get TRAEFIK_HEADER_AUTHORIZATION_GROUPS) + echo + echo "${TRAEFIK_HEADER_AUTHORIZATION_GROUPS}" | jq -r ".\"${GROUP}\"[]" + echo +} + +add_group_members() { + set -eo pipefail + local GROUP=$1 + local TRAEFIK_HEADER_AUTHORIZATION_GROUPS=$(${BIN}/dotenv -f ${ENV_FILE} get TRAEFIK_HEADER_AUTHORIZATION_GROUPS) + readarray -t users < <(echo "${TRAEFIK_HEADER_AUTHORIZATION_GROUPS}" | jq -r ".\"${GROUP}\"[]") + echo "Enter the new user id(s) to add, one per line:" + while : + do + user_id=$(wizard ask 'Enter a user ID (press Esc or enter a blank value to finish)' --allow-blank) + if [[ $user_id == "" ]]; then + break + fi + users+=("${user_id}") + done + ## Add user IDs to group + users=$(jq --compact-output --null-input '$ARGS.positional | unique' --args -- "${users[@]}") + TRAEFIK_HEADER_AUTHORIZATION_GROUPS=$(echo "${TRAEFIK_HEADER_AUTHORIZATION_GROUPS}" | jq --compact-output "(.${GROUP} = ${users})") + ${BIN}/dotenv -f ${ENV_FILE} set TRAEFIK_HEADER_AUTHORIZATION_GROUPS="${TRAEFIK_HEADER_AUTHORIZATION_GROUPS}" + echo "Wrote new TRAEFIK_HEADER_AUTHORIZATION_GROUPS to ${ENV_FILE}" +} + +edit_group_users() { + set -eo pipefail + local GROUP=$1 + if [[ -z "${GROUP}" ]]; then return 1; fi + local TRAEFIK_HEADER_AUTHORIZATION_GROUPS=$(${BIN}/dotenv -f ${ENV_FILE} get TRAEFIK_HEADER_AUTHORIZATION_GROUPS) + readarray -t existing_users < <(echo "${TRAEFIK_HEADER_AUTHORIZATION_GROUPS}" | jq -r ".\"${GROUP}\"[]") + local membership="${existing_users[@]@Q}" + membership=$(jq -c -n '$ARGS.positional' --args ${membership}) + membership=$(eval wizard select "'Managing group ${GROUP} :: unselect users to remove from group ::'" --default "'${membership}'" --json -- ${existing_users[@]}) + TRAEFIK_HEADER_AUTHORIZATION_GROUPS=$(echo "${TRAEFIK_HEADER_AUTHORIZATION_GROUPS}" | jq --compact-output "(.${GROUP} = ${membership})") + echo TRAEFIK_HEADER_AUTHORIZATION_GROUPS=$TRAEFIK_HEADER_AUTHORIZATION_GROUPS + ${BIN}/dotenv -f ${ENV_FILE} set TRAEFIK_HEADER_AUTHORIZATION_GROUPS="${TRAEFIK_HEADER_AUTHORIZATION_GROUPS}" + echo "Wrote new TRAEFIK_HEADER_AUTHORIZATION_GROUPS to ${ENV_FILE}" +} + + +list_groups() { + set -eo pipefail + local TRAEFIK_HEADER_AUTHORIZATION_GROUPS=$(${BIN}/dotenv -f ${ENV_FILE} get TRAEFIK_HEADER_AUTHORIZATION_GROUPS) + if [[ $TRAEFIK_HEADER_AUTHORIZATION_GROUPS == "{}" || $TRAEFIK_HEADER_AUTHORIZATION_GROUPS == "" ]]; then + echo "No authorization groups have been defined yet." + return 0 + fi + + echo "Here is a list of your current authorization groups and their users:" + echo + echo "${TRAEFIK_HEADER_AUTHORIZATION_GROUPS}" | jq -C . | sed 's/^/ /' +} + +create_group() { + set -eo pipefail + local GROUP + GROUP=$(wizard ask "Enter the name of the group to create:") + + ## Check if the group exists already: + local TRAEFIK_HEADER_AUTHORIZATION_GROUPS=$(${BIN}/dotenv -f ${ENV_FILE} get TRAEFIK_HEADER_AUTHORIZATION_GROUPS) + local INDEX=$(echo "$TRAEFIK_HEADER_AUTHORIZATION_GROUPS" | jq --compact-output 'keys | index("'${GROUP}'")') + if [[ $INDEX != "null" ]]; then + echo "Group ${GROUP} already exists." + return 0 + fi + + users=() + if wizard confirm "Do you want to add users to this group now?" no; then + echo "Enter the new user id(s) to add, one per line:" + while : + do + user_id=$(wizard ask "Enter a user ID (Press Esc or enter a blank value to finish)" --allow-blank) + if [[ $user_id == "" ]]; then + break + fi + users+=("${user_id}") + done + fi + + ## Add the new group: + users=$(jq --compact-output --null-input '$ARGS.positional | unique' --args -- "${users[@]}") + local TRAEFIK_HEADER_AUTHORIZATION_GROUPS=$(echo "${TRAEFIK_HEADER_AUTHORIZATION_GROUPS}" | jq --compact-output "(.${GROUP} = ${users})") + ${BIN}/dotenv -f ${ENV_FILE} set TRAEFIK_HEADER_AUTHORIZATION_GROUPS="${TRAEFIK_HEADER_AUTHORIZATION_GROUPS}" + echo "Wrote new TRAEFIK_HEADER_AUTHORIZATION_GROUPS to ${ENV_FILE}" + +} + +delete_group() { + set -eo pipefail + local GROUP=$1 + if [[ -z "${GROUP}" ]]; then return 1; fi + local TRAEFIK_HEADER_AUTHORIZATION_GROUPS=$(${BIN}/dotenv -f ${ENV_FILE} get TRAEFIK_HEADER_AUTHORIZATION_GROUPS) + ## Delete group + TRAEFIK_HEADER_AUTHORIZATION_GROUPS=$(echo "${TRAEFIK_HEADER_AUTHORIZATION_GROUPS}" | jq --compact-output "del(.${GROUP})") + ${BIN}/dotenv -f ${ENV_FILE} set TRAEFIK_HEADER_AUTHORIZATION_GROUPS="${TRAEFIK_HEADER_AUTHORIZATION_GROUPS}" + echo "Wrote new TRAEFIK_HEADER_AUTHORIZATION_GROUPS to ${ENV_FILE}" +} + +check_does_traefik_need_restart() { + local NEW_AUTHORIZATION_GROUPS=$(${BIN}/dotenv -f ${ENV_FILE} get TRAEFIK_HEADER_AUTHORIZATION_GROUPS) + local EXISTING_AUTHORIZATION_GROUPS=$(docker inspect traefik-traefik-1 | jq -r '.[0].Config.Labels."TRAEFIK_HEADER_AUTHORIZATION_GROUPS"') + if [[ "$EXISTING_AUTHORIZATION_GROUPS" != "$NEW_AUTHORIZATION_GROUPS" ]]; then + if ${BIN}/confirm no "Config change detected. Do you want to restart Traefik now" "?"; then + make install + else + echo 'Remember to restart Traefik (`make install`) for group changes to take effect!' + fi + fi +} + +list_middlewares() { + set -eo pipefail + exe docker compose --env-file=${ENV_FILE} exec -it traefik /bin/sh -c "cat /data/config/dynamic/header-authorization.yml" + echo + echo '## REMEMBER: if you have not yet re-run `make install` for Traefik,' + echo '## your live configration may be out sync with your .env file.' +} + +list_callback_urls() { + set -eo pipefail + readarray -t projects < <(docker compose ls -q) + local DOCKER_CONTEXT=$(docker context ls --format "{{.Current}} {{.Name}}" | grep -oP "^true \K.*") + local ROOT_ENV_FILE=${ROOT_DIR}/.env_${DOCKER_CONTEXT} + https_port=$(${BIN}/dotenv -f ${ROOT_ENV_FILE} get PUBLIC_HTTPS_PORT) + if [[ -z "$https_port" ]]; then + fault "Could not get PUBLIC_HTTPS_PORT from root .env file" + fi + domains=() + echo "## This is the list of OAuth2 callback URLs for all deployed apps:" + echo "## If you installed traefik-forward-auth in AUTH HOST mode (default) you don't need this, otherwise keep reading:" + echo "## You need to copy/paste these into your Gitea OAuth2 application 'Redirect URIs' setting." + echo "## Generate this list again with this command: d.rymcg.tech make traefik sentry-callback" + echo "## Collecting callback URLs for all deployed applications ..." + echo "" + for project in ${projects[@]}; do + readarray -t services < <(docker compose -p "$project" ps --services -q) + for service in ${services[@]}; do + env_file=$(docker inspect "${service}" | jq -r '.[0].Config.Labels["com.docker.compose.project.environment_file"]') + if [[ -n "${env_file}" ]] && [[ "${env_file}" != "null" ]] && [[ -f "${env_file}" ]]; then + traefik_host=$(grep -E "^.*_TRAEFIK_HOST" "${env_file}" | head -1 | cut -d '=' -f 2) + if [[ -n "${traefik_host}" ]]; then + domains+=("${traefik_host}") + fi + fi + done + done + IFS=$'\n' domains=($(sort <<<"${domains[*]}" | uniq)); unset IFS; + for domain in ${domains[@]}; do + if [[ "${https_port}" == "443" ]]; then + echo "https://${domain}/_oauth" + else + echo "https://${domain}:${https_port}/_oauth" + fi + done + echo "" +} + +CMD=$1; shift; +if [[ -z "$CMD" ]]; then + while : + do + echo "" + set +e + TASK=$(wizard choose "Sentry Authorization Manager (main menu):" "Group Manager" "User Manager" "List all members" "List authorized callback URLs" "Quit") + if [[ $? != 0 ]]; then + check_does_traefik_need_restart + exit 0 + fi + case "${TASK}" in + "Group Manager") + (manage_groups);; + "User Manager") + (manage_users);; + "List all members") + (list_groups);; + "List authorized callback URLs") + (list_callback_urls || fault "Error");; + "Quit") + check_does_traefik_need_restart + exit 0;; + esac + done +else + case "${CMD}" in + "list-callback-urls") + (list_callback_urls || fault "Error");; + *) + fault "Invalid command";; + esac +fi diff --git a/_scripts/reconfigure_htpasswd b/_scripts/reconfigure_htpasswd index 4f0644a2..eeb9cf2f 100755 --- a/_scripts/reconfigure_htpasswd +++ b/_scripts/reconfigure_htpasswd @@ -9,6 +9,12 @@ BIN=$(dirname ${BASH_SOURCE}) source ${BIN}/funcs.sh set -e +if [[ ${__D_RY_CONFIG_ENTRY} != reconfigure_auth ]]; then + echo "\`${BASH_SOURCE}\` was designed to be called from \`reconfigure_auth\`." + echo + exit 1 +fi + ENV_FILE=${1} shift @@ -58,7 +64,9 @@ enable_http_basic_authentication() { disable_http_basic_authentication() { ${BIN}/reconfigure ${ENV_FILE} ${VAR}= TMP_PASSWORD=$(mktemp) - cat passwords.json | jq "del(.\"${CONTEXT_INSTANCE}\")" > ${TMP_PASSWORD} && mv ${TMP_PASSWORD} passwords.json + if [[ -f passwords.json ]]; then + cat passwords.json | jq "del(.\"${CONTEXT_INSTANCE}\")" > ${TMP_PASSWORD} && mv ${TMP_PASSWORD} passwords.json + fi exit 0 } @@ -74,9 +82,5 @@ if [[ ${1} =~ ^default= ]]; then if [[ ${1} =~ ^default=yes$ ]] || [[ -n "$(${BIN}/dotenv -f ${ENV_FILE} get ${VAR})" ]]; then DEFAULT_ENABLED_AUTH=yes fi - echo "" - echo "This configuration has optional HTTP Basic Authentication, provided by Traefik." - echo "This authentication is applied *before* any other authentication that the application itself may provide." - ${BIN}/confirm ${DEFAULT_ENABLED_AUTH} "Do you want to require Traefik HTTP Basic Authentication" "?" || disable_http_basic_authentication fi enable_http_basic_authentication diff --git a/_scripts/reconfigure_oauth2 b/_scripts/reconfigure_oauth2 index 25f705df..480e356a 100755 --- a/_scripts/reconfigure_oauth2 +++ b/_scripts/reconfigure_oauth2 @@ -1,26 +1,33 @@ #!/bin/bash -## reconfigure_oauth2 VAR [default=yes|no] +## reconfigure_oauth2 VAR_PREFIX ## If no default given, unconditionally set the Oauth2 environment variable VAR via wizard. -## If the default is given, the user will be asked if they want oaut2h2 or not, defaulting to the yes or no supplied. +## If the default is given, the user will be asked if they want Oauth2 or not, defaulting to the yes or no supplied. BIN=$(dirname ${BASH_SOURCE}) source ${BIN}/funcs.sh set -e +DOCKER_CONTEXT=$(docker context ls --format "{{.Current}} {{.Name}}" | grep -oP "^true \K.*") + +if [[ ${__D_RY_CONFIG_ENTRY} != reconfigure_auth ]]; then + echo "\`${BASH_SOURCE}\` was designed to be called from \`reconfigure_auth\`." + echo + exit 1 +fi ENV_FILE=${1} shift -VAR=${1} +VAR_PREFIX=${1} shift enable_oauth2() { - ${BIN}/reconfigure ${ENV_FILE} ${VAR}=yes + ${BIN}/reconfigure ${ENV_FILE} ${VAR_PREFIX}_OAUTH2=true } disable_oauth2() { - ${BIN}/reconfigure ${ENV_FILE} ${VAR}=no + ${BIN}/reconfigure ${ENV_FILE} ${VAR_PREFIX}_OAUTH2= exit 0 } @@ -30,14 +37,22 @@ test -f ${ENV_FILE} || cp .env-dist ${ENV_FILE} CONTEXT_INSTANCE=$(basename $ENV_FILE | sed 's/.env_//') -if [[ ${1} =~ ^default= ]]; then - DEFAULT_ENABLED_OAUTH2=no - if [[ ${1} =~ ^default=yes$ ]] || [[ $(${BIN}/dotenv -f ${ENV_FILE} get ${VAR}) == "yes" ]]; then - DEFAULT_ENABLED_OAUTH2=yes - fi - echo "" - echo "Using OpenID/OAuth2 will require a login to access your app, but it will not affect what a successfully logged-in person can do in your app. If your app has a built-in authorization mechanism that can check for the user header that traefik-forward-auth sends, then your app can limit what the logged-in person can do in the app. But if your app can't check the user header, or if your app doesn't have built-in authorization at all, then any person with an account on your Gitea server can log into your app and have full acces" - #$(${BIN}/confirm $$([[ $$(${BIN}/dotenv -f ${ENV_FILE} get ${VAR}) == "true" ]] && echo "yes" || echo "no") "Do you want to enable OpenID/OAuth2" "?" && ${BIN}/reconfigure ${ENV_FILE} ${VAR}=true || ${BIN}/reconfigure ${ENV_FILE} ${VAR}=false) - ${BIN}/confirm ${DEFAULT_ENABLED_OAUTH2} "Do you want to enable OpenID/OAuth2" "?" || disable_oauth2 -fi +docker inspect traefik-forward-auth >/dev/null || (echo "traefik-forward-auth is not installed. Follow the directions at https://github.com/EnigmaCurry/d.rymcg.tech/tree/master/traefik-forward-auth#readme and then retry this" && exit 1) + + +## Update var in .env* file enable_oauth2 + +echo + +## header-authorization-group + +## Get list of configured groups +TRAEFIK_HEADER_AUTHORIZATION_GROUPS=$(${BIN}/dotenv -f ../traefik/.env_${DOCKER_CONTEXT}_default get TRAEFIK_HEADER_AUTHORIZATION_GROUPS) +if [[ $TRAEFIK_HEADER_AUTHORIZATION_GROUPS == "" ]]; then + TRAEFIK_HEADER_AUTHORIZATION_GROUPS="{}" +fi +groups=($(jq -r '. | keys[]' <<< ${TRAEFIK_HEADER_AUTHORIZATION_GROUPS})) +## Ask user which header authorization group to permit access to this app +authorized_group=$(${BIN}/dotenv -f ${ENV_FILE} get ${VAR_PREFIX}_OAUTH2_AUTHORIZED_GROUP) +${BIN}/reconfigure_choose ${ENV_FILE} ${VAR_PREFIX}_OAUTH2_AUTHORIZED_GROUP "Which authorization group do you want to permit access to this app?" ${groups[@]} diff --git a/_scripts/reconfigure_password b/_scripts/reconfigure_password index 0cb60253..7e630034 100755 --- a/_scripts/reconfigure_password +++ b/_scripts/reconfigure_password @@ -19,7 +19,7 @@ fi if [[ -z "$(${BIN}/dotenv -f ${ENV_FILE} get ${PASSWORD_VAR})" ]]; then - PASSWORD="$(openssl rand -base64 ${LENGTH} | tr '=' '0' | tr '+' '0' | tr '/' '0' | tr '\n' '0' | head -c ${LENGTH})" + PASSWORD="$(gen_password ${LENGTH})" ${BIN}/reconfigure ${ENV_FILE} ${PASSWORD_VAR}="${PASSWORD}" else echo "# Using existing password for ${PASSWORD_VAR}" diff --git a/_scripts/reconfigure_select b/_scripts/reconfigure_select new file mode 100755 index 00000000..0dfcc2cb --- /dev/null +++ b/_scripts/reconfigure_select @@ -0,0 +1,25 @@ +#!/bin/bash + +## reconfigure_select ENV_FILE VAR PROMPT CHOICE... +## Shows the user a multiple select UI and edits the config file variable VAR with a JSON list of all the selections + +## Example: +## ${BIN}/reconfigure_select .env THINGS "select many THINGS" "Thing 1" "Thing 2" "Something else" + +ENV_FILE="$1"; shift +var="$1"; shift +prompt="$1"; shift +selections=("$@") + +BIN=$(dirname ${BASH_SOURCE}) +source ${BIN}/funcs.sh + +check_var ENV_FILE var prompt selections + +## Make new .env if it doesn't exist: +test -f ${ENV_FILE} || cp .env-dist ${ENV_FILE} + +DEFAULT=$(${BIN}/dotenv -f "${ENV_FILE}" get "${var}") + +answer=$(eval "${BIN}/script-wizard select --json ${prompt@Q} ${selections[@]@Q} --default '$DEFAULT'") +${BIN}/reconfigure ${ENV_FILE} ${var}="${answer}" diff --git a/_scripts/version_spec b/_scripts/version_spec new file mode 120000 index 00000000..fd4f62d6 --- /dev/null +++ b/_scripts/version_spec @@ -0,0 +1 @@ +run_func \ No newline at end of file diff --git a/archivebox/.env-dist b/archivebox/.env-dist index 7f38102c..17665b67 100644 --- a/archivebox/.env-dist +++ b/archivebox/.env-dist @@ -3,6 +3,23 @@ ARCHIVEBOX_INSTANCE= ## htpasswd encoded username/passwords: ARCHIVEBOX_HTTP_AUTH= +# OAUTH2 +# Set to `true` to use OpenID/OAuth2 authentication via the +# traefik-forward-auth service in d.rymcg.tech. +# Using OpenID/OAuth2 will require login to access your app, +# but it will not affect what a successfully logged-in person can do in your +# app. If your app has built-in authentication and can check the user +# header that traefik-forward-auth sends, then your app can limit what the +# logged-in person can do in the app. But if your app can't check the user +# header, or if your app doesn't have built-in authentication at all, then +# any person with an account on your Gitea server can log into your app and +# have full access. +ARCHIVEBOX_OAUTH2= +# In addition to Oauth2 authentication, you can configure basic authorization +# by entering which authorization group can log into your app. You create +# groups of email addresses in the `traefik` folder by running `make groups`. +ARCHIVEBOX_OAUTH2_AUTHORIZED_GROUP= + ## Create a secure secret passphrase used for hashing URLs: SECRET_KEY=this_is_insecure_change_me_right_now diff --git a/archivebox/Makefile b/archivebox/Makefile index 8bfa3cac..f44cca60 100644 --- a/archivebox/Makefile +++ b/archivebox/Makefile @@ -6,11 +6,11 @@ include ${ROOT_DIR}/_scripts/Makefile.instance config-hook: @${BIN}/reconfigure ${ENV_FILE} ARCHIVEBOX_INSTANCE=${instance} @${BIN}/reconfigure_ask ${ENV_FILE} ARCHIVEBOX_TRAEFIK_HOST "Enter the archivebox domain name" archivebox${INSTANCE_URL_SUFFIX}.${ROOT_DOMAIN} - @CONTEXT_INSTANCE=${CONTEXT_INSTANCE} ${BIN}/reconfigure_htpasswd ${ENV_FILE} ARCHIVEBOX_HTTP_AUTH @${BIN}/reconfigure ${ENV_FILE} SECRET_KEY=$$(openssl rand -hex 45) @${BIN}/reconfigure ${ENV_FILE} ARCHIVEBOX_USERNAME=admin @${BIN}/reconfigure ${ENV_FILE} ARCHIVEBOX_EMAIL=admin@localhost @${BIN}/reconfigure ${ENV_FILE} ARCHIVEBOX_PASSWORD=$$(openssl rand -hex 30) + @${BIN}/reconfigure_auth ${ENV_FILE} ARCHIVEBOX .PHONY: admin # Create admin account admin: @@ -21,3 +21,17 @@ api-dev: make build @docker-compose --env-file=${ENV_FILE} stop public-api-gateway @docker-compose --env-file=${ENV_FILE} run --rm public-api-gateway + +.PHONY: override-hook +override-hook: +#### This sets the override template variables for docker-compose.instance.yaml: +#### The template dynamically renders to docker-compose.override_{DOCKER_CONTEXT}_{INSTANCE}.yaml +#### These settings are used to automatically generate the service container labels, and traefik config, inside the template. +#### The variable arguments have three forms: `=` `=:` `=@` +#### name=VARIABLE_NAME # sets the template 'name' field to the value of VARIABLE_NAME found in the .env file +#### # (this hardcodes the value into docker-compose.override.yaml) +#### name=:VARIABLE_NAME # sets the template 'name' field to the literal string 'VARIABLE_NAME' +#### # (this hardcodes the string into docker-compose.override.yaml) +#### name=@VARIABLE_NAME # sets the template 'name' field to the literal string '${VARIABLE_NAME}' +#### # (used for regular docker-compose expansion of env vars by name.) + @${BIN}/docker_compose_override ${ENV_FILE} project=:archivebox instance=@ARCHIVEBOX_INSTANCE traefik_host=@ARCHIVEBOX_TRAEFIK_HOST http_auth=ARCHIVEBOX_HTTP_AUTH http_auth_var=@ARCHIVEBOX_HTTP_AUTH ip_sourcerange=@ARCHIVEBOX_IP_SOURCERANGE oauth2=ARCHIVEBOX_OAUTH2 authorized_group=ARCHIVEBOX_OAUTH2_AUTHORIZED_GROUP diff --git a/archivebox/docker-compose.instance.yaml b/archivebox/docker-compose.instance.yaml new file mode 100644 index 00000000..cc9ef749 --- /dev/null +++ b/archivebox/docker-compose.instance.yaml @@ -0,0 +1,51 @@ +#! This is a ytt template file for docker-compose.override.yaml +#! References: +#! https://carvel.dev/ytt +#! https://docs.docker.com/compose/extends/#adding-and-overriding-configuration +#! https://github.com/enigmacurry/d.rymcg.tech#overriding-docker-composeyaml-per-instance + +#! ### Standard project vars: +#@ load("@ytt:data", "data") +#@ project = data.values.project +#@ instance = data.values.instance +#@ context = data.values.context +#@ traefik_host = data.values.traefik_host +#@ ip_sourcerange = data.values.ip_sourcerange +#@ enable_http_auth = len(data.values.http_auth.strip()) > 0 +#@ http_auth = data.values.http_auth_var +#@ enable_oauth2 = data.values.oauth2 == "true" +#@ authorized_group = data.values.authorized_group +#@ enabled_middlewares = [] + +#@yaml/text-templated-strings +services: + archivebox: + #@ service = "archivebox" + labels: + #! Services must opt-in to be proxied by Traefik: + - "traefik.enable=true" + + #! 'router' is the fully qualified key in traefik for this router/service: project + instance + service + #@ router = "{}-{}-{}".format(project,instance,service) + + #! The host matching router rule: + - "traefik.http.routers.(@= router @).rule=Host(`(@= traefik_host @)`)" + - "traefik.http.routers.(@= router @).entrypoints=websecure" + #@ enabled_middlewares.append("{}-ipwhitelist".format(router)) + - "traefik.http.middlewares.(@= router @)-ipwhitelist.ipwhitelist.sourcerange=(@= ip_sourcerange @)" + + #@ if enable_http_auth: + #@ enabled_middlewares.append("{}-basicauth".format(router)) + - "traefik.http.middlewares.(@= router @)-basicauth.basicauth.users=(@= http_auth @)" + #@ end + + #@ if enable_oauth2: + #@ enabled_middlewares.append("traefik-forward-auth@docker") + #@ enabled_middlewares.append("header-authorization-group-{}@file".format(authorized_group)) + #@ end + + #! Override the default port that archivebox binds to: + - "traefik.http.services.(@= router @).loadbalancer.server.port=8000" + + #! Apply all middlewares (do this at the end!) + - "traefik.http.routers.(@= router @).middlewares=(@= ','.join(enabled_middlewares) @)" diff --git a/archivebox/docker-compose.yaml b/archivebox/docker-compose.yaml index fe3128db..1b865620 100644 --- a/archivebox/docker-compose.yaml +++ b/archivebox/docker-compose.yaml @@ -35,17 +35,7 @@ services: volumes: - data:/data restart: unless-stopped - labels: - - "traefik.enable=true" - - "traefik.http.routers.archivebox-${ARCHIVEBOX_INSTANCE:-default}.rule=Host(`${ARCHIVEBOX_TRAEFIK_HOST}`)" - - "traefik.http.routers.archivebox-${ARCHIVEBOX_INSTANCE:-default}.entrypoints=websecure" - - "traefik.http.routers.archivebox-${ARCHIVEBOX_INSTANCE:-default}.service=archivebox-${ARCHIVEBOX_INSTANCE:-default}" - - "traefik.http.services.archivebox-${ARCHIVEBOX_INSTANCE:-default}.loadbalancer.server.port=8000" - ## Authentication: - - "traefik.http.middlewares.archivebox-${ARCHIVEBOX_INSTANCE:-default}-auth.basicauth.users=${ARCHIVEBOX_HTTP_AUTH}" - - "traefik.http.middlewares.archivebox-${ARCHIVEBOX_INSTANCE:-default}-auth.basicauth.removeHeader=true" - - "traefik.http.middlewares.archivebox-${ARCHIVEBOX_INSTANCE:-default}-ipwhitelist.ipwhitelist.sourcerange=${ARCHIVEBOX_IP_SOURCERANGE}" - - "traefik.http.routers.archivebox-${ARCHIVEBOX_INSTANCE:-default}.middlewares=archivebox-${ARCHIVEBOX_INSTANCE:-default}-ipwhitelist,archivebox-${ARCHIVEBOX_INSTANCE:-default}-auth" + labels: [] public-api-gateway: build: diff --git a/audiobookshelf/.env-dist b/audiobookshelf/.env-dist index 62bb3593..6898292d 100644 --- a/audiobookshelf/.env-dist +++ b/audiobookshelf/.env-dist @@ -11,7 +11,7 @@ AUDIOBOOKSHELF_VERSION=2.2.18 AUDIOBOOKSHELF_INSTANCE= # OAUTH2 -# Set to `yes` to use OpenID/OAuth2 authentication via the +# Set to `true` to use OpenID/OAuth2 authentication via the # traefik-forward-auth service in d.rymcg.tech. # Using OpenID/OAuth2 will require login to access your app, # but it will not affect what a successfully logged-in person can do in your @@ -21,7 +21,11 @@ AUDIOBOOKSHELF_INSTANCE= # header, or if your app doesn't have built-in authentication at all, then # any person with an account on your Gitea server can log into your app and # have full access. -AUDIOBOOKSHELF_OAUTH2=no +AUDIOBOOKSHELF_OAUTH2= +# In addition to Oauth2 authentication, you can configure basic authorization +# by entering which authorization group can log into your app. You create +# groups of email addresses in the `traefik` folder by running `make groups`. +AUDIOBOOKSHELF_OAUTH2_AUTHORIZED_GROUP= # Filter access by IP address source range (CIDR): # See: https://doc.traefik.io/traefik/middlewares/http/ipwhitelist/ @@ -30,3 +34,5 @@ AUDIOBOOKSHELF_OAUTH2=no ## Allow all access: #AUDIOBOOKSHELF_IP_SOURCERANGE="0.0.0.0/0" AUDIOBOOKSHELF_IP_SOURCERANGE="0.0.0.0/0" + +AUDIOBOOKSHELF_HTTP_AUTH= diff --git a/audiobookshelf/Makefile b/audiobookshelf/Makefile index 1a54bb6c..fde083d2 100644 --- a/audiobookshelf/Makefile +++ b/audiobookshelf/Makefile @@ -6,7 +6,7 @@ include ${ROOT_DIR}/_scripts/Makefile.instance config-hook: @${BIN}/reconfigure_ask ${ENV_FILE} AUDIOBOOKSHELF_TRAEFIK_HOST "Enter the website domain name" audiobookshelf${INSTANCE_URL_SUFFIX}.${ROOT_DOMAIN} @${BIN}/reconfigure ${ENV_FILE} AUDIOBOOKSHELF_INSTANCE=${instance} - @${BIN}/reconfigure_oauth2 ${ENV_FILE} AUDIOBOOKSHELF_OAUTH2 default=$$( ${BIN}/dotenv -f ${ENV_FILE} get AUDIOBOOKSHELF_OAUTH2 ) + @${BIN}/reconfigure_auth ${ENV_FILE} AUDIOBOOKSHELF @echo "" .PHONY: override-hook @@ -21,7 +21,7 @@ override-hook: #### # (this hardcodes the string into docker-compose.override.yaml) #### name=@VARIABLE_NAME # sets the template 'name' field to the literal string '${VARIABLE_NAME}' #### # (used for regular docker-compose expansion of env vars by name.) - @${BIN}/docker_compose_override ${ENV_FILE} project=:audiobookshelf instance=@AUDIOBOOKSHELF_INSTANCE traefik_host=@AUDIOBOOKSHELF_TRAEFIK_HOST http_auth=AUDIOBOOKSHELF_HTTP_AUTH http_auth_var=@AUDIOBOOKSHELF_HTTP_AUTH ip_sourcerange=@AUDIOBOOKSHELF_IP_SOURCERANGE oauth2=AUDIOBOOKSHELF_OAUTH2 + @${BIN}/docker_compose_override ${ENV_FILE} project=:audiobookshelf instance=@AUDIOBOOKSHELF_INSTANCE traefik_host=@AUDIOBOOKSHELF_TRAEFIK_HOST http_auth=AUDIOBOOKSHELF_HTTP_AUTH http_auth_var=@AUDIOBOOKSHELF_HTTP_AUTH ip_sourcerange=@AUDIOBOOKSHELF_IP_SOURCERANGE oauth2=AUDIOBOOKSHELF_OAUTH2 authorized_group=AUDIOBOOKSHELF_OAUTH2_AUTHORIZED_GROUP .PHONY: shell # Enter container shell diff --git a/audiobookshelf/README.md b/audiobookshelf/README.md index ee12e552..a31bb8da 100644 --- a/audiobookshelf/README.md +++ b/audiobookshelf/README.md @@ -11,19 +11,28 @@ Run: make config ``` -This will ask you to enter the domain name to use, and whether or not -you want to configure a username/password via HTTP Basic Authentication. +This will ask you to enter the domain name to use. It automatically saves your responses into the configuration file `.env_{INSTANCE}`. -It will also ask if you want to use OpenID/OAuth2 authentication. -Using OpenID/OAuth2 will require a login to access your app, but it will not -affect what a successfully logged-in person can do in your app. If your app has -a built-in authorization mechanism that can check for the user header that -traefik-forward-auth sends, then your app can limit what the logged-in person -can do in the app. But if your app can't check the user header, or if your app -doesn't have built-in authorization at all, then any person with an account -on your Gitea server can log into your app and have full acces +### Authentication and Authorization + +Running `make config` will ask whether or not you want to configure +authentication for your app (on top of any authentication your app provides). +You can configure OpenID/OAuth2 or HTTP Basic Authentication. + +OAuth2 uses traefik-forward-auth to delegate authentication to an external +authority (eg. a self-deployed Gitea instance). Accessing this app will +require all users to login through that external service first. Once +authenticated, they may be authorized access only if their login id matches the +member list of the predefined authorization group configured for the app +(`AUDIOBOOKSHELF_OAUTH2_AUTHORIZED_GROUP`). Authorization groups are defined in the +Traefik config (`TRAEFIK_HEADER_AUTHORIZATION_GROUPS`) and can be +[created/modified](https://github.com/EnigmaCurry/d.rymcg.tech/blob/master/traefik/README.md#oauth2-authentication) +by running `make groups` in the `traefik` directory. + +For HTTP Basic Authentication, you will be prompted to enter username/password +logins which are stored in that app's `.env_{INSTANCE}` file. ## Install diff --git a/audiobookshelf/docker-compose.instance.yaml b/audiobookshelf/docker-compose.instance.yaml index 04cc1429..592e5af9 100644 --- a/audiobookshelf/docker-compose.instance.yaml +++ b/audiobookshelf/docker-compose.instance.yaml @@ -11,7 +11,8 @@ #@ context = data.values.context #@ traefik_host = data.values.traefik_host #@ ip_sourcerange = data.values.ip_sourcerange -#@ enable_oauth2 = data.values.oauth2 +#@ enable_oauth2 = data.values.oauth2 == "true" +#@ authorized_group = data.values.authorized_group #@ enabled_middlewares = [] #@yaml/text-templated-strings @@ -29,8 +30,9 @@ services: #@ enabled_middlewares.append("{}-ipwhitelist".format(router)) - "traefik.http.middlewares.(@= router @)-ipwhitelist.ipwhitelist.sourcerange=(@= ip_sourcerange @)" - #@ if enable_oauth2 == "yes": + #@ if enable_oauth2: #@ enabled_middlewares.append("traefik-forward-auth@docker") + #@ enabled_middlewares.append("header-authorization-group-{}@file".format(authorized_group)) #@ end #! Apply all middlewares (do this at the end!) diff --git a/baikal/.env-dist b/baikal/.env-dist index 47bc8569..474f6e83 100644 --- a/baikal/.env-dist +++ b/baikal/.env-dist @@ -8,3 +8,8 @@ BAIKAL_INSTANCE= ##Disallow all access: 0.0.0.0/32 ##Allow all access: 0.0.0.0/0 BAIKAL_IP_SOURCERANGE=0.0.0.0/0 + +BAIKAL_HTTP_AUTH= +BAIKAL_IP_SOURCERANGE=0.0.0.0/0 +BAIKAL_OAUTH2= +BAIKAL_OAUTH2_AUTHORIZED_GROUP= diff --git a/baikal/Makefile b/baikal/Makefile index e8e9d92b..9ea34dba 100644 --- a/baikal/Makefile +++ b/baikal/Makefile @@ -6,4 +6,19 @@ include ${ROOT_DIR}/_scripts/Makefile.instance config-hook: @${BIN}/reconfigure ${ENV_FILE} BAIKAL_INSTANCE=${instance} @${BIN}/reconfigure_ask ${ENV_FILE} BAIKAL_TRAEFIK_HOST "Enter the baikal domain name" cal${INSTANCE_URL_SUFFIX}.${ROOT_DOMAIN} + @${BIN}/reconfigure_auth ${ENV_FILE} BAIKAL + +.PHONY: override-hook +override-hook: +#### This sets the override template variables for docker-compose.instance.yaml: +#### The template dynamically renders to docker-compose.override_{DOCKER_CONTEXT}_{INSTANCE}.yaml +#### These settings are used to automatically generate the service container labels, and traefik config, inside the template. +#### The variable arguments have three forms: `=` `=:` `=@` +#### name=VARIABLE_NAME # sets the template 'name' field to the value of VARIABLE_NAME found in the .env file +#### # (this hardcodes the value into docker-compose.override.yaml) +#### name=:VARIABLE_NAME # sets the template 'name' field to the literal string 'VARIABLE_NAME' +#### # (this hardcodes the string into docker-compose.override.yaml) +#### name=@VARIABLE_NAME # sets the template 'name' field to the literal string '${VARIABLE_NAME}' +#### # (used for regular docker-compose expansion of env vars by name.) + @${BIN}/docker_compose_override ${ENV_FILE} project=:baikal instance=@BAIKAL_INSTANCE traefik_host=@BAIKAL_TRAEFIK_HOST http_auth=BAIKAL_HTTP_AUTH http_auth_var=@BAIKAL_HTTP_AUTH ip_sourcerange=@BAIKAL_IP_SOURCERANGE oauth2=BAIKAL_OAUTH2 authorized_group=BAIKAL_OAUTH2_AUTHORIZED_GROUP diff --git a/baikal/docker-compose.instance.yaml b/baikal/docker-compose.instance.yaml new file mode 100644 index 00000000..1840442f --- /dev/null +++ b/baikal/docker-compose.instance.yaml @@ -0,0 +1,48 @@ +#! This is a ytt template file for docker-compose.override.yaml +#! References: +#! https://carvel.dev/ytt +#! https://docs.docker.com/compose/extends/#adding-and-overriding-configuration +#! https://github.com/enigmacurry/d.rymcg.tech#overriding-docker-composeyaml-per-instance + +#! ### Standard project vars: +#@ load("@ytt:data", "data") +#@ project = data.values.project +#@ instance = data.values.instance +#@ context = data.values.context +#@ traefik_host = data.values.traefik_host +#@ ip_sourcerange = data.values.ip_sourcerange +#@ enable_http_auth = len(data.values.http_auth.strip()) > 0 +#@ http_auth = data.values.http_auth_var +#@ enable_oauth2 = data.values.oauth2 == "true" +#@ authorized_group = data.values.authorized_group +#@ enabled_middlewares = [] + +#@yaml/text-templated-strings +services: + baikal: + #@ service = "baikal" + labels: + #! Services must opt-in to be proxied by Traefik: + - "traefik.enable=true" + + #! 'router' is the fully qualified key in traefik for this router/service: project + instance + service + #@ router = "{}-{}-{}".format(project,instance,service) + + #! The host matching router rule: + - "traefik.http.routers.(@= router @).rule=Host(`(@= traefik_host @)`)" + - "traefik.http.routers.(@= router @).entrypoints=websecure" + #@ enabled_middlewares.append("{}-ipwhitelist".format(router)) + - "traefik.http.middlewares.(@= router @)-ipwhitelist.ipwhitelist.sourcerange=(@= ip_sourcerange @)" + + #@ if enable_http_auth: + #@ enabled_middlewares.append("{}-basicauth".format(router)) + - "traefik.http.middlewares.(@= router @)-basicauth.basicauth.users=(@= http_auth @)" + #@ end + + #@ if enable_oauth2: + #@ enabled_middlewares.append("traefik-forward-auth@docker") + #@ enabled_middlewares.append("header-authorization-group-{}@file".format(authorized_group)) + #@ end + + #! Apply all middlewares (do this at the end!) + - "traefik.http.routers.(@= router @).middlewares=(@= ','.join(enabled_middlewares) @)" diff --git a/baikal/docker-compose.yaml b/baikal/docker-compose.yaml index 7e24c6dd..87cffdcc 100644 --- a/baikal/docker-compose.yaml +++ b/baikal/docker-compose.yaml @@ -27,12 +27,7 @@ services: # - KILL # - AUDIT_WRITE restart: unless-stopped - labels: - - "traefik.enable=true" - - "traefik.http.routers.baikal-${BAIKAL_INSTANCE:-default}.rule=Host(`${BAIKAL_TRAEFIK_HOST}`)" - - "traefik.http.routers.baikal-${BAIKAL_INSTANCE:-default}.entrypoints=websecure" - - "traefik.http.middlewares.baikal-${BAIKAL_INSTANCE:-default}-ipwhitelist.ipwhitelist.sourcerange=${BAIKAL_IP_SOURCERANGE}" - - "traefik.http.routers.baikal-${BAIKAL_INSTANCE:-default}.middlewares=baikal-${BAIKAL_INSTANCE:-default}-ipwhitelist" + labels: [] volumes: - baikal_config:/var/www/baikal/config - baikal_data:/var/www/baikal/Specific diff --git a/calcpad/.env-dist b/calcpad/.env-dist index 1c42cfd9..c4dd4190 100644 --- a/calcpad/.env-dist +++ b/calcpad/.env-dist @@ -24,3 +24,20 @@ CALCPAD_GID=54321 ## Redbean server version: ## https://redbean.dev/#download CALCPAD_REDBEAN_VERSION=2.2 + +# OAUTH2 +# Set to `true` to use OpenID/OAuth2 authentication via the +# traefik-forward-auth service in d.rymcg.tech. +# Using OpenID/OAuth2 will require login to access your app, +# but it will not affect what a successfully logged-in person can do in your +# app. If your app has built-in authentication and can check the user +# header that traefik-forward-auth sends, then your app can limit what the +# logged-in person can do in the app. But if your app can't check the user +# header, or if your app doesn't have built-in authentication at all, then +# any person with an account on your Gitea server can log into your app and +# have full access. +CALCPAD_OAUTH2= +# In addition to Oauth2 authentication, you can configure basic authorization +# by entering which authorization group can log into your app. You create +# groups of email addresses in the `traefik` folder by running `make groups`. +CALCPAD_OAUTH2_AUTHORIZED_GROUP= diff --git a/calcpad/Makefile b/calcpad/Makefile index edc5ba32..5f23aa7e 100644 --- a/calcpad/Makefile +++ b/calcpad/Makefile @@ -10,7 +10,7 @@ config-hook: #### reconfigure_htpasswd will configure the HTTP Basic Authentication setting the var name and with a provided default value. @${BIN}/reconfigure_ask ${ENV_FILE} CALCPAD_TRAEFIK_HOST "Enter the calcpad domain name" calcpad${INSTANCE_URL_SUFFIX}.${ROOT_DOMAIN} @${BIN}/reconfigure ${ENV_FILE} CALCPAD_INSTANCE=$${instance:-default} - @${BIN}/reconfigure_htpasswd ${ENV_FILE} CALCPAD_HTTP_AUTH default=no + @${BIN}/reconfigure_auth ${ENV_FILE} CALCPAD .PHONY: override-hook override-hook: @@ -24,4 +24,4 @@ override-hook: #### # (this hardcodes the string into docker-compose.override.yaml) #### name=@VARIABLE_NAME # sets the template 'name' field to the literal string '${VARIABLE_NAME}' #### # (used for regular docker-compose expansion of env vars by name.) - @${BIN}/docker_compose_override ${ENV_FILE} project=:calcpad instance=@CALCPAD_INSTANCE traefik_host=@CALCPAD_TRAEFIK_HOST http_auth=CALCPAD_HTTP_AUTH http_auth_var=@CALCPAD_HTTP_AUTH ip_sourcerange=@CALCPAD_IP_SOURCERANGE + @${BIN}/docker_compose_override ${ENV_FILE} project=:calcpad instance=@CALCPAD_INSTANCE traefik_host=@CALCPAD_TRAEFIK_HOST http_auth=CALCPAD_HTTP_AUTH http_auth_var=@CALCPAD_HTTP_AUTH ip_sourcerange=@CALCPAD_IP_SOURCERANGE oauth2=CALCPAD_OAUTH2 authorized_group=CALCPAD_OAUTH2_AUTHORIZED_GROUP diff --git a/calcpad/docker-compose.instance.yaml b/calcpad/docker-compose.instance.yaml index 4905ce57..4784feb0 100644 --- a/calcpad/docker-compose.instance.yaml +++ b/calcpad/docker-compose.instance.yaml @@ -13,6 +13,8 @@ #@ ip_sourcerange = data.values.ip_sourcerange #@ enable_http_auth = len(data.values.http_auth.strip()) > 0 #@ http_auth = data.values.http_auth_var +#@ enable_oauth2 = data.values.oauth2 == "true" +#@ authorized_group = data.values.authorized_group #@ enabled_middlewares = [] #@yaml/text-templated-strings @@ -37,5 +39,10 @@ services: - "traefik.http.middlewares.(@= router @)-basicauth.basicauth.users=(@= http_auth @)" #@ end + #@ if enable_oauth2: + #@ enabled_middlewares.append("traefik-forward-auth@docker") + #@ enabled_middlewares.append("header-authorization-group-{}@file".format(authorized_group)) + #@ end + #! Apply all middlewares (do this at the end!) - "traefik.http.routers.(@= router @).middlewares=(@= ','.join(enabled_middlewares) @)" diff --git a/drawio/.env-dist b/drawio/.env-dist index bcde5cc4..8ddb81b2 100644 --- a/drawio/.env-dist +++ b/drawio/.env-dist @@ -11,7 +11,7 @@ DRAWIO_IP_SOURCERANGE=0.0.0.0/0 DRAWIO_HTTP_AUTH= # OAUTH2 -# Set to `yes` to use OpenID/OAuth2 authentication via the +# Set to `true` to use OpenID/OAuth2 authentication via the # traefik-forward-auth service in d.rymcg.tech. # Using OpenID/OAuth2 will require login to access your app, # but it will not affect what a successfully logged-in person can do in your @@ -21,7 +21,12 @@ DRAWIO_HTTP_AUTH= # header, or if your app doesn't have built-in authentication at all, then # any person with an account on your Gitea server can log into your app and # have full access. -DRAWIO_OAUTH2=no +DRAWIO_OAUTH2= +# In addition to Oauth2 authentication, you can configure basic authorization +# by entering which authorization group can log into your app. You create +# groups of email addresses in the `traefik` folder by running `make groups`. +DRAWIO_OAUTH2_AUTHORIZED_GROUP= + DRAWIO_CSP_HEADER= diff --git a/drawio/Makefile b/drawio/Makefile index 47b4115e..b260c70f 100644 --- a/drawio/Makefile +++ b/drawio/Makefile @@ -6,8 +6,7 @@ include ${ROOT_DIR}/_scripts/Makefile.instance config-hook: @${BIN}/reconfigure ${ENV_FILE} DRAWIO_INSTANCE=${instance} @${BIN}/reconfigure_ask ${ENV_FILE} DRAWIO_TRAEFIK_HOST "Enter the drawio domain name" diagram${INSTANCE_URL_SUFFIX}.${ROOT_DOMAIN} - @${BIN}/reconfigure_htpasswd ${ENV_FILE} DRAWIO_HTTP_AUTH default=no - @${BIN}/reconfigure_oauth2 ${ENV_FILE} DRAWIO_OAUTH2 default=$$( ${BIN}/dotenv -f ${ENV_FILE} get DRAWIO_OAUTH2 ) + @${BIN}/reconfigure_auth ${ENV_FILE} DRAWIO @echo "" @@ -23,4 +22,4 @@ override-hook: #### # (this hardcodes the string into docker-compose.override.yaml) #### name=@VARIABLE_NAME # sets the template 'name' field to the literal string '${VARIABLE_NAME}' #### # (used for regular docker-compose expansion of env vars by name.) - @${BIN}/docker_compose_override ${ENV_FILE} project=:drawio instance=@DRAWIO_INSTANCE traefik_host=@DRAWIO_TRAEFIK_HOST http_auth=DRAWIO_HTTP_AUTH http_auth_var=@DRAWIO_HTTP_AUTH ip_sourcerange=@DRAWIO_IP_SOURCERANGE oauth2=DRAWIO_OAUTH2 + @${BIN}/docker_compose_override ${ENV_FILE} project=:drawio instance=@DRAWIO_INSTANCE traefik_host=@DRAWIO_TRAEFIK_HOST http_auth=DRAWIO_HTTP_AUTH http_auth_var=@DRAWIO_HTTP_AUTH ip_sourcerange=@DRAWIO_IP_SOURCERANGE oauth2=DRAWIO_OAUTH2 authorized_group=DRAWIO_OAUTH2_AUTHORIZED_GROUP diff --git a/drawio/README.md b/drawio/README.md index fb2fee30..dea7d2d2 100644 --- a/drawio/README.md +++ b/drawio/README.md @@ -13,19 +13,30 @@ Note: this image is not working on arm64. make config ``` -This will ask you to enter the domain name to use, and whether or not -you want to configure a username/password via HTTP Basic Authentication. +This will ask you to enter the domain name to use. It automatically saves your responses into the configuration file `.env_{INSTANCE}`. -It will also ask if you want to use OpenID/OAuth2 authentication. -Using OpenID/OAuth2 will require a login to access your app, but it will not -affect what a successfully logged-in person can do in your app. If your app has -a built-in authorization mechanism that can check for the user header that -traefik-forward-auth sends, then your app can limit what the logged-in person -can do in the app. But if your app can't check the user header, or if your app -doesn't have built-in authorization at all, then any person with an account -on your Gitea server can log into your app and have full acces +### Authentication and Authorization + +Running `make config` will ask whether or not you want to configure +authentication for your app (on top of any authentication your app provides). +You can configure OpenID/OAuth2 or HTTP Basic Authentication. + +OAuth2 uses traefik-forward-auth to delegate authentication to an external +authority (eg. a self-deployed Gitea instance). Accessing this app will +require all users to login through that external service first. Once +authenticated, they may be authorized access only if their login id matches the +member list of the predefined authorization group configured for the app +(`DRAWIO_OAUTH2_AUTHORIZED_GROUP`). Authorization groups are defined in the +Traefik config (`TRAEFIK_HEADER_AUTHORIZATION_GROUPS`) and can be +[created/modified](https://github.com/EnigmaCurry/d.rymcg.tech/blob/master/traefik/README.md#oauth2-authentication) +by running `make groups` in the `traefik` directory. + +For HTTP Basic Authentication, you will be prompted to enter username/password +logins which are stored in that app's `.env_{INSTANCE}` file. + + ## Install diff --git a/drawio/docker-compose.instance.yaml b/drawio/docker-compose.instance.yaml index c7abe043..da147fde 100644 --- a/drawio/docker-compose.instance.yaml +++ b/drawio/docker-compose.instance.yaml @@ -13,7 +13,8 @@ #@ ip_sourcerange = data.values.ip_sourcerange #@ enable_http_auth = len(data.values.http_auth.strip()) > 0 #@ http_auth = data.values.http_auth_var -#@ enable_oauth2 = data.values.oauth2 +#@ enable_oauth2 = data.values.oauth2 == "true" +#@ authorized_group = data.values.authorized_group #@ enabled_middlewares = [] #@yaml/text-templated-strings @@ -38,8 +39,9 @@ services: - "traefik.http.middlewares.(@= router @)-basicauth.basicauth.users=(@= http_auth @)" #@ end - #@ if enable_oauth2 == "yes": + #@ if enable_oauth2: #@ enabled_middlewares.append("traefik-forward-auth@docker") + #@ enabled_middlewares.append("header-authorization-group-{}@file".format(authorized_group)) #@ end - "traefik.http.services.(@= router @).loadbalancer.server.port=8080" diff --git a/filestash/.env-dist b/filestash/.env-dist index 146f9484..3334ff04 100644 --- a/filestash/.env-dist +++ b/filestash/.env-dist @@ -5,7 +5,25 @@ FILESTASH_INSTANCE= FILESTASH_IP_SOURCERANGE=0.0.0.0/0 -## htpasswd encoded username/passwords: -## Run `make config` to reconfigure this: -FILESTASH_AUTH= +# HTTP Basic Authentication: +# Use `make config` to fill this in properly, or set this to blank to disable. +FILESTASH_HTTP_AUTH= + + +# OAUTH2 +# Set to `true` to use OpenID/OAuth2 authentication via the +# traefik-forward-auth service in d.rymcg.tech. +# Using OpenID/OAuth2 will require login to access your app, +# but it will not affect what a successfully logged-in person can do in your +# app. If your app has built-in authentication and can check the user +# header that traefik-forward-auth sends, then your app can limit what the +# logged-in person can do in the app. But if your app can't check the user +# header, or if your app doesn't have built-in authentication at all, then +# any person with an account on your Gitea server can log into your app and +# have full access. +FILESTASH_OAUTH2= +# In addition to Oauth2 authentication, you can configure basic authorization +# by entering which authorization group can log into your app. You create +# groups of email addresses in the `traefik` folder by running `make groups`. +FILESTASH_OAUTH2_AUTHORIZED_GROUP= diff --git a/filestash/Makefile b/filestash/Makefile index 2b5f4db7..67bf52da 100644 --- a/filestash/Makefile +++ b/filestash/Makefile @@ -6,9 +6,23 @@ include ${ROOT_DIR}/_scripts/Makefile.instance config-hook: @${BIN}/reconfigure_ask ${ENV_FILE} FILESTASH_TRAEFIK_HOST "Enter the filestash domain name" filestash${INSTANCE_URL_SUFFIX}.${ROOT_DOMAIN} @${BIN}/reconfigure ${ENV_FILE} FILESTASH_INSTANCE=${instance} - @${BIN}/reconfigure_htpasswd ${ENV_FILE} FILESTASH_AUTH + @${BIN}/reconfigure_auth ${ENV_FILE} FILESTASH .PHONY: admin # Open the admin page admin: @ENV_FILE="${ENV_FILE}" CONTEXT_INSTANCE="${CONTEXT_INSTANCE}" ${BIN}/open /admin + +.PHONY: override-hook +override-hook: +#### This sets the override template variables for docker-compose.instance.yaml: +#### The template dynamically renders to docker-compose.override_{DOCKER_CONTEXT}_{INSTANCE}.yaml +#### These settings are used to automatically generate the service container labels, and traefik config, inside the template. +#### The variable arguments have three forms: `=` `=:` `=@` +#### name=VARIABLE_NAME # sets the template 'name' field to the value of VARIABLE_NAME found in the .env file +#### # (this hardcodes the value into docker-compose.override.yaml) +#### name=:VARIABLE_NAME # sets the template 'name' field to the literal string 'VARIABLE_NAME' +#### # (this hardcodes the string into docker-compose.override.yaml) +#### name=@VARIABLE_NAME # sets the template 'name' field to the literal string '${VARIABLE_NAME}' +#### # (used for regular docker-compose expansion of env vars by name.) + @${BIN}/docker_compose_override ${ENV_FILE} project=:filestash instance=@FILESTASH_INSTANCE traefik_host=@FILESTASH_TRAEFIK_HOST http_auth=FILESTASH_HTTP_AUTH http_auth_var=@FILESTASH_HTTP_AUTH ip_sourcerange=@FILESTASH_IP_SOURCERANGE oauth2=FILESTASH_OAUTH2 authorized_group=FILESTASH_OAUTH2_AUTHORIZED_GROUP diff --git a/filestash/docker-compose.instance.yaml b/filestash/docker-compose.instance.yaml new file mode 100644 index 00000000..545bcc4b --- /dev/null +++ b/filestash/docker-compose.instance.yaml @@ -0,0 +1,48 @@ +#! This is a ytt template file for docker-compose.override.yaml +#! References: +#! https://carvel.dev/ytt +#! https://docs.docker.com/compose/extends/#adding-and-overriding-configuration +#! https://github.com/enigmacurry/d.rymcg.tech#overriding-docker-composeyaml-per-instance + +#! ### Standard project vars: +#@ load("@ytt:data", "data") +#@ project = data.values.project +#@ instance = data.values.instance +#@ context = data.values.context +#@ traefik_host = data.values.traefik_host +#@ ip_sourcerange = data.values.ip_sourcerange +#@ enable_http_auth = len(data.values.http_auth.strip()) > 0 +#@ http_auth = data.values.http_auth_var +#@ enable_oauth2 = data.values.oauth2 == "true" +#@ authorized_group = data.values.authorized_group +#@ enabled_middlewares = [] + +#@yaml/text-templated-strings +services: + filestash: + #@ service = "filestash" + labels: + #! Services must opt-in to be proxied by Traefik: + - "traefik.enable=true" + + #! 'router' is the fully qualified key in traefik for this router/service: project + instance + service + #@ router = "{}-{}-{}".format(project,instance,service) + + #! The host matching router rule: + - "traefik.http.routers.(@= router @).rule=Host(`(@= traefik_host @)`)" + - "traefik.http.routers.(@= router @).entrypoints=websecure" + #@ enabled_middlewares.append("{}-ipwhitelist".format(router)) + - "traefik.http.middlewares.(@= router @)-ipwhitelist.ipwhitelist.sourcerange=(@= ip_sourcerange @)" + + #@ if enable_http_auth: + #@ enabled_middlewares.append("{}-basicauth".format(router)) + - "traefik.http.middlewares.(@= router @)-basicauth.basicauth.users=(@= http_auth @)" + #@ end + + #@ if enable_oauth2: + #@ enabled_middlewares.append("traefik-forward-auth@docker") + #@ enabled_middlewares.append("header-authorization-group-{}@file".format(authorized_group)) + #@ end + + #! Apply all middlewares (do this at the end!) + - "traefik.http.routers.(@= router @).middlewares=(@= ','.join(enabled_middlewares) @)" diff --git a/filestash/docker-compose.yaml b/filestash/docker-compose.yaml index 2409ea8c..2dccf066 100644 --- a/filestash/docker-compose.yaml +++ b/filestash/docker-compose.yaml @@ -11,14 +11,6 @@ services: restart: unless-stopped environment: - APPLICATION_URL=${FILESTASH_TRAEFIK_HOST} - labels: - - "traefik.enable=true" - ## Password protected main router: - - "traefik.http.routers.filestash-${FILESTASH_INSTANCE:-default}.rule=Host(`${FILESTASH_TRAEFIK_HOST}`)" - - "traefik.http.routers.filestash-${FILESTASH_INSTANCE:-default}.entrypoints=websecure" - - "traefik.http.middlewares.filestash-${FILESTASH_INSTANCE:-default}-auth.basicauth.users=${FILESTASH_AUTH}" - - "traefik.http.middlewares.filestash-${FILESTASH_INSTANCE:-default}-ipwhitelist.ipwhitelist.sourcerange=${FILESTASH_IP_SOURCERANGE}" - - "traefik.http.routers.filestash-${FILESTASH_INSTANCE:-default}.middlewares=filestash-${FILESTASH_INSTANCE:-default}-ipwhitelist,filestash-${FILESTASH_INSTANCE:-default}-auth" - + labels: [] volumes: - state:/app/data/state diff --git a/freshrss/.env-dist b/freshrss/.env-dist index c2fe156f..e685da42 100644 --- a/freshrss/.env-dist +++ b/freshrss/.env-dist @@ -1,6 +1,8 @@ FRESHRSS_TRAEFIK_HOST=freshrss.example.com FRESHRSS_INSTANCE= +FRESHRSS_VERSION=latest + ## Time zone for cron: TIME_ZONE=Europe/Paris @@ -8,3 +10,24 @@ TIME_ZONE=Europe/Paris ##Disallow all access: 0.0.0.0/32 ##Allow all access: 0.0.0.0/0 FRESHRSS_IP_SOURCERANGE=0.0.0.0/0 + +# HTTP Basic Authentication: +# Use `make config` to fill this in properly, or set this to blank to disable. +FRESHRSS_HTTP_AUTH= + +# OAUTH2 +# Set to `true` to use OpenID/OAuth2 authentication via the +# traefik-forward-auth service in d.rymcg.tech. +# Using OpenID/OAuth2 will require login to access your app, +# but it will not affect what a successfully logged-in person can do in your +# app. If your app has built-in authentication and can check the user +# header that traefik-forward-auth sends, then your app can limit what the +# logged-in person can do in the app. But if your app can't check the user +# header, or if your app doesn't have built-in authentication at all, then +# any person with an account on your Gitea server can log into your app and +# have full access. +FRESHRSS_OAUTH2= +# In addition to Oauth2 authentication, you can configure basic authorization +# by entering which authorization group can log into your app. You create +# groups of email addresses in the `traefik` folder by running `make groups`. +FRESHRSS_OAUTH2_AUTHORIZED_GROUP= diff --git a/freshrss/Makefile b/freshrss/Makefile index 0163349b..8584c66d 100644 --- a/freshrss/Makefile +++ b/freshrss/Makefile @@ -6,4 +6,19 @@ include ${ROOT_DIR}/_scripts/Makefile.instance config-hook: @${BIN}/reconfigure_ask ${ENV_FILE} FRESHRSS_TRAEFIK_HOST "Enter the ttrss domain name" freshrss${INSTANCE_URL_SUFFIX}.${ROOT_DOMAIN} @${BIN}/reconfigure ${ENV_FILE} FRESHRSS_INSTANCE=$${instance:-default} + @${BIN}/reconfigure_auth ${ENV_FILE} FRESHRSS + +.PHONY: override-hook +override-hook: +#### This sets the override template variables for docker-compose.instance.yaml: +#### The template dynamically renders to docker-compose.override_{DOCKER_CONTEXT}_{INSTANCE}.yaml +#### These settings are used to automatically generate the service container labels, and traefik config, inside the template. +#### The variable arguments have three forms: `=` `=:` `=@` +#### name=VARIABLE_NAME # sets the template 'name' field to the value of VARIABLE_NAME found in the .env file +#### # (this hardcodes the value into docker-compose.override.yaml) +#### name=:VARIABLE_NAME # sets the template 'name' field to the literal string 'VARIABLE_NAME' +#### # (this hardcodes the string into docker-compose.override.yaml) +#### name=@VARIABLE_NAME # sets the template 'name' field to the literal string '${VARIABLE_NAME}' +#### # (used for regular docker-compose expansion of env vars by name.) + @${BIN}/docker_compose_override ${ENV_FILE} project=:freshrss instance=@FRESHRSS_INSTANCE traefik_host=@FRESHRSS_TRAEFIK_HOST http_auth=FRESHRSS_HTTP_AUTH http_auth_var=@FRESHRSS_HTTP_AUTH ip_sourcerange=@FRESHRSS_IP_SOURCERANGE oauth2=FRESHRSS_OAUTH2 authorized_group=FRESHRSS_OAUTH2_AUTHORIZED_GROUP diff --git a/freshrss/docker-compose.instance.yaml b/freshrss/docker-compose.instance.yaml new file mode 100644 index 00000000..e6a0dbd9 --- /dev/null +++ b/freshrss/docker-compose.instance.yaml @@ -0,0 +1,48 @@ +#! This is a ytt template file for docker-compose.override.yaml +#! References: +#! https://carvel.dev/ytt +#! https://docs.docker.com/compose/extends/#adding-and-overriding-configuration +#! https://github.com/enigmacurry/d.rymcg.tech#overriding-docker-composeyaml-per-instance + +#! ### Standard project vars: +#@ load("@ytt:data", "data") +#@ project = data.values.project +#@ instance = data.values.instance +#@ context = data.values.context +#@ traefik_host = data.values.traefik_host +#@ ip_sourcerange = data.values.ip_sourcerange +#@ enable_http_auth = len(data.values.http_auth.strip()) > 0 +#@ http_auth = data.values.http_auth_var +#@ enable_oauth2 = data.values.oauth2 == "true" +#@ authorized_group = data.values.authorized_group +#@ enabled_middlewares = [] + +#@yaml/text-templated-strings +services: + freshrss: + #@ service = "freshrss" + labels: + #! Services must opt-in to be proxied by Traefik: + - "traefik.enable=true" + + #! 'router' is the fully qualified key in traefik for this router/service: project + instance + service + #@ router = "{}-{}-{}".format(project,instance,service) + + #! The host matching router rule: + - "traefik.http.routers.(@= router @).rule=Host(`(@= traefik_host @)`)" + - "traefik.http.routers.(@= router @).entrypoints=websecure" + #@ enabled_middlewares.append("{}-ipwhitelist".format(router)) + - "traefik.http.middlewares.(@= router @)-ipwhitelist.ipwhitelist.sourcerange=(@= ip_sourcerange @)" + + #@ if enable_http_auth: + #@ enabled_middlewares.append("{}-basicauth".format(router)) + - "traefik.http.middlewares.(@= router @)-basicauth.basicauth.users=(@= http_auth @)" + #@ end + + #@ if enable_oauth2: + #@ enabled_middlewares.append("traefik-forward-auth@docker") + #@ enabled_middlewares.append("header-authorization-group-{}@file".format(authorized_group)) + #@ end + + #! Apply all middlewares (do this at the end!) + - "traefik.http.routers.(@= router @).middlewares=(@= ','.join(enabled_middlewares) @)" diff --git a/freshrss/docker-compose.yaml b/freshrss/docker-compose.yaml index e343d6c0..03cbd968 100644 --- a/freshrss/docker-compose.yaml +++ b/freshrss/docker-compose.yaml @@ -1,8 +1,8 @@ version: "3.9" services: - freshrss-app: - image: freshrss/freshrss:latest + freshrss: + image: freshrss/freshrss:${FRESHRSS_VERSION} restart: unless-stopped volumes: - data:/var/www/FreshRSS/data @@ -10,13 +10,7 @@ services: environment: CRON_MIN: '*/20' TZ: ${TIME_ZONE} - labels: - - "traefik.enable=true" - - "traefik.http.routers.freshrss-${INSTANCE:-default}.rule=Host(`${FRESHRSS_TRAEFIK_HOST}`)" - - "traefik.http.routers.freshrss-${INSTANCE:-default}.entrypoints=websecure" - - "traefik.http.middlewares.freshrss-${INSTANCE:-default}-ipwhitelist.ipwhitelist.sourcerange=${FRESHRSS_IP_SOURCERANGE}" - - "traefik.http.routers.freshrss-${INSTANCE:-default}.middlewares=freshrss-${INSTANCE:-default}-ipwhitelist" - + labels: [] volumes: data: extensions: diff --git a/github-actions-runner/.env-dist b/github-actions-runner/.env-dist index 33250972..99ebac4c 100644 --- a/github-actions-runner/.env-dist +++ b/github-actions-runner/.env-dist @@ -1,4 +1,4 @@ -RUNNER_NAME=docker-test -REPOSITORY=https://github.com/YOUR_ORG/YOUR_REPO -RUNNER_TOKEN=ASDFASDFASDFASDFASDFASDFASDF +GITHUB_ACTIONS_RUNNER_INSTANCE= +GITHUB_ACTIONS_RUNNER_REPOSITORY=https://github.com/YOUR_ORG/YOUR_REPO +GITHUB_ACTIONS_RUNNER_TOKEN=ASDFASDFASDFASDFASDFASDFASDF diff --git a/github-actions-runner/Makefile b/github-actions-runner/Makefile index bdce7269..6bed7477 100644 --- a/github-actions-runner/Makefile +++ b/github-actions-runner/Makefile @@ -1,9 +1,8 @@ ROOT_DIR = .. include ${ROOT_DIR}/_scripts/Makefile.projects +include ${ROOT_DIR}/_scripts/Makefile.instance .PHONY: config-hook config-hook: - @${BIN}/reconfigure_ask ${ENV_FILE} RUNNER_NAME "Enter a name for this runner (hostname)" docker-test @${BIN}/reconfigure_ask ${ENV_FILE} REPOSITORY "Enter the github repository URL" @${BIN}/reconfigure_ask ${ENV_FILE} RUNNER_TOKEN "Enter the GitHub runner token" - diff --git a/github-actions-runner/docker-compose.yaml b/github-actions-runner/docker-compose.yaml index d029712f..de46053f 100644 --- a/github-actions-runner/docker-compose.yaml +++ b/github-actions-runner/docker-compose.yaml @@ -23,9 +23,9 @@ services: build: context: . environment: - - REPOSITORY - - RUNNER_TOKEN - hostname: ${RUNNER_NAME} + - REPOSITORY=${GITHUB_ACTIONS_RUNNER_REPOSITORY} + - RUNNER_TOKEN=${GITHUB_ACTIONS_RUNNER_TOKEN} + hostname: ${GITHUB_ACTIONS_RUNNER_INSTANCE} security_opt: - no-new-privileges:true volumes: diff --git a/grocy/.env-dist b/grocy/.env-dist index 1d2e80e1..82f1fbe0 100644 --- a/grocy/.env-dist +++ b/grocy/.env-dist @@ -24,7 +24,7 @@ GROCY_IP_SOURCERANGE="0.0.0.0/0" GROCY_HTTP_AUTH= # OAUTH2 -# Set to `yes` to use OpenID/OAuth2 authentication via the +# Set to `true` to use OpenID/OAuth2 authentication via the # traefik-forward-auth service in d.rymcg.tech. # Using OpenID/OAuth2 will require login to access your app, # but it will not affect what a successfully logged-in person can do in your @@ -34,7 +34,11 @@ GROCY_HTTP_AUTH= # header, or if your app doesn't have built-in authentication at all, then # any person with an account on your Gitea server can log into your app and # have full access. -DRAWIO_OAUTH2=no +GROCY_OAUTH2= +# In addition to Oauth2 authentication, you can configure basic authorization +# by entering which authorization group can log into your app. You create +# groups of email addresses in the `traefik` folder by running `make groups`. +GROCY_OAUTH2_AUTHORIZED_GROUP= ## You may customize the UID and GID that the container runs as, this ## is passed into the Dockerfile as a build ARG. diff --git a/grocy/Makefile b/grocy/Makefile index c611a243..50f9534b 100644 --- a/grocy/Makefile +++ b/grocy/Makefile @@ -10,8 +10,7 @@ config-hook: #### reconfigure_htpasswd will configure the HTTP Basic Authentication setting the var name and with a provided default value. @${BIN}/reconfigure_ask ${ENV_FILE} GROCY_TRAEFIK_HOST "Enter the grocy domain name" grocy${INSTANCE_URL_SUFFIX}.${ROOT_DOMAIN} @${BIN}/reconfigure ${ENV_FILE} GROCY_INSTANCE=$${instance:-default} - @${BIN}/reconfigure_htpasswd ${ENV_FILE} GROCY_HTTP_AUTH default=no - @${BIN}/reconfigure_oauth2 ${ENV_FILE} GROCY_OAUTH2 default=$$( ${BIN}/dotenv -f ${ENV_FILE} get GROCY_OAUTH2 ) + @${BIN}/reconfigure_auth ${ENV_FILE} GROCY @echo "" .PHONY: override-hook @@ -26,4 +25,4 @@ override-hook: #### # (this hardcodes the string into docker-compose.override.yaml) #### name=@VARIABLE_NAME # sets the template 'name' field to the literal string '${VARIABLE_NAME}' #### # (used for regular docker-compose expansion of env vars by name.) - @${BIN}/docker_compose_override ${ENV_FILE} project=:grocy instance=@GROCY_INSTANCE traefik_host=@GROCY_TRAEFIK_HOST http_auth=GROCY_HTTP_AUTH http_auth_var=@GROCY_HTTP_AUTH ip_sourcerange=@GROCY_IP_SOURCERANGE oauth2=GROCY_OAUTH2 + @${BIN}/docker_compose_override ${ENV_FILE} project=:grocy instance=@GROCY_INSTANCE traefik_host=@GROCY_TRAEFIK_HOST http_auth=GROCY_HTTP_AUTH http_auth_var=@GROCY_HTTP_AUTH ip_sourcerange=@GROCY_IP_SOURCERANGE oauth2=GROCY_OAUTH2 authorized_group=GROCY_OAUTH2_AUTHORIZED_GROUP diff --git a/grocy/README.md b/grocy/README.md index e111d8d4..9e258f4c 100644 --- a/grocy/README.md +++ b/grocy/README.md @@ -12,19 +12,29 @@ Run: make config ``` -This will ask you to enter the domain name to use, and whether or not -you want to configure a username/password via HTTP Basic Authentication. +This will ask you to enter the domain name to use. It automatically saves your responses into the configuration file `.env_{INSTANCE}`. -It will also ask if you want to use OpenID/OAuth2 authentication. -Using OpenID/OAuth2 will require a login to access your app, but it will not -affect what a successfully logged-in person can do in your app. If your app has -a built-in authorization mechanism that can check for the user header that -traefik-forward-auth sends, then your app can limit what the logged-in person -can do in the app. But if your app can't check the user header, or if your app -doesn't have built-in authorization at all, then any person with an account -on your Gitea server can log into your app and have full acces +### Authentication and Authorization + +Running `make config` will ask whether or not you want to configure +authentication for your app (on top of any authentication your app provides). +You can configure OpenID/OAuth2 or HTTP Basic Authentication. + +OAuth2 uses traefik-forward-auth to delegate authentication to an external +authority (eg. a self-deployed Gitea instance). Accessing this app will +require all users to login through that external service first. Once +authenticated, they may be authorized access only if their login id matches the +member list of the predefined authorization group configured for the app +(`GROCY_OAUTH2_AUTHORIZED_GROUP`). Authorization groups are defined in the +Traefik config (`TRAEFIK_HEADER_AUTHORIZATION_GROUPS`) and can be +[created/modified](https://github.com/EnigmaCurry/d.rymcg.tech/blob/master/traefik/README.md#oauth2-authentication) +by running `make groups` in the `traefik` directory. + +For HTTP Basic Authentication, you will be prompted to enter username/password +logins which are stored in that app's `.env_{INSTANCE}` file. + ## Install diff --git a/grocy/docker-compose.instance.yaml b/grocy/docker-compose.instance.yaml index 60c69036..c066d508 100644 --- a/grocy/docker-compose.instance.yaml +++ b/grocy/docker-compose.instance.yaml @@ -13,7 +13,8 @@ #@ ip_sourcerange = data.values.ip_sourcerange #@ enable_http_auth = len(data.values.http_auth.strip()) > 0 #@ http_auth = data.values.http_auth_var -#@ enable_oauth2 = data.values.oauth2 +#@ enable_oauth2 = data.values.oauth2 == "true" +#@ authorized_group = data.values.authorized_group #@ enabled_middlewares = [] #@yaml/text-templated-strings @@ -38,8 +39,9 @@ services: - "traefik.http.middlewares.(@= router @)-basicauth.basicauth.users=(@= http_auth @)" #@ end - #@ if enable_oauth2 == "yes": + #@ if enable_oauth2: #@ enabled_middlewares.append("traefik-forward-auth@docker") + #@ enabled_middlewares.append("header-authorization-group-{}@file".format(authorized_group)) #@ end #! Apply all middlewares (do this at the end!) diff --git a/homepage/.env-dist b/homepage/.env-dist index fd692c43..89d2a274 100644 --- a/homepage/.env-dist +++ b/homepage/.env-dist @@ -17,7 +17,7 @@ HOMEPAGE_IP_SOURCERANGE=0.0.0.0/0 HOMEPAGE_HTTP_AUTH= # OAUTH2 -# Set to `yes` to use OpenID/OAuth2 authentication via the +# Set to `true` to use OpenID/OAuth2 authentication via the # traefik-forward-auth service in d.rymcg.tech. # Using OpenID/OAuth2 will require login to access your app, # but it will not affect what a successfully logged-in person can do in your @@ -27,7 +27,11 @@ HOMEPAGE_HTTP_AUTH= # header, or if your app doesn't have built-in authentication at all, then # any person with an account on your Gitea server can log into your app and # have full access. -DRAWIO_OAUTH2=no +HOMEPAGE_OAUTH2= +# In addition to Oauth2 authentication, you can configure basic authorization +# by entering which authorization group can log into your app. You create +# groups of email addresses in the `traefik` folder by running `make groups`. +HOMEPAGE_OAUTH2_AUTHORIZED_GROUP= ## To support automatic Docker service discovery, you need to mount the docker socket. ## Since this is a security concern, it should be disabled by default, unless you want it. diff --git a/homepage/Makefile b/homepage/Makefile index 3969028c..0d08c8b9 100644 --- a/homepage/Makefile +++ b/homepage/Makefile @@ -13,8 +13,7 @@ config-hook: @${BIN}/confirm $$([[ $$(${BIN}/dotenv -f ${ENV_FILE} get HOMEPAGE_ENABLE_DOCKER) == "true" ]] && echo "yes" || echo "no") "Do you want to enable docker service discovery" "?" && ${BIN}/reconfigure ${ENV_FILE} HOMEPAGE_ENABLE_DOCKER=true || ${BIN}/reconfigure ${ENV_FILE} HOMEPAGE_ENABLE_DOCKER=false @echo "" @${BIN}/reconfigure ${ENV_FILE} HOMEPAGE_INSTANCE=$${instance:-default} - @${BIN}/reconfigure_htpasswd ${ENV_FILE} HOMEPAGE_HTTP_AUTH default=no - @${BIN}/reconfigure_oauth2 ${ENV_FILE} HOMEPAGE_OAUTH2 default=$$( ${BIN}/dotenv -f ${ENV_FILE} get HOMEPAGE_OAUTH2 ) + @${BIN}/reconfigure_auth ${ENV_FILE} HOMEPAGE @echo "" @${BIN}/reconfigure_password ${ENV_FILE} HOMEPAGE_RELOADER_HMAC_SECRET 45 @echo "" @@ -36,7 +35,7 @@ override-hook: #### # (this hardcodes the string into docker-compose.override.yaml) #### name=@VARIABLE_NAME # sets the template 'name' field to the literal string '${VARIABLE_NAME}' #### # (used for regular docker-compose expansion of env vars by name.) - @${BIN}/docker_compose_override ${ENV_FILE} project=:homepage instance=@HOMEPAGE_INSTANCE traefik_host=@HOMEPAGE_TRAEFIK_HOST http_auth=HOMEPAGE_HTTP_AUTH http_auth_var=@HOMEPAGE_HTTP_AUTH ip_sourcerange=@HOMEPAGE_IP_SOURCERANGE enable_docker=HOMEPAGE_ENABLE_DOCKER reloader_path_prefix=HOMEPAGE_RELOADER_PATH_PREFIX oauth2=HOMEPAGE_OAUTH2 + @${BIN}/docker_compose_override ${ENV_FILE} project=:homepage instance=@HOMEPAGE_INSTANCE traefik_host=@HOMEPAGE_TRAEFIK_HOST http_auth=HOMEPAGE_HTTP_AUTH http_auth_var=@HOMEPAGE_HTTP_AUTH ip_sourcerange=@HOMEPAGE_IP_SOURCERANGE enable_docker=HOMEPAGE_ENABLE_DOCKER reloader_path_prefix=HOMEPAGE_RELOADER_PATH_PREFIX oauth2=HOMEPAGE_OAUTH2 authorized_group=HOMEPAGE_OAUTH2_AUTHORIZED_GROUP .PHONY: shell shell: diff --git a/homepage/README.md b/homepage/README.md index 1ca5b845..13ef9561 100644 --- a/homepage/README.md +++ b/homepage/README.md @@ -11,20 +11,10 @@ languages. make config ``` -This will ask you to enter the domain name to use, and whether or not -you want to configure a username/password via HTTP Basic Authentication. +This will ask you to enter the domain name to use. It automatically saves your responses into the configuration file `.env_{INSTANCE}`. -It will also ask if you want to use OpenID/OAuth2 authentication. -Using OpenID/OAuth2 will require a login to access your app, but it will not -affect what a successfully logged-in person can do in your app. If your app has -a built-in authorization mechanism that can check for the user header that -traefik-forward-auth sends, then your app can limit what the logged-in person -can do in the app. But if your app can't check the user header, or if your app -doesn't have built-in authorization at all, then any person with an account -on your Gitea server can log into your app and have full acces - By default (when HOMEPAGE_AUTO_CONFIG=true), Homepage will automatically be configured for all your d.rymcg.tech apps running on the current Docker context. To update the Homepage configuration whenever your running @@ -46,6 +36,26 @@ wish to enable this support, answer the question posed by `make config` and/or set `HOMEPAGE_ENABLE_DOCKER=true` in your `.env_{INSTANCE}` file. +### Authentication and Authorization + +Running `make config` will ask whether or not you want to configure +authentication for your app (on top of any authentication your app provides). +You can configure OpenID/OAuth2 or HTTP Basic Authentication. + +OAuth2 uses traefik-forward-auth to delegate authentication to an external +authority (eg. a self-deployed Gitea instance). Accessing this app will +require all users to login through that external service first. Once +authenticated, they may be authorized access only if their login id matches the +member list of the predefined authorization group configured for the app +(`HOMEPAGE_OAUTH2_AUTHORIZED_GROUP`). Authorization groups are defined in the +Traefik config (`TRAEFIK_HEADER_AUTHORIZATION_GROUPS`) and can be +[created/modified](https://github.com/EnigmaCurry/d.rymcg.tech/blob/master/traefik/README.md#oauth2-authentication) +by running `make groups` in the `traefik` directory. + +For HTTP Basic Authentication, you will be prompted to enter username/password +logins which are stored in that app's `.env_{INSTANCE}` file. + + ## Generate deploy key (if using a private template repository) Before you install, if `HOMEPAGE_AUTO_CONFIG=false` and you have customized @@ -88,7 +98,7 @@ it with someone else. make destroy ``` -This completely removes the container and all of the data. +This completely removes the container and all of the data, including the Git deploy key. ## Reloading Webhook (optional) diff --git a/homepage/docker-compose.instance.yaml b/homepage/docker-compose.instance.yaml index 63b480e5..d97bb85b 100644 --- a/homepage/docker-compose.instance.yaml +++ b/homepage/docker-compose.instance.yaml @@ -13,7 +13,8 @@ #@ ip_sourcerange = data.values.ip_sourcerange #@ enable_http_auth = len(data.values.http_auth.strip()) > 0 #@ http_auth = data.values.http_auth_var -#@ enable_oauth2 = data.values.oauth2 +#@ enable_oauth2 = data.values.oauth2 == "true" +#@ authorized_group = data.values.authorized_group #@ enabled_middlewares = [] #@ enable_docker = data.values.enable_docker == "true" @@ -48,8 +49,9 @@ services: - "traefik.http.middlewares.(@= router @)-basicauth.basicauth.users=(@= http_auth @)" #@ end - #@ if enable_oauth2 == "yes": + #@ if enable_oauth2: #@ enabled_middlewares.append("traefik-forward-auth@docker") + #@ enabled_middlewares.append("header-authorization-group-{}@file".format(authorized_group)) #@ end - "traefik.http.services.(@= router @).loadbalancer.server.port=3000" diff --git a/invidious/.env-dist b/invidious/.env-dist index 1ce0558a..bd180eb6 100644 --- a/invidious/.env-dist +++ b/invidious/.env-dist @@ -7,7 +7,7 @@ INVIDIOUS_POSTGRES_PASSWORD= INVIDIOUS_HTTP_AUTH= # OAUTH2 -# Set to `yes` to use OpenID/OAuth2 authentication via the +# Set to `true` to use OpenID/OAuth2 authentication via the # traefik-forward-auth service in d.rymcg.tech. # Using OpenID/OAuth2 will require login to access your app, # but it will not affect what a successfully logged-in person can do in your @@ -17,7 +17,11 @@ INVIDIOUS_HTTP_AUTH= # header, or if your app doesn't have built-in authentication at all, then # any person with an account on your Gitea server can log into your app and # have full access. -INVIDIOUS_OAUTH2=no +INVIDIOUS_OAUTH2= +# In addition to Oauth2 authentication, you can configure basic authorization +# by entering which authorization group can log into your app. You create +# groups of email addresses in the `traefik` folder by running `make groups`. +INVIDIOUS_OAUTH2_AUTHORIZED_GROUP= INVIDIOUS_REALM=Invidious diff --git a/invidious/Makefile b/invidious/Makefile index 87fba8a0..beb656bc 100644 --- a/invidious/Makefile +++ b/invidious/Makefile @@ -7,8 +7,7 @@ config-hook: @${BIN}/reconfigure_ask ${ENV_FILE} INVIDIOUS_TRAEFIK_HOST "Enter the invidious domain name" yt${INSTANCE_URL_SUFFIX}.${ROOT_DOMAIN} @${BIN}/reconfigure_password ${ENV_FILE} INVIDIOUS_POSTGRES_PASSWORD @${BIN}/reconfigure ${ENV_FILE} INVIDIOUS_INSTANCE=$${instance:-default} - @${BIN}/reconfigure_htpasswd ${ENV_FILE} INVIDIOUS_HTTP_AUTH default=yes - @${BIN}/reconfigure_oauth2 ${ENV_FILE} INVIDIOUS_OAUTH2 default=$$( ${BIN}/dotenv -f ${ENV_FILE} get INVIDIOUS_OAUTH2 ) + @${BIN}/reconfigure_auth ${ENV_FILE} INVIDIOUS @echo "" @${BIN}/reconfigure_password ${ENV_FILE} INVIDIOUS_HMAC_KEY 32 @@ -24,4 +23,4 @@ override-hook: #### # (this hardcodes the string into docker-compose.override.yaml) #### name=@VARIABLE_NAME # sets the template 'name' field to the literal string '${VARIABLE_NAME}' #### # (used for regular docker-compose expansion of env vars by name.) - @${BIN}/docker_compose_override ${ENV_FILE} project=:invidious instance=@INVIDIOUS_INSTANCE traefik_host=@INVIDIOUS_TRAEFIK_HOST http_auth=INVIDIOUS_HTTP_AUTH http_auth_var=@INVIDIOUS_HTTP_AUTH ip_sourcerange=@INVIDIOUS_IP_SOURCERANGE oauth2=INVIDIOUS_OAUTH2 + @${BIN}/docker_compose_override ${ENV_FILE} project=:invidious instance=@INVIDIOUS_INSTANCE traefik_host=@INVIDIOUS_TRAEFIK_HOST http_auth=INVIDIOUS_HTTP_AUTH http_auth_var=@INVIDIOUS_HTTP_AUTH ip_sourcerange=@INVIDIOUS_IP_SOURCERANGE oauth2=INVIDIOUS_OAUTH2 authorized_group=INVIDIOUS_OAUTH2_AUTHORIZED_GROUP diff --git a/invidious/README.md b/invidious/README.md index 26244710..5e959f89 100644 --- a/invidious/README.md +++ b/invidious/README.md @@ -11,19 +11,29 @@ username/password. If not, comment out the `Authentication` section in the make config ``` -This will ask you to enter the domain name to use, and whether or not -you want to configure a username/password via HTTP Basic Authentication. +This will ask you to enter the domain name to use. It automatically saves your responses into the configuration file `.env_{INSTANCE}`. -It will also ask if you want to use OpenID/OAuth2 authentication. -Using OpenID/OAuth2 will require a login to access your app, but it will not -affect what a successfully logged-in person can do in your app. If your app has -a built-in authorization mechanism that can check for the user header that -traefik-forward-auth sends, then your app can limit what the logged-in person -can do in the app. But if your app can't check the user header, or if your app -doesn't have built-in authorization at all, then any person with an account -on your Gitea server can log into your app and have full acces +### Authentication and Authorization + +Running `make config` will ask whether or not you want to configure +authentication for your app (on top of any authentication your app provides). +You can configure OpenID/OAuth2 or HTTP Basic Authentication. + +OAuth2 uses traefik-forward-auth to delegate authentication to an external +authority (eg. a self-deployed Gitea instance). Accessing this app will +require all users to login through that external service first. Once +authenticated, they may be authorized access only if their login id matches the +member list of the predefined authorization group configured for the app +(`INVIDIOUS_OAUTH2_AUTHORIZED_GROUP`). Authorization groups are defined in the +Traefik config (`TRAEFIK_HEADER_AUTHORIZATION_GROUPS`) and can be +[created/modified](https://github.com/EnigmaCurry/d.rymcg.tech/blob/master/traefik/README.md#oauth2-authentication) +by running `make groups` in the `traefik` directory. + +For HTTP Basic Authentication, you will be prompted to enter username/password +logins which are stored in that app's `.env_{INSTANCE}` file. + ``` make install diff --git a/invidious/docker-compose.instance.yaml b/invidious/docker-compose.instance.yaml index f41a5ee6..3f3febf6 100644 --- a/invidious/docker-compose.instance.yaml +++ b/invidious/docker-compose.instance.yaml @@ -13,7 +13,8 @@ #@ ip_sourcerange = data.values.ip_sourcerange #@ enable_http_auth = len(data.values.http_auth.strip()) > 0 #@ http_auth = data.values.http_auth_var -#@ enable_oauth2 = data.values.oauth2 +#@ enable_oauth2 = data.values.oauth2 == "true" +#@ authorized_group = data.values.authorized_group #@ enabled_middlewares = [] #@yaml/text-templated-strings @@ -38,8 +39,9 @@ services: - "traefik.http.middlewares.(@= router @)-basicauth.basicauth.users=(@= http_auth @)" #@ end - #@ if enable_oauth2 == "yes": + #@ if enable_oauth2: #@ enabled_middlewares.append("traefik-forward-auth@docker") + #@ enabled_middlewares.append("header-authorization-group-{}@file".format(authorized_group)) #@ end #! Apply all middlewares (do this at the end!) diff --git a/jupyterlab/.env-dist b/jupyterlab/.env-dist index 7c280538..c591167b 100644 --- a/jupyterlab/.env-dist +++ b/jupyterlab/.env-dist @@ -11,3 +11,24 @@ JUPYTERLAB_IMAGE=jupyter/scipy-notebook JUPYTERLAB_IP_SOURCERANGE=0.0.0.0/0 JUPYTERLAB_JUPYTER_TOKEN= + +# HTTP Basic Authentication: +# Use `make config` to fill this in properly, or set this to blank to disable. +JUPYTERLAB_HTTP_AUTH= + +# OAUTH2 +# Set to `true` to use OpenID/OAuth2 authentication via the +# traefik-forward-auth service in d.rymcg.tech. +# Using OpenID/OAuth2 will require login to access your app, +# but it will not affect what a successfully logged-in person can do in your +# app. If your app has built-in authentication and can check the user +# header that traefik-forward-auth sends, then your app can limit what the +# logged-in person can do in the app. But if your app can't check the user +# header, or if your app doesn't have built-in authentication at all, then +# any person with an account on your Gitea server can log into your app and +# have full access. +JUPYTERLAB_OAUTH2= +# In addition to Oauth2 authentication, you can configure basic authorization +# by entering which authorization group can log into your app. You create +# groups of email addresses in the `traefik` folder by running `make groups`. +JUPYTERLAB_OAUTH2_AUTHORIZED_GROUP= diff --git a/jupyterlab/Dockerfile b/jupyterlab/Dockerfile index c44d73e4..d26323c4 100644 --- a/jupyterlab/Dockerfile +++ b/jupyterlab/Dockerfile @@ -7,8 +7,8 @@ RUN apt update -y && apt install less USER jovyan ENV port 8888 -RUN jupyter labextension install jupyterlab-emacskeys && \ - mkdir -p $HOME/.jupyter/lab/user-settings/@jupyterlab/apputils-extension +#RUN jupyter labextension install jupyterlab-emacskeys +RUN mkdir -p $HOME/.jupyter/lab/user-settings/@jupyterlab/apputils-extension COPY themes.jupyterlab-settings $HOME/.jupyter/lab/user-settings/@jupyterlab/apputils-extension/ COPY commands.jupyterlab-settings $HOME/.jupyter/lab/user-settings/@jupyterlab/codemirror-extension/ COPY shortcuts.jupyterlab-settings $HOME/.jupyter/lab/user-settings/@jupyterlab/shortcuts-extension/ diff --git a/jupyterlab/Makefile b/jupyterlab/Makefile index d981dc6b..8713ffa5 100644 --- a/jupyterlab/Makefile +++ b/jupyterlab/Makefile @@ -7,6 +7,7 @@ config-hook: @${BIN}/reconfigure_ask ${ENV_FILE} JUPYTERLAB_TRAEFIK_HOST "Enter the jupyterlab domain name" jupyterlab${INSTANCE_URL_SUFFIX}.${ROOT_DOMAIN} @${BIN}/reconfigure ${ENV_FILE} JUPYTERLAB_INSTANCE=$${instance:-default} @${BIN}/reconfigure_password ${ENV_FILE} JUPYTERLAB_JUPYTER_TOKEN + @${BIN}/reconfigure_auth ${ENV_FILE} JUPYTERLAB .PHONY: shell shell: @@ -15,3 +16,18 @@ shell: .PHONY: token token: @echo $$(${BIN}/dotenv -f ${ENV_FILE} get JUPYTERLAB_JUPYTER_TOKEN) + + +.PHONY: override-hook +override-hook: +#### This sets the override template variables for docker-compose.instance.yaml: +#### The template dynamically renders to docker-compose.override_{DOCKER_CONTEXT}_{INSTANCE}.yaml +#### These settings are used to automatically generate the service container labels, and traefik config, inside the template. +#### The variable arguments have three forms: `=` `=:` `=@` +#### name=VARIABLE_NAME # sets the template 'name' field to the value of VARIABLE_NAME found in the .env file +#### # (this hardcodes the value into docker-compose.override.yaml) +#### name=:VARIABLE_NAME # sets the template 'name' field to the literal string 'VARIABLE_NAME' +#### # (this hardcodes the string into docker-compose.override.yaml) +#### name=@VARIABLE_NAME # sets the template 'name' field to the literal string '${VARIABLE_NAME}' +#### # (used for regular docker-compose expansion of env vars by name.) + @${BIN}/docker_compose_override ${ENV_FILE} project=:jupyterlab instance=@JUPYTERLAB_INSTANCE traefik_host=@JUPYTERLAB_TRAEFIK_HOST http_auth=JUPYTERLAB_HTTP_AUTH http_auth_var=@JUPYTERLAB_HTTP_AUTH ip_sourcerange=@JUPYTERLAB_IP_SOURCERANGE oauth2=JUPYTERLAB_OAUTH2 authorized_group=JUPYTERLAB_OAUTH2_AUTHORIZED_GROUP diff --git a/jupyterlab/docker-compose.instance.yaml b/jupyterlab/docker-compose.instance.yaml new file mode 100644 index 00000000..7111aac2 --- /dev/null +++ b/jupyterlab/docker-compose.instance.yaml @@ -0,0 +1,48 @@ +#! This is a ytt template file for docker-compose.override.yaml +#! References: +#! https://carvel.dev/ytt +#! https://docs.docker.com/compose/extends/#adding-and-overriding-configuration +#! https://github.com/enigmacurry/d.rymcg.tech#overriding-docker-composeyaml-per-instance + +#! ### Standard project vars: +#@ load("@ytt:data", "data") +#@ project = data.values.project +#@ instance = data.values.instance +#@ context = data.values.context +#@ traefik_host = data.values.traefik_host +#@ ip_sourcerange = data.values.ip_sourcerange +#@ enable_http_auth = len(data.values.http_auth.strip()) > 0 +#@ http_auth = data.values.http_auth_var +#@ enable_oauth2 = data.values.oauth2 == "true" +#@ authorized_group = data.values.authorized_group +#@ enabled_middlewares = [] + +#@yaml/text-templated-strings +services: + jupyterlab: + #@ service = "jupyterlab" + labels: + #! Services must opt-in to be proxied by Traefik: + - "traefik.enable=true" + + #! 'router' is the fully qualified key in traefik for this router/service: project + instance + service + #@ router = "{}-{}-{}".format(project,instance,service) + + #! The host matching router rule: + - "traefik.http.routers.(@= router @).rule=Host(`(@= traefik_host @)`)" + - "traefik.http.routers.(@= router @).entrypoints=websecure" + #@ enabled_middlewares.append("{}-ipwhitelist".format(router)) + - "traefik.http.middlewares.(@= router @)-ipwhitelist.ipwhitelist.sourcerange=(@= ip_sourcerange @)" + + #@ if enable_http_auth: + #@ enabled_middlewares.append("{}-basicauth".format(router)) + - "traefik.http.middlewares.(@= router @)-basicauth.basicauth.users=(@= http_auth @)" + #@ end + + #@ if enable_oauth2: + #@ enabled_middlewares.append("traefik-forward-auth@docker") + #@ enabled_middlewares.append("header-authorization-group-{}@file".format(authorized_group)) + #@ end + + #! Apply all middlewares (do this at the end!) + - "traefik.http.routers.(@= router @).middlewares=(@= ','.join(enabled_middlewares) @)" diff --git a/larynx/.env-dist b/larynx/.env-dist index 7a32ae76..0194268a 100644 --- a/larynx/.env-dist +++ b/larynx/.env-dist @@ -1,9 +1,30 @@ # https://hub.docker.com/r/rhasspy/larynx/tags LARYNX_VERSION=1.1.0 LARYNX_TRAEFIK_HOST=larynx.example.com -LARYNX_HTTP_AUTH= +LARYNX_INSTANCE= # Filter access by IP address source range (CIDR): ##Disallow all access: 0.0.0.0/32 ##Allow all access: 0.0.0.0/0 LARYNX_IP_SOURCERANGE=0.0.0.0/0 + +# HTTP Basic Authentication: +# Use `make config` to fill this in properly, or set this to blank to disable. +LARYNX_HTTP_AUTH= + +# OAUTH2 +# Set to `true` to use OpenID/OAuth2 authentication via the +# traefik-forward-auth service in d.rymcg.tech. +# Using OpenID/OAuth2 will require login to access your app, +# but it will not affect what a successfully logged-in person can do in your +# app. If your app has built-in authentication and can check the user +# header that traefik-forward-auth sends, then your app can limit what the +# logged-in person can do in the app. But if your app can't check the user +# header, or if your app doesn't have built-in authentication at all, then +# any person with an account on your Gitea server can log into your app and +# have full access. +LARYNX_OAUTH2= +# In addition to Oauth2 authentication, you can configure basic authorization +# by entering which authorization group can log into your app. You create +# groups of email addresses in the `traefik` folder by running `make groups`. +LARYNX_OAUTH2_AUTHORIZED_GROUP= diff --git a/larynx/Makefile b/larynx/Makefile index 0e306493..961dd2b2 100644 --- a/larynx/Makefile +++ b/larynx/Makefile @@ -1,11 +1,27 @@ ROOT_DIR = .. include ${ROOT_DIR}/_scripts/Makefile.projects +include ${ROOT_DIR}/_scripts/Makefile.instance .PHONY: config-hook config-hook: - @${BIN}/reconfigure_ask ${ENV_FILE} LARYNX_TRAEFIK_HOST "Enter the gitea domain name" larynx.${ROOT_DOMAIN} - @${BIN}/reconfigure_htpasswd ${ENV_FILE} LARYNX_HTTP_AUTH + @${BIN}/reconfigure_ask ${ENV_FILE} LARYNX_TRAEFIK_HOST "Enter the gitea domain name" larynx${INSTANCE_URL_SUFFIX}.${ROOT_DOMAIN} + @${BIN}/reconfigure_auth ${ENV_FILE} LARYNX .PHONY: shell shell: @docker-compose --env-file ${ENV_FILE} exec -it $${service:-app} /bin/sh -c "/bin/bash || /bin/sh" + + +.PHONY: override-hook +override-hook: +#### This sets the override template variables for docker-compose.instance.yaml: +#### The template dynamically renders to docker-compose.override_{DOCKER_CONTEXT}_{INSTANCE}.yaml +#### These settings are used to automatically generate the service container labels, and traefik config, inside the template. +#### The variable arguments have three forms: `=` `=:` `=@` +#### name=VARIABLE_NAME # sets the template 'name' field to the value of VARIABLE_NAME found in the .env file +#### # (this hardcodes the value into docker-compose.override.yaml) +#### name=:VARIABLE_NAME # sets the template 'name' field to the literal string 'VARIABLE_NAME' +#### # (this hardcodes the string into docker-compose.override.yaml) +#### name=@VARIABLE_NAME # sets the template 'name' field to the literal string '${VARIABLE_NAME}' +#### # (used for regular docker-compose expansion of env vars by name.) + @${BIN}/docker_compose_override ${ENV_FILE} project=:larynx instance=@LARYNX_INSTANCE traefik_host=@LARYNX_TRAEFIK_HOST http_auth=LARYNX_HTTP_AUTH http_auth_var=@LARYNX_HTTP_AUTH ip_sourcerange=@LARYNX_IP_SOURCERANGE oauth2=LARYNX_OAUTH2 authorized_group=LARYNX_OAUTH2_AUTHORIZED_GROUP diff --git a/larynx/docker-compose.instance.yaml b/larynx/docker-compose.instance.yaml new file mode 100644 index 00000000..ef434379 --- /dev/null +++ b/larynx/docker-compose.instance.yaml @@ -0,0 +1,48 @@ +#! This is a ytt template file for docker-compose.override.yaml +#! References: +#! https://carvel.dev/ytt +#! https://docs.docker.com/compose/extends/#adding-and-overriding-configuration +#! https://github.com/enigmacurry/d.rymcg.tech#overriding-docker-composeyaml-per-instance + +#! ### Standard project vars: +#@ load("@ytt:data", "data") +#@ project = data.values.project +#@ instance = data.values.instance +#@ context = data.values.context +#@ traefik_host = data.values.traefik_host +#@ ip_sourcerange = data.values.ip_sourcerange +#@ enable_http_auth = len(data.values.http_auth.strip()) > 0 +#@ http_auth = data.values.http_auth_var +#@ enable_oauth2 = data.values.oauth2 == "true" +#@ authorized_group = data.values.authorized_group +#@ enabled_middlewares = [] + +#@yaml/text-templated-strings +services: + larynx: + #@ service = "larynx" + labels: + #! Services must opt-in to be proxied by Traefik: + - "traefik.enable=true" + + #! 'router' is the fully qualified key in traefik for this router/service: project + instance + service + #@ router = "{}-{}-{}".format(project,instance,service) + + #! The host matching router rule: + - "traefik.http.routers.(@= router @).rule=Host(`(@= traefik_host @)`)" + - "traefik.http.routers.(@= router @).entrypoints=websecure" + #@ enabled_middlewares.append("{}-ipwhitelist".format(router)) + - "traefik.http.middlewares.(@= router @)-ipwhitelist.ipwhitelist.sourcerange=(@= ip_sourcerange @)" + + #@ if enable_http_auth: + #@ enabled_middlewares.append("{}-basicauth".format(router)) + - "traefik.http.middlewares.(@= router @)-basicauth.basicauth.users=(@= http_auth @)" + #@ end + + #@ if enable_oauth2: + #@ enabled_middlewares.append("traefik-forward-auth@docker") + #@ enabled_middlewares.append("header-authorization-group-{}@file".format(authorized_group)) + #@ end + + #! Apply all middlewares (do this at the end!) + - "traefik.http.routers.(@= router @).middlewares=(@= ','.join(enabled_middlewares) @)" diff --git a/larynx/docker-compose.yaml b/larynx/docker-compose.yaml index 830bc394..9284a720 100644 --- a/larynx/docker-compose.yaml +++ b/larynx/docker-compose.yaml @@ -1,23 +1,14 @@ version: "3.9" services: - app: + larynx: image: rhasspy/larynx:${LARYNX_VERSION} restart: unless-stopped security_opt: - no-new-privileges:true volumes: - home:/root - labels: - - "traefik.enable=true" - - "traefik.http.routers.larynx-${LARYNX_INSTANCE:-default}.rule=Host(`${LARYNX_TRAEFIK_HOST}`)" - - "traefik.http.routers.larynx-${LARYNX_INSTANCE:-default}.entrypoints=websecure" - ## Authentication: - - "traefik.http.routers.larynx-${LARYNX_INSTANCE:-default}.middlewares=larynx-auth@docker" - - "traefik.http.middlewares.larynx-${LARYNX_INSTANCE:-default}-auth.basicauth.users=${LARYNX_HTTP_AUTH}" - - "traefik.http.middlewares.larynx-${LARYNX_INSTANCE:-default}-auth.basicauth.realm=Larynx" - - "traefik.http.middlewares.larynx-${LARYNX_INSTANCE:-default}-ipwhitelist.ipwhitelist.sourcerange=${LARYNX_IP_SOURCERANGE}" - - "traefik.http.routers.larynx-${LARYNX_INSTANCE:-default}.middlewares=larynx-${LARYNX_INSTANCE:-default}-ipwhitelist,larynx-${LARYNX_INSTANCE:-default}-auth" + labels: [] volumes: home: diff --git a/lemmy/.env-dist b/lemmy/.env-dist index eacfdf4b..b4bf7926 100644 --- a/lemmy/.env-dist +++ b/lemmy/.env-dist @@ -17,21 +17,10 @@ LEMMY_IP_SOURCERANGE=0.0.0.0/0 # HTTP Basic Authentication: # Use `make config` to fill this in properly, or set this to blank to disable. +### NOTE: For Lemmy, turning on auth will make your server +### non-federated and fully private. LEMMY_HTTP_AUTH= -# OAUTH2 -# Set to `yes` to use OpenID/OAuth2 authentication via the -# traefik-forward-auth service in d.rymcg.tech. -# Using OpenID/OAuth2 will require login to access your app, -# but it will not affect what a successfully logged-in person can do in your -# app. If your app has built-in authentication and can check the user -# header that traefik-forward-auth sends, then your app can limit what the -# logged-in person can do in the app. But if your app can't check the user -# header, or if your app doesn't have built-in authentication at all, then -# any person with an account on your Gitea server can log into your app and -# have full access. -LEMMY_OAUTH2=no - # You can run the whoami service as any user/group: LEMMY_UID=54321 LEMMY_GID=54321 @@ -49,3 +38,22 @@ LEMMY_PRINT_CONFIG=true # leaking through the RSS feeds, which happens due to # a bug: https://github.com/LemmyNet/lemmy/issues/3785 LEMMY_PRIVATE=false + +# OAUTH2 +### NOTE: For Lemmy, turning on Oauth will make your server +### non-federated and fully private. +# Set to `true` to use OpenID/OAuth2 authentication via the +# traefik-forward-auth service in d.rymcg.tech. +# Using OpenID/OAuth2 will require login to access your app, +# but it will not affect what a successfully logged-in person can do in your +# app. If your app has built-in authentication and can check the user +# header that traefik-forward-auth sends, then your app can limit what the +# logged-in person can do in the app. But if your app can't check the user +# header, or if your app doesn't have built-in authentication at all, then +# any person with an account on your Gitea server can log into your app and +# have full access. +LEMMY_OAUTH2= +# In addition to Oauth2 authentication, you can configure basic authorization +# by entering which authorization group can log into your app. You create +# groups of email addresses in the `traefik` folder by running `make groups`. +LEMMY_OAUTH2_AUTHORIZED_GROUP= diff --git a/lemmy/Makefile b/lemmy/Makefile index c83705a5..bb3157d4 100644 --- a/lemmy/Makefile +++ b/lemmy/Makefile @@ -10,10 +10,9 @@ config-hook: #### reconfigure_htpasswd will configure the HTTP Basic Authentication setting the var name and with a provided default value. @${BIN}/reconfigure_ask ${ENV_FILE} LEMMY_TRAEFIK_HOST "Enter the lemmy domain name" lemmy${INSTANCE_URL_SUFFIX}.${ROOT_DOMAIN} @${BIN}/reconfigure ${ENV_FILE} LEMMY_INSTANCE=$${instance:-default} - @${BIN}/reconfigure_htpasswd ${ENV_FILE} LEMMY_HTTP_AUTH default=no - @${BIN}/reconfigure_oauth2 ${ENV_FILE} LEMMY_OAUTH2 default=$$( ${BIN}/dotenv -f ${ENV_FILE} get LEMMY_OAUTH2 ) @echo "" @${BIN}/reconfigure_password ${ENV_FILE} LEMMY_POSTGRES_PW 32 + @${BIN}/confirm no "Would you like to reconfigure authentication? (NOTE: this will make your Lemmy instance fully private with no federation features, so don't do this if you want to read from other lemmy instances!)" "" && ${BIN}/reconfigure_auth ${ENV_FILE} LEMMY .PHONY: override-hook override-hook: @@ -27,7 +26,7 @@ override-hook: #### # (this hardcodes the string into docker-compose.override.yaml) #### name=@VARIABLE_NAME # sets the template 'name' field to the literal string '${VARIABLE_NAME}' #### # (used for regular docker-compose expansion of env vars by name.) - @${BIN}/docker_compose_override ${ENV_FILE} project=:lemmy instance=@LEMMY_INSTANCE traefik_host=@LEMMY_TRAEFIK_HOST http_auth=LEMMY_HTTP_AUTH http_auth_var=@LEMMY_HTTP_AUTH ip_sourcerange=@LEMMY_IP_SOURCERANGE lemmy_private=LEMMY_PRIVATE oauth2=LEMMY_OAUTH2 + @${BIN}/docker_compose_override ${ENV_FILE} project=:lemmy instance=@LEMMY_INSTANCE traefik_host=@LEMMY_TRAEFIK_HOST http_auth=LEMMY_HTTP_AUTH http_auth_var=@LEMMY_HTTP_AUTH ip_sourcerange=@LEMMY_IP_SOURCERANGE lemmy_private=LEMMY_PRIVATE oauth2=LEMMY_OAUTH2 authorized_group=LEMMY_OAUTH2_AUTHORIZED_GROUP .PHONY: shell shell: diff --git a/lemmy/README.md b/lemmy/README.md index 37f6fa8f..fc9eaa94 100644 --- a/lemmy/README.md +++ b/lemmy/README.md @@ -9,19 +9,18 @@ for the fediverse. make config ``` -This will ask you to enter the domain name to use, and whether or not -you want to configure a username/password via HTTP Basic Authentication. +This will ask you to enter the domain name to use. It automatically saves your responses into the configuration file `.env_{INSTANCE}`. -It will also ask if you want to use OpenID/OAuth2 authentication. -Using OpenID/OAuth2 will require a login to access your app, but it will not -affect what a successfully logged-in person can do in your app. If your app has -a built-in authorization mechanism that can check for the user header that -traefik-forward-auth sends, then your app can limit what the logged-in person -can do in the app. But if your app can't check the user header, or if your app -doesn't have built-in authorization at all, then any person with an account -on your Gitea server can log into your app and have full acces +### Auth + +If you turn on the Traefik auth middlewares, Lemmy cannot federate +properly (not even to pull from other instances). However, with auth +turned on, the app will still work as a fully private instance. + +If you wish to be able to pull posts from other instances, make sure +you select `No` when asked if you wish to turn on authentication. ## Install diff --git a/lemmy/docker-compose.instance.yaml b/lemmy/docker-compose.instance.yaml index a650f303..b8e4a2ee 100644 --- a/lemmy/docker-compose.instance.yaml +++ b/lemmy/docker-compose.instance.yaml @@ -13,7 +13,8 @@ #@ ip_sourcerange = data.values.ip_sourcerange #@ enable_http_auth = len(data.values.http_auth.strip()) > 0 #@ http_auth = data.values.http_auth_var -#@ enable_oauth2 = data.values.oauth2 +#@ enable_oauth2 = data.values.oauth2 == "true" +#@ authorized_group = data.values.authorized_group #@ enabled_middlewares = [] #! ### Custom project vars: @@ -36,15 +37,6 @@ services: #@ enabled_middlewares.append("{}-ipwhitelist".format(router)) - "traefik.http.middlewares.(@= router @)-ipwhitelist.ipwhitelist.sourcerange=(@= ip_sourcerange @)" - #@ if enable_http_auth: - #@ enabled_middlewares.append("{}-basicauth".format(router)) - - "traefik.http.middlewares.(@= router @)-basicauth.basicauth.users=(@= http_auth @)" - #@ end - - #@ if enable_oauth2 == "yes": - #@ enabled_middlewares.append("traefik-forward-auth@docker") - #@ end - #! Override the default port that proxy binds to, so that it lives in userspace >1024: #! You don't normally need to do this, as long as your image has #! an EXPOSE directive in it, Traefik will autodetect it, but this is how you can override it: @@ -56,6 +48,17 @@ services: #@ enabled_middlewares.append("{}-blockpath".format(router)) - "traefik.http.middlewares.(@= router @)-blockpath.plugin.blockpath.regex=^/feeds.*" #@ end - + + #@ if enable_http_auth: + #@ enabled_middlewares.append("{}-basicauth".format(router)) + - "traefik.http.middlewares.(@= router @)-basicauth.basicauth.users=(@= http_auth @)" + #@ end + + #@ if enable_oauth2: + #@ enabled_middlewares.append("traefik-forward-auth@docker") + #@ enabled_middlewares.append("header-authorization-group-{}@file".format(authorized_group)) + #@ end + + #! Apply all middlewares (do this at the end!) - "traefik.http.routers.(@= router @).middlewares=(@= ','.join(enabled_middlewares) @)" diff --git a/maubot/.env-dist b/maubot/.env-dist index c4be00f0..5e10745e 100644 --- a/maubot/.env-dist +++ b/maubot/.env-dist @@ -18,3 +18,23 @@ MAUBOT_GIT_REPO=https://github.com/maubot/maubot.git # but it contains all of the secrets in clear text! PRINT_CONFIG=false +# HTTP Basic Authentication: +# Use `make config` to fill this in properly, or set this to blank to disable. +MAUBOT_HTTP_AUTH= + +# OAUTH2 +# Set to `true` to use OpenID/OAuth2 authentication via the +# traefik-forward-auth service in d.rymcg.tech. +# Using OpenID/OAuth2 will require login to access your app, +# but it will not affect what a successfully logged-in person can do in your +# app. If your app has built-in authentication and can check the user +# header that traefik-forward-auth sends, then your app can limit what the +# logged-in person can do in the app. But if your app can't check the user +# header, or if your app doesn't have built-in authentication at all, then +# any person with an account on your Gitea server can log into your app and +# have full access. +MAUBOT_OAUTH2= +# In addition to Oauth2 authentication, you can configure basic authorization +# by entering which authorization group can log into your app. You create +# groups of email addresses in the `traefik` folder by running `make groups`. +MAUBOT_OAUTH2_AUTHORIZED_GROUP= diff --git a/maubot/Makefile b/maubot/Makefile index 6267e566..095a1f36 100644 --- a/maubot/Makefile +++ b/maubot/Makefile @@ -1,13 +1,15 @@ ROOT_DIR = .. include ${ROOT_DIR}/_scripts/Makefile.projects-no-open +include ${ROOT_DIR}/_scripts/Makefile.instance .PHONY: config-hook config-hook: - @${BIN}/reconfigure_ask ${ENV_FILE} MAUBOT_TRAEFIK_HOST "Enter the maubot domain name" maubot.${ROOT_DOMAIN} + @${BIN}/reconfigure_ask ${ENV_FILE} MAUBOT_TRAEFIK_HOST "Enter the maubot domain name" maubot${INSTANCE_URL_SUFFIX}.${ROOT_DOMAIN} @${BIN}/reconfigure ${ENV_FILE} MAUBOT_INSTANCE=$${instance:-default} @${BIN}/reconfigure_ask ${ENV_FILE} MATRIX_HOMESERVER "Enter the matrix homeserver" @${BIN}/reconfigure_ask ${ENV_FILE} MAUBOT_ADMIN_USER "Enter the maubot admin username to create" admin @${BIN}/reconfigure_password ${ENV_FILE} MAUBOT_ADMIN_PASSWORD + @${BIN}/reconfigure_auth ${ENV_FILE} MAUBOT .PHONY: open # Open the web-browser to the service URL open: @@ -28,3 +30,17 @@ login: @echo "Enter the Matrix password account." @docker-compose --env-file ${ENV_FILE} exec maubot mbc auth --update-client + +.PHONY: override-hook +override-hook: +#### This sets the override template variables for docker-compose.instance.yaml: +#### The template dynamically renders to docker-compose.override_{DOCKER_CONTEXT}_{INSTANCE}.yaml +#### These settings are used to automatically generate the service container labels, and traefik config, inside the template. +#### The variable arguments have three forms: `=` `=:` `=@` +#### name=VARIABLE_NAME # sets the template 'name' field to the value of VARIABLE_NAME found in the .env file +#### # (this hardcodes the value into docker-compose.override.yaml) +#### name=:VARIABLE_NAME # sets the template 'name' field to the literal string 'VARIABLE_NAME' +#### # (this hardcodes the string into docker-compose.override.yaml) +#### name=@VARIABLE_NAME # sets the template 'name' field to the literal string '${VARIABLE_NAME}' +#### # (used for regular docker-compose expansion of env vars by name.) + @${BIN}/docker_compose_override ${ENV_FILE} project=:maubot instance=@MAUBOT_INSTANCE traefik_host=@MAUBOT_TRAEFIK_HOST http_auth=MAUBOT_HTTP_AUTH http_auth_var=@MAUBOT_HTTP_AUTH ip_sourcerange=@MAUBOT_IP_SOURCERANGE oauth2=MAUBOT_OAUTH2 authorized_group=MAUBOT_OAUTH2_AUTHORIZED_GROUP diff --git a/maubot/docker-compose.instance.yaml b/maubot/docker-compose.instance.yaml new file mode 100644 index 00000000..bcb0e83b --- /dev/null +++ b/maubot/docker-compose.instance.yaml @@ -0,0 +1,51 @@ +#! This is a ytt template file for docker-compose.override.yaml +#! References: +#! https://carvel.dev/ytt +#! https://docs.docker.com/compose/extends/#adding-and-overriding-configuration +#! https://github.com/enigmacurry/d.rymcg.tech#overriding-docker-composeyaml-per-instance + +#! ### Standard project vars: +#@ load("@ytt:data", "data") +#@ project = data.values.project +#@ instance = data.values.instance +#@ context = data.values.context +#@ traefik_host = data.values.traefik_host +#@ ip_sourcerange = data.values.ip_sourcerange +#@ enable_http_auth = len(data.values.http_auth.strip()) > 0 +#@ http_auth = data.values.http_auth_var +#@ enable_oauth2 = data.values.oauth2 == "true" +#@ authorized_group = data.values.authorized_group +#@ enabled_middlewares = [] + +#@yaml/text-templated-strings +services: + maubot: + #@ service = "maubot" + labels: + #! Services must opt-in to be proxied by Traefik: + - "traefik.enable=true" + + #! 'router' is the fully qualified key in traefik for this router/service: project + instance + service + #@ router = "{}-{}-{}".format(project,instance,service) + + #! The host matching router rule: + - "traefik.http.routers.(@= router @).rule=Host(`(@= traefik_host @)`)" + - "traefik.http.routers.(@= router @).entrypoints=websecure" + #@ enabled_middlewares.append("{}-ipwhitelist".format(router)) + - "traefik.http.middlewares.(@= router @)-ipwhitelist.ipwhitelist.sourcerange=(@= ip_sourcerange @)" + + #@ if enable_http_auth: + #@ enabled_middlewares.append("{}-basicauth".format(router)) + - "traefik.http.middlewares.(@= router @)-basicauth.basicauth.users=(@= http_auth @)" + #@ end + + #@ if enable_oauth2: + #@ enabled_middlewares.append("traefik-forward-auth@docker") + #@ enabled_middlewares.append("header-authorization-group-{}@file".format(authorized_group)) + #@ end + + #! Override the default port that maubot binds to, so that it lives in userspace >1024: + - "traefik.http.services.(@= router @).loadbalancer.server.port=29316" + + #! Apply all middlewares (do this at the end!) + - "traefik.http.routers.(@= router @).middlewares=(@= ','.join(enabled_middlewares) @)" diff --git a/maubot/docker-compose.yaml b/maubot/docker-compose.yaml index 07d56242..a92288ad 100644 --- a/maubot/docker-compose.yaml +++ b/maubot/docker-compose.yaml @@ -29,11 +29,4 @@ services: restart: unless-stopped volumes: - data:/data - labels: - - "traefik.enable=true" - - "traefik.http.routers.maubot-${MAUBOT_INSTANCE:-default}.rule=Host(`${MAUBOT_TRAEFIK_HOST}`)" - - "traefik.http.routers.maubot-${MAUBOT_INSTANCE:-default}.entrypoints=websecure" - - "traefik.http.services.maubot-${MAUBOT_INSTANCE:-default}.loadBalancer.server.port=29316" - - "traefik.http.middlewares.maubot-${MAUBOT_INSTANCE:-default}-ipwhitelist.ipwhitelist.sourcerange=${MAUBOT_IP_SOURCERANGE}" - - "traefik.http.routers.maubot-${MAUBOT_INSTANCE:-default}.middlewares=maubot-${MAUBOT_INSTANCE:-default}-ipwhitelist" - + labels: [] diff --git a/nodered/.env-dist b/nodered/.env-dist index 0ae5f99b..042bbd37 100644 --- a/nodered/.env-dist +++ b/nodered/.env-dist @@ -1,4 +1,25 @@ NODERED_TRAEFIK_HOST=nodered.example.com -NODERED_HTTP_AUTH= NODERED_INSTANCE= -NODERED_IP_SOURCERANGE=0.0.0.0/0 \ No newline at end of file +NODERED_IP_SOURCERANGE=0.0.0.0/0 + + +# HTTP Basic Authentication: +# Use `make config` to fill this in properly, or set this to blank to disable. +NODERED_HTTP_AUTH= + +# OAUTH2 +# Set to `true` to use OpenID/OAuth2 authentication via the +# traefik-forward-auth service in d.rymcg.tech. +# Using OpenID/OAuth2 will require login to access your app, +# but it will not affect what a successfully logged-in person can do in your +# app. If your app has built-in authentication and can check the user +# header that traefik-forward-auth sends, then your app can limit what the +# logged-in person can do in the app. But if your app can't check the user +# header, or if your app doesn't have built-in authentication at all, then +# any person with an account on your Gitea server can log into your app and +# have full access. +NODERED_OAUTH2= +# In addition to Oauth2 authentication, you can configure basic authorization +# by entering which authorization group can log into your app. You create +# groups of email addresses in the `traefik` folder by running `make groups`. +NODERED_OAUTH2_AUTHORIZED_GROUP= diff --git a/nodered/Makefile b/nodered/Makefile index 401f19c0..ff966bea 100644 --- a/nodered/Makefile +++ b/nodered/Makefile @@ -6,4 +6,20 @@ include ${ROOT_DIR}/_scripts/Makefile.instance config-hook: @${BIN}/reconfigure_ask ${ENV_FILE} NODERED_TRAEFIK_HOST "Enter the nodered domain name" nodered${INSTANCE_URL_SUFFIX}.${ROOT_DOMAIN} @${BIN}/reconfigure ${ENV_FILE} NODERED_INSTANCE=$${instance:-default} - @${BIN}/reconfigure_htpasswd ${ENV_FILE} NODERED_HTTP_AUTH + @${BIN}/reconfigure_auth ${ENV_FILE} NODERED + + +.PHONY: override-hook +override-hook: +#### This sets the override template variables for docker-compose.instance.yaml: +#### The template dynamically renders to docker-compose.override_{DOCKER_CONTEXT}_{INSTANCE}.yaml +#### These settings are used to automatically generate the service container labels, and traefik config, inside the template. +#### The variable arguments have three forms: `=` `=:` `=@` +#### name=VARIABLE_NAME # sets the template 'name' field to the value of VARIABLE_NAME found in the .env file +#### # (this hardcodes the value into docker-compose.override.yaml) +#### name=:VARIABLE_NAME # sets the template 'name' field to the literal string 'VARIABLE_NAME' +#### # (this hardcodes the string into docker-compose.override.yaml) +#### name=@VARIABLE_NAME # sets the template 'name' field to the literal string '${VARIABLE_NAME}' +#### # (used for regular docker-compose expansion of env vars by name.) + @${BIN}/docker_compose_override ${ENV_FILE} project=:nodered instance=@NODERED_INSTANCE traefik_host=@NODERED_TRAEFIK_HOST http_auth=NODERED_HTTP_AUTH http_auth_var=@NODERED_HTTP_AUTH ip_sourcerange=@NODERED_IP_SOURCERANGE oauth2=NODERED_OAUTH2 authorized_group=NODERED_OAUTH2_AUTHORIZED_GROUP + diff --git a/nodered/docker-compose.instance.yaml b/nodered/docker-compose.instance.yaml new file mode 100644 index 00000000..2f1529d9 --- /dev/null +++ b/nodered/docker-compose.instance.yaml @@ -0,0 +1,48 @@ +#! This is a ytt template file for docker-compose.override.yaml +#! References: +#! https://carvel.dev/ytt +#! https://docs.docker.com/compose/extends/#adding-and-overriding-configuration +#! https://github.com/enigmacurry/d.rymcg.tech#overriding-docker-composeyaml-per-instance + +#! ### Standard project vars: +#@ load("@ytt:data", "data") +#@ project = data.values.project +#@ instance = data.values.instance +#@ context = data.values.context +#@ traefik_host = data.values.traefik_host +#@ ip_sourcerange = data.values.ip_sourcerange +#@ enable_http_auth = len(data.values.http_auth.strip()) > 0 +#@ http_auth = data.values.http_auth_var +#@ enable_oauth2 = data.values.oauth2 == "true" +#@ authorized_group = data.values.authorized_group +#@ enabled_middlewares = [] + +#@yaml/text-templated-strings +services: + nodered: + #@ service = "nodered" + labels: + #! Services must opt-in to be proxied by Traefik: + - "traefik.enable=true" + + #! 'router' is the fully qualified key in traefik for this router/service: project + instance + service + #@ router = "{}-{}-{}".format(project,instance,service) + + #! The host matching router rule: + - "traefik.http.routers.(@= router @).rule=Host(`(@= traefik_host @)`)" + - "traefik.http.routers.(@= router @).entrypoints=websecure" + #@ enabled_middlewares.append("{}-ipwhitelist".format(router)) + - "traefik.http.middlewares.(@= router @)-ipwhitelist.ipwhitelist.sourcerange=(@= ip_sourcerange @)" + + #@ if enable_http_auth: + #@ enabled_middlewares.append("{}-basicauth".format(router)) + - "traefik.http.middlewares.(@= router @)-basicauth.basicauth.users=(@= http_auth @)" + #@ end + + #@ if enable_oauth2: + #@ enabled_middlewares.append("traefik-forward-auth@docker") + #@ enabled_middlewares.append("header-authorization-group-{}@file".format(authorized_group)) + #@ end + + #! Apply all middlewares (do this at the end!) + - "traefik.http.routers.(@= router @).middlewares=(@= ','.join(enabled_middlewares) @)" diff --git a/nodered/docker-compose.yaml b/nodered/docker-compose.yaml index 1453c8c1..193969ce 100644 --- a/nodered/docker-compose.yaml +++ b/nodered/docker-compose.yaml @@ -4,19 +4,11 @@ volumes: node-red: services: - node-red: + nodered: image: "nodered/node-red" security_opt: - no-new-privileges:true restart: unless-stopped - labels: - - "traefik.enable=true" - - "traefik.http.routers.nodered-${NODERED_INSTANCE:-default}.rule=Host(`${NODERED_TRAEFIK_HOST}`)" - - "traefik.http.routers.nodered-${NODERED_INSTANCE:-default}.entrypoints=websecure" - ## Authentication: - - "traefik.http.middlewares.nodered-${NODERED_INSTANCE:-default}-auth.basicauth.users=${NODERED_HTTP_AUTH}" - - "traefik.http.middlewares.nodered-${NODERED_INSTANCE:-default}-auth.basicauth.realm=Node-RED" - - "traefik.http.middlewares.nodered-${NODERED_INSTANCE:-default}-ipwhitelist.ipwhitelist.sourcerange=${NODERED_IP_SOURCERANGE}" - - "traefik.http.routers.nodered-${NODERED_INSTANCE:-default}.middlewares=nodered-${NODERED_INSTANCE:-default}-ipwhitelist,nodered-${NODERED_INSTANCE:-default}-auth@docker" + labels: [] volumes: - node-red:/data diff --git a/pairdrop/.env-dist b/pairdrop/.env-dist index 5c791f61..76d5607a 100644 --- a/pairdrop/.env-dist +++ b/pairdrop/.env-dist @@ -16,6 +16,23 @@ PAIRDROP_IP_SOURCERANGE=0.0.0.0/0 # Use `make config` to fill this in properly, or set this to blank to disable. PAIRDROP_HTTP_AUTH= +# OAUTH2 +# Set to `true` to use OpenID/OAuth2 authentication via the +# traefik-forward-auth service in d.rymcg.tech. +# Using OpenID/OAuth2 will require login to access your app, +# but it will not affect what a successfully logged-in person can do in your +# app. If your app has built-in authentication and can check the user +# header that traefik-forward-auth sends, then your app can limit what the +# logged-in person can do in the app. But if your app can't check the user +# header, or if your app doesn't have built-in authentication at all, then +# any person with an account on your Gitea server can log into your app and +# have full access. +PAIRDROP_OAUTH2= +# In addition to Oauth2 authentication, you can configure basic authorization +# by entering which authorization group can log into your app. You create +# groups of email addresses in the `traefik` folder by running `make groups`. +PAIRDROP_OAUTH2_AUTHORIZED_GROUP= + ## You can run the pairdrop service as any user/group: PAIRDROP_UID=54321 PAIRDROP_GID=54321 diff --git a/pairdrop/Makefile b/pairdrop/Makefile index 8b168419..2c99658f 100644 --- a/pairdrop/Makefile +++ b/pairdrop/Makefile @@ -10,7 +10,7 @@ config-hook: #### reconfigure_htpasswd will configure the HTTP Basic Authentication setting the var name and with a provided default value. @${BIN}/reconfigure_ask ${ENV_FILE} PAIRDROP_TRAEFIK_HOST "Enter the pairdrop domain name" pairdrop${INSTANCE_URL_SUFFIX}.${ROOT_DOMAIN} @${BIN}/reconfigure ${ENV_FILE} PAIRDROP_INSTANCE=$${instance:-default} - @${BIN}/reconfigure_htpasswd ${ENV_FILE} PAIRDROP_HTTP_AUTH default=no + @${BIN}/reconfigure_auth ${ENV_FILE} PAIRDROP .PHONY: override-hook override-hook: @@ -24,7 +24,7 @@ override-hook: #### # (this hardcodes the string into docker-compose.override.yaml) #### name=@VARIABLE_NAME # sets the template 'name' field to the literal string '${VARIABLE_NAME}' #### # (used for regular docker-compose expansion of env vars by name.) - @${BIN}/docker_compose_override ${ENV_FILE} project=:pairdrop instance=@PAIRDROP_INSTANCE traefik_host=@PAIRDROP_TRAEFIK_HOST http_auth=PAIRDROP_HTTP_AUTH http_auth_var=@PAIRDROP_HTTP_AUTH ip_sourcerange=@PAIRDROP_IP_SOURCERANGE + @${BIN}/docker_compose_override ${ENV_FILE} project=:pairdrop instance=@PAIRDROP_INSTANCE traefik_host=@PAIRDROP_TRAEFIK_HOST http_auth=PAIRDROP_HTTP_AUTH http_auth_var=@PAIRDROP_HTTP_AUTH ip_sourcerange=@PAIRDROP_IP_SOURCERANGE oauth2=PAIRDROP_OAUTH2 authorized_group=PAIRDROP_OAUTH2_AUTHORIZED_GROUP .PHONY: shell shell: diff --git a/pairdrop/docker-compose.instance.yaml b/pairdrop/docker-compose.instance.yaml index 4ba73a98..7df35850 100644 --- a/pairdrop/docker-compose.instance.yaml +++ b/pairdrop/docker-compose.instance.yaml @@ -13,6 +13,8 @@ #@ ip_sourcerange = data.values.ip_sourcerange #@ enable_http_auth = len(data.values.http_auth.strip()) > 0 #@ http_auth = data.values.http_auth_var +#@ enable_oauth2 = data.values.oauth2 == "true" +#@ authorized_group = data.values.authorized_group #@ enabled_middlewares = [] #@yaml/text-templated-strings @@ -37,5 +39,10 @@ services: - "traefik.http.middlewares.(@= router @)-basicauth.basicauth.users=(@= http_auth @)" #@ end + #@ if enable_oauth2: + #@ enabled_middlewares.append("traefik-forward-auth@docker") + #@ enabled_middlewares.append("header-authorization-group-{}@file".format(authorized_group)) + #@ end + #! Apply all middlewares (do this at the end!) - "traefik.http.routers.(@= router @).middlewares=(@= ','.join(enabled_middlewares) @)" diff --git a/photoprism/.env-dist b/photoprism/.env-dist index e8eb5de0..1d8512fa 100644 --- a/photoprism/.env-dist +++ b/photoprism/.env-dist @@ -35,7 +35,11 @@ PHOTOPRISM_HTTP_AUTH= # header, or if your app doesn't have built-in authentication at all, then # any person with an account on your Gitea server can log into your app and # have full access. -PHOTOPRISM_OAUTH2=no +PHOTOPRISM_OAUTH2= +# In addition to Oauth2 authentication, you can configure basic authorization +# by entering which authorization group can log into your app. You create +# groups of email addresses in the `traefik` folder by running `make groups`. +PHOTOPRISM_OAUTH2_AUTHORIZED_GROUP= ## Traefik IP whitelist filter to block access to the client interface: ## (does not affect peer connections) diff --git a/photoprism/Makefile b/photoprism/Makefile index f2aa868d..a50b3bad 100644 --- a/photoprism/Makefile +++ b/photoprism/Makefile @@ -10,8 +10,7 @@ config-hook: #### reconfigure_htpasswd will configure the HTTP Basic Authentication setting the var name and with a provided default value. @${BIN}/reconfigure_ask ${ENV_FILE} PHOTOPRISM_TRAEFIK_HOST "Enter the photoprism domain name" photoprism${INSTANCE_URL_SUFFIX}.${ROOT_DOMAIN} @${BIN}/reconfigure ${ENV_FILE} PHOTOPRISM_INSTANCE=$${instance:-default} - @${BIN}/reconfigure_htpasswd ${ENV_FILE} PHOTOPRISM_HTTP_AUTH default=no - @${BIN}/reconfigure_oauth2 ${ENV_FILE} PHOTOPRISM_OAUTH2 default=$$( ${BIN}/dotenv -f ${ENV_FILE} get PHOTOPRISM_OAUTH2 ) + @${BIN}/reconfigure_auth ${ENV_FILE} PHOTOPRISM @echo "" @${BIN}/reconfigure_password ${ENV_FILE} PHOTOPRISM_DATABASE_PASSWORD 32 @@ -42,4 +41,4 @@ override-hook: #### # (this hardcodes the string into docker-compose.override.yaml) #### name=@VARIABLE_NAME # sets the template 'name' field to the literal string '${VARIABLE_NAME}' #### # (used for regular docker-compose expansion of env vars by name.) - @${BIN}/docker_compose_override ${ENV_FILE} project=:photoprism instance=@PHOTOPRISM_INSTANCE traefik_host=@PHOTOPRISM_TRAEFIK_HOST http_auth=PHOTOPRISM_HTTP_AUTH http_auth_var=@PHOTOPRISM_HTTP_AUTH ip_sourcerange=@PHOTOPRISM_IP_SOURCERANGE oauth2=PHOTOPRISM_OAUTH2 + @${BIN}/docker_compose_override ${ENV_FILE} project=:photoprism instance=@PHOTOPRISM_INSTANCE traefik_host=@PHOTOPRISM_TRAEFIK_HOST http_auth=PHOTOPRISM_HTTP_AUTH http_auth_var=@PHOTOPRISM_HTTP_AUTH ip_sourcerange=@PHOTOPRISM_IP_SOURCERANGE oauth2=PHOTOPRISM_OAUTH2 authorized_group=PHOTOPRISM_OAUTH2_AUTHORIZED_GROUP diff --git a/photoprism/README.md b/photoprism/README.md index 04ccf517..f8ae7492 100644 --- a/photoprism/README.md +++ b/photoprism/README.md @@ -9,19 +9,10 @@ AI-Powered Photos App for the Decentralized Web. make config ``` -This will ask you to enter the domain name to use, and whether or not -you want to configure a username/password via HTTP Basic Authentication. +This will ask you to enter the domain name to use. It automatically saves your responses into the configuration file `.env_{INSTANCE}`. -It will also ask if you want to use OpenID/OAuth2 authentication. -Using OpenID/OAuth2 will require a login to access your app, but it will not -affect what a successfully logged-in person can do in your app. If your app has -a built-in authorization mechanism that can check for the user header that -traefik-forward-auth sends, then your app can limit what the logged-in person -can do in the app. But if your app can't check the user header, or if your app -doesn't have built-in authorization at all, then any person with an account -on your Gitea server can log into your app and have full acces You'll also be prompted to enter a few configurations for PhotoPrism, but there are other Photoprism options you can configure by manually @@ -30,6 +21,26 @@ be sure to add them to `docker-compose.yaml` as well; and if you add an import volume, be sure to uncomment the corresponding line in the `photoprism` service in `docker-compose.yaml` as well. +### Authentication and Authorization + +Running `make config` will ask whether or not you want to configure +authentication for your app (on top of any authentication your app provides). +You can configure OpenID/OAuth2 or HTTP Basic Authentication. + +OAuth2 uses traefik-forward-auth to delegate authentication to an external +authority (eg. a self-deployed Gitea instance). Accessing this app will +require all users to login through that external service first. Once +authenticated, they may be authorized access only if their login id matches the +member list of the predefined authorization group configured for the app +(`PHOTOPRISM_OAUTH2_AUTHORIZED_GROUP`). Authorization groups are defined in the +Traefik config (`TRAEFIK_HEADER_AUTHORIZATION_GROUPS`) and can be +[created/modified](https://github.com/EnigmaCurry/d.rymcg.tech/blob/master/traefik/README.md#oauth2-authentication) +by running `make groups` in the `traefik` directory. + +For HTTP Basic Authentication, you will be prompted to enter username/password +logins which are stored in that app's `.env_{INSTANCE}` file. + + ## Install ``` diff --git a/photoprism/docker-compose.instance.yaml b/photoprism/docker-compose.instance.yaml index bd81d3d3..95ffad53 100644 --- a/photoprism/docker-compose.instance.yaml +++ b/photoprism/docker-compose.instance.yaml @@ -14,6 +14,7 @@ #@ enable_http_auth = len(data.values.http_auth.strip()) > 0 #@ http_auth = data.values.http_auth_var #@ enable_oauth2 = data.values.oauth2 +#@ authorized_group = data.values.authorized_group #@ enabled_middlewares = [] #@yaml/text-templated-strings @@ -40,6 +41,7 @@ services: #@ if enable_oauth2 == "yes": #@ enabled_middlewares.append("traefik-forward-auth@docker") + #@ enabled_middlewares.append("header-authorization-group-{}@file".format(authorized_group)) #@ end #! Override the default port that photoprism binds to, so that it lives in userspace >1024: diff --git a/piwigo/.env-dist b/piwigo/.env-dist index f77e85cc..a595788d 100644 --- a/piwigo/.env-dist +++ b/piwigo/.env-dist @@ -18,7 +18,7 @@ PIWIGO_IP_SOURCERANGE=0.0.0.0/0 PIWIGO_HTTP_AUTH= # OAUTH2 -# Set to `yes` to use OpenID/OAuth2 authentication via the +# Set to `true` to use OpenID/OAuth2 authentication via the # traefik-forward-auth service in d.rymcg.tech. # Using OpenID/OAuth2 will require login to access your app, # but it will not affect what a successfully logged-in person can do in your @@ -28,4 +28,8 @@ PIWIGO_HTTP_AUTH= # header, or if your app doesn't have built-in authentication at all, then # any person with an account on your Gitea server can log into your app and # have full access. -PIWIGO_OAUTH2=no \ No newline at end of file +PIWIGO_OAUTH2= +# In addition to Oauth2 authentication, you can configure basic authorization +# by entering which authorization group can log into your app. You create +# groups of email addresses in the `traefik` folder by running `make groups`. +PIWIGO_OAUTH2_AUTHORIZED_GROUP= diff --git a/piwigo/Makefile b/piwigo/Makefile index 8b97c7c5..bc3a0087 100644 --- a/piwigo/Makefile +++ b/piwigo/Makefile @@ -8,8 +8,7 @@ config-hook: @${BIN}/reconfigure_password ${ENV_FILE} PIWIGO_MARIADB_ROOT_PASSWORD @${BIN}/reconfigure_password ${ENV_FILE} PIWIGO_MARIADB_PASSWORD @${BIN}/reconfigure_ask ${ENV_FILE} TIMEZONE "Enter your timezone" UTC - @${BIN}/reconfigure_htpasswd ${ENV_FILE} PIWIGO_HTTP_AUTH default=no - @${BIN}/reconfigure_oauth2 ${ENV_FILE} PIWIGO_OAUTH2 default=$$( ${BIN}/dotenv -f ${ENV_FILE} get PIWIGO_OAUTH2 ) + @${BIN}/reconfigure_auth ${ENV_FILE} PIWIGO @echo "" .PHONY: override-hook @@ -24,4 +23,4 @@ override-hook: #### # (this hardcodes the string into docker-compose.override.yaml) #### name=@VARIABLE_NAME # sets the template 'name' field to the literal string '${VARIABLE_NAME}' #### # (used for regular docker-compose expansion of env vars by name.) - @${BIN}/docker_compose_override ${ENV_FILE} project=:piwigo instance=@PIWIGO_INSTANCE traefik_host=@PIWIGO_TRAEFIK_HOST http_auth=PIWIGO_HTTP_AUTH http_auth_var=@PIWIGO_HTTP_AUTH ip_sourcerange=@PIWIGO_IP_SOURCERANGE oauth2=PIWIGO_OAUTH2 + @${BIN}/docker_compose_override ${ENV_FILE} project=:piwigo instance=@PIWIGO_INSTANCE traefik_host=@PIWIGO_TRAEFIK_HOST http_auth=PIWIGO_HTTP_AUTH http_auth_var=@PIWIGO_HTTP_AUTH ip_sourcerange=@PIWIGO_IP_SOURCERANGE oauth2=PIWIGO_OAUTH2 authorized_group=PIWIGO_OAUTH2_AUTHORIZED_GROUP diff --git a/piwigo/README.md b/piwigo/README.md index 52cec03c..4fe954dd 100644 --- a/piwigo/README.md +++ b/piwigo/README.md @@ -5,20 +5,10 @@ Run `make config` to automatically configure your `.env_${DOCKER_CONTEXT}_default` file. -This will ask you to enter the domain name to use, and whether or not -you want to configure a username/password via HTTP Basic Authentication. +This will ask you to enter the domain name to use. It automatically saves your responses into the configuration file `.env_{INSTANCE}`. -It will also ask if you want to use OpenID/OAuth2 authentication. -Using OpenID/OAuth2 will require a login to access your app, but it will not -affect what a successfully logged-in person can do in your app. If your app has -a built-in authorization mechanism that can check for the user header that -traefik-forward-auth sends, then your app can limit what the logged-in person -can do in the app. But if your app can't check the user header, or if your app -doesn't have built-in authorization at all, then any person with an account -on your Gitea server can log into your app and have full acces - Alternatively to running `make config`, you can manually copy `.env-dist` to `.env_${DOCKER_CONTEXT}_default` and edit the variables accordingly: @@ -29,8 +19,30 @@ Alternatively to running `make config`, you can manually copy * `MARIADB_PASSWORD` the mysql user password * `TIMEZONE` the timezone, in the format of [TZ database name](https://en.wikipedia.org/wiki/List_of_tz_database_time_zones) * `PIWIGO_OAUTH2` set to `yes` to configure Oauth2 authentication (in addition to Piwigo's own internal authentication) + * `PIWIGO_OAUTH2_AUTHORIZED_GROUP` the name of an authorization group that you a created when you ran `make groups` in the `traefik` directory * `PIWIGO_HTTP_AUTH` it's easiest to configure this variable via `make config`, but you can manually enter 1 or more credentials in the format `:[,:...]` +### Authentication and Authorization + +Running `make config` will ask whether or not you want to configure +authentication for your app (on top of any authentication your app provides). +You can configure OpenID/OAuth2 or HTTP Basic Authentication. + +OAuth2 uses traefik-forward-auth to delegate authentication to an external +authority (eg. a self-deployed Gitea instance). Accessing this app will +require all users to login through that external service first. Once +authenticated, they may be authorized access only if their login id matches the +member list of the predefined authorization group configured for the app +(`PIWIGO_OAUTH2_AUTHORIZED_GROUP`). Authorization groups are defined in the +Traefik config (`TRAEFIK_HEADER_AUTHORIZATION_GROUPS`) and can be +[created/modified](https://github.com/EnigmaCurry/d.rymcg.tech/blob/master/traefik/README.md#oauth2-authentication) +by running `make groups` in the `traefik` directory. + +For HTTP Basic Authentication, you will be prompted to enter username/password +logins which are stored in that app's `.env_{INSTANCE}` file. + + + Once the `.env_${DOCKER_CONTEXT}_default` file is configured install piwigo: ``` diff --git a/piwigo/docker-compose.instance.yaml b/piwigo/docker-compose.instance.yaml index e0916776..7589a33c 100644 --- a/piwigo/docker-compose.instance.yaml +++ b/piwigo/docker-compose.instance.yaml @@ -13,7 +13,8 @@ #@ ip_sourcerange = data.values.ip_sourcerange #@ enable_http_auth = len(data.values.http_auth.strip()) > 0 #@ http_auth = data.values.http_auth_var -#@ enable_oauth2 = data.values.oauth2 +#@ enable_oauth2 = data.values.oauth2 == "true" +#@ authorized_group = data.values.authorized_group #@ enabled_middlewares = [] #@yaml/text-templated-strings @@ -38,8 +39,9 @@ services: - "traefik.http.middlewares.(@= router @)-basicauth.basicauth.users=(@= http_auth @)" #@ end - #@ if enable_oauth2 == "yes": + #@ if enable_oauth2: #@ enabled_middlewares.append("traefik-forward-auth@docker") + #@ enabled_middlewares.append("header-authorization-group-{}@file".format(authorized_group)) #@ end #! Apply all middlewares (do this at the end!) diff --git a/privatebin/.env-dist b/privatebin/.env-dist index 2d7a9d0d..8b12b89d 100644 --- a/privatebin/.env-dist +++ b/privatebin/.env-dist @@ -1,4 +1,24 @@ PRIVATEBIN_TRAEFIK_HOST=bin.example.com PRIVATEBIN_INSTANCE= PRIVATEBIN_IP_SOURCERANGE=0.0.0.0/0 + +# HTTP Basic Authentication: +# Use `make config` to fill this in properly, or set this to blank to disable. PRIVATEBIN_HTTP_AUTH= + +# OAUTH2 +# Set to `true` to use OpenID/OAuth2 authentication via the +# traefik-forward-auth service in d.rymcg.tech. +# Using OpenID/OAuth2 will require login to access your app, +# but it will not affect what a successfully logged-in person can do in your +# app. If your app has built-in authentication and can check the user +# header that traefik-forward-auth sends, then your app can limit what the +# logged-in person can do in the app. But if your app can't check the user +# header, or if your app doesn't have built-in authentication at all, then +# any person with an account on your Gitea server can log into your app and +# have full access. +PRIVATEBIN_OAUTH2= +# In addition to Oauth2 authentication, you can configure basic authorization +# by entering which authorization group can log into your app. You create +# groups of email addresses in the `traefik` folder by running `make groups`. +PRIVATEBIN_OAUTH2_AUTHORIZED_GROUP= diff --git a/privatebin/Makefile b/privatebin/Makefile index 94b74e6f..dc2732f5 100644 --- a/privatebin/Makefile +++ b/privatebin/Makefile @@ -1,11 +1,12 @@ ROOT_DIR = .. include ${ROOT_DIR}/_scripts/Makefile.projects +include ${ROOT_DIR}/_scripts/Makefile.instance .PHONY: config-hook config-hook: - @${BIN}/reconfigure_ask ${ENV_FILE} PRIVATEBIN_TRAEFIK_HOST "Enter the privatebin domain name" bin.${ROOT_DOMAIN} + @${BIN}/reconfigure_ask ${ENV_FILE} PRIVATEBIN_TRAEFIK_HOST "Enter the privatebin domain name" bin${INSTANCE_URL_SUFFIX}.${ROOT_DOMAIN} @${BIN}/reconfigure ${ENV_FILE} PRIVATEBIN_INSTANCE=$${instance:-default} - @${BIN}/reconfigure_htpasswd ${ENV_FILE} PRIVATEBIN_HTTP_AUTH default=no + @${BIN}/reconfigure_auth ${ENV_FILE} PRIVATEBIN .PHONY: override-hook override-hook: @@ -19,4 +20,4 @@ override-hook: #### # (this hardcodes the string into docker-compose.override.yaml) #### name=@VARIABLE_NAME # sets the template 'name' field to the literal string '${VARIABLE_NAME}' #### # (used for regular docker-compose expansion of env vars by name.) - @${BIN}/docker_compose_override ${ENV_FILE} project=:privatebin instance=@PRIVATEBIN_INSTANCE traefik_host=@PRIVATEBIN_TRAEFIK_HOST http_auth=PRIVATEBIN_HTTP_AUTH http_auth_var=@PRIVATEBIN_HTTP_AUTH ip_sourcerange=@PRIVATEBIN_IP_SOURCERANGE + @${BIN}/docker_compose_override ${ENV_FILE} project=:privatebin instance=@PRIVATEBIN_INSTANCE traefik_host=@PRIVATEBIN_TRAEFIK_HOST http_auth=PRIVATEBIN_HTTP_AUTH http_auth_var=@PRIVATEBIN_HTTP_AUTH ip_sourcerange=@PRIVATEBIN_IP_SOURCERANGE oauth2=PRIVATEBIN_OAUTH2 authorized_group=PRIVATEBIN_OAUTH2_AUTHORIZED_GROUP diff --git a/privatebin/docker-compose.instance.yaml b/privatebin/docker-compose.instance.yaml index 543fa3bb..2b138d46 100644 --- a/privatebin/docker-compose.instance.yaml +++ b/privatebin/docker-compose.instance.yaml @@ -13,6 +13,8 @@ #@ ip_sourcerange = data.values.ip_sourcerange #@ enable_http_auth = len(data.values.http_auth.strip()) > 0 #@ http_auth = data.values.http_auth_var +#@ enable_oauth2 = data.values.oauth2 == "true" +#@ authorized_group = data.values.authorized_group #@ enabled_middlewares = [] #@yaml/text-templated-strings @@ -37,5 +39,10 @@ services: - "traefik.http.middlewares.(@= router @)-basicauth.basicauth.users=(@= http_auth @)" #@ end + #@ if enable_oauth2: + #@ enabled_middlewares.append("traefik-forward-auth@docker") + #@ enabled_middlewares.append("header-authorization-group-{}@file".format(authorized_group)) + #@ end + #! Apply all middlewares (do this at the end!) - "traefik.http.routers.(@= router @).middlewares=(@= ','.join(enabled_middlewares) @)" diff --git a/qbittorrent-wireguard/.env-dist b/qbittorrent-wireguard/.env-dist index 85e8d8c7..58e41c8f 100644 --- a/qbittorrent-wireguard/.env-dist +++ b/qbittorrent-wireguard/.env-dist @@ -16,6 +16,23 @@ QBITTORRENT_PEER_PORT=51413 ## Traefik htpasswd encoded authentication ## (automatically set via `make config`): QBITTORRENT_HTTP_AUTH= +# OAUTH2 +# Set to `true` to use OpenID/OAuth2 authentication via the +# traefik-forward-auth service in d.rymcg.tech. +# Using OpenID/OAuth2 will require login to access your app, +# but it will not affect what a successfully logged-in person can do in your +# app. If your app has built-in authentication and can check the user +# header that traefik-forward-auth sends, then your app can limit what the +# logged-in person can do in the app. But if your app can't check the user +# header, or if your app doesn't have built-in authentication at all, then +# any person with an account on your Gitea server can log into your app and +# have full access. +QBITTORRENT_OAUTH2= +# In addition to Oauth2 authentication, you can configure basic authorization +# by entering which authorization group can log into your app. You create +# groups of email addresses in the `traefik` folder by running `make groups`. +QBITTORRENT_OAUTH2_AUTHORIZED_GROUP= + ## Traefik IP whitelist filter to block access to the client interface: ## (does not affect peer connections) QBITTORRENT_IP_SOURCERANGE=0.0.0.0/0 @@ -40,19 +57,19 @@ QBITTORRENT_BLOCKLIST_URL=https://github.com/Naunter/BT_BlockLists/raw/master/bt ## qBittorrent config options ## Once up and running, you can configure qBittorrent in its web UI, but ## qBittorrent's configs are reset on each startup of the Docker container. -## So we set them in environment variables on each startup. -## -## The qBittorrent configurations are not include in `make config` - you'll +## So we set them in environment variables on each startup. +## +## The qBittorrent configurations are not included in `make config` - you'll ## need to manually edit your `.env` file to adjust them. -## +## ## You might need to install qBittorrent and set the variable in its web ## UI, then copy the value from ## `/var/lib/docker/volumes//qBittorrent/_data/qBittorrent/qBittorrent.conf` -## (on the host) and paste it your `.env` file. -## +## (on the host) and paste it your `.env` file. +## ## In your `.env` file, the lines in \[brackets\] are simply qBittorrent ## configuration categories, for your reference. -## +## ## If you change any qBittorrent config values, run `make install`. # [AutoRun] @@ -75,8 +92,9 @@ QBITTORRENT_SessionMaxActiveDownloads=3 QBITTORRENT_SessionMaxActiveTorrents=3 QBITTORRENT_SessionMaxActiveUploads=3 QBITTORRENT_SessionTempPath=/downloads/incomplete/ -QBITTORRENT_SessionTempPathEnabled=true QBITTORRENT_SessionTorrentExportDirectory=/downloads/torrents +QBITTORRENT_SessionTempPathEnabled=true +QBITTORRENT_SessionTorrentContentLayout=Subfolder QBITTORRENT_SessionUseAlternativeGlobalSpeedLimit=false QBITTORRENT_SessionBandwidthSchedulerEnabled=false @@ -95,11 +113,6 @@ QBITTORRENT_MailNotificationreq_ssl=false QBITTORRENT_MailNotificationsender=qBittorrent_notification@example.com QBITTORRENT_MailNotificationsmtp_server=smtp.changeme.com QBITTORRENT_MailNotificationusername= -QBITTORRENT_WebUICustomHTTPHeaders= -QBITTORRENT_WebUICustomHTTPHeadersEnabled=false -QBITTORRENT_WebUIHostHeaderValidation=true -QBITTORRENT_WebUIReverseProxySupportEnabled=false -QBITTORRENT_WebUITrustedReverseProxiesList= QBITTORRENT_Schedulerdays= QBITTORRENT_Schedulerend_time= QBITTORRENT_Schedulerstart_time= diff --git a/qbittorrent-wireguard/Makefile b/qbittorrent-wireguard/Makefile index 32e28eb3..7e85b58e 100644 --- a/qbittorrent-wireguard/Makefile +++ b/qbittorrent-wireguard/Makefile @@ -18,17 +18,33 @@ config-hook: @${BIN}/reconfigure_ask ${ENV_FILE} QBITTORRENT_VPN_CLIENT_PEER_PUBLIC_KEY "Enter the Peer PublicKey (ends with =)" @${BIN}/reconfigure_ask ${ENV_FILE} QBITTORRENT_VPN_CLIENT_PEER_ENDPOINT "Enter the Peer Endpoint (host:port)" @${BIN}/reconfigure_ask ${ENV_FILE} QBITTORRENT_IP_SOURCERANGE "Enter the allowed client IP source range (eg. 192.168.1.1/24 or 0.0.0.0/0)" - @${BIN}/reconfigure_htpasswd ${ENV_FILE} QBITTORRENT_HTTP_AUTH + @${BIN}/reconfigure_auth ${ENV_FILE} QBITTORRENT .PHONY: shell shell: - docker compose --env-file=${ENV_FILE} exec -it $${service:-qbittorrent} /bin/sh -c "/bin/bash || /bin/sh" + @make --no-print-directory docker-compose-shell SERVICE=qbittorrent COMMAND=/bin/bash .PHONY: check-vpn check-vpn: docker compose --env-file=${ENV_FILE} exec -it qbittorrent /bin/sh -c "curl https://am.i.mullvad.net/json | jq" -.PHONY: destroy # Deletes containers AND data volumes -destroy: check-instance-project - @${BIN}/confirm no "Do you want to destroy all $$(basename $${PWD}) services AND volumes for the given context/instance (${ENV_FILE})" "?" && (make --no-print-directory destroy-hook-pre-rule-exists 2>/dev/null && make --no-print-directory destroy-hook-pre || true) && make --no-print-directory docker-compose-lifecycle-cmd EXTRA_ARGS="down -v" && (make --no-print-directory destroy-hook-post-rule-exists 2>/dev/null && make --no-print-directory destroy-hook-post || true) +### Why was destroy here? +# .PHONY: destroy # Deletes containers AND data volumes +# destroy: check-instance-project +# @${BIN}/confirm no "Do you want to destroy all $$(basename $${PWD}) services AND volumes for the given context/instance (${ENV_FILE})" "?" && (make --no-print-directory destroy-hook-pre-rule-exists 2>/dev/null && make --no-print-directory destroy-hook-pre || true) && make --no-print-directory docker-compose-lifecycle-cmd EXTRA_ARGS="down -v" && (make --no-print-directory destroy-hook-post-rule-exists 2>/dev/null && make --no-print-directory destroy-hook-post || true) + +.PHONY: override-hook +override-hook: +#### This sets the override template variables for docker-compose.instance.yaml: +#### The template dynamically renders to docker-compose.override_{DOCKER_CONTEXT}_{INSTANCE}.yaml +#### These settings are used to automatically generate the service container labels, and traefik config, inside the template. +#### The variable arguments have three forms: `=` `=:` `=@` +#### name=VARIABLE_NAME # sets the template 'name' field to the value of VARIABLE_NAME found in the .env file +#### # (this hardcodes the value into docker-compose.override.yaml) +#### name=:VARIABLE_NAME # sets the template 'name' field to the literal string 'VARIABLE_NAME' +#### # (this hardcodes the string into docker-compose.override.yaml) +#### name=@VARIABLE_NAME # sets the template 'name' field to the literal string '${VARIABLE_NAME}' +#### # (used for regular docker-compose expansion of env vars by name.) + @${BIN}/docker_compose_override ${ENV_FILE} project=:qbitorrent instance=@QBITTORRENT_INSTANCE traefik_host=@QBITTORRENT_TRAEFIK_HOST http_auth=QBITTORRENT_HTTP_AUTH http_auth_var=@QBITTORRENT_HTTP_AUTH ip_sourcerange=@QBITTORRENT_IP_SOURCERANGE oauth2=QBITTORRENT_OAUTH2 authorized_group=QBITTORRENT_OAUTH2_AUTHORIZED_GROUP + diff --git a/qbittorrent-wireguard/README.md b/qbittorrent-wireguard/README.md index 90d63852..5eff2d1f 100644 --- a/qbittorrent-wireguard/README.md +++ b/qbittorrent-wireguard/README.md @@ -5,6 +5,12 @@ client combined with the [Wireguard](https://www.wireguard.com/) VPN service. Connect wireguard to your VPN provider and anonymize your peer connections. +(Historical note: there is also an older version of this config based +upon [Transmission](https://transmissionbt.com/), and this is still +available in the project's [_attic](../_attic) directory, but it is +unsupported. You should prefer qbittorrent anyway, to benefit from +bittorrent v2 spec.) + ## Setup ### Gather VPN provider config diff --git a/qbittorrent-wireguard/docker-compose.instance.yaml b/qbittorrent-wireguard/docker-compose.instance.yaml new file mode 100644 index 00000000..e451fbc0 --- /dev/null +++ b/qbittorrent-wireguard/docker-compose.instance.yaml @@ -0,0 +1,50 @@ +#! This is a ytt template file for docker-compose.override.yaml +#! References: +#! https://carvel.dev/ytt +#! https://docs.docker.com/compose/extends/#adding-and-overriding-configuration +#! https://github.com/enigmacurry/d.rymcg.tech#overriding-docker-composeyaml-per-instance + +#! ### Standard project vars: +#@ load("@ytt:data", "data") +#@ project = data.values.project +#@ instance = data.values.instance +#@ context = data.values.context +#@ traefik_host = data.values.traefik_host +#@ ip_sourcerange = data.values.ip_sourcerange +#@ enable_http_auth = len(data.values.http_auth.strip()) > 0 +#@ http_auth = data.values.http_auth_var +#@ enable_oauth2 = data.values.oauth2 == "true" +#@ authorized_group = data.values.authorized_group +#@ enabled_middlewares = [] + +#@yaml/text-templated-strings +services: + wireguard: + #@ service = "wireguard" + labels: + #! Services must opt-in to be proxied by Traefik: + - "traefik.enable=true" + + #! 'router' is the fully qualified key in traefik for this router/service: project + instance + service + #@ router = "{}-{}-{}".format(project,instance,service) + + #! The host matching router rule: + - "traefik.http.routers.(@= router @).rule=Host(`(@= traefik_host @)`)" + - "traefik.http.routers.(@= router @).entrypoints=websecure" + #@ enabled_middlewares.append("{}-ipwhitelist".format(router)) + - "traefik.http.middlewares.(@= router @)-ipwhitelist.ipwhitelist.sourcerange=(@= ip_sourcerange @)" + + #@ if enable_http_auth: + #@ enabled_middlewares.append("{}-basicauth".format(router)) + - "traefik.http.middlewares.(@= router @)-basicauth.basicauth.users=(@= http_auth @)" + #@ end + + #@ if enable_oauth2: + #@ enabled_middlewares.append("traefik-forward-auth@docker") + #@ enabled_middlewares.append("header-authorization-group-{}@file".format(authorized_group)) + #@ end + + - "traefik.http.services.(@= router @).loadbalancer.server.port=8080" + + #! Apply all middlewares (do this at the end!) + - "traefik.http.routers.(@= router @).middlewares=(@= ','.join(enabled_middlewares) @)" diff --git a/qbittorrent-wireguard/docker-compose.yaml b/qbittorrent-wireguard/docker-compose.yaml index 7ec873a0..add2ce74 100644 --- a/qbittorrent-wireguard/docker-compose.yaml +++ b/qbittorrent-wireguard/docker-compose.yaml @@ -35,15 +35,7 @@ services: - wireguard:/config - /lib/modules:/lib/modules restart: always - labels: - - "traefik.enable=true" - - "traefik.http.routers.qbittorrent-${QBITTORRENT_INSTANCE:-default}.rule=Host(`${QBITTORRENT_TRAEFIK_HOST}`)" - - "traefik.http.routers.qbittorrent-${QBITTORRENT_INSTANCE:-default}.entrypoints=websecure" - - "traefik.http.services.qbittorrent-${QBITTORRENT_INSTANCE:-default}.loadBalancer.server.port=8080" - ## Authentication: - - "traefik.http.middlewares.qbittorrent-${QBITTORRENT_INSTANCE:-default}-auth.basicauth.users=${QBITTORRENT_HTTP_AUTH}" - - "traefik.http.middlewares.qbittorrent-${QBITTORRENT_INSTANCE:-default}-ipwhitelist.ipwhitelist.sourcerange=${QBITTORRENT_IP_SOURCERANGE}" - - "traefik.http.routers.qbittorrent-${QBITTORRENT_INSTANCE:-default}.middlewares=qbittorrent-${QBITTORRENT_INSTANCE:-default}-ipwhitelist,qbittorrent-${QBITTORRENT_INSTANCE:-default}-auth" + labels: [] qbittorrent-config: build: @@ -88,22 +80,11 @@ services: - QBITTORRENT_MailNotificationsender - QBITTORRENT_MailNotificationsmtp_server - QBITTORRENT_MailNotificationusername - - QBITTORRENT_WebUICustomHTTPHeaders - - QBITTORRENT_WebUICustomHTTPHeadersEnabled - - QBITTORRENT_WebUIHostHeaderValidation - - QBITTORRENT_WebUILocalHostAuth - - QBITTORRENT_WebUIReverseProxySupportEnabled - - QBITTORRENT_WebUITrustedReverseProxiesList - QBITTORRENT_Schedulerdays - QBITTORRENT_Schedulerend_time - QBITTORRENT_Schedulerstart_time - QBITTORRENT_SessionUseAlternativeGlobalSpeedLimit - QBITTORRENT_SessionBandwidthSchedulerEnabled - - QBITTORRENT_WebUIPassword_PBKDF2 - - QBITTORRENT_WebUIServerDomains - - QBITTORRENT_WebUIUsername - - QBITTORRENT_WebUIAuthSubnetWhitelistEnabled - - QBITTORRENT_WebUIAuthSubnetWhitelist volumes: - qbittorrent-config:/config diff --git a/qbittorrent-wireguard/qbittorrent-config/template/qBittorrent.conf b/qbittorrent-wireguard/qbittorrent-config/template/qBittorrent.conf index 18ccd372..e00b89f3 100644 --- a/qbittorrent-wireguard/qbittorrent-config/template/qBittorrent.conf +++ b/qbittorrent-wireguard/qbittorrent-config/template/qBittorrent.conf @@ -21,20 +21,20 @@ MailNotification\sender=${QBITTORRENT_MailNotificationsender} MailNotification\smtp_server=${QBITTORRENT_MailNotificationsmtp_server} MailNotification\username=${QBITTORRENT_MailNotificationusername} WebUI\ServerDomains=${QBITTORRENT_TRAEFIK_HOST} -WebUI\AuthSubnetWhitelist=0.0.0.0/0 -WebUI\AuthSubnetWhitelistEnabled=true WebUI\AlternativeUIEnabled=${QBITTORRENT_WebUIAlternativeUIEnabled} -WebUI\CustomHTTPHeaders=${QBITTORRENT_WebUICustomHTTPHeaders} -WebUI\CustomHTTPHeadersEnabled=${QBITTORRENT_WebUICustomHTTPHeadersEnabled} -WebUI\HostHeaderValidation=${QBITTORRENT_WebUIHostHeaderValidation} -WebUI\ReverseProxySupportEnabled=${QBITTORRENT_WebUIReverseProxySupportEnabled} -WebUI\TrustedReverseProxiesList=${QBITTORRENT_WebUITrustedReverseProxiesList} Scheduler\days=${QBITTORRENT_Schedulerdays} Scheduler\end_time=${QBITTORRENT_Schedulerend_time} Scheduler\start_time=${QBITTORRENT_Schedulerstart_time} WebUI\LocalHostAuth=false -WebUI\Password_PBKDF2=${QBITTORRENT_WebUIPassword_PBKDF2} -WebUI\Username=admin +WebUI\CSRFProtection= +WebUI\ClickjackingProtection= +WebUI\AuthSubnetWhitelist=0.0.0.0/0 +WebUI\AuthSubnetWhitelistEnabled=true +WebUI\CustomHTTPHeaders= +WebUI\CustomHTTPHeadersEnabled= +WebUI\HostHeaderValidation=false +WebUI\ReverseProxySupportEnabled=false +WebUI\TrustedReverseProxiesList= [BitTorrent] Session\AddExtensionToIncompleteFiles=${QBITTORRENT_SessionAddExtensionToIncompleteFiles} diff --git a/redbean/.env-dist b/redbean/.env-dist index 547f633e..19fb2afd 100644 --- a/redbean/.env-dist +++ b/redbean/.env-dist @@ -33,6 +33,24 @@ REDBEAN_IP_SOURCERANGE=0.0.0.0/0 # Use `make config` to fill this in properly, or set this to blank to disable. REDBEAN_HTTP_AUTH= +# OAUTH2 +# Set to `true` to use OpenID/OAuth2 authentication via the +# traefik-forward-auth service in d.rymcg.tech. +# Using OpenID/OAuth2 will require login to access your app, +# but it will not affect what a successfully logged-in person can do in your +# app. If your app has built-in authentication and can check the user +# header that traefik-forward-auth sends, then your app can limit what the +# logged-in person can do in the app. But if your app can't check the user +# header, or if your app doesn't have built-in authentication at all, then +# any person with an account on your Gitea server can log into your app and +# have full access. +REDBEAN_OAUTH2= +# In addition to Oauth2 authentication, you can configure basic authorization +# by entering which authorization group can log into your app. You create +# groups of email addresses in the `traefik` folder by running `make groups`. +REDBEAN_OAUTH2_AUTHORIZED_GROUP= + + ## You can run the redbean service as any user/group: REDBEAN_UID=54321 REDBEAN_GID=54321 diff --git a/redbean/Makefile b/redbean/Makefile index 4089e3f8..99a86da2 100644 --- a/redbean/Makefile +++ b/redbean/Makefile @@ -10,7 +10,7 @@ config-hook: #### reconfigure_htpasswd will configure the HTTP Basic Authentication setting the var name and with a provided default value. @${BIN}/reconfigure_ask ${ENV_FILE} REDBEAN_TRAEFIK_HOST "Enter the redbean domain name" redbean${INSTANCE_URL_SUFFIX}.${ROOT_DOMAIN} @${BIN}/reconfigure ${ENV_FILE} REDBEAN_INSTANCE=$${instance:-default} - @${BIN}/reconfigure_htpasswd ${ENV_FILE} REDBEAN_HTTP_AUTH default=no + @${BIN}/reconfigure_auth ${ENV_FILE} REDBEAN .PHONY: override-hook override-hook: @@ -24,7 +24,7 @@ override-hook: #### # (this hardcodes the string into docker-compose.override.yaml) #### name=@VARIABLE_NAME # sets the template 'name' field to the literal string '${VARIABLE_NAME}' #### # (used for regular docker-compose expansion of env vars by name.) - @${BIN}/docker_compose_override ${ENV_FILE} project=:redbean instance=@REDBEAN_INSTANCE traefik_host=@REDBEAN_TRAEFIK_HOST http_auth=REDBEAN_HTTP_AUTH http_auth_var=@REDBEAN_HTTP_AUTH ip_sourcerange=@REDBEAN_IP_SOURCERANGE traefik_mode=REDBEAN_TRAEFIK_MODE + @${BIN}/docker_compose_override ${ENV_FILE} project=:redbean instance=@REDBEAN_INSTANCE traefik_host=@REDBEAN_TRAEFIK_HOST http_auth=REDBEAN_HTTP_AUTH http_auth_var=@REDBEAN_HTTP_AUTH ip_sourcerange=@REDBEAN_IP_SOURCERANGE traefik_mode=REDBEAN_TRAEFIK_MODE oauth2=REDBEAN_OAUTH2 authorized_group=REDBEAN_OAUTH2_AUTHORIZED_GROUP .PHONY: shell diff --git a/redbean/docker-compose.instance.yaml b/redbean/docker-compose.instance.yaml index ba0d85ff..c2339ddb 100644 --- a/redbean/docker-compose.instance.yaml +++ b/redbean/docker-compose.instance.yaml @@ -13,7 +13,10 @@ #@ ip_sourcerange = data.values.ip_sourcerange #@ enable_http_auth = len(data.values.http_auth.strip()) > 0 #@ http_auth = data.values.http_auth_var +#@ enable_oauth2 = data.values.oauth2 == "true" +#@ authorized_group = data.values.authorized_group #@ traefik_mode = data.values.traefik_mode + #@ enabled_middlewares = [] #@yaml/text-templated-strings @@ -37,6 +40,10 @@ services: #@ enabled_middlewares.append("{}-basicauth".format(router)) - "traefik.http.middlewares.(@= router @)-basicauth.basicauth.users=(@= http_auth @)" #@ end + #@ if enable_oauth2: + #@ enabled_middlewares.append("traefik-forward-auth@docker") + #@ enabled_middlewares.append("header-authorization-group-{}@file".format(authorized_group)) + #@ end #! Apply all middlewares (do this at the end!) - "traefik.http.routers.(@= router @).middlewares=(@= ','.join(enabled_middlewares) @)" #@ elif traefik_mode == "service": diff --git a/s3-proxy/.env-dist b/s3-proxy/.env-dist index d9e0b269..78ddf335 100644 --- a/s3-proxy/.env-dist +++ b/s3-proxy/.env-dist @@ -1,8 +1,6 @@ S3PROXY_TRAEFIK_HOST=s3-proxy.example.com S3PROXY_INSTANCE= -## generate htpasswd hash: see traefik-htpasswd README in this same project: -S3PROXY_HTTP_AUTH= S3PROXY_REALM=s3-proxy ## S3 credentials (defaults are for self-hosted minio): S3PROXY_S3_ENDPOINT=s3.example.com @@ -17,3 +15,24 @@ S3PROXY_S3_SECRET_ACCESS_KEY= S3PROXY_IP_SOURCERANGE="0.0.0.0/0" S3PROXY_PRINT_CONFIG=true + +# HTTP Basic Authentication: +# Use `make config` to fill this in properly, or set this to blank to disable. +S3PROXY_HTTP_AUTH= + +# OAUTH2 +# Set to `true` to use OpenID/OAuth2 authentication via the +# traefik-forward-auth service in d.rymcg.tech. +# Using OpenID/OAuth2 will require login to access your app, +# but it will not affect what a successfully logged-in person can do in your +# app. If your app has built-in authentication and can check the user +# header that traefik-forward-auth sends, then your app can limit what the +# logged-in person can do in the app. But if your app can't check the user +# header, or if your app doesn't have built-in authentication at all, then +# any person with an account on your Gitea server can log into your app and +# have full access. +S3PROXY_OAUTH2= +# In addition to Oauth2 authentication, you can configure basic authorization +# by entering which authorization group can log into your app. You create +# groups of email addresses in the `traefik` folder by running `make groups`. +S3PROXY_OAUTH2_AUTHORIZED_GROUP= diff --git a/s3-proxy/Makefile b/s3-proxy/Makefile index 2329d081..c1ab9261 100644 --- a/s3-proxy/Makefile +++ b/s3-proxy/Makefile @@ -10,7 +10,7 @@ config-hook: @${BIN}/reconfigure_ask ${ENV_FILE} S3PROXY_S3_BUCKET "Enter the s3 bucket name to proxy" @${BIN}/reconfigure_ask ${ENV_FILE} S3PROXY_S3_ACCESS_KEY_ID "Enter the S3 Access Key" @${BIN}/reconfigure_ask ${ENV_FILE} S3PROXY_S3_SECRET_ACCESS_KEY "Enter the S3 Secret Key" - @${BIN}/reconfigure_htpasswd ${ENV_FILE} S3PROXY_HTTP_AUTH default=no + @${BIN}/reconfigure_auth ${ENV_FILE} S3PROXY .PHONY: override-hook override-hook: @@ -24,6 +24,6 @@ override-hook: #### # (this hardcodes the string into docker-compose.override.yaml) #### name=@VARIABLE_NAME # sets the template 'name' field to the literal string '${VARIABLE_NAME}' #### # (used for regular docker-compose expansion of env vars by name.) - @${BIN}/docker_compose_override ${ENV_FILE} project=:s3proxy instance=@S3PROXY_INSTANCE traefik_host=@S3PROXY_TRAEFIK_HOST http_auth=S3PROXY_HTTP_AUTH http_auth_var=@S3PROXY_HTTP_AUTH ip_sourcerange=@S3PROXY_IP_SOURCERANGE + @${BIN}/docker_compose_override ${ENV_FILE} project=:s3proxy instance=@S3PROXY_INSTANCE traefik_host=@S3PROXY_TRAEFIK_HOST http_auth=S3PROXY_HTTP_AUTH http_auth_var=@S3PROXY_HTTP_AUTH ip_sourcerange=@S3PROXY_IP_SOURCERANGE oauth2=S3PROXY_OAUTH2 authorized_group=S3PROXY_OAUTH2_AUTHORIZED_GROUP diff --git a/s3-proxy/docker-compose.instance.yaml b/s3-proxy/docker-compose.instance.yaml index d958bfab..a2c32465 100644 --- a/s3-proxy/docker-compose.instance.yaml +++ b/s3-proxy/docker-compose.instance.yaml @@ -13,6 +13,8 @@ #@ ip_sourcerange = data.values.ip_sourcerange #@ enable_http_auth = len(data.values.http_auth.strip()) > 0 #@ http_auth = data.values.http_auth_var +#@ enable_oauth2 = data.values.oauth2 == "true" +#@ authorized_group = data.values.authorized_group #@ enabled_middlewares = [] #@yaml/text-templated-strings @@ -36,6 +38,11 @@ services: #@ enabled_middlewares.append("{}-basicauth".format(router)) - "traefik.http.middlewares.(@= router @)-basicauth.basicauth.users=(@= http_auth @)" #@ end - + + #@ if enable_oauth2: + #@ enabled_middlewares.append("traefik-forward-auth@docker") + #@ enabled_middlewares.append("header-authorization-group-{}@file".format(authorized_group)) + #@ end + #! Apply all middlewares (do this at the end!) - "traefik.http.routers.(@= router @).middlewares=(@= ','.join(enabled_middlewares) @)" diff --git a/shaarli/.env-dist b/shaarli/.env-dist index 199d90ac..48ce795e 100644 --- a/shaarli/.env-dist +++ b/shaarli/.env-dist @@ -1,7 +1,29 @@ SHAARLI_TRAEFIK_HOST=shaarli.example.com +SHAARLI_INSTANCE= ## Docker tag to use ## See https://shaarli.readthedocs.io/en/master/Docker/#get-and-run-a-shaarli-image SHAARLI_DOCKER_TAG=latest -SHAARLI_IP_SOURCERANGE=0.0.0.0/0 \ No newline at end of file +SHAARLI_IP_SOURCERANGE=0.0.0.0/0 + +# HTTP Basic Authentication: +# Use `make config` to fill this in properly, or set this to blank to disable. +SHAARLI_HTTP_AUTH= + +# OAUTH2 +# Set to `true` to use OpenID/OAuth2 authentication via the +# traefik-forward-auth service in d.rymcg.tech. +# Using OpenID/OAuth2 will require login to access your app, +# but it will not affect what a successfully logged-in person can do in your +# app. If your app has built-in authentication and can check the user +# header that traefik-forward-auth sends, then your app can limit what the +# logged-in person can do in the app. But if your app can't check the user +# header, or if your app doesn't have built-in authentication at all, then +# any person with an account on your Gitea server can log into your app and +# have full access. +SHAARLI_OAUTH2= +# In addition to Oauth2 authentication, you can configure basic authorization +# by entering which authorization group can log into your app. You create +# groups of email addresses in the `traefik` folder by running `make groups`. +SHAARLI_OAUTH2_AUTHORIZED_GROUP= diff --git a/shaarli/Makefile b/shaarli/Makefile index 56715bed..54176783 100644 --- a/shaarli/Makefile +++ b/shaarli/Makefile @@ -1,7 +1,23 @@ ROOT_DIR = .. include ${ROOT_DIR}/_scripts/Makefile.projects +include ${ROOT_DIR}/_scripts/Makefile.instance .PHONY: config-hook config-hook: - @${BIN}/reconfigure_ask ${ENV_FILE} SHAARLI_TRAEFIK_HOST "Enter the shaarli domain name" shaarli.${ROOT_DOMAIN} + @${BIN}/reconfigure_ask ${ENV_FILE} SHAARLI_TRAEFIK_HOST "Enter the shaarli domain name" shaarli${INSTANCE_URL_SUFFIX}.${ROOT_DOMAIN} + @${BIN}/reconfigure_auth ${ENV_FILE} SHAARLI + +.PHONY: override-hook +override-hook: +#### This sets the override template variables for docker-compose.instance.yaml: +#### The template dynamically renders to docker-compose.override_{DOCKER_CONTEXT}_{INSTANCE}.yaml +#### These settings are used to automatically generate the service container labels, and traefik config, inside the template. +#### The variable arguments have three forms: `=` `=:` `=@` +#### name=VARIABLE_NAME # sets the template 'name' field to the value of VARIABLE_NAME found in the .env file +#### # (this hardcodes the value into docker-compose.override.yaml) +#### name=:VARIABLE_NAME # sets the template 'name' field to the literal string 'VARIABLE_NAME' +#### # (this hardcodes the string into docker-compose.override.yaml) +#### name=@VARIABLE_NAME # sets the template 'name' field to the literal string '${VARIABLE_NAME}' +#### # (used for regular docker-compose expansion of env vars by name.) + @${BIN}/docker_compose_override ${ENV_FILE} project=:shaarli instance=@SHAARLI_INSTANCE traefik_host=@SHAARLI_TRAEFIK_HOST http_auth=SHAARLI_HTTP_AUTH http_auth_var=@SHAARLI_HTTP_AUTH ip_sourcerange=@SHAARLI_IP_SOURCERANGE oauth2=SHAARLI_OAUTH2 authorized_group=SHAARLI_OAUTH2_AUTHORIZED_GROUP diff --git a/shaarli/docker-compose.instance.yaml b/shaarli/docker-compose.instance.yaml new file mode 100644 index 00000000..e0d64207 --- /dev/null +++ b/shaarli/docker-compose.instance.yaml @@ -0,0 +1,48 @@ +#! This is a ytt template file for docker-compose.override.yaml +#! References: +#! https://carvel.dev/ytt +#! https://docs.docker.com/compose/extends/#adding-and-overriding-configuration +#! https://github.com/enigmacurry/d.rymcg.tech#overriding-docker-composeyaml-per-instance + +#! ### Standard project vars: +#@ load("@ytt:data", "data") +#@ project = data.values.project +#@ instance = data.values.instance +#@ context = data.values.context +#@ traefik_host = data.values.traefik_host +#@ ip_sourcerange = data.values.ip_sourcerange +#@ enable_http_auth = len(data.values.http_auth.strip()) > 0 +#@ http_auth = data.values.http_auth_var +#@ enable_oauth2 = data.values.oauth2 == "true" +#@ authorized_group = data.values.authorized_group +#@ enabled_middlewares = [] + +#@yaml/text-templated-strings +services: + shaarli: + #@ service = "shaarli" + labels: + #! Services must opt-in to be proxied by Traefik: + - "traefik.enable=true" + + #! 'router' is the fully qualified key in traefik for this router/service: project + instance + service + #@ router = "{}-{}-{}".format(project,instance,service) + + #! The host matching router rule: + - "traefik.http.routers.(@= router @).rule=Host(`(@= traefik_host @)`)" + - "traefik.http.routers.(@= router @).entrypoints=websecure" + #@ enabled_middlewares.append("{}-ipwhitelist".format(router)) + - "traefik.http.middlewares.(@= router @)-ipwhitelist.ipwhitelist.sourcerange=(@= ip_sourcerange @)" + + #@ if enable_http_auth: + #@ enabled_middlewares.append("{}-basicauth".format(router)) + - "traefik.http.middlewares.(@= router @)-basicauth.basicauth.users=(@= http_auth @)" + #@ end + + #@ if enable_oauth2: + #@ enabled_middlewares.append("traefik-forward-auth@docker") + #@ enabled_middlewares.append("header-authorization-group-{}@file".format(authorized_group)) + #@ end + + #! Apply all middlewares (do this at the end!) + - "traefik.http.routers.(@= router @).middlewares=(@= ','.join(enabled_middlewares) @)" diff --git a/shaarli/docker-compose.yaml b/shaarli/docker-compose.yaml index 7ffc4337..2a139880 100644 --- a/shaarli/docker-compose.yaml +++ b/shaarli/docker-compose.yaml @@ -23,9 +23,4 @@ services: volumes: - shaarli-cache:/var/www/shaarli/cache - shaarli-data:/var/www/shaarli/data - labels: - - "traefik.enable=true" - - "traefik.http.routers.shaarli-${SHAARLI_INSTANCE:-default}.rule=Host(`${SHAARLI_TRAEFIK_HOST}`)" - - "traefik.http.routers.shaarli-${SHAARLI_INSTANCE:-default}.entrypoints=websecure" - - "traefik.http.middlewares.shaarli-${SHAARLI_INSTANCE:-default}-ipwhitelist.ipwhitelist.sourcerange=${SHAARLI_IP_SOURCERANGE}" - - "traefik.http.routers.shaarli-${SHAARLI_INSTANCE:-default}.middlewares=shaarli-${SHAARLI_INSTANCE:-default}-ipwhitelist" + labels: [] diff --git a/smokeping/.env-dist b/smokeping/.env-dist index 09ddfa6b..4f7d3c87 100644 --- a/smokeping/.env-dist +++ b/smokeping/.env-dist @@ -16,6 +16,24 @@ SMOKEPING_IP_SOURCERANGE=0.0.0.0/0 # Use `make config` to fill this in properly, or set this to blank to disable. SMOKEPING_HTTP_AUTH= +# OAUTH2 +# Set to `true` to use OpenID/OAuth2 authentication via the +# traefik-forward-auth service in d.rymcg.tech. +# Using OpenID/OAuth2 will require login to access your app, +# but it will not affect what a successfully logged-in person can do in your +# app. If your app has built-in authentication and can check the user +# header that traefik-forward-auth sends, then your app can limit what the +# logged-in person can do in the app. But if your app can't check the user +# header, or if your app doesn't have built-in authentication at all, then +# any person with an account on your Gitea server can log into your app and +# have full access. +SMOKEPING_OAUTH2= +# In addition to Oauth2 authentication, you can configure basic authorization +# by entering which authorization group can log into your app. You create +# groups of email addresses in the `traefik` folder by running `make groups`. +SMOKEPING_OAUTH2_AUTHORIZED_GROUP= + + ## You can run the smokeping service as any user/group: SMOKEPING_UID=54321 SMOKEPING_GID=54321 diff --git a/smokeping/Makefile b/smokeping/Makefile index 9e184322..b35de31f 100644 --- a/smokeping/Makefile +++ b/smokeping/Makefile @@ -10,7 +10,7 @@ config-hook: #### reconfigure_htpasswd will configure the HTTP Basic Authentication setting the var name and with a provided default value. @${BIN}/reconfigure_ask ${ENV_FILE} SMOKEPING_TRAEFIK_HOST "Enter the smokeping domain name" smokeping${INSTANCE_URL_SUFFIX}.${ROOT_DOMAIN} @${BIN}/reconfigure ${ENV_FILE} SMOKEPING_INSTANCE=$${instance:-default} - @${BIN}/reconfigure_htpasswd ${ENV_FILE} SMOKEPING_HTTP_AUTH default=no + @${BIN}/reconfigure_auth ${ENV_FILE} SMOKEPING .PHONY: override-hook override-hook: @@ -24,7 +24,7 @@ override-hook: #### # (this hardcodes the string into docker-compose.override.yaml) #### name=@VARIABLE_NAME # sets the template 'name' field to the literal string '${VARIABLE_NAME}' #### # (used for regular docker-compose expansion of env vars by name.) - @${BIN}/docker_compose_override ${ENV_FILE} project=:smokeping instance=@SMOKEPING_INSTANCE traefik_host=@SMOKEPING_TRAEFIK_HOST http_auth=SMOKEPING_HTTP_AUTH http_auth_var=@SMOKEPING_HTTP_AUTH ip_sourcerange=@SMOKEPING_IP_SOURCERANGE + @${BIN}/docker_compose_override ${ENV_FILE} project=:smokeping instance=@SMOKEPING_INSTANCE traefik_host=@SMOKEPING_TRAEFIK_HOST http_auth=SMOKEPING_HTTP_AUTH http_auth_var=@SMOKEPING_HTTP_AUTH ip_sourcerange=@SMOKEPING_IP_SOURCERANGE oauth2=SMOKEPING_OAUTH2 authorized_group=SMOKEPING_OAUTH2_AUTHORIZED_GROUP .PHONY: shell shell: diff --git a/smokeping/docker-compose.instance.yaml b/smokeping/docker-compose.instance.yaml index 7de18c98..750b1843 100644 --- a/smokeping/docker-compose.instance.yaml +++ b/smokeping/docker-compose.instance.yaml @@ -13,6 +13,8 @@ #@ ip_sourcerange = data.values.ip_sourcerange #@ enable_http_auth = len(data.values.http_auth.strip()) > 0 #@ http_auth = data.values.http_auth_var +#@ enable_oauth2 = data.values.oauth2 == "true" +#@ authorized_group = data.values.authorized_group #@ enabled_middlewares = [] #@yaml/text-templated-strings @@ -37,5 +39,10 @@ services: - "traefik.http.middlewares.(@= router @)-basicauth.basicauth.users=(@= http_auth @)" #@ end + #@ if enable_oauth2: + #@ enabled_middlewares.append("traefik-forward-auth@docker") + #@ enabled_middlewares.append("header-authorization-group-{}@file".format(authorized_group)) + #@ end + #! Apply all middlewares (do this at the end!) - "traefik.http.routers.(@= router @).middlewares=(@= ','.join(enabled_middlewares) @)" diff --git a/thttpd/.env-dist b/thttpd/.env-dist index 6c98ea0a..3a8ec62d 100644 --- a/thttpd/.env-dist +++ b/thttpd/.env-dist @@ -12,3 +12,21 @@ THTTPD_IP_SOURCERANGE=0.0.0.0/0 # HTTP Basic Authentication: # Use `make config` to fill this in properly, or set this to blank to disable. THTTPD_HTTP_AUTH= + + +# OAUTH2 +# Set to `true` to use OpenID/OAuth2 authentication via the +# traefik-forward-auth service in d.rymcg.tech. +# Using OpenID/OAuth2 will require login to access your app, +# but it will not affect what a successfully logged-in person can do in your +# app. If your app has built-in authentication and can check the user +# header that traefik-forward-auth sends, then your app can limit what the +# logged-in person can do in the app. But if your app can't check the user +# header, or if your app doesn't have built-in authentication at all, then +# any person with an account on your Gitea server can log into your app and +# have full access. +THTTPD_OAUTH2= +# In addition to Oauth2 authentication, you can configure basic authorization +# by entering which authorization group can log into your app. You create +# groups of email addresses in the `traefik` folder by running `make groups`. +THTTPD_OAUTH2_AUTHORIZED_GROUP= diff --git a/thttpd/Makefile b/thttpd/Makefile index a1984f07..f60aa6f5 100644 --- a/thttpd/Makefile +++ b/thttpd/Makefile @@ -6,7 +6,7 @@ include ${ROOT_DIR}/_scripts/Makefile.instance config-hook: @${BIN}/reconfigure_ask ${ENV_FILE} THTTPD_TRAEFIK_HOST "Enter the website domain name" thttpd${INSTANCE_URL_SUFFIX}.${ROOT_DOMAIN} @${BIN}/reconfigure ${ENV_FILE} THTTPD_INSTANCE=${instance} - @${BIN}/reconfigure_htpasswd ${ENV_FILE} THTTPD_HTTP_AUTH default=no + @${BIN}/reconfigure_auth ${ENV_FILE} THTTPD .PHONY: shell shell: @@ -24,4 +24,4 @@ override-hook: #### # (this hardcodes the string into docker-compose.override.yaml) #### name=@VARIABLE_NAME # sets the template 'name' field to the literal string '${VARIABLE_NAME}' #### # (used for regular docker-compose expansion of env vars by name.) - @${BIN}/docker_compose_override ${ENV_FILE} project=:thttpd instance=@THTTPD_INSTANCE traefik_host=@THTTPD_TRAEFIK_HOST http_auth=THTTPD_HTTP_AUTH http_auth_var=@THTTPD_HTTP_AUTH ip_sourcerange=@THTTPD_IP_SOURCERANGE + @${BIN}/docker_compose_override ${ENV_FILE} project=:thttpd instance=@THTTPD_INSTANCE traefik_host=@THTTPD_TRAEFIK_HOST http_auth=THTTPD_HTTP_AUTH http_auth_var=@THTTPD_HTTP_AUTH ip_sourcerange=@THTTPD_IP_SOURCERANGE oauth2=THTTPD_OAUTH2 authorized_group=THTTPD_OAUTH2_AUTHORIZED_GROUP diff --git a/thttpd/docker-compose.instance.yaml b/thttpd/docker-compose.instance.yaml index 5801d0b9..62f4e6cb 100644 --- a/thttpd/docker-compose.instance.yaml +++ b/thttpd/docker-compose.instance.yaml @@ -13,6 +13,8 @@ #@ ip_sourcerange = data.values.ip_sourcerange #@ enable_http_auth = len(data.values.http_auth.strip()) > 0 #@ http_auth = data.values.http_auth_var +#@ enable_oauth2 = data.values.oauth2 == "true" +#@ authorized_group = data.values.authorized_group #@ enabled_middlewares = [] #@yaml/text-templated-strings @@ -37,5 +39,10 @@ services: - "traefik.http.middlewares.(@= router @)-basicauth.basicauth.users=(@= http_auth @)" #@ end + #@ if enable_oauth2: + #@ enabled_middlewares.append("traefik-forward-auth@docker") + #@ enabled_middlewares.append("header-authorization-group-{}@file".format(authorized_group)) + #@ end + #! Apply all middlewares (do this at the end!) - "traefik.http.routers.(@= router @).middlewares=(@= ','.join(enabled_middlewares) @)" diff --git a/tiddlywiki-nodejs/Makefile b/tiddlywiki-nodejs/Makefile index 262cf1a8..4095625a 100644 --- a/tiddlywiki-nodejs/Makefile +++ b/tiddlywiki-nodejs/Makefile @@ -12,7 +12,7 @@ config-hook: @${BIN}/reconfigure ${ENV_FILE} TIDDLYWIKI_NODEJS_INSTANCE=${instance} @echo "" @echo "Configure the admin account credentials:" - @${BIN}/reconfigure_htpasswd ${ENV_FILE} TIDDLYWIKI_NODEJS_HTTP_AUTH + @__D_RY_CONFIG_ENTRY=reconfigure_auth ${BIN}/reconfigure_htpasswd ${ENV_FILE} TIDDLYWIKI_NODEJS_HTTP_AUTH @echo "" @${BIN}/confirm $$([[ "$$(${BIN}/dotenv -f ${ENV_FILE} get TIDDLYWIKI_PUBLIC_IP_SOURCERANGE)" == "0.0.0.0/0" ]] && echo "yes" || echo "no") "Do you want to enable public read-only access" "?" && ${BIN}/reconfigure ${ENV_FILE} TIDDLYWIKI_PUBLIC_IP_SOURCERANGE=0.0.0.0/0 || ${BIN}/reconfigure ${ENV_FILE} TIDDLYWIKI_PUBLIC_IP_SOURCERANGE=0.0.0.0/32 @echo "" @@ -34,10 +34,9 @@ shell: watcher-shell: @make --no-print-directory docker-compose-lifecycle-cmd EXTRA_ARGS="exec watcher /bin/bash" - # .PHONY: override-hook # override-hook: -# @${BIN}/docker_compose_override ${ENV_FILE} TIDDLYWIKI_NODEJS_PUBLIC_HTTP_AUTH +# @${BIN}/docker_compose_override ${ENV_FILE} .PHONY: ssh-keygen ssh-keygen: diff --git a/tiddlywiki-webdav/Makefile b/tiddlywiki-webdav/Makefile index fc275e70..ba3fe9af 100644 --- a/tiddlywiki-webdav/Makefile +++ b/tiddlywiki-webdav/Makefile @@ -8,10 +8,10 @@ config-hook: @${BIN}/reconfigure ${ENV_FILE} TIDDLYWIKI_INSTANCE=${instance} @echo "" @echo "Configure the admin account credentials:" - @${BIN}/reconfigure_htpasswd ${ENV_FILE} TIDDLYWIKI_ADMIN_HTTP_AUTH + @__D_RY_CONFIG_ENTRY=reconfigure_auth ${BIN}/reconfigure_htpasswd ${ENV_FILE} TIDDLYWIKI_ADMIN_HTTP_AUTH @echo "" @${BIN}/confirm $$([[ "$$(${BIN}/dotenv -f ${ENV_FILE} get TIDDLYWIKI_PUBLIC_IP_SOURCERANGE)" == "0.0.0.0/0" ]] && echo "yes" || echo "no") "Do you want to enable public read-only access" "?" && ${BIN}/reconfigure ${ENV_FILE} TIDDLYWIKI_PUBLIC_IP_SOURCERANGE=0.0.0.0/0 || (${BIN}/reconfigure ${ENV_FILE} TIDDLYWIKI_PUBLIC_IP_SOURCERANGE=0.0.0.0/32 && ${BIN}/reconfigure ${ENV_FILE} TIDDLYWIKI_PUBLIC_HTTP_AUTH="") - @[[ "$$(${BIN}/dotenv -f ${ENV_FILE} get TIDDLYWIKI_PUBLIC_IP_SOURCERANGE)" == "0.0.0.0/32" ]] || (${BIN}/confirm $$([[ "$$(${BIN}/dotenv -f ${ENV_FILE} get TIDDLYWIKI_PUBLIC_HTTP_AUTH)" == "" ]] && echo "no" || echo "yes") "Should public read-only access require a password" "?" && ${BIN}/reconfigure_htpasswd ${ENV_FILE} TIDDLYWIKI_PUBLIC_HTTP_AUTH || ${BIN}/reconfigure ${ENV_FILE} TIDDLYWIKI_PUBLIC_HTTP_AUTH="") + @[[ "$$(${BIN}/dotenv -f ${ENV_FILE} get TIDDLYWIKI_PUBLIC_IP_SOURCERANGE)" == "0.0.0.0/32" ]] || (${BIN}/confirm $$([[ "$$(${BIN}/dotenv -f ${ENV_FILE} get TIDDLYWIKI_PUBLIC_HTTP_AUTH)" == "" ]] && echo "no" || echo "yes") "Should public read-only access require a password" "?" && __D_RY_CONFIG_ENTRY=reconfigure_auth ${BIN}/reconfigure_htpasswd ${ENV_FILE} TIDDLYWIKI_PUBLIC_HTTP_AUTH || ${BIN}/reconfigure ${ENV_FILE} TIDDLYWIKI_PUBLIC_HTTP_AUTH="") .PHONY: shell shell: @@ -23,7 +23,7 @@ open: .PHONY: override-hook override-hook: - @${BIN}/docker_compose_override ${ENV_FILE} TIDDLYWIKI_PUBLIC_HTTP_AUTH + @${BIN}/docker_compose_override ${ENV_FILE} .PHONY: ssh-keygen ssh-keygen: diff --git a/traefik-forward-auth/.env-dist b/traefik-forward-auth/.env-dist index c5171a0a..fd080053 100644 --- a/traefik-forward-auth/.env-dist +++ b/traefik-forward-auth/.env-dist @@ -6,6 +6,11 @@ TRAEFIK_FORWARD_AUTH_LOG_LEVEL=debug ## Set central auth specific domain that will handle auth for all other domains: TRAEFIK_FORWARD_AUTH_HOST=auth.example.com +TRAEFIK_FORWARD_AUTH_HTTPS_PORT=443 + +## Set your gitea domain (only used for helping construct the other URLs) +TRAEFIK_FORWARD_AUTH_GITEA_DOMAIN=git.example.com + ## Set cookie domain as the root domain for all subdomains: TRAEFIK_FORWARD_AUTH_COOKIE_DOMAIN=example.com diff --git a/traefik-forward-auth/Makefile b/traefik-forward-auth/Makefile index b8962507..4f916f4f 100644 --- a/traefik-forward-auth/Makefile +++ b/traefik-forward-auth/Makefile @@ -3,16 +3,18 @@ include ${ROOT_DIR}/_scripts/Makefile.projects .PHONY: config-hook config-hook: - @${BIN}/reconfigure_ask ${ENV_FILE} TRAEFIK_FORWARD_AUTH_HOST "Enter the auth host domain name" auth.${ROOT_DOMAIN} - @${BIN}/reconfigure_ask ${ENV_FILE} TRAEFIK_FORWARD_AUTH_COOKIE_DOMAIN "Enter the cookie domain name" ${ROOT_DOMAIN} + @${BIN}/reconfigure_ask ${ENV_FILE} TRAEFIK_FORWARD_AUTH_HOST "Enter the traefik-foward-auth host domain name" auth.${ROOT_DOMAIN} + @${BIN}/reconfigure_ask ${ENV_FILE} TRAEFIK_FORWARD_AUTH_COOKIE_DOMAIN "Enter the cookie domain name (ie ROOT domain)" ${ROOT_DOMAIN} + @${BIN}/reconfigure_ask ${ENV_FILE} TRAEFIK_FORWARD_AUTH_GITEA_DOMAIN "Enter your gitea domain name" git.${ROOT_DOMAIN} @echo "" @echo "Opening Gitea applications page... (login as root)" @echo "You should now create a new OAuth2 application: " @echo "Set the 'Application Name' the same as AUTH_HOST (or whatever you like)" @echo "Set the 'Redirect URL' using https://AUTH_HOST/_oauth, eg. https://auth.${ROOT_DOMAIN}/_oauth" - xdg-open https://git.${ROOT_DOMAIN}/user/settings/applications + @PUBLIC_HTTPS_PORT=$$(${BIN}/dotenv -f ../.env_${DOCKER_CONTEXT} get PUBLIC_HTTPS_PORT); if [[ "$$PUBLIC_HTTPS_PORT" == "443" ]]; then PUBLIC_HTTPS_PORT=""; else PUBLIC_HTTPS_PORT=":${PUBLIC_HTTPS_PORT}"; fi; ${BIN}/reconfigure ${ENV_FILE} TRAEFIK_FORWARD_AUTH_HTTPS_PORT=$${PUBLIC_HTTPS_PORT} + @GITEA_DOMAIN=$$(${BIN}/dotenv -f ${ENV_FILE} get TRAEFIK_FORWARD_AUTH_GITEA_DOMAIN); HTTPS_PORT=$$(${BIN}/dotenv -f ${ENV_FILE} get TRAEFIK_FORWARD_AUTH_HTTPS_PORT); xdg-open https://$${GITEA_DOMAIN}$${HTTPS_PORT}/user/settings/applications @${BIN}/reconfigure_ask ${ENV_FILE} TRAEFIK_FORWARD_AUTH_PROVIDERS_GENERIC_OAUTH_CLIENT_ID "Copy and Paste the OAuth2 client ID here" @${BIN}/reconfigure_ask ${ENV_FILE} TRAEFIK_FORWARD_AUTH_PROVIDERS_GENERIC_OAUTH_CLIENT_SECRET "Copy and Paste the OAuth2 client secret here" - @${BIN}/reconfigure_ask ${ENV_FILE} TRAEFIK_FORWARD_AUTH_LOGOUT_REDIRECT "Enter the logout redirect URL" https://git.${ROOT_DOMAIN}/logout - @${BIN}/reconfigure ${ENV_FILE} TRAEFIK_FORWARD_AUTH_SECRET="$(shell openssl rand -base64 45)" TRAEFIK_FORWARD_AUTH_PROVIDERS_GENERIC_OAUTH_AUTH_URL="https://git.${ROOT_DOMAIN}/login/oauth/authorize" TRAEFIK_FORWARD_AUTH_PROVIDERS_GENERIC_OAUTH_TOKEN_URL="https://git.${ROOT_DOMAIN}/login/oauth/access_token" TRAEFIK_FORWARD_AUTH_PROVIDERS_GENERIC_OAUTH_USER_URL="https://git.${ROOT_DOMAIN}/api/v1/user" + @GITEA_DOMAIN=$$(${BIN}/dotenv -f ${ENV_FILE} get TRAEFIK_FORWARD_AUTH_GITEA_DOMAIN); ${BIN}/reconfigure_ask ${ENV_FILE} TRAEFIK_FORWARD_AUTH_LOGOUT_REDIRECT "Enter the logout redirect URL" https://$${GITEA_DOMAIN}$$(${BIN}/dotenv -f ${ENV_FILE} get TRAEFIK_FORWARD_AUTH_HTTPS_PORT)/logout + @GITEA_DOMAIN=$$(${BIN}/dotenv -f ${ENV_FILE} get TRAEFIK_FORWARD_AUTH_GITEA_DOMAIN); HTTPS_PORT=$$(${BIN}/dotenv -f ${ENV_FILE} get TRAEFIK_FORWARD_AUTH_HTTPS_PORT); ${BIN}/reconfigure ${ENV_FILE} TRAEFIK_FORWARD_AUTH_SECRET="$(shell openssl rand -base64 45)" TRAEFIK_FORWARD_AUTH_PROVIDERS_GENERIC_OAUTH_AUTH_URL="https://$${GITEA_DOMAIN}$${HTTPS_PORT}/login/oauth/authorize" TRAEFIK_FORWARD_AUTH_PROVIDERS_GENERIC_OAUTH_TOKEN_URL="https://$${GITEA_DOMAIN}$${HTTPS_PORT}/login/oauth/access_token" TRAEFIK_FORWARD_AUTH_PROVIDERS_GENERIC_OAUTH_USER_URL="https://$${GITEA_DOMAIN}$${HTTPS_PORT}/api/v1/user" diff --git a/traefik-forward-auth/README.md b/traefik-forward-auth/README.md index 10d56466..6a42d7c6 100644 --- a/traefik-forward-auth/README.md +++ b/traefik-forward-auth/README.md @@ -8,16 +8,6 @@ account. The default `.env-dist` is setup to use a self-hosted commented out example, or use [any other oauth2 provider](https://github.com/thomseddon/traefik-forward-auth/wiki/Provider-Setup). -## Important Security Note - -Using OpenID/OAuth2 will require a login to access your app, but it will not -affect what a successfully logged-in person can do in your app. If your app has -a built-in authorization mechanism that can check for the user header that -traefik-forward-auth sends, then your app can limit what the logged-in person -can do in the app. But if your app can't check the user header, or if your app -doesn't have built-in authorization at all, then any person with an account -on your Gitea server can log into your app and have full access. - ## Configuration Follow the directions to deploy [gitea](../gitea), create a root @@ -40,6 +30,8 @@ Answer the questions to configure the following environment variables: * Enter the `Application Name`, the same as `TRAEFIK_FORWARD_AUTH_HOST`, eg `auth.example.com`. * Enter the `Redirect URL`, eg `https://auth.example.com/_oauth`. + * (If you use a non-standard HTTPS port, make sure to include it, eg. `https://auth.example.com:8443/_oauth`.) + * Click `Create Application`. * Copy the Client ID and Secret shown. * `TRAEFIK_FORWARD_AUTH_PROVIDERS_GENERIC_OAUTH_CLIENT_ID` enter the Client ID shown on the OAuth2 application page in gitea. @@ -48,21 +40,62 @@ Answer the questions to configure the following environment variables: * `TRAEFIK_FORWARD_AUTH_LOGOUT_REDIRECT` Enter the URL to redirect after logging out, eg `https://git.example.com/logout`. -## Enable Traefik Routes for authentication +## Enable sentry authentication for Traefik routes + +When you run `make config` for an app, you will be asked whether or +not you want to configure sentry authentication for the app (on top of +any authentication the app provides). You can choose None, Basic +Authentication, or OpenID/OAuth2 through traefik-forward-auth. + +OAuth2 uses traefik-forward-auth to delegate authentication to an +external authority (eg. a self-deployed Gitea instance). Accessing +this app will require all users to login through that external service +first. Once authenticated, they may be authorized access only if their +login id matches the member list of the predefined authorization group +configured for the app (eg. `WHOAMI_OAUTH2_AUTHORIZED_GROUP`). +Authorization groups are defined in the Traefik config +(`TRAEFIK_HEADER_AUTHORIZATION_GROUPS`) and can be +[created/modified](https://github.com/EnigmaCurry/d.rymcg.tech/blob/master/traefik/README.md#oauth2-authentication) +by running `make sentry` in the `traefik` directory: + +``` +cd ~/git/vendor/enigmacurry/d.rymcg.tech/traefik +make sentry +``` + +Use the interactive menus to create all the groups and users that you +need for all your apps (you can return later to add more). Each app +can only have one group, but many apps can share the same group. +Groups are comprised of email addresses of the users that are members +of that group. The email addresses of the group must match those of +accounts on your Gitea instance (or external OAuth provider). + +For example, if you have accounts on your Gitea instance for +`alice@example.com` and `bob@demo.com`, and you only want Alice to be +able to access the `whoami` app, create a new group (eg. `whoami`), +and add `alice@example.com` as the only member of that group. Then +configure the whoami instance to require OAuth2 (eg. set +`WHOAMI_OAUTH2=true` in the whoami .env) and set the group to use too +(eg. `WHOAMI_OAUTH2_AUTHORIZED_GROUP=whoami`), reinstall both Traefik +and the whoami app, and test that only `alice@example.com` can login. ### d.rymcg.tech configured apps Many d.rymcg.tech apps have been configured to ask you when you run their `make config` if you want to configure Oauth2 for them. As an alternative to running `make config`, you can manually edit your `.env_{INSTANCE}` file for -an app and set the value of `_OAUTH2` to `yes`. +an app and set the value of `_OAUTH2` to `true`, and +`_OAUTH2_AUTHORIZED_GROUP` to the name of an authorization group you +created when you ran `make groups` in the `traefik` folder. ### Manually configure any Traefik router Or you can manually add Oauth2 authentication to any Traefik router by applying the -middleware: `traefik-forward-auth` (In this example, the name of the -route is `foo`): +middleware: `traefik-forward-auth`, and basic authorization by applying the middleware: +`header-authorizatin-group-AUTHORIZED_GROUP` (be sure to replace AUTHORIZED_GROUP +with the name of an authorization group you created when you ran `make groups` in +the `traefik` folder). In this example, the name of the route is `foo`: ``` - - "traefik.http.routers.foo.middlewares=traefik-forward-auth@docker" + - "traefik.http.routers.foo.middlewares=traefik-forward-auth@docker,header-authorization-group-AUTHORIZED_GROUP@file" ``` ### Configure a d.rymcg.tech app to ask for Oauth2 when running `make config` @@ -70,11 +103,13 @@ To configure a d.rymcg.tech app to ask about Oauth2 when running `make config` (if it hasn't already been so configured), you'll need to edit the `.env-dist`, `docker-compose.instance.yaml`, and `Makefile` files. See the [whoami](../whoami/) app for examples. - * `.env-dist` - * Add the following env var to `.env-dist` (and adding the comment is a good idea): +``` +.env-dist +``` +* Add the following env vars to `.env-dist` (and adding the comments is a good idea): ``` # OAUTH2 -# Set to `yes` to use OpenID/OAuth2 authentication via the +# Set to `true` to use OpenID/OAuth2 authentication via the # traefik-forward-auth service in d.rymcg.tech. # Using OpenID/OAuth2 will require login to access your app, # but it will not affect what a successfully logged-in person can do in your @@ -85,31 +120,40 @@ See the [whoami](../whoami/) app for examples. # any person with an account on your Gitea server can log into your app and # have full access. WHOAMI_OAUTH2=no +# In addition to Oauth2 authentication, you can configure basic authorization +# by entering which authorization group can log into your app. You create +# groups of email addresses in the `traefik` folder by running `make groups`. +WHOAMI_OAUTH2_AUTHORIZED_GROUP= ``` - * (Be sure to change `WHOAMI` to the same prefix as the rest of the env vars in your `.env_{INSTANCE}` file) +* (Be sure to replace `WHOAMI` with the same prefix as the rest of the env vars in your `.env_{INSTANCE}` file.) - * `Makefile` - * Add the following line to the recipe for the `config-hook` target: ``` - @${BIN}/reconfigure_oauth2 ${ENV_FILE} WHOAMI_OAUTH2 default=$$( ${BIN}/dotenv -f ${ENV_FILE} get WHOAMI_OAUTH2 ) +Makefile +``` +* Add the following line to the recipe for the `config-hook` target: +``` + @${BIN}/reconfigure_auth ${ENV_FILE} WHOAMI ``` - * Add ` oauth2=WHOAMI_OAUTH2` to the end of the existing line in the receipe for the `override-hook` target. - * (For all 3 instances of `WHOAMI_OAUTH2`, be sure to change `WHOAMI` to the same prefix as the rest of the env vars in your `.env_{INSTANCE}` file) - * `docker-compose.instance.yaml` - * Add the following line to the `#! ### Standard project vars:` section: +* Add ` oauth2=WHOAMI_OAUTH2 authorized_group=WHOAMI_OAUTH2_AUTHORIZED_GROUP` to the end of the existing line in the receipe for the `override-hook` target. +* (Be sure to replace all instances of `WHOAMI` with the same prefix as the rest of the env vars in your `.env_{INSTANCE}` file.) ``` -#@ enable_oauth2 = data.values.oauth2 +docker-compose.instance.yaml ``` - * Add the following 3 lines in the `labels` section, *before* the `#! Apply all middlewares (do this at the end!)` line: +* Add the following line to the `#! ### Standard project vars:` section: ``` - #@ if enable_oauth2 == "yes": +#@ authorized_group = data.values.authorized_group +``` +* Add the following 4 lines in the `labels` section, *before* the `#! Apply all middlewares (do this at the end!)` line: +``` + #@ if enable_oauth2: #@ enabled_middlewares.append("traefik-forward-auth@docker") + #@ enabled_middlewares.append("header-authorization-group-{}@file".format(authorized_group)) #@ end ``` ## Logging out -User logout is a multi-phase endevour: +User logout is a multi-phase endeavor: * The user browses to any authenticated domain + `/_oauth/logout`. (eg. `https://whatever.example.com/_oauth/logout`). This deletes @@ -121,3 +165,28 @@ User logout is a multi-phase endevour: * Finally the user is redirected to the main gitea login page, eg. `https://git.example.com/user/login` and is now completely logged out. + +You might call this a "deep logout", and honestly, its kind of hacky. +So, think about it this way: don't use logout, and don't expect your +users to logout. Google never intends for you to logout. When's the +last time you had to login to Stack Overlow? So, why should your +self-hosted docker stack implement logout? Just stay logged in, and +manage your cookies through your web browser's features. You have +several good options when it comes to browser cookie management: + + * Use incognito mode for a quick way to test with a brand new session. + * Tune your browser settings so that it clears cookies when you close + it. + * Use [Firefox Multi-Account + Containers](https://support.mozilla.org/en-US/kb/containers) so + that new tabs are created in a new temporary session by default. + * Use + [SessionBox](https://microsoftedge.microsoft.com/addons/detail/sessionbox-free-multi-l/hmedjmnkphdghfpnbibnibobaliahhfn) + on Microsoft Edge, which I am told is similar to Firefox + Multi-Account containers. + +Any of these tools will help the developer or admin to test multiple +accounts, however regular users will not need these, as they are +expected to only have a single gitea account, and it is usually +expected for them to always stay logged in unless the gitea session +and traefik-forward-auth cookies both expire. diff --git a/traefik-forward-auth/docker-compose.yaml b/traefik-forward-auth/docker-compose.yaml index 909b0b68..4ca7c43e 100644 --- a/traefik-forward-auth/docker-compose.yaml +++ b/traefik-forward-auth/docker-compose.yaml @@ -9,7 +9,7 @@ services: environment: - SECRET=${TRAEFIK_FORWARD_AUTH_SECRET} - LOG_LEVEL=${TRAEFIK_FORWARD_AUTH_LOG_LEVEL} - - AUTH_HOST=${TRAEFIK_FORWARD_AUTH_HOST} + - AUTH_HOST=${TRAEFIK_FORWARD_AUTH_HOST}${TRAEFIK_FORWARD_AUTH_HTTPS_PORT} - COOKIE_DOMAIN=${TRAEFIK_FORWARD_AUTH_COOKIE_DOMAIN} - DEFAULT_PROVIDER=${TRAEFIK_FORWARD_AUTH_DEFAULT_PROVIDER} - PROVIDERS_GENERIC_OAUTH_AUTH_URL=${TRAEFIK_FORWARD_AUTH_PROVIDERS_GENERIC_OAUTH_AUTH_URL} diff --git a/traefik/.env-dist b/traefik/.env-dist index 625529bc..1913c8e7 100644 --- a/traefik/.env-dist +++ b/traefik/.env-dist @@ -1,5 +1,5 @@ ## Traefik docker image: -TRAEFIK_IMAGE=traefik:v2.9 +TRAEFIK_IMAGE=traefik:v2.10 ## Log levels: error, warn, info, debug TRAEFIK_LOG_LEVEL=warn @@ -24,7 +24,7 @@ TRAEFIK_DOCKER_GID=999 TRAEFIK_DASHBOARD_ENTRYPOINT_ENABLED=false ## Enter htpasswd encoded username/password for accessing the dashboard ## Run `make config` to automatically fill this in -TRAEFIK_DASHBOARD_AUTH= +TRAEFIK_DASHBOARD_HTTP_AUTH= TRAEFIK_DASHBOARD_ENTRYPOINT_PORT=8080 TRAEFIK_DASHBOARD_ENTRYPOINT_HOST=127.0.0.1 @@ -99,6 +99,10 @@ TRAEFIK_GEOIP_MODULE=github.com/enigmacurry/traefik-geoip2-plugin TRAEFIK_GEOIPUPDATE_ACCOUNT_ID= TRAEFIK_GEOIPUPDATE_LICENSE_KEY= TRAEFIK_GEOIPUPDATE_EDITION_IDS=GeoLite2-ASN GeoLite2-City GeoLite2-Country +## Header Authorization (authorization in addition to traefik-forward-auth): +TRAEFIK_PLUGIN_HEADER_AUTHORIZATION=true +TRAEFIK_HEADER_AUTHORIZATION_MODULE=github.com/enigmacurry/traefik-header-authorization + ## Entrypoints: ## Traefik web entrypoint (only used for redirecting to websecure entrypoint) @@ -168,3 +172,8 @@ TRAEFIK_VPN_CLIENT_PEER_SERVICES= TRAEFIK_ERROR_HANDLER_403_SERVICE= TRAEFIK_ERROR_HANDLER_404_SERVICE= TRAEFIK_ERROR_HANDLER_500_SERVICE= + +## Configure header authorization middlewares. +## Define this as a JSON formatted string of a map of group names: +## TRAEFIK_HEADER_AUTHORIZATION_GROUPS={"admin": ["root@localhost"], "users": ["root@localhost","ryan@localhost"]} +TRAEFIK_HEADER_AUTHORIZATION_GROUPS={} diff --git a/traefik/Dockerfile b/traefik/Dockerfile index 0754f3cf..7ba51ac8 100644 --- a/traefik/Dockerfile +++ b/traefik/Dockerfile @@ -8,6 +8,8 @@ ARG REFERER_MODULE ARG REFERER_GIT_BRANCH ARG MAXMIND_GEOIP_MODULE ARG MAXMIND_GIT_BRANCH +ARG HEADER_AUTHORIZATION_MODULE +ARG HEADER_AUTHORIZATION_GIT_BRANCH RUN apk add --update git && \ git clone https://${BLOCKPATH_MODULE}.git /plugins-local/src/github.com/traefik/plugin-blockpath \ --depth 1 --single-branch --branch ${BLOCKPATH_GIT_BRANCH} @@ -16,6 +18,8 @@ RUN git clone https://${MAXMIND_GEOIP_MODULE}.git \ --depth 1 --single-branch --branch ${MAXMIND_GIT_BRANCH} RUN git clone https://${REFERER_MODULE}.git /plugins-local/src/github.com/moonlightwatch/referer \ --depth 1 --single-branch --branch ${REFERER_GIT_BRANCH} +RUN git clone https://${HEADER_AUTHORIZATION_MODULE}.git /plugins-local/src/github.com/poloyacero/headauth \ + --depth 1 --single-branch --branch ${HEADER_AUTHORIZATION_GIT_BRANCH} FROM ${TRAEFIK_IMAGE} ARG TRAEFIK_UID TRAEFIK_GID TRAEFIK_DOCKER_GID diff --git a/traefik/Makefile b/traefik/Makefile index d5e3ef4d..faf791bf 100644 --- a/traefik/Makefile +++ b/traefik/Makefile @@ -127,3 +127,12 @@ logs-access: .PHONY: debug # Restart Traefik with debug logging for one time only debug: @EXISTING_TRAEFIK_LOG_LEVEL=$$(${BIN}/dotenv -f ${ENV_FILE} get TRAEFIK_LOG_LEVEL) && ${BIN}/reconfigure ${ENV_FILE} TRAEFIK_LOG_LEVEL=debug && make --no-print-directory install; STARTED=$$?; ${BIN}/reconfigure ${ENV_FILE} TRAEFIK_LOG_LEVEL=$${EXISTING_TRAEFIK_LOG_LEVEL} && [[ "$$STARTED" == "0" ]] && make --no-print-directory logs-traefik + +.PHONY: sentry +sentry: + @make --no-print-directory -C "${ROOT_DIR}" script-wizard + @${BIN}/reconfigure_header_authorization ${ENV_FILE} + +.PHONY: sentry-callback +sentry-callback: + @${BIN}/reconfigure_header_authorization ${ENV_FILE} list-callback-urls diff --git a/traefik/README.md b/traefik/README.md index 20b33123..0fa6b4b2 100644 --- a/traefik/README.md +++ b/traefik/README.md @@ -45,6 +45,9 @@ should be the first thing you install in your deployment. it can read all the environment variables of all your containers, and can escalate itself to root level access, through the use of the docker API.) + * Authentication is provided on a per app basis with HTTP Basic + Authentication or OAuth2, with a general group based authorization + middleware adaptable to secure any application. ## Config @@ -81,6 +84,16 @@ make certs (Follow the [certificate manager](#certificate-manager) section for a detailed example of creating certificates and then come back here.) +``` +# Optional: Make security groups for header authorization middleware: +make sentry +``` + +(`make sentry` is only required if you want to configure Oauth2 +authentication - follow the [Oauth2 +authentication](#oauth2-authentication) section for instructions how +to create authorization groups, or you can do that anytime later.) + Double check that the config has now been created in your `.env_${DOCKER_CONTEXT}_default` file and make any final edits (there are a few settings that are not covered by the wizard, so you may want to @@ -234,7 +247,7 @@ a description of the most relevant options to edit: * `TRAEFIK_DASHBOARD` if set to `true`, this will turn on the [Traefik Dashboard](https://doc.traefik.io/traefik/operations/dashboard/). - * `TRAEFIK_DASHBOARD_AUTH` this is the htpasswd encoded username/password to + * `TRAEFIK_DASHBOARD_HTTP_AUTH` this is the htpasswd encoded username/password to access the Traefik API and dashboard. If you ran `make config` this would be filled in for you, simply by answering the questions. @@ -353,8 +366,65 @@ to web servers running in project containers. ## OAuth2 authentication -You can start the [traefik-forward-auth](../traefik-forward-auth) service to -enable OAuth2 authentication to your [gitea](../gitea) identity provider. +You can start the [traefik-forward-auth](../traefik-forward-auth) +service to enable OAuth2 authentication to your [gitea](../gitea) +identity provider (or any external OAuth2 provider). + +It is important to understand the difference between authentication +and authorization: + + * authentication identifies who a user *is*. (This is what + traefik-forward-auth does for you, sitting in front of your app.) + * authorization is a process that determines what a user should be + *allowed to do* (This is what every application should do for + itself, or another middleware described below). + +To summarize: traefik-forward-auth, by itself, only cares about +identity, not about permissions. + +Permissions (authorization) are to be implemented in the app itself. +Traefik-Forward-Auth operates by setting a trusted header +`X-Forwarded-User` that contains the authenticated users email +address. The application receives this header on every request coming +from the proxy. It should trust this header to be a real authenticated +user for the session, and it only needs to decide what that user is +allowed to do (ie. the app should define a map of email address to +permissions that it enforces per request; the app database only needs +to store user registrations, and their permission roles, but doesn't +need to store any user passwords.). + +However, many applications do not support this style of delegated +authentication by trusted header. To add authorization to an +unsupported application, you may use the provided [header +authorization +middleware](https://github.com/enigmacurry/traefik-header-authorization), +and it can be configured simply by running this make target: + +``` +# Configure the header authorization middleware: +make sentry +``` + +This will configure the `TRAEFIK_HEADER_AUTHORIZATION_GROUPS` +environment variable in your .env file (which is a serialized JSON map +of groups and allowed usernames). Email addresses must match those of +accounts on your Gitea instance. For example, if you have accounts on +your Gitea instance for alice@example.com and bob@demo.com, and you +only want Alice to be able to access this app, only enter +`alice@example.com`. Remember to re-install traefik after making any +changes to your authorization groups or permitted email addresses. + +Each app must apply the middleware to filter users based on the group +the middleware is designed for. Once you run `make sentry` and configure +authorization groups in the `traefik` folder, when you run `make config` for +that app and elect to configure Oauth2 authentication, you will be asked to +assign one of those groups to your app. + +While this extra middleware can get you "in the door" of any app, its +still ultimately up to the app as to what you can do when you get +there, so if the app doesn't understand the `X-Forwarded-User` header, +you may also need to login through the app interface itself, after +having already logged in through gitea. ## Wireguard VPN @@ -498,79 +568,80 @@ Traefik [.env](.env-dist) file : | Variable name | Description | Examples | |--------------------------------------------|----------------------------------------------------------------------------------|-----------------------------------------------| -| `TRAEFIK_IMAGE` | The Traefik docker image | `traefik:v2.9` | -| `TRAEFIK_LOG_LEVEL` | Traefik log level | `warn`,`error`,`info`, `debug` | -| `TRAEFIK_CONFIG_VERBOSE` | (bool) Print config to logs | `false`,`true` | -| `TRAEFIK_NETWORK_MODE` | Bind Traefik to host or serivce container networking | `host`,`wireguard`,`wireguard-client` | +| (various LEGO DNS variables) | All of your tokens for DNS provider | `DO_AUTH_TOKEN` | | `DOCKER_COMPOSE_PROFILES` | List of docker-compose profiles to enable | `default`,`wireguard`,`wireguard-client` | -| `TRAEFIK_DASHBOARD_ENTRYPOINT_ENABLED` | (bool) Enable the dashboard entrypoint | `true`, `false` | -| `TRAEFIK_DASHBOARD_AUTH` | The htpasswd encoded password for the dashboard | `$$apr1$$125jLjJS$$9WiXscLMURiMbC0meZXMv1` | -| `TRAEFIK_DASHBOARD_ENTRYPOINT_PORT` | The TCP port for the daashboard | `8080` | -| `TRAEFIK_DASHBOARD_ENTRYPOINT_HOST` | The IP address to bind to | `127.0.0.1` (host networking) `0.0.0.0` (VPN) | | `TRAEFIK_ACCESS_LOGS_ENABLED` | (bool) enable the Traefik access logs | `true`,`false` | | `TRAEFIK_ACCESS_LOGS_PATH` | The path to the access logs inside the volume | `/data/access.log` | -| `TRAEFIK_SEND_ANONYMOUS_USAGE` | (bool) Whether to send usage data to Traefik Labs | `false`, `true` | -| `TRAEFIK_ACME_ENABLED` | (bool) Enable ACME TLS certificate resolver | `true`,`false` | | `TRAEFIK_ACME_CA_EMAIL` | Your email to send to Lets Encrypt | `you@example.com` (can be blank) | +| `TRAEFIK_ACME_CERT_DOMAINS` | The JSON list of all certificate domans | Use `make certs` to manage | +| `TRAEFIK_ACME_CERT_RESOLVER` | Lets Encrypt API environment | `production`,`staging` | | `TRAEFIK_ACME_CHALLENGE` | The ACME challenge type | `tls`,`dns` | -| `TRAEFIK_ROOT_DOMAIN` | The default root domain of every service | `d.rymcg.tech` | | `TRAEFIK_ACME_DNS_PROVIDER` | The LEGO DNS provider name | `digitalocean` | | `TRAEFIK_ACME_DNS_VARNAME_1` | The first LEGO DNS variable name | `DO_AUTH_TOKEN` | | `TRAEFIK_ACME_DNS_VARNAME_2` | The second LEGO DNS variable name | leave blank if there are no more | | `TRAEFIK_ACME_DNS_VARNAME_3` | The thrid LEGO DNS variable name | leave blank if there are no more | | `TRAEFIK_ACME_DNS_VARNAME_4` | The fourth LEGO DNS variable name | leave blank if there are no more | | `TRAEFIK_ACME_DNS_VARNAME_5` | The fifth LEGO DNS variable name | leave blank if there are no more | -| (various LEGO DNS variables) | All of your tokens for DNS provider | `DO_AUTH_TOKEN` | -| `TRAEFIK_ACME_CERT_DOMAINS` | The JSON list of all certificate domans | Use `make certs` to manage | -| `TRAEFIK_ACME_CERT_RESOLVER` | Lets Encrypt API environment | `production`,`staging` | +| `TRAEFIK_ACME_ENABLED` | (bool) Enable ACME TLS certificate resolver | `true`,`false` | +| `TRAEFIK_CONFIG_VERBOSE` | (bool) Print config to logs | `false`,`true` | | `TRAEFIK_CONFIG_YTT_VERSION` | YTT tool version | `v0.43.0` | -| `TRAEFIK_FILE_PROVIDER` | (bool) Enable the Traefik file provider | `true`,`false` | -| `TRAEFIK_FILE_PROVIDER_WATCH` | (bool) Enable automatic file reloading | `false`,`true` | -| `TRAEFIK_DOCKER_PROVIDER` | (bool) Enable the Traefik docker provider | `true`,`false` | +| `TRAEFIK_DASHBOARD_HTTP_AUTH` | The htpasswd encoded password for the dashboard | `$$apr1$$125jLjJS$$9WiXscLMURiMbC0meZXMv1` | +| `TRAEFIK_DASHBOARD_ENTRYPOINT_ENABLED` | (bool) Enable the dashboard entrypoint | `true`, `false` | +| `TRAEFIK_DASHBOARD_ENTRYPOINT_HOST` | The IP address to bind to | `127.0.0.1` (host networking) `0.0.0.0` (VPN) | +| `TRAEFIK_DASHBOARD_ENTRYPOINT_PORT` | The TCP port for the daashboard | `8080` | | `TRAEFIK_DOCKER_PROVIDER_CONSTRAINTS` | [Constraints](https://doc.traefik.io/traefik/providers/docker/#constraints) rule | None | -| `TRAEFIK_PLUGINS` | (bool) Enable Traefik plugins | `true`,`false` | -| `TRAEFIK_PLUGIN_BLOCKPATH` | (bool) Enable BlockPath plugin | `true`,`false` | -| `TRAEFIK_PLUGIN_MAXMIND_GEOIP` | (bool) Enable GeoIP plugin | `false`, `true` | +| `TRAEFIK_DOCKER_PROVIDER` | (bool) Enable the Traefik docker provider | `true`,`false` | +| `TRAEFIK_FILE_PROVIDER_WATCH` | (bool) Enable automatic file reloading | `false`,`true` | +| `TRAEFIK_FILE_PROVIDER` | (bool) Enable the Traefik file provider | `true`,`false` | | `TRAEFIK_GEOIPUPDATE_ACCOUNT_ID` | MaxMind account id for GeoIP database download | | -| `TRAEFIK_GEOIPUPDATE_LICENSE_KEY` | MaxMind license key for GeoIP database download | | | `TRAEFIK_GEOIPUPDATE_EDITION_IDS` | The list of GeoIP databases to download | `GeoLite2-ASN GeoLite2-City GeoLite2-Country` | -| `TRAEFIK_WEB_ENTRYPOINT_ENABLED` | (bool) Enable web (port 80) entrypoint | `true`,`false` | -| `TRAEFIK_WEB_ENTRYPOINT_HOST` | Host ip address to bind web entrypoint | `0.0.0.0` | -| `TRAEFIK_WEB_ENTRYPOINT_PORT` | Host TCP port to bind web entrypoint | `80` | -| `TRAEFIK_WEBSECURE_ENTRYPOINT_ENABLED` | (bool) Enable websecure (port 443) entrypoint | `true`,`false` | -| `TRAEFIK_WEBSECURE_ENTRYPOINT_HOST` | Host ip address to bind websecure entrypoint | `0.0.0.0` | -| `TRAEFIK_WEBSECURE_ENTRYPOINT_PORT` | Host TCP port to bind websecure entrypoint | `443` | -| `TRAEFIK_WEB_PLAIN_ENTRYPOINT_ENABLED` | (bool) Enable web_plain (port 8000) entrypoint | `true`,`false` | -| `TRAEFIK_WEB_PLAIN_ENTRYPOINT_HOST` | Host ip address to bind web_plain entrypoint | `0.0.0.0` | -| `TRAEFIK_WEB_PLAIN_ENTRYPOINT_PORT` | Host TCP port to bind web_plain entrypoint | `8000` | +| `TRAEFIK_GEOIPUPDATE_LICENSE_KEY` | MaxMind license key for GeoIP database download | | +| `TRAEFIK_HEADER_AUTHORIZATION_GROUPS` | JSON list of user groups for OAuth2 authorization | `{"admin":["root@localhost"]}` | +| `TRAEFIK_IMAGE` | The Traefik docker image | `traefik:v2.9` | +| `TRAEFIK_LOG_LEVEL` | Traefik log level | `warn`,`error`,`info`, `debug` | +| `TRAEFIK_MPD_ENTRYPOINT_ENABLED` | (bool) Enable mpd (unencrypted) entrypoint | | +| `TRAEFIK_MPD_ENTRYPOINT_HOST` | Host ip address to bind mpd entrypoint | `0.0.0.0` | +| `TRAEFIK_MPD_ENTRYPOINT_PORT` | Host TCP port to bind mpd entrypoint | `6600` | | `TRAEFIK_MQTT_ENTRYPOINT_ENABLED` | (bool) Enable mqtt (port 443) entrypoint | | | `TRAEFIK_MQTT_ENTRYPOINT_HOST` | Host ip address to bind mqtt entrypoint | `0.0.0.0` | | `TRAEFIK_MQTT_ENTRYPOINT_PORT` | Host TCP port to bind mqtt entrypoint | `8883` | +| `TRAEFIK_NETWORK_MODE` | Bind Traefik to host or serivce container networking | `host`,`wireguard`,`wireguard-client` | +| `TRAEFIK_PLUGINS` | (bool) Enable Traefik plugins | `true`,`false` | +| `TRAEFIK_PLUGIN_BLOCKPATH` | (bool) Enable BlockPath plugin | `true`,`false` | +| `TRAEFIK_PLUGIN_MAXMIND_GEOIP` | (bool) Enable GeoIP plugin | `false`, `true` | +| `TRAEFIK_ROOT_DOMAIN` | The default root domain of every service | `d.rymcg.tech` | +| `TRAEFIK_SEND_ANONYMOUS_USAGE` | (bool) Whether to send usage data to Traefik Labs | `false`, `true` | +| `TRAEFIK_SNAPCAST_ENTRYPOINT_ENABLED` | (bool) Enable snapcast (unencrypted) entrypoint | | +| `TRAEFIK_SNAPCAST_ENTRYPOINT_HOST` | Host ip address to bind snapcast entrypoint | `0.0.0.0` | +| `TRAEFIK_SNAPCAST_ENTRYPOINT_PORT` | Host TCP port to bind snapcast entrypoint | `1704` | | `TRAEFIK_SSH_ENTRYPOINT_ENABLED` | (bool) Enable ssh (port 2222) entrypoint | `true`,`false` | | `TRAEFIK_SSH_ENTRYPOINT_HOST` | Host ip address to bind ssh entrypoint | `0.0.0.0` | | `TRAEFIK_SSH_ENTRYPOINT_PORT` | Host TCP port to bind ssh entrypoint | `2222` | -| `TRAEFIK_VPN_ENABLED` | (bool) enable VPN server | `false`,`true` | -| `TRAEFIK_VPN_HOST` | Public hostname of VPN server | `vpn.example.com` | -| `TRAEFIK_VPN_ROOT_DOMAIN` | Root domain of the VPN services | `d.rymcg.tech` | | `TRAEFIK_VPN_ADDRESS` | Private VPN IP address of Traefik server | `10.13.16.1` | -| `TRAEFIK_VPN_PORT` | The TCP port to bind the VPN server to | `51820` | -| `TRAEFIK_VPN_PEERS` | The number or list of clients to create | `client1,client2`, `1` | -| `TRAEFIK_VPN_PEER_DNS` | The DNS server that clients are advertised to use | `auto` (uses host), `1.1.1.1` | -| `TRAEFIK_VPN_SUBNET` | The first .0 IP address of the private VPN subnet | `10.13.16.0` | | `TRAEFIK_VPN_ALLOWED_IPS` | Which IP subnets are routable by the VPN | `10.13.16.0/24`, `0.0.0.0` (all traffic) | | `TRAEFIK_VPN_CLIENT_ENABLED` | (bool) Enable the VPN client | `false`,`true` | | `TRAEFIK_VPN_CLIENT_INTERFACE_ADDRESS` | The VPN client private IP address | `10.13.16.2` | -| `TRAEFIK_VPN_CLIENT_INTERFACE_PRIVATE_KEY` | The VPN client private key | `4xxxxxxx=` | | `TRAEFIK_VPN_CLIENT_INTERFACE_LISTEN_PORT` | The VPN client listen port | `51820` | | `TRAEFIK_VPN_CLIENT_INTERFACE_PEER_DNS` | The VPN client peer DNS | `10.13.16.1` | -| `TRAEFIK_VPN_CLIENT_PEER_PUBLIC_KEY` | The VPN client public key | `5xxxxxxx=` | -| `TRAEFIK_VPN_CLIENT_PEER_PRESHARED_KEY` | The VPN client preshared key | `6xxxxxxx=` | -| `TRAEFIK_VPN_CLIENT_PEER_ENDPOINT` | The VPN server public endpoint | `vpn.example.com:51820` | +| `TRAEFIK_VPN_CLIENT_INTERFACE_PRIVATE_KEY` | The VPN client private key | `4xxxxxxx=` | | `TRAEFIK_VPN_CLIENT_PEER_ALLOWED_IPS` | The VPN client allowed routable IP addresses | `10.13.16.1/32` | +| `TRAEFIK_VPN_CLIENT_PEER_ENDPOINT` | The VPN server public endpoint | `vpn.example.com:51820` | +| `TRAEFIK_VPN_CLIENT_PEER_PRESHARED_KEY` | The VPN client preshared key | `6xxxxxxx=` | +| `TRAEFIK_VPN_CLIENT_PEER_PUBLIC_KEY` | The VPN client public key | `5xxxxxxx=` | | `TRAEFIK_VPN_CLIENT_PEER_SERVICES` | The list of VPN services to forward | `whoami,piwigo,freshrss` | -| `TRAEFIK_MPD_ENTRYPOINT_ENABLED` | (bool) Enable mpd (unencrypted) entrypoint | | -| `TRAEFIK_MPD_ENTRYPOINT_HOST` | Host ip address to bind mpd entrypoint | `0.0.0.0` | -| `TRAEFIK_MPD_ENTRYPOINT_PORT` | Host TCP port to bind mpd entrypoint | `6600` | -| `TRAEFIK_SNAPCAST_ENTRYPOINT_ENABLED` | (bool) Enable snapcast (unencrypted) entrypoint | | -| `TRAEFIK_SNAPCAST_ENTRYPOINT_HOST` | Host ip address to bind snapcast entrypoint | `0.0.0.0` | -| `TRAEFIK_SNAPCAST_ENTRYPOINT_PORT` | Host TCP port to bind snapcast entrypoint | `1704` | +| `TRAEFIK_VPN_ENABLED` | (bool) enable VPN server | `false`,`true` | +| `TRAEFIK_VPN_HOST` | Public hostname of VPN server | `vpn.example.com` | +| `TRAEFIK_VPN_PEERS` | The number or list of clients to create | `client1,client2`, `1` | +| `TRAEFIK_VPN_PEER_DNS` | The DNS server that clients are advertised to use | `auto` (uses host), `1.1.1.1` | +| `TRAEFIK_VPN_PORT` | The TCP port to bind the VPN server to | `51820` | +| `TRAEFIK_VPN_ROOT_DOMAIN` | Root domain of the VPN services | `d.rymcg.tech` | +| `TRAEFIK_VPN_SUBNET` | The first .0 IP address of the private VPN subnet | `10.13.16.0` | +| `TRAEFIK_WEBSECURE_ENTRYPOINT_ENABLED` | (bool) Enable websecure (port 443) entrypoint | `true`,`false` | +| `TRAEFIK_WEBSECURE_ENTRYPOINT_HOST` | Host ip address to bind websecure entrypoint | `0.0.0.0` | +| `TRAEFIK_WEBSECURE_ENTRYPOINT_PORT` | Host TCP port to bind websecure entrypoint | `443` | +| `TRAEFIK_WEB_ENTRYPOINT_ENABLED` | (bool) Enable web (port 80) entrypoint | `true`,`false` | +| `TRAEFIK_WEB_ENTRYPOINT_HOST` | Host ip address to bind web entrypoint | `0.0.0.0` | +| `TRAEFIK_WEB_ENTRYPOINT_PORT` | Host TCP port to bind web entrypoint | `80` | +| `TRAEFIK_WEB_PLAIN_ENTRYPOINT_ENABLED` | (bool) Enable web_plain (port 8000) entrypoint | `true`,`false` | +| `TRAEFIK_WEB_PLAIN_ENTRYPOINT_HOST` | Host ip address to bind web_plain entrypoint | `0.0.0.0` | +| `TRAEFIK_WEB_PLAIN_ENTRYPOINT_PORT` | Host TCP port to bind web_plain entrypoint | `8000` | diff --git a/traefik/config/config-template/header-authorization.yml b/traefik/config/config-template/header-authorization.yml new file mode 100644 index 00000000..4e08ab10 --- /dev/null +++ b/traefik/config/config-template/header-authorization.yml @@ -0,0 +1,22 @@ +#@ load("@ytt:data", "data") +#@yaml/text-templated-strings +http: + middlewares: + #@ for group in data.values.header_authorization_groups: + header-authorization-group-(@= group @): + plugin: + headauth: + methods: + - GET + - HEAD + - POST + - PUT + - DELETE + - CONNECT + - OPTIONS + - TRACE + - PATCH + header: + name: X-Forwarded-User + allowed: #@ data.values.header_authorization_groups[group] + #@ end diff --git a/traefik/config/setup.sh b/traefik/config/setup.sh index 29daf58d..348d9297 100644 --- a/traefik/config/setup.sh +++ b/traefik/config/setup.sh @@ -24,6 +24,7 @@ ytt_template() { -v plugins="${TRAEFIK_PLUGINS}" \ -v plugin_blockpath="${TRAEFIK_PLUGIN_BLOCKPATH}" \ -v plugin_maxmind_geoip="${TRAEFIK_PLUGIN_MAXMIND_GEOIP}" \ + -v plugin_header_authorization="${TRAEFIK_PLUGIN_HEADER_AUTHORIZATION}" \ -v plugin_referer="${TRAEFIK_PLUGIN_REFERER}" \ -v web_entrypoint_enabled="${TRAEFIK_WEB_ENTRYPOINT_ENABLED}" \ -v web_entrypoint_host="${TRAEFIK_WEB_ENTRYPOINT_HOST}" \ @@ -43,7 +44,7 @@ ytt_template() { -v dashboard_entrypoint_enabled="${TRAEFIK_DASHBOARD_ENTRYPOINT_ENABLED}" \ -v dashboard_entrypoint_host="${TRAEFIK_DASHBOARD_ENTRYPOINT_HOST}" \ -v dashboard_entrypoint_port="${TRAEFIK_DASHBOARD_ENTRYPOINT_PORT}" \ - -v dashboard_auth="${TRAEFIK_DASHBOARD_AUTH}" \ + -v dashboard_auth="${TRAEFIK_DASHBOARD_HTTP_AUTH}" \ -v vpn_address="${TRAEFIK_VPN_ADDRESS}" \ -v vpn_enabled="${TRAEFIK_VPN_ENABLED}" \ -v vpn_subnet="${TRAEFIK_VPN_SUBNET}" \ @@ -73,6 +74,7 @@ ytt_template() { -v error_handler_403_service="${TRAEFIK_ERROR_HANDLER_403_SERVICE}" \ -v error_handler_404_service="${TRAEFIK_ERROR_HANDLER_404_SERVICE}" \ -v error_handler_500_service="${TRAEFIK_ERROR_HANDLER_500_SERVICE}" \ + --data-value-yaml header_authorization_groups="${TRAEFIK_HEADER_AUTHORIZATION_GROUPS}" \ > ${dst} success=$? echo "[ ! ] GENERATED NEW CONFIG FILE ::: ${dst}" diff --git a/traefik/config/traefik.yml b/traefik/config/traefik.yml index dc19f452..02eeb998 100644 --- a/traefik/config/traefik.yml +++ b/traefik/config/traefik.yml @@ -28,6 +28,10 @@ experimental: referer: modulename: github.com/moonlightwatch/referer #@ end + #@ if data.values.plugin_header_authorization == "true": + headauth: + modulename: github.com/poloyacero/headauth + #@ end #@ end providers: diff --git a/traefik/docker-compose.yaml b/traefik/docker-compose.yaml index c913d439..0459abbd 100644 --- a/traefik/docker-compose.yaml +++ b/traefik/docker-compose.yaml @@ -39,6 +39,7 @@ services: - TRAEFIK_PLUGIN_BLOCKPATH=${TRAEFIK_PLUGIN_BLOCKPATH:-true} - TRAEFIK_PLUGIN_MAXMIND_GEOIP=${TRAEFIK_PLUGIN_MAXMIND_GEOIP:-false} - TRAEFIK_PLUGIN_REFERER=${TRAEFIK_PLUGIN_REFERER:-true} + - TRAEFIK_PLUGIN_HEADER_AUTHORIZATION=${TRAEFIK_PLUGIN_HEADER_AUTHORIZATION:-true} - TRAEFIK_WEB_ENTRYPOINT_ENABLED=${TRAEFIK_WEB_ENTRYPOINT_ENABLED:-false} - TRAEFIK_WEB_ENTRYPOINT_HOST=${TRAEFIK_WEB_ENTRYPOINT_HOST:-0.0.0.0} - TRAEFIK_WEB_ENTRYPOINT_PORT=${TRAEFIK_WEB_ENTRYPOINT_PORT:-80} @@ -72,7 +73,7 @@ services: - TRAEFIK_DASHBOARD_ENTRYPOINT_ENABLED=${TRAEFIK_DASHBOARD_ENTRYPOINT_ENABLED:-false} - TRAEFIK_DASHBOARD_ENTRYPOINT_HOST=${TRAEFIK_DASHBOARD_ENTRYPOINT_HOST:-127.0.0.1} - TRAEFIK_DASHBOARD_ENTRYPOINT_PORT=${TRAEFIK_DASHBOARD_ENTRYPOINT_PORT:-8080} - - TRAEFIK_DASHBOARD_AUTH + - TRAEFIK_DASHBOARD_HTTP_AUTH - TRAEFIK_VPN_ADDRESS - TRAEFIK_VPN_ENABLED=${TRAEFIK_VPN_ENABLED:-false} - TRAEFIK_VPN_CLIENT_ENABLED=${TRAEFIK_VPN_CLIENT_ENABLED:-false} @@ -87,6 +88,7 @@ services: - TRAEFIK_ERROR_HANDLER_403_SERVICE - TRAEFIK_ERROR_HANDLER_404_SERVICE - TRAEFIK_ERROR_HANDLER_500_SERVICE + - TRAEFIK_HEADER_AUTHORIZATION_GROUPS traefik: profiles: - default @@ -101,6 +103,8 @@ services: REFERER_GIT_BRANCH: master MAXMIND_GEOIP_MODULE: ${TRAEFIK_GEOIP_MODULE} MAXMIND_GIT_BRANCH: main + HEADER_AUTHORIZATION_MODULE: ${TRAEFIK_HEADER_AUTHORIZATION_MODULE} + HEADER_AUTHORIZATION_GIT_BRANCH: main TRAEFIK_UID: ${TRAEFIK_UID} TRAEFIK_GID: ${TRAEFIK_GID} TRAEFIK_DOCKER_GID: ${TRAEFIK_DOCKER_GID} @@ -122,6 +126,8 @@ services: - "traefik:/data" - "/var/run/docker.sock:/var/run/docker.sock:ro" - "geoip_database:/var/lib/traefikgeoip2/" + labels: + - "TRAEFIK_HEADER_AUTHORIZATION_GROUPS=${TRAEFIK_HEADER_AUTHORIZATION_GROUPS}" geoip_database: profiles: diff --git a/ttrss/.env-dist b/ttrss/.env-dist index c044adc7..7facc5a4 100644 --- a/ttrss/.env-dist +++ b/ttrss/.env-dist @@ -1,4 +1,5 @@ TTRSS_TRAEFIK_HOST=ttrss.example.com +TTRSS_INSTANCE= ## If using the default HTTPS port (443) this should be left blank: TTRSS_TRAEFIK_PORT= @@ -8,3 +9,22 @@ TTRSS_TRAEFIK_PORT= TTRSS_DB_PASS=change_me TTRSS_IP_SOURCERANGE=0.0.0.0/0 + +TTRSS_HTTP_AUTH= + +# OAUTH2 +# Set to `true` to use OpenID/OAuth2 authentication via the +# traefik-forward-auth service in d.rymcg.tech. +# Using OpenID/OAuth2 will require login to access your app, +# but it will not affect what a successfully logged-in person can do in your +# app. If your app has built-in authentication and can check the user +# header that traefik-forward-auth sends, then your app can limit what the +# logged-in person can do in the app. But if your app can't check the user +# header, or if your app doesn't have built-in authentication at all, then +# any person with an account on your Gitea server can log into your app and +# have full access. +TTRSS_OAUTH2= +# In addition to Oauth2 authentication, you can configure basic authorization +# by entering which authorization group can log into your app. You create +# groups of email addresses in the `traefik` folder by running `make groups`. +TTRSS_OAUTH2_AUTHORIZED_GROUP= diff --git a/ttrss/Makefile b/ttrss/Makefile index fa7ccdd3..d32df099 100644 --- a/ttrss/Makefile +++ b/ttrss/Makefile @@ -1,12 +1,28 @@ ROOT_DIR = .. include ${ROOT_DIR}/_scripts/Makefile.projects-custom-build +include ${ROOT_DIR}/_scripts/Makefile.instance .PHONY: config-hook config-hook: - @${BIN}/reconfigure_ask ${ENV_FILE} TTRSS_TRAEFIK_HOST "Enter the ttrss domain name" ttrss.${ROOT_DOMAIN} + @${BIN}/reconfigure_ask ${ENV_FILE} TTRSS_TRAEFIK_HOST "Enter the ttrss domain name" ttrss${INSTANCE_URL_SUFFIX}.${ROOT_DOMAIN} @${BIN}/reconfigure_password ${ENV_FILE} TTRSS_DB_PASS @PUBLIC_HTTPS_PORT="$$(${BIN}/dotenv -f ${ROOT_DIR}/${ROOT_ENV} get PUBLIC_HTTPS_PORT)"; test "$${PUBLIC_HTTPS_PORT}" == "443" && ${BIN}/reconfigure ${ENV_FILE} TTRSS_TRAEFIK_PORT='' || ${BIN}/reconfigure ${ENV_FILE} TTRSS_TRAEFIK_PORT="$${PUBLIC_HTTPS_PORT}" + @${BIN}/reconfigure_auth ${ENV_FILE} TTRSS .PHONY: build build: @make --no-print-directory docker-compose-build EXTRA_ARGS="${EXTRA_ARGS}" DOCKER_BUILDKIT=0 + +.PHONY: override-hook +override-hook: +#### This sets the override template variables for docker-compose.instance.yaml: +#### The template dynamically renders to docker-compose.override_{DOCKER_CONTEXT}_{INSTANCE}.yaml +#### These settings are used to automatically generate the service container labels, and traefik config, inside the template. +#### The variable arguments have three forms: `=` `=:` `=@` +#### name=VARIABLE_NAME # sets the template 'name' field to the value of VARIABLE_NAME found in the .env file +#### # (this hardcodes the value into docker-compose.override.yaml) +#### name=:VARIABLE_NAME # sets the template 'name' field to the literal string 'VARIABLE_NAME' +#### # (this hardcodes the string into docker-compose.override.yaml) +#### name=@VARIABLE_NAME # sets the template 'name' field to the literal string '${VARIABLE_NAME}' +#### # (used for regular docker-compose expansion of env vars by name.) + @${BIN}/docker_compose_override ${ENV_FILE} project=:ttrss instance=@TTRSS_INSTANCE traefik_host=@TTRSS_TRAEFIK_HOST http_auth=TTRSS_HTTP_AUTH http_auth_var=@TTRSS_HTTP_AUTH ip_sourcerange=@TTRSS_IP_SOURCERANGE oauth2=TTRSS_OAUTH2 authorized_group=TTRSS_OAUTH2_AUTHORIZED_GROUP diff --git a/ttrss/docker-compose.instance.yaml b/ttrss/docker-compose.instance.yaml new file mode 100644 index 00000000..573aa262 --- /dev/null +++ b/ttrss/docker-compose.instance.yaml @@ -0,0 +1,48 @@ +#! This is a ytt template file for docker-compose.override.yaml +#! References: +#! https://carvel.dev/ytt +#! https://docs.docker.com/compose/extends/#adding-and-overriding-configuration +#! https://github.com/enigmacurry/d.rymcg.tech#overriding-docker-composeyaml-per-instance + +#! ### Standard project vars: +#@ load("@ytt:data", "data") +#@ project = data.values.project +#@ instance = data.values.instance +#@ context = data.values.context +#@ traefik_host = data.values.traefik_host +#@ ip_sourcerange = data.values.ip_sourcerange +#@ enable_http_auth = len(data.values.http_auth.strip()) > 0 +#@ http_auth = data.values.http_auth_var +#@ enable_oauth2 = data.values.oauth2 == "true" +#@ authorized_group = data.values.authorized_group +#@ enabled_middlewares = [] + +#@yaml/text-templated-strings +services: + web-nginx: + #@ service = "ttrss" + labels: + #! Services must opt-in to be proxied by Traefik: + - "traefik.enable=true" + + #! 'router' is the fully qualified key in traefik for this router/service: project + instance + service + #@ router = "{}-{}-{}".format(project,instance,service) + + #! The host matching router rule: + - "traefik.http.routers.(@= router @).rule=Host(`(@= traefik_host @)`)" + - "traefik.http.routers.(@= router @).entrypoints=websecure" + #@ enabled_middlewares.append("{}-ipwhitelist".format(router)) + - "traefik.http.middlewares.(@= router @)-ipwhitelist.ipwhitelist.sourcerange=(@= ip_sourcerange @)" + + #@ if enable_http_auth: + #@ enabled_middlewares.append("{}-basicauth".format(router)) + - "traefik.http.middlewares.(@= router @)-basicauth.basicauth.users=(@= http_auth @)" + #@ end + + #@ if enable_oauth2: + #@ enabled_middlewares.append("traefik-forward-auth@docker") + #@ enabled_middlewares.append("header-authorization-group-{}@file".format(authorized_group)) + #@ end + + #! Apply all middlewares (do this at the end!) + - "traefik.http.routers.(@= router @).middlewares=(@= ','.join(enabled_middlewares) @)" diff --git a/ttrss/docker-compose.yaml b/ttrss/docker-compose.yaml index e6cc767d..b58f1dbd 100644 --- a/ttrss/docker-compose.yaml +++ b/ttrss/docker-compose.yaml @@ -14,8 +14,7 @@ services: - db:/var/lib/postgresql/data app: - build: - context: https://github.com/EnigmaCurry/fox-ttrss-docker-compose.git#master:app + image: cthulhoo/ttrss-fpm-pgsql-static:latest security_opt: - no-new-privileges:true restart: unless-stopped @@ -26,13 +25,12 @@ services: - TTRSS_SELF_URL_PATH=https://${TTRSS_TRAEFIK_HOST}${TTRSS_TRAEFIK_PORT}/tt-rss volumes: - app:/var/www/html - - ./config.d:/opt/tt-rss/config.d:ro + - config:/opt/tt-rss/config.d:ro depends_on: - db backups: - build: - context: https://github.com/EnigmaCurry/fox-ttrss-docker-compose.git#master:app + image: cthulhoo/ttrss-fpm-pgsql-static:latest environment: - TTRSS_DB_USER=ttrss - TTRSS_DB_NAME=ttrss @@ -49,8 +47,7 @@ services: command: /opt/tt-rss/dcron.sh -f updater: - build: - context: https://github.com/EnigmaCurry/fox-ttrss-docker-compose.git#master:app + image: cthulhoo/ttrss-fpm-pgsql-static:latest environment: - TTRSS_DB_USER=ttrss - TTRSS_DB_NAME=ttrss @@ -67,8 +64,7 @@ services: command: /opt/tt-rss/updater.sh web-nginx: - build: - context: https://github.com/EnigmaCurry/fox-ttrss-docker-compose.git#master:web-nginx + image: cthulhoo/ttrss-web-nginx:latest security_opt: - no-new-privileges:true restart: unless-stopped @@ -76,15 +72,11 @@ services: - app:/var/www/html:ro depends_on: - app - labels: - - "traefik.enable=true" - - "traefik.http.routers.ttrss.rule=Host(`${TTRSS_TRAEFIK_HOST}`)" - - "traefik.http.routers.ttrss.entrypoints=websecure" - - "traefik.http.middlewares.ttrss-${TTRSS_INSTANCE:-default}-ipwhitelist.ipwhitelist.sourcerange=${TTRSS_IP_SOURCERANGE}" - - "traefik.http.routers.ttrss-${TTRSS_INSTANCE:-default}.middlewares=ttrss-${TTRSS_INSTANCE:-default}-ipwhitelist" + labels: [] volumes: db: app: certs: backups: + config: diff --git a/vaultwarden/.env-dist b/vaultwarden/.env-dist index 58338524..c2be9dd3 100644 --- a/vaultwarden/.env-dist +++ b/vaultwarden/.env-dist @@ -1,5 +1,5 @@ # https://hub.docker.com/r/vaultwarden/server/tags -VAULTWARDEN_VERSION=1.27.0 +VAULTWARDEN_VERSION=1.29.2 VAULTWARDEN_TRAEFIK_HOST=vaultwarden.example.com @@ -14,4 +14,23 @@ VAULTWARDEN_SIGNUPS_ALLOWED=true VAULTWARDEN_SHOW_PASSWORD_HINT=false VAULTWARDEN_INVITATIONS_ALLOWED=true -VAULTWARDEN_BASE_PATH= +# HTTP Basic Authentication: +# Use `make config` to fill this in properly, or set this to blank to disable. +VAULTWARDEN_HTTP_AUTH= + +# OAUTH2 +# Set to `true` to use OpenID/OAuth2 authentication via the +# traefik-forward-auth service in d.rymcg.tech. +# Using OpenID/OAuth2 will require login to access your app, +# but it will not affect what a successfully logged-in person can do in your +# app. If your app has built-in authentication and can check the user +# header that traefik-forward-auth sends, then your app can limit what the +# logged-in person can do in the app. But if your app can't check the user +# header, or if your app doesn't have built-in authentication at all, then +# any person with an account on your Gitea server can log into your app and +# have full access. +VAULTWARDEN_OAUTH2= +# In addition to Oauth2 authentication, you can configure basic authorization +# by entering which authorization group can log into your app. You create +# groups of email addresses in the `traefik` folder by running `make groups`. +VAULTWARDEN_OAUTH2_AUTHORIZED_GROUP= diff --git a/vaultwarden/Makefile b/vaultwarden/Makefile index f936ccf2..03323844 100644 --- a/vaultwarden/Makefile +++ b/vaultwarden/Makefile @@ -6,8 +6,8 @@ include ${ROOT_DIR}/_scripts/Makefile.instance config-hook: @${BIN}/reconfigure_ask ${ENV_FILE} VAULTWARDEN_TRAEFIK_HOST "Enter the vaultwarden domain name" vaultwarden${INSTANCE_URL_SUFFIX}.${ROOT_DOMAIN} @${BIN}/reconfigure ${ENV_FILE} VAULTWARDEN_INSTANCE=$${instance:-default} - @${BIN}/confirm yes "Would you like to set a base path to obfuscate the URL with an appended security string" "?" && ${BIN}/reconfigure_password ${ENV_FILE} VAULTWARDEN_BASE_PATH || ${BIN}/reconfigure ${ENV_FILE} VAULTWARDEN_BASE_PATH='' @test "$$(${BIN}/dotenv -f ${ENV_FILE} get VAULTWARDEN_SIGNUPS_ALLOWED)" == "true" && echo && echo "Warning: Public registration is enabled." && echo "After creating your first account, you may disable registration: \`make disable-registration\`." || true + @${BIN}/reconfigure_auth ${ENV_FILE} VAULTWARDEN .PHONY: enable-registration enable-registration: @@ -21,4 +21,18 @@ disable-registration: .PHONY: open open: - @ENV_FILE=${ENV_FILE} CONTEXT_INSTANCE=${CONTEXT_INSTANCE} ${BIN}/open "/$$(dotenv -f ${ENV_FILE} get VAULTWARDEN_BASE_PATH)/" + @ENV_FILE=${ENV_FILE} CONTEXT_INSTANCE=${CONTEXT_INSTANCE} ${BIN}/open "$$(${BIN}/dotenv -f ${ENV_FILE} get VAULTWARDEN_BASE_PATH)/" + +.PHONY: override-hook +override-hook: +#### This sets the override template variables for docker-compose.instance.yaml: +#### The template dynamically renders to docker-compose.override_{DOCKER_CONTEXT}_{INSTANCE}.yaml +#### These settings are used to automatically generate the service container labels, and traefik config, inside the template. +#### The variable arguments have three forms: `=` `=:` `=@` +#### name=VARIABLE_NAME # sets the template 'name' field to the value of VARIABLE_NAME found in the .env file +#### # (this hardcodes the value into docker-compose.override.yaml) +#### name=:VARIABLE_NAME # sets the template 'name' field to the literal string 'VARIABLE_NAME' +#### # (this hardcodes the string into docker-compose.override.yaml) +#### name=@VARIABLE_NAME # sets the template 'name' field to the literal string '${VARIABLE_NAME}' +#### # (used for regular docker-compose expansion of env vars by name.) + @${BIN}/docker_compose_override ${ENV_FILE} project=:vaultwarden instance=@VAULTWARDEN_INSTANCE traefik_host=@VAULTWARDEN_TRAEFIK_HOST http_auth=VAULTWARDEN_HTTP_AUTH http_auth_var=@VAULTWARDEN_HTTP_AUTH ip_sourcerange=@VAULTWARDEN_IP_SOURCERANGE oauth2=VAULTWARDEN_OAUTH2 authorized_group=VAULTWARDEN_OAUTH2_AUTHORIZED_GROUP diff --git a/vaultwarden/README.md b/vaultwarden/README.md index cf97fc4a..d1d5ca4e 100644 --- a/vaultwarden/README.md +++ b/vaultwarden/README.md @@ -8,6 +8,10 @@ rewritten in Rust, formerly known as bitwarden_rs. make config ``` +Check the config in the `.env_${DOCKER_CONTEXT}_${INSTANCE}` file. +Check that the pinned `VAULTWARDEN_VERSION` is actually the latest +version. + ``` make install ``` @@ -64,10 +68,6 @@ few mitigations you can apply to make this a bit more secure: `VAULTWARDEN_SIGNUPS_ALLOWED=false` in the `.env_{DOCKER_CONTEXT}_{INSTANCE}` file (or run `make disable-registration`). - * Make sure to set the base path security string, set - `VAULTWARDEN_BASE_PATH=xxxxxxxxxxx` in the - `.env_{DOCKER_CONTEXT}_{INSTANCE}` file (this is asked for in `make - config`). * By default this container accepts connections from all IP addresses, however you can limit this by editing the `VAULTWARDEN_IP_SOURCERANGE` variable in the diff --git a/vaultwarden/docker-compose.instance.yaml b/vaultwarden/docker-compose.instance.yaml new file mode 100644 index 00000000..d0b16e1c --- /dev/null +++ b/vaultwarden/docker-compose.instance.yaml @@ -0,0 +1,59 @@ +#! This is a ytt template file for docker-compose.override.yaml +#! References: +#! https://carvel.dev/ytt +#! https://docs.docker.com/compose/extends/#adding-and-overriding-configuration +#! https://github.com/enigmacurry/d.rymcg.tech#overriding-docker-composeyaml-per-instance + +#! ### Standard project vars: +#@ load("@ytt:data", "data") +#@ project = data.values.project +#@ instance = data.values.instance +#@ context = data.values.context +#@ traefik_host = data.values.traefik_host +#@ ip_sourcerange = data.values.ip_sourcerange +#@ enable_http_auth = len(data.values.http_auth.strip()) > 0 +#@ http_auth = data.values.http_auth_var +#@ enable_oauth2 = data.values.oauth2 == "true" +#@ authorized_group = data.values.authorized_group +#@ enabled_middlewares = [] + +#@yaml/text-templated-strings +services: + vaultwarden: + #@ service = "vaultwarden" + labels: + #! Services must opt-in to be proxied by Traefik: + - "traefik.enable=true" + + #! 'router' is the fully qualified key in traefik for this router/service: project + instance + service + #@ router = "{}-{}-{}".format(project,instance,service) + + #! The host matching router rule: + - "traefik.http.routers.(@= router @).rule=Host(`(@= traefik_host @)`)" + - "traefik.http.routers.(@= router @).service=vaultwarden-${INSTANCE:-default}-vaultwarden" + - "traefik.http.routers.(@= router @).entrypoints=websecure" + - "traefik.http.services.(@= router @).loadbalancer.server.port=80" + + #! vaultwarden websocket proxy: + - "traefik.http.routers.(@= router @)-websocket.rule=Host(`(@= traefik_host @)`) && Path(`/notifications/hub`)" + - "traefik.http.routers.(@= router @)-websocket.service=vaultwarden-${INSTANCE:-default}-websocket" + - "traefik.http.routers.(@= router @)-websocket.entrypoints=websecure" + - "traefik.http.services.(@= router @)-websocket.loadbalancer.server.port=3012" + + #@ enabled_middlewares.append("{}-ipwhitelist".format(router)) + - "traefik.http.middlewares.(@= router @)-ipwhitelist.ipwhitelist.sourcerange=(@= ip_sourcerange @)" + + #@ if enable_http_auth: + #@ enabled_middlewares.append("{}-basicauth".format(router)) + - "traefik.http.middlewares.(@= router @)-basicauth.basicauth.users=(@= http_auth @)" + #@ end + + #@ if enable_oauth2: + #@ enabled_middlewares.append("traefik-forward-auth@docker") + #@ enabled_middlewares.append("header-authorization-group-{}@file".format(authorized_group)) + #@ end + + #! Apply all middlewares (do this at the end!) + - "traefik.http.routers.(@= router @).middlewares=(@= ','.join(enabled_middlewares) @)" + - "traefik.http.routers.(@= router @)-websocket.middlewares=(@= ','.join(enabled_middlewares) @)" + diff --git a/vaultwarden/docker-compose.yaml b/vaultwarden/docker-compose.yaml index c6f29626..d83a89a9 100644 --- a/vaultwarden/docker-compose.yaml +++ b/vaultwarden/docker-compose.yaml @@ -20,27 +20,11 @@ services: - SIGNUPS_ALLOWED=${VAULTWARDEN_SIGNUPS_ALLOWED} - SHOW_PASSWORD_HINT=${VAULTWARDEN_SHOW_PASSWORD_HINT} - INVITATIONS_ALLOWED=${VAULTWARDEN_INVITATIONS_ALLOWED} - - DOMAIN=https://${VAULTWARDEN_TRAEFIK_HOST}/${VAULTWARDEN_BASE_PATH} + - DOMAIN=https://${VAULTWARDEN_TRAEFIK_HOST}/ volumes: - data:/data # labels are defined in docker-compose.instance.yaml - labels: - - "traefik.enable=true" - - - "traefik.http.routers.vaultwarden-${INSTANCE:-default}.rule=Host(`${VAULTWARDEN_TRAEFIK_HOST}`) && PathPrefix(`/${VAULTWARDEN_BASE_PATH}`)" - - "traefik.http.routers.vaultwarden-${INSTANCE:-default}.service=vaultwarden-${INSTANCE:-default}" - - "traefik.http.routers.vaultwarden-${INSTANCE:-default}.entrypoints=websecure" - - "traefik.http.services.vaultwarden-${INSTANCE:-default}.loadbalancer.server.port=80" - - - "traefik.http.middlewares.vaultwarden-${INSTANCE:-default}-ipwhitelist.ipwhitelist.sourcerange=${VAULTWARDEN_IP_SOURCERANGE}" - - - "traefik.http.routers.vaultwarden-${INSTANCE:-default}-websocket.rule=Host(`${VAULTWARDEN_TRAEFIK_HOST}`) && Path(`/notifications/hub`)" - - "traefik.http.routers.vaultwarden-${INSTANCE:-default}-websocket.service=vaultwarden-${INSTANCE:-default}-websocket" - - "traefik.http.routers.vaultwarden-${INSTANCE:-default}-websocket.entrypoints=websecure" - - "traefik.http.services.vaultwarden-${INSTANCE:-default}-websocket.loadbalancer.server.port=3012" - - - "traefik.http.routers.vaultwarden-${INSTANCE:-default}.middlewares=vaultwarden-${INSTANCE:-default}-ipwhitelist" - - "traefik.http.routers.vaultwarden-${INSTANCE:-default}-websocket.middlewares=vaultwarden-${INSTANCE:-default}-ipwhitelist" + labels: [] volumes: data: diff --git a/websocketd/.env-dist b/websocketd/.env-dist index 1307e764..1795a067 100644 --- a/websocketd/.env-dist +++ b/websocketd/.env-dist @@ -9,3 +9,20 @@ WEBSOCKETD_IP_SOURCERANGE=0.0.0.0/0 WEBSOCKETD_APP_PATH=/app # Turn on dev console: WEBSOCKETD_DEV_CONSOLE=false + +# OAUTH2 +# Set to `true` to use OpenID/OAuth2 authentication via the +# traefik-forward-auth service in d.rymcg.tech. +# Using OpenID/OAuth2 will require login to access your app, +# but it will not affect what a successfully logged-in person can do in your +# app. If your app has built-in authentication and can check the user +# header that traefik-forward-auth sends, then your app can limit what the +# logged-in person can do in the app. But if your app can't check the user +# header, or if your app doesn't have built-in authentication at all, then +# any person with an account on your Gitea server can log into your app and +# have full access. +WEBSOCKETD_OAUTH2= +# In addition to Oauth2 authentication, you can configure basic authorization +# by entering which authorization group can log into your app. You create +# groups of email addresses in the `traefik` folder by running `make groups`. +WEBSOCKETD_OAUTH2_AUTHORIZED_GROUP= diff --git a/websocketd/Makefile b/websocketd/Makefile index e4c211f1..2d3bd34b 100644 --- a/websocketd/Makefile +++ b/websocketd/Makefile @@ -8,7 +8,7 @@ config-hook: @${BIN}/reconfigure_ask ${ENV_FILE} WEBSOCKETD_TRAEFIK_HOST "Enter the websocketd domain name" socket${INSTANCE_URL_SUFFIX}.${ROOT_DOMAIN} @${BIN}/reconfigure_ask ${ENV_FILE} WEBSOCKETD_APP_PATH "Enter the websocketd application path" /app @${BIN}/confirm $$([[ $$(${BIN}/dotenv -f ${ENV_FILE} get WEBSOCKETD_DEV_CONSOLE) == "true" ]] && echo "yes" || echo "no") "Do you want to enable dev console?" "?" && ${BIN}/reconfigure ${ENV_FILE} WEBSOCKETD_DEV_CONSOLE=true || ${BIN}/reconfigure ${ENV_FILE} WEBSOCKETD_DEV_CONSOLE=false - @${BIN}/reconfigure_htpasswd ${ENV_FILE} WEBSOCKETD_HTTP_AUTH default=no + @${BIN}/reconfigure_auth ${ENV_FILE} WEBSOCKETD .PHONY: open # Open dev console open: @@ -26,4 +26,4 @@ override-hook: #### # (this hardcodes the string into docker-compose.override.yaml) #### name=@VARIABLE_NAME # sets the template 'name' field to the literal string '${VARIABLE_NAME}' #### # (used for regular docker-compose expansion of env vars by name.) - @${BIN}/docker_compose_override ${ENV_FILE} project=:websocketd instance=@WEBSOCKETD_INSTANCE traefik_host=@WEBSOCKETD_TRAEFIK_HOST http_auth=WEBSOCKETD_HTTP_AUTH http_auth_var=@WEBSOCKETD_HTTP_AUTH ip_sourcerange=@WEBSOCKETD_IP_SOURCERANGE + @${BIN}/docker_compose_override ${ENV_FILE} project=:websocketd instance=@WEBSOCKETD_INSTANCE traefik_host=@WEBSOCKETD_TRAEFIK_HOST http_auth=WEBSOCKETD_HTTP_AUTH http_auth_var=@WEBSOCKETD_HTTP_AUTH ip_sourcerange=@WEBSOCKETD_IP_SOURCERANGE oauth2=WEBSOCKETD_OAUTH2 authorized_group=WEBSOCKETD_OAUTH2_AUTHORIZED_GROUP diff --git a/websocketd/docker-compose.instance.yaml b/websocketd/docker-compose.instance.yaml index 9639ede3..457230b5 100644 --- a/websocketd/docker-compose.instance.yaml +++ b/websocketd/docker-compose.instance.yaml @@ -13,6 +13,8 @@ #@ ip_sourcerange = data.values.ip_sourcerange #@ enable_http_auth = len(data.values.http_auth.strip()) > 0 #@ http_auth = data.values.http_auth_var +#@ enable_oauth2 = data.values.oauth2 == "true" +#@ authorized_group = data.values.authorized_group #@ enabled_middlewares = [] #@yaml/text-templated-strings @@ -40,5 +42,10 @@ services: - "traefik.http.middlewares.(@= router @)-stripprefix.stripprefix.prefixes=${WEBSOCKETD_APP_PATH}" #@ enabled_middlewares.append("{}-stripprefix".format(router)) + #@ if enable_oauth2: + #@ enabled_middlewares.append("traefik-forward-auth@docker") + #@ enabled_middlewares.append("header-authorization-group-{}@file".format(authorized_group)) + #@ end + #! Apply all middlewares (do this at the end!) - "traefik.http.routers.(@= router @).middlewares=(@= ','.join(enabled_middlewares) @)" diff --git a/whoami/.env-dist b/whoami/.env-dist index 1091e22b..794906f9 100644 --- a/whoami/.env-dist +++ b/whoami/.env-dist @@ -14,7 +14,7 @@ WHOAMI_IP_SOURCERANGE=0.0.0.0/0 WHOAMI_HTTP_AUTH= # OAUTH2 -# Set to `yes` to use OpenID/OAuth2 authentication via the +# Set to `true` to use OpenID/OAuth2 authentication via the # traefik-forward-auth service in d.rymcg.tech. # Using OpenID/OAuth2 will require login to access your app, # but it will not affect what a successfully logged-in person can do in your @@ -24,7 +24,11 @@ WHOAMI_HTTP_AUTH= # header, or if your app doesn't have built-in authentication at all, then # any person with an account on your Gitea server can log into your app and # have full access. -WHOAMI_OAUTH2=no +WHOAMI_OAUTH2= +# In addition to Oauth2 authentication, you can configure basic authorization +# by entering which authorization group can log into your app. You create +# groups of email addresses in the `traefik` folder by running `make groups`. +WHOAMI_OAUTH2_AUTHORIZED_GROUP= ## You can run the whoami service as any user/group: WHOAMI_UID=54321 diff --git a/whoami/Makefile b/whoami/Makefile index adc717dd..7694005d 100644 --- a/whoami/Makefile +++ b/whoami/Makefile @@ -10,8 +10,7 @@ config-hook: #### reconfigure_htpasswd will configure the HTTP Basic Authentication setting the var name and with a provided default value. @${BIN}/reconfigure_ask ${ENV_FILE} WHOAMI_TRAEFIK_HOST "Enter the whoami domain name" whoami${INSTANCE_URL_SUFFIX}.${ROOT_DOMAIN} @${BIN}/reconfigure ${ENV_FILE} WHOAMI_INSTANCE=$${instance:-default} - @${BIN}/reconfigure_htpasswd ${ENV_FILE} WHOAMI_HTTP_AUTH default=no - @${BIN}/reconfigure_oauth2 ${ENV_FILE} WHOAMI_OAUTH2 default=$$( ${BIN}/dotenv -f ${ENV_FILE} get WHOAMI_OAUTH2 ) + @${BIN}/reconfigure_auth ${ENV_FILE} WHOAMI @echo "" .PHONY: override-hook @@ -26,4 +25,4 @@ override-hook: #### # (this hardcodes the string into docker-compose.override.yaml) #### name=@VARIABLE_NAME # sets the template 'name' field to the literal string '${VARIABLE_NAME}' #### # (used for regular docker-compose expansion of env vars by name.) - @${BIN}/docker_compose_override ${ENV_FILE} project=:whoami instance=@WHOAMI_INSTANCE traefik_host=@WHOAMI_TRAEFIK_HOST http_auth=WHOAMI_HTTP_AUTH http_auth_var=@WHOAMI_HTTP_AUTH ip_sourcerange=@WHOAMI_IP_SOURCERANGE oauth2=WHOAMI_OAUTH2 + @${BIN}/docker_compose_override ${ENV_FILE} project=:whoami instance=@WHOAMI_INSTANCE traefik_host=@WHOAMI_TRAEFIK_HOST http_auth=WHOAMI_HTTP_AUTH http_auth_var=@WHOAMI_HTTP_AUTH ip_sourcerange=@WHOAMI_IP_SOURCERANGE oauth2=WHOAMI_OAUTH2 authorized_group=WHOAMI_OAUTH2_AUTHORIZED_GROUP diff --git a/whoami/README.md b/whoami/README.md index 13bceb80..7fe94400 100644 --- a/whoami/README.md +++ b/whoami/README.md @@ -10,19 +10,28 @@ a basic deployment and connectivity test. make config ``` -This will ask you to enter the domain name to use, and whether or not -you want to configure a username/password via HTTP Basic Authentication. +This will ask you to enter the domain name to use. It automatically saves your responses into the configuration file `.env_{INSTANCE}`. -It will also ask if you want to use OpenID/OAuth2 authentication. -Using OpenID/OAuth2 will require a login to access your app, but it will not -affect what a successfully logged-in person can do in your app. If your app has -a built-in authorization mechanism that can check for the user header that -traefik-forward-auth sends, then your app can limit what the logged-in person -can do in the app. But if your app can't check the user header, or if your app -doesn't have built-in authorization at all, then any person with an account -on your Gitea server can log into your app and have full acces +### Authentication and Authorization + +Running `make config` will ask whether or not you want to configure +authentication for your app (on top of any authentication your app provides). +You can configure OpenID/OAuth2 or HTTP Basic Authentication. + +OAuth2 uses traefik-forward-auth to delegate authentication to an external +authority (eg. a self-deployed Gitea instance). Accessing this app will +require all users to login through that external service first. Once +authenticated, they may be authorized access only if their login id matches the +member list of the predefined authorization group configured for the app +(`WHOAMI_OAUTH2_AUTHORIZED_GROUP`). Authorization groups are defined in the +Traefik config (`TRAEFIK_HEADER_AUTHORIZATION_GROUPS`) and can be +[created/modified](https://github.com/EnigmaCurry/d.rymcg.tech/blob/master/traefik/README.md#oauth2-authentication) +by running `make groups` in the `traefik` directory. + +For HTTP Basic Authentication, you will be prompted to enter username/password +logins which are stored in that app's `.env_{INSTANCE}` file. ## Install diff --git a/whoami/docker-compose.instance.yaml b/whoami/docker-compose.instance.yaml index 37512ee0..d9838f04 100644 --- a/whoami/docker-compose.instance.yaml +++ b/whoami/docker-compose.instance.yaml @@ -13,7 +13,8 @@ #@ ip_sourcerange = data.values.ip_sourcerange #@ enable_http_auth = len(data.values.http_auth.strip()) > 0 #@ http_auth = data.values.http_auth_var -#@ enable_oauth2 = data.values.oauth2 +#@ enable_oauth2 = data.values.oauth2 == "true" +#@ authorized_group = data.values.authorized_group #@ enabled_middlewares = [] #@yaml/text-templated-strings @@ -38,8 +39,9 @@ services: - "traefik.http.middlewares.(@= router @)-basicauth.basicauth.users=(@= http_auth @)" #@ end - #@ if enable_oauth2 == "yes": + #@ if enable_oauth2: #@ enabled_middlewares.append("traefik-forward-auth@docker") + #@ enabled_middlewares.append("header-authorization-group-{}@file".format(authorized_group)) #@ end #! Override the default port that whoami binds to, so that it lives in userspace >1024: diff --git a/wordpress/.env-dist b/wordpress/.env-dist index 4b533dc1..25e40ae9 100644 --- a/wordpress/.env-dist +++ b/wordpress/.env-dist @@ -17,7 +17,7 @@ WORDPRESS_IP_SOURCERANGE=0.0.0.0/0 WORDPRESS_HTTP_AUTH= # OAUTH2 -# Set to `yes` to use OpenID/OAuth2 authentication via the +# Set to `true` to use OpenID/OAuth2 authentication via the # traefik-forward-auth service in d.rymcg.tech. # Using OpenID/OAuth2 will require login to access your app, # but it will not affect what a successfully logged-in person can do in your @@ -27,7 +27,11 @@ WORDPRESS_HTTP_AUTH= # header, or if your app doesn't have built-in authentication at all, then # any person with an account on your Gitea server can log into your app and # have full access. -WORDPRESS_OAUTH2=no +WORDPRESS_OAUTH2= +# In addition to Oauth2 authentication, you can configure basic authorization +# by entering which authorization group can log into your app. You create +# groups of email addresses in the `traefik` folder by running `make groups`. +WORDPRESS_OAUTH2_AUTHORIZED_GROUP= ## Enable anti-hotlinking of images (or not): WORDPRESS_ANTI_HOTLINK=true diff --git a/wordpress/Makefile b/wordpress/Makefile index cd6cb1b4..b41b3ef0 100644 --- a/wordpress/Makefile +++ b/wordpress/Makefile @@ -10,9 +10,7 @@ config-hook: @${BIN}/reconfigure_ask ${ENV_FILE} WORDPRESS_TRAEFIK_HOST "Enter the wp domain name" wp${INSTANCE_URL_SUFFIX}.${ROOT_DOMAIN} @${BIN}/reconfigure_password ${ENV_FILE} WORDPRESS_DB_ROOT_PASSWORD @${BIN}/reconfigure_password ${ENV_FILE} WORDPRESS_DB_PASSWORD - @${BIN}/reconfigure_htpasswd ${ENV_FILE} WORDPRESS_HTTP_AUTH default=no - @echo "" - @${BIN}/reconfigure_oauth2 ${ENV_FILE} WORDPRESS_OAUTH2 default=$$( ${BIN}/dotenv -f ${ENV_FILE} get WORDPRESS_OAUTH2 ) + @${BIN}/reconfigure_auth ${ENV_FILE} WORDPRESS @echo "" @${BIN}/confirm $$([[ $$(${BIN}/dotenv -f ${ENV_FILE} get WORDPRESS_ANTI_HOTLINK) == "true" ]] && echo "yes" || echo "no") "Do you want to enable anti-hotlinking of images" "?" && ${BIN}/reconfigure ${ENV_FILE} WORDPRESS_ANTI_HOTLINK=true || ${BIN}/reconfigure ${ENV_FILE} WORDPRESS_ANTI_HOTLINK=false || true @echo "" @@ -35,7 +33,7 @@ override-hook: #### # (this hardcodes the string into docker-compose.override.yaml) #### name=@VARIABLE_NAME # sets the template 'name' field to the literal string '${VARIABLE_NAME}' #### # (used for regular docker-compose expansion of env vars by name.) - @${BIN}/docker_compose_override ${ENV_FILE} project=:wp instance=@WORDPRESS_INSTANCE traefik_host=@WORDPRESS_TRAEFIK_HOST http_auth=WORDPRESS_HTTP_AUTH http_auth_var=@WORDPRESS_HTTP_AUTH ip_sourcerange=@WORDPRESS_IP_SOURCERANGE enable_anti_hotlink=WORDPRESS_ANTI_HOTLINK anti_hotlink_referers_extra=@WORDPRESS_ANTI_HOTLINK_REFERERS_EXTRA anti_hotlink_allow_empty_referer=@WORDPRESS_ANTI_HOTLINK_ALLOW_EMPTY_REFERER enable_wp2static=WORDPRESS_WP2STATIC traefik_host_static=@WORDPRESS_TRAEFIK_HOST_STATIC http_auth_static=WORDPRESS_HTTP_AUTH_STATIC http_auth_static_var=@WORDPRESS_HTTP_AUTH_STATIC ip_sourcerange_static=@WORDPRESS_IP_SOURCERANGE_STATIC oauth2=WORDPRESS_OAUTH2 + @${BIN}/docker_compose_override ${ENV_FILE} project=:wp instance=@WORDPRESS_INSTANCE traefik_host=@WORDPRESS_TRAEFIK_HOST http_auth=WORDPRESS_HTTP_AUTH http_auth_var=@WORDPRESS_HTTP_AUTH ip_sourcerange=@WORDPRESS_IP_SOURCERANGE enable_anti_hotlink=WORDPRESS_ANTI_HOTLINK anti_hotlink_referers_extra=@WORDPRESS_ANTI_HOTLINK_REFERERS_EXTRA anti_hotlink_allow_empty_referer=@WORDPRESS_ANTI_HOTLINK_ALLOW_EMPTY_REFERER enable_wp2static=WORDPRESS_WP2STATIC traefik_host_static=@WORDPRESS_TRAEFIK_HOST_STATIC http_auth_static=WORDPRESS_HTTP_AUTH_STATIC http_auth_static_var=@WORDPRESS_HTTP_AUTH_STATIC ip_sourcerange_static=@WORDPRESS_IP_SOURCERANGE_STATIC oauth2=WORDPRESS_OAUTH2 authorized_group=WORDPRESS_OAUTH2_AUTHORIZED_GROUP .PHONY: shell shell: diff --git a/wordpress/README.md b/wordpress/README.md index 6492c7a8..52466926 100644 --- a/wordpress/README.md +++ b/wordpress/README.md @@ -8,20 +8,10 @@ This is the [wordpress](https://wordpress.org) CMS/blogging platform. make config ``` -This will ask you to enter the domain name to use, and whether or not -you want to configure a username/password via HTTP Basic Authentication. +This will ask you to enter the domain name to use. It automatically saves your responses into the configuration file `.env_{INSTANCE}`. -It will also ask if you want to use OpenID/OAuth2 authentication. -Using OpenID/OAuth2 will require a login to access your app, but it will not -affect what a successfully logged-in person can do in your app. If your app has -a built-in authorization mechanism that can check for the user header that -traefik-forward-auth sends, then your app can limit what the logged-in person -can do in the app. But if your app can't check the user header, or if your app -doesn't have built-in authorization at all, then any person with an account -on your Gitea server can log into your app and have full acces - It will also ask you some questions to generate the WordPress config. Here is a guide to the answers you should pick depending on which kind of deployment you want: @@ -42,6 +32,25 @@ you want: to both private and/or public static websites). * Say `N` or `Y` to creating a **public** static HTML wordpress export. +### Authentication and Authorization + +Running `make config` will ask whether or not you want to configure +authentication for your app (on top of any authentication your app provides). +You can configure OpenID/OAuth2 or HTTP Basic Authentication. + +OAuth2 uses traefik-forward-auth to delegate authentication to an external +authority (eg. a self-deployed Gitea instance). Accessing this app will +require all users to login through that external service first. Once +authenticated, they may be authorized access only if their login id matches the +member list of the predefined authorization group configured for the app +(`WORDPRESS_OAUTH2_AUTHORIZED_GROUP`). Authorization groups are defined in the +Traefik config (`TRAEFIK_HEADER_AUTHORIZATION_GROUPS`) and can be +[created/modified](https://github.com/EnigmaCurry/d.rymcg.tech/blob/master/traefik/README.md#oauth2-authentication) +by running `make groups` in the `traefik` directory. + +For HTTP Basic Authentication, you will be prompted to enter username/password +logins which are stored in that app's `.env_{INSTANCE}` file. + ``` make install diff --git a/wordpress/docker-compose.instance.yaml b/wordpress/docker-compose.instance.yaml index 007ec030..36dab82d 100644 --- a/wordpress/docker-compose.instance.yaml +++ b/wordpress/docker-compose.instance.yaml @@ -21,7 +21,8 @@ #@ ip_sourcerange_static = data.values.ip_sourcerange_static #@ enable_http_auth_static = len(data.values.http_auth_static.strip()) > 0 #@ http_auth_static = data.values.http_auth_static_var -#@ enable_oauth2 = data.values.oauth2 +#@ enable_oauth2 = data.values.oauth2 == "true" +#@ authorized_group = data.values.authorized_group #@ enabled_middlewares = [] #@yaml/text-templated-strings @@ -51,8 +52,9 @@ services: - "traefik.http.middlewares.(@= router @)-basicauth.basicauth.users=(@= http_auth @)" #@ end - #@ if enable_oauth2 == "yes": + #@ if enable_oauth2: #@ enabled_middlewares.append("traefik-forward-auth@docker") + #@ enabled_middlewares.append("header-authorization-group-{}@file".format(authorized_group)) #@ end #! Apply all middlewares (do this at the end!) diff --git a/xbs/.env-dist b/xbs/.env-dist index ff13b666..41e9f3d4 100644 --- a/xbs/.env-dist +++ b/xbs/.env-dist @@ -1,6 +1,30 @@ +# https://hub.docker.com/r/xbrowsersync/api/tags +XBS_VERSION=1.1.13 XBS_TRAEFIK_HOST=xbs.example.com +XBS_INSTANCE= + XBS_DB_NAME=xbrowsersync XBS_DB_USERNAME=xbsdb XBS_DB_PASSWORD= COMPOSE_CONVERT_WINDOWS_PATHS=1 -XBS_IP_SOURCERANGE=0.0.0.0/0 \ No newline at end of file +XBS_IP_SOURCERANGE=0.0.0.0/0 +# HTTP Basic Authentication: +# Use `make config` to fill this in properly, or set this to blank to disable. +XBS_HTTP_AUTH= + +# OAUTH2 +# Set to `true` to use OpenID/OAuth2 authentication via the +# traefik-forward-auth service in d.rymcg.tech. +# Using OpenID/OAuth2 will require login to access your app, +# but it will not affect what a successfully logged-in person can do in your +# app. If your app has built-in authentication and can check the user +# header that traefik-forward-auth sends, then your app can limit what the +# logged-in person can do in the app. But if your app can't check the user +# header, or if your app doesn't have built-in authentication at all, then +# any person with an account on your Gitea server can log into your app and +# have full access. +XBS_OAUTH2= +# In addition to Oauth2 authentication, you can configure basic authorization +# by entering which authorization group can log into your app. You create +# groups of email addresses in the `traefik` folder by running `make groups`. +XBS_OAUTH2_AUTHORIZED_GROUP= diff --git a/xbs/Makefile b/xbs/Makefile index e331faa5..2c56316d 100644 --- a/xbs/Makefile +++ b/xbs/Makefile @@ -6,7 +6,23 @@ include ${ROOT_DIR}/_scripts/Makefile.instance config-hook: @${BIN}/reconfigure_ask ${ENV_FILE} XBS_TRAEFIK_HOST "Enter the xbs domain name" xbs${INSTANCE_URL_SUFFIX}.${ROOT_DOMAIN} @${BIN}/reconfigure_password ${ENV_FILE} XBS_DB_PASSWORD + @${BIN}/reconfigure_auth ${ENV_FILE} XBS .PHONY: shell shell: @make --no-print-directory docker-compose-lifecycle-cmd EXTRA_ARGS="exec -it api /bin/sh" + + +.PHONY: override-hook +override-hook: +#### This sets the override template variables for docker-compose.instance.yaml: +#### The template dynamically renders to docker-compose.override_{DOCKER_CONTEXT}_{INSTANCE}.yaml +#### These settings are used to automatically generate the service container labels, and traefik config, inside the template. +#### The variable arguments have three forms: `=` `=:` `=@` +#### name=VARIABLE_NAME # sets the template 'name' field to the value of VARIABLE_NAME found in the .env file +#### # (this hardcodes the value into docker-compose.override.yaml) +#### name=:VARIABLE_NAME # sets the template 'name' field to the literal string 'VARIABLE_NAME' +#### # (this hardcodes the string into docker-compose.override.yaml) +#### name=@VARIABLE_NAME # sets the template 'name' field to the literal string '${VARIABLE_NAME}' +#### # (used for regular docker-compose expansion of env vars by name.) + @${BIN}/docker_compose_override ${ENV_FILE} project=:xbs instance=@XBS_INSTANCE traefik_host=@XBS_TRAEFIK_HOST http_auth=XBS_HTTP_AUTH http_auth_var=@XBS_HTTP_AUTH ip_sourcerange=@XBS_IP_SOURCERANGE oauth2=XBS_OAUTH2 authorized_group=XBS_OAUTH2_AUTHORIZED_GROUP diff --git a/xbs/api/Dockerfile b/xbs/api/Dockerfile index 805ef2e4..3ddd48ae 100644 --- a/xbs/api/Dockerfile +++ b/xbs/api/Dockerfile @@ -1,3 +1,4 @@ -FROM xbrowsersync/api:1.1.13 +ARG XBS_VERSION +FROM xbrowsersync/api:${XBS_VERSION} COPY settings.json /usr/src/api/config/settings.json COPY healthcheck.js /usr/src/api/healthcheck.js diff --git a/xbs/docker-compose.instance.yaml b/xbs/docker-compose.instance.yaml new file mode 100644 index 00000000..4c82dd25 --- /dev/null +++ b/xbs/docker-compose.instance.yaml @@ -0,0 +1,50 @@ +#! This is a ytt template file for docker-compose.override.yaml +#! References: +#! https://carvel.dev/ytt +#! https://docs.docker.com/compose/extends/#adding-and-overriding-configuration +#! https://github.com/enigmacurry/d.rymcg.tech#overriding-docker-composeyaml-per-instance + +#! ### Standard project vars: +#@ load("@ytt:data", "data") +#@ project = data.values.project +#@ instance = data.values.instance +#@ context = data.values.context +#@ traefik_host = data.values.traefik_host +#@ ip_sourcerange = data.values.ip_sourcerange +#@ enable_http_auth = len(data.values.http_auth.strip()) > 0 +#@ http_auth = data.values.http_auth_var +#@ enable_oauth2 = data.values.oauth2 == "true" +#@ authorized_group = data.values.authorized_group +#@ enabled_middlewares = [] + +#@yaml/text-templated-strings +services: + api: + #@ service = "xbs" + labels: + #! Services must opt-in to be proxied by Traefik: + - "traefik.enable=true" + + #! 'router' is the fully qualified key in traefik for this router/service: project + instance + service + #@ router = "{}-{}-{}".format(project,instance,service) + + #! The host matching router rule: + - "traefik.http.routers.(@= router @).rule=Host(`(@= traefik_host @)`)" + - "traefik.http.routers.(@= router @).entrypoints=websecure" + #@ enabled_middlewares.append("{}-ipwhitelist".format(router)) + - "traefik.http.middlewares.(@= router @)-ipwhitelist.ipwhitelist.sourcerange=(@= ip_sourcerange @)" + + #@ if enable_http_auth: + #@ enabled_middlewares.append("{}-basicauth".format(router)) + - "traefik.http.middlewares.(@= router @)-basicauth.basicauth.users=(@= http_auth @)" + #@ end + + #@ if enable_oauth2: + #@ enabled_middlewares.append("traefik-forward-auth@docker") + #@ enabled_middlewares.append("header-authorization-group-{}@file".format(authorized_group)) + #@ end + + - "traefik.http.services.(@= router @).loadbalancer.server.port=8080" + + #! Apply all middlewares (do this at the end!) + - "traefik.http.routers.(@= router @).middlewares=(@= ','.join(enabled_middlewares) @)" diff --git a/xbs/docker-compose.yaml b/xbs/docker-compose.yaml index bb199507..ed7d5668 100644 --- a/xbs/docker-compose.yaml +++ b/xbs/docker-compose.yaml @@ -18,7 +18,10 @@ services: - "xbs-db-backups:/data/backups" api: - build: ./api + build: + context: ./api + args: + XBS_VERSION: 1.1.13 depends_on: - "db" security_opt: @@ -33,13 +36,7 @@ services: retries: 5 start_period: "30s" restart: "unless-stopped" - labels: - - "traefik.enable=true" - - "traefik.http.routers.xbs-${XBS_INSTANCE:-default}.rule=Host(`${XBS_TRAEFIK_HOST}`)" - - "traefik.http.routers.xbs-${XBS_INSTANCE:-default}.entrypoints=websecure" - - "traefik.http.services.xbs-${XBS_INSTANCE:-default}.loadbalancer.server.port=8080" - - "traefik.http.middlewares.xbs-${XBS_INSTANCE:-default}-ipwhitelist.ipwhitelist.sourcerange=${XBS_IP_SOURCERANGE}" - - "traefik.http.routers.xbs-${XBS_INSTANCE:-default}.middlewares=xbs-${XBS_INSTANCE:-default}-ipwhitelist" + labels: [] volumes: xbs-db-backups: From c7c53d55e662817c9ddcdc6d06892766e597915c Mon Sep 17 00:00:00 2001 From: Ryan McGuire Date: Wed, 25 Oct 2023 11:15:39 -0600 Subject: [PATCH 2/2] Update README.md (#130)