Allow per-app allow/deny.
This commit is contained in:
@@ -51,6 +51,14 @@ API-key-protected endpoints, health checks) can be excluded per app.
|
||||
and authenticated with a secret generated at configure time. Session
|
||||
cookies are `Secure`, `HttpOnly`, `SameSite=Lax`, and pinned to the host
|
||||
they were minted on.
|
||||
- **Per-app access lists** work because every generated location tells the
|
||||
service which app the request belongs to, via an `X-Google-Auth-App` header
|
||||
that nginx sets itself — overwriting anything a client sent. The plugin's
|
||||
`apps/` directory is bind-mounted into the container read-only, so the
|
||||
service reads an app's lists on demand instead of needing a restart. The
|
||||
destination app also rides through the OAuth `state` parameter, because
|
||||
Google's callback lands on the auth host, which may belong to a different
|
||||
app than the one being signed in to.
|
||||
|
||||
### Headers your apps receive
|
||||
|
||||
@@ -88,8 +96,12 @@ it into `/var/lib/dokku/plugins/available/<name>` and:
|
||||
the app's current upstream (whose name changes if you remap ports).
|
||||
- `post-delete`, `post-app-rename`, `post-app-clone` — keep plugin state in
|
||||
sync with app lifecycle.
|
||||
- **State** lives under `/var/lib/dokku/data/google-auth/` (global config +
|
||||
one directory per enabled app). Secrets are `0600`.
|
||||
- **State** lives under `/var/lib/dokku/data/google-auth/`: `global/` for the
|
||||
shared config and `apps/<app>/` for each app's exclusions and access lists.
|
||||
The data root is `0700` and secrets are `0600`. `apps/` is `0711` with
|
||||
`0644` list files, because it is bind-mounted into the service container,
|
||||
which runs as an unprivileged uid and has to read those lists — the `0700`
|
||||
root still keeps other host users out, and `global/` is never mounted.
|
||||
|
||||
The nginx integration relies on a stable, documented dokku feature: the
|
||||
generated vhost for every app contains
|
||||
@@ -161,51 +173,78 @@ Other flags (all optional, all persisted):
|
||||
| `--port <p>` | host port (127.0.0.1 only) for the auth service | `2999` |
|
||||
| `--regenerate-cookie-secret` | rotate the session encryption key (signs everyone out) | — |
|
||||
|
||||
These flags **replace** the list they name, which suits initial setup. For
|
||||
one-at-a-time changes afterwards, see the next section.
|
||||
These flags set the **global** lists and **replace** the list they name, which
|
||||
suits initial setup. For one-at-a-time changes afterwards, see the next
|
||||
section.
|
||||
|
||||
### 3. Decide who is allowed in
|
||||
|
||||
`--allow-domain` and `--allow-email` together form the **allowlist**, and at
|
||||
least one entry is required. Any account matching neither is rejected — there
|
||||
is no "allow everyone" mode. So to limit access to a specific list of people,
|
||||
use only addresses and no domain:
|
||||
Access lists exist in two scopes — one global default and an optional override
|
||||
per app. Every command takes the scope as its first argument: an app name, or
|
||||
`--global`.
|
||||
|
||||
```bash
|
||||
dokku google-auth:allow greyson@signal.org alice@signal.org
|
||||
dokku google-auth:allow --global signal.org # everyone at signal.org, by default
|
||||
dokku google-auth:allow my-app ceo@signal.org # …but my-app is just this person
|
||||
dokku google-auth:deny --global former@signal.org # nobody, anywhere
|
||||
dokku google-auth:deny my-app bob@signal.org # bob, only on my-app
|
||||
```
|
||||
|
||||
The **deny list** is checked first and wins over both allow rules, which is
|
||||
how you cut off one person without narrowing the whole domain:
|
||||
The rules, in the order the auth service applies them:
|
||||
|
||||
1. **A deny match rejects the account.** The global and per-app deny lists are
|
||||
*combined*, so a global denial cannot be lifted by an app.
|
||||
2. **Otherwise the allow list decides**, and it is strict: an account matching
|
||||
nothing is rejected. There is no "allow everyone" mode, and at least one
|
||||
global allow entry is required.
|
||||
3. **An app with its own allow entries uses only those**, ignoring the global
|
||||
list entirely. An app with none inherits the global list.
|
||||
|
||||
That third rule is the useful one and the surprising one. It lets a single app
|
||||
be narrowed to a few people, or opened to an outside collaborator who is not
|
||||
in the global list at all:
|
||||
|
||||
| | global: `signal.org` | effect |
|
||||
|---|---|---|
|
||||
| `app-a` | no entries | anyone `@signal.org` |
|
||||
| `app-b` | `ceo@signal.org` | **only** `ceo@signal.org` |
|
||||
| `app-c` | `guest@partner.com` | **only** `guest@partner.com` — not `@signal.org` |
|
||||
|
||||
It also means the global list is a default, not a ceiling: an app can admit
|
||||
someone it does not cover. `google-auth:allow` warns the first time an app
|
||||
gains an entry, since that is the moment it stops inheriting.
|
||||
|
||||
All eight forms:
|
||||
|
||||
```bash
|
||||
dokku google-auth:allow signal.org # everyone at signal.org…
|
||||
dokku google-auth:deny former@signal.org # …except this account
|
||||
```
|
||||
dokku google-auth:allow --global # show the global allow list
|
||||
dokku google-auth:allow my-app # show my-app's (or that it inherits)
|
||||
dokku google-auth:allow --global signal.org # a domain — any verified account there
|
||||
dokku google-auth:allow my-app guest@partner.com # one address
|
||||
dokku google-auth:unallow my-app guest@partner.com # remove either kind
|
||||
|
||||
These four commands each change one entry at a time and restart the auth
|
||||
service for you:
|
||||
|
||||
```bash
|
||||
dokku google-auth:allow # show the allow list
|
||||
dokku google-auth:allow signal.org # a domain (any verified account there)
|
||||
dokku google-auth:allow guest@partner.com # one address
|
||||
dokku google-auth:unallow guest@partner.com # remove either kind
|
||||
|
||||
dokku google-auth:deny # show the deny list
|
||||
dokku google-auth:deny former@signal.org
|
||||
dokku google-auth:undeny former@signal.org
|
||||
dokku google-auth:deny --global # show the global deny list
|
||||
dokku google-auth:deny my-app # show my-app's, plus the global ones
|
||||
dokku google-auth:undeny --global former@signal.org
|
||||
```
|
||||
|
||||
Changes take effect on the affected user's **next request**: session cookies
|
||||
are re-checked against the current lists rather than trusted until they
|
||||
expire, so denying (or unallowing) someone with a live session ends it. To
|
||||
sign out everyone at once instead, use
|
||||
expire, so denying (or unallowing) someone with a live session ends it — and a
|
||||
session minted for one app is not accepted by an app whose list excludes them.
|
||||
To sign out everyone at once instead, use
|
||||
`configure --regenerate-cookie-secret`.
|
||||
|
||||
`unallow` refuses to remove the last allow entry, since an empty allowlist
|
||||
locks everyone out of every enabled app. `dokku google-auth:report` shows
|
||||
both lists as they currently stand.
|
||||
Two guardrails: `unallow --global` refuses to remove the last global entry,
|
||||
since an empty global allow list locks everyone out of every app that inherits
|
||||
it (emptying an *app's* list is fine — it goes back to inheriting).
|
||||
`dokku google-auth:report` shows the global lists and each app's, and states
|
||||
whether an app inherits or overrides.
|
||||
|
||||
Global lists reach the auth service in its environment, so changing one
|
||||
restarts the shared container. Per-app lists are read from disk on demand, so
|
||||
changing one takes effect within a couple of seconds with no restart and no
|
||||
interruption to other apps.
|
||||
|
||||
### 4. Protect apps
|
||||
|
||||
@@ -240,8 +279,9 @@ protect them yourself (API key, HMAC signature, etc.).
|
||||
```bash
|
||||
dokku google-auth:report # global + per-app status, incl. both access lists
|
||||
dokku google-auth:report my-app # one app
|
||||
dokku google-auth:allow alice@signal.org # let someone in
|
||||
dokku google-auth:deny former@signal.org # cut someone off
|
||||
dokku google-auth:allow --global alice@signal.org # let someone in everywhere
|
||||
dokku google-auth:allow my-app alice@signal.org # …or just on one app
|
||||
dokku google-auth:deny --global former@signal.org # cut someone off
|
||||
dokku google-auth:disable my-app # turn SSO off for an app
|
||||
dokku google-auth:logs -t # follow auth service logs (sign-ins, denials)
|
||||
dokku google-auth:restart # restart the auth service
|
||||
@@ -274,6 +314,11 @@ and sign out at `https://<any-app>/_google-auth/logout`.
|
||||
stock nginx template.
|
||||
- **Plugin updates:** `sudo dokku plugin:update google-auth` rebuilds the
|
||||
service image and restarts the container automatically.
|
||||
- **An app's own lists need its nginx config to be current**, since that is
|
||||
what tells the service which app a request belongs to. The plugin rewrites
|
||||
the config whenever you change an app's lists (and on every deploy), so this
|
||||
is normally invisible — but an app that has not been deployed or touched
|
||||
since an upgrade falls back to the global lists until then.
|
||||
|
||||
## Uninstall
|
||||
|
||||
@@ -293,7 +338,9 @@ shellcheck):
|
||||
```bash
|
||||
mise install # install pinned toolchain
|
||||
mise run test # Go unit tests (full OAuth flow against a fake Google)
|
||||
mise run test-nginx-conf # generate nginx config + validate with real nginx in docker
|
||||
mise run test-nginx-conf # generate nginx config + validate with real nginx in docker
|
||||
mise run test-access-lists # allow/deny commands against a fake dokku layout
|
||||
mise run test-help # help output shapes + every subcommand documented
|
||||
mise run shellcheck # lint all plugin scripts
|
||||
mise run check # everything CI would run
|
||||
mise run docker-build # build the service image locally
|
||||
@@ -307,6 +354,6 @@ internal/authproxy/ OAuth flow, session crypto, HTTP handlers (+ tests)
|
||||
functions shared bash for the plugin (config store, nginx generation)
|
||||
subcommands/ dokku google-auth:* commands
|
||||
install, nginx-pre-reload, core-post-deploy, post-* dokku lifecycle triggers
|
||||
test/nginx-conf-test.sh bash integration test for the generated nginx config
|
||||
test/ bash tests: nginx config generation, access lists, help output
|
||||
Dockerfile multi-stage build → static binary in a scratch image
|
||||
```
|
||||
|
||||
Reference in New Issue
Block a user