Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
32 commits
Select commit Hold shift + click to select a range
5adc353
cleanup
ivan-c Feb 13, 2023
f010bc0
Rename auth server to generic role-based name
ivan-c Feb 13, 2023
be85bdc
Add comment;fixup quoting
ivan-c Feb 14, 2023
de6eeb5
Remove default redirect URL
ivan-c Feb 14, 2023
fdbd4ff
Add comment; cleanup format
ivan-c Feb 14, 2023
7258834
Move CLI config to env var
ivan-c Feb 14, 2023
a018060
Add comment
ivan-c Feb 14, 2023
dcad468
Rename middleware
ivan-c Feb 14, 2023
2a6bea2
WIP Switch to env var
ivan-c Feb 14, 2023
296413e
Revert "WIP Switch to env var"
ivan-c Feb 14, 2023
4a12e0d
Remove quoting
ivan-c Feb 14, 2023
130911a
Add docs
ivan-c Feb 14, 2023
084aea2
Reorganize and document settings
ivan-c Feb 14, 2023
ee44593
Make label formatting consistent
ivan-c Feb 14, 2023
00afd12
Remove testing override
ivan-c Feb 14, 2023
97b1102
Update postgres
ivan-c Feb 14, 2023
cb23062
Make init SQL read-only
ivan-c Feb 14, 2023
b8bd473
Regroup labels
ivan-c Feb 14, 2023
2207336
Remove whitespace
ivan-c Feb 14, 2023
f55e0af
Add missing LE config
ivan-c Feb 14, 2023
5f01e51
Fixup traefik port config
ivan-c Feb 14, 2023
e90cccf
Merge branch 'main' into fixup/cleanup
ivan-c Feb 14, 2023
39bbf93
Add missing SQL file
ivan-c Feb 14, 2023
5a794e7
Make secrets configurable; remove default IP
ivan-c Feb 14, 2023
53dcc56
Rearrange header section
ivan-c Feb 14, 2023
0fc3ddd
Remove unnecessary config; add comment
ivan-c Feb 14, 2023
000b902
Move final CLI arg to env var
ivan-c Feb 14, 2023
1ab87e3
Remove unnesc. quoting
ivan-c Feb 14, 2023
f360e05
Rearrange sections
ivan-c Feb 14, 2023
e0299dd
Add middleware for postgrest, to strip Authorization header (#4)
ivan-c Feb 16, 2023
91241c3
Reword comment
ivan-c Feb 16, 2023
4678954
Remove example override
ivan-c Feb 21, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 8 additions & 2 deletions default.env
Original file line number Diff line number Diff line change
@@ -1,4 +1,10 @@
ACME_EMAIL=
OAUTH_COOKIE_SECRET=
COMPOSE_PROJECT_NAME=

OAUTH2_PROXY_CLIENT_ID=auth_proxy_test
OAUTH2_PROXY_CLIENT_SECRET=74c6fdc4-4086-4a21-bf40-49c7ee74357e

OAUTH2_PROXY_COOKIE_SECRET=OQINaROshtE9TcZkNAm-5Zs2Pv3xaWytBmc5W7sPX7w=
PGRST_JWT_SECRET=

# your public IP address
IP=
207 changes: 84 additions & 123 deletions docker-compose.yaml
Original file line number Diff line number Diff line change
@@ -1,128 +1,89 @@
version: '3.9'

x-orig:
traefik:
image: traefik:v2.8
restart: unless-stopped
command:
- "--accesslog=true"
- "--providers.docker"
- "--api.dashboard=true"
- "--experimental.http3=true"
- "--entrypoints.web.address=:80"
- "--entrypoints.web.http.redirections.entryPoint.to=websecure"
- "--entrypoints.web.http.redirections.entryPoint.scheme=https"
- "--entrypoints.web.http.redirections.entrypoint.permanent=true"
- "--entrypoints.websecure.address=:443"
- "--entrypoints.websecure.http.tls=true"
- "--entrypoints.websecure.http.tls.certresolver=letsencrypt"
- "--entrypoints.websecure.http3.advertisedPort=443"
- "--certificatesresolvers.letsencrypt.acme.email=${ACME_EMAIL}"
- "--certificatesresolvers.letsencrypt.acme.storage=/data/acme.json"
- "--certificatesresolvers.letsencrypt.acme.httpchallenge.entrypoint=web"

ports:
- "80:80/tcp"
- "443:443/tcp"
- "443:443/udp"

labels:
traefik.http.routers.traefik.rule: Host(`traefik.fsn1.<REDACTED>`)
traefik.http.routers.traefik.middlewares: github-auth
traefik.http.routers.traefik.service: api@internal

volumes:
- "/var/run/docker.sock:/var/run/docker.sock"
- "traefik_data:/data"
version: "3.9"
services:

oauth2-proxy:
auth-proxy:
image: quay.io/oauth2-proxy/oauth2-proxy:v7.3.0
restart: unless-stopped
command:
# - "--client-id=${OAUTH_CLIENT_ID}"
# - "--client-secret=${OAUTH_CLIENT_SECRET}"
# - "--cookie-domain=.nip.io"
# - "--cookie-name=_auth_9668c2cf"
# - "--cookie-secret=${OAUTH_COOKIE_SECRET}"
# - "--email-domain=*"
# - "--http-address=http://[::]:4180"
# - "--reverse-proxy=true"
# - "--redirect-url=https://auth.fsn1.<REDACTED>/oauth2/callback"
# - "--scope=user:email"
# - "--set-xauthrequest=true"
- "--upstream=static://202"
# - "--whitelist-domain=.<REDACTED>"
# - "--whitelist-domain=.fsn1.<REDACTED>"
- "--skip-jwt-bearer-tokens=true"
environment:
# service settings
OAUTH2_PROXY_HTTP_ADDRESS: 0.0.0.0:4180
OAUTH2_PROXY_PROVIDER: oidc
OAUTH2_PROXY_PROVIDER_DISPLAY_NAME: Keeycloak
OAUTH2_PROXY_OIDC_ISSUER_URL: https://keycloak.${IP:-128.208.230.197}.nip.io/auth/realms/fEMR
OAUTH2_PROXY_EMAIL_DOMAINS: "*"
OAUTH2_PROXY_INSECURE_OIDC_ALLOW_UNVERIFIED_EMAIL: "true"
OAUTH2_PROXY_USER_ID_CLAIM: "preferred_username"
OAUTH2_PROXY_SCOPE: openid profile email
OAUTH2_PROXY_COOKIE_DOMAINS: .${IP:-128.208.230.197}.nip.io
OAUTH2_PROXY_WHITELIST_DOMAINS: .${IP:-128.208.230.197}.nip.io
OAUTH2_PROXY_REVERSE_PROXY: "true"
OAUTH2_PROXY_SET_XAUTHREQUEST: "true"
OAUTH2_PROXY_SET_AUTHORIZATION_HEADER: "true"
OAUTH2_PROXY_PASS_AUTHORIZATION_HEADER: "true"
OAUTH2_PROXY_PASS_ACCESS_TOKEN: "true"
OAUTH2_PROXY_PASS_USER_HEADERS: "true"
OAUTH2_PROXY_SET_AUTHORIZATION_HEADER: "true"
OAUTH2_PROXY_SET_XAUTHREQUEST: "true"
OAUTH2_PROXY_COOKIE_REFRESH: 1m
OAUTH2_PROXY_PASS_ACCESS_TOKEN: "true"
# allow validation of JWTs in Authorization HTTP header
# https://medium.com/in-the-weeds/service-to-service-authentication-on-kubernetes-94dcb8216cdc#20d0
OAUTH2_PROXY_SKIP_JWT_BEARER_TOKENS: "true"

# when authenticated, return a static 202 response
# https://oauth2-proxy.github.io/oauth2-proxy/docs/configuration/overview/#forwardauth-with-static-upstreams-configuration
OAUTH2_PROXY_UPSTREAMS: static://202

# general cookie settings
OAUTH2_PROXY_EMAIL_DOMAINS: "*"
OAUTH2_PROXY_COOKIE_EXPIRE: 30m
OAUTH2_PROXY_COOKIE_SECRET: OQINaROshtE9TcZkNAm-5Zs2Pv3xaWytBmc5W7sPX7w=
# OAUTH2_PROXY_REDIRECT_URL: http://auth-proxy-${COMPOSE_PROJECT_NAME}:4180/oauth2/callback
# OAUTH2_PROXY_REDIRECT_URL: http://oauth:4180/oauth2/callback
# OAUTH2_PROXY_REDIRECT_URL: https://${SERVER_NAME}/oauth2/callback
OAUTH2_PROXY_REDIRECT_URL: https://oauth2-proxy.${IP:-128.208.230.197}.nip.io/oauth2/callback
OAUTH2_PROXY_CLIENT_ID: auth_proxy_test
OAUTH2_PROXY_CLIENT_SECRET: 74c6fdc4-4086-4a21-bf40-49c7ee74357e
OAUTH2_PROXY_REVERSE_PROXY: "true"
expose:
- '4180'
OAUTH2_PROXY_COOKIE_REFRESH: 1m
OAUTH2_PROXY_INSECURE_OIDC_ALLOW_UNVERIFIED_EMAIL: "true"
# base session cookie on Keycloak username (email not always set in Keycloak)
OAUTH2_PROXY_USER_ID_CLAIM: preferred_username

# OIDC integration settings
OAUTH2_PROXY_PROVIDER: oidc
OAUTH2_PROXY_SCOPE: openid profile email
OAUTH2_PROXY_OIDC_ISSUER_URL: https://keycloak.${IP}.nip.io/auth/realms/fEMR
OAUTH2_PROXY_CLIENT_ID: ${OAUTH2_PROXY_CLIENT_ID}
OAUTH2_PROXY_CLIENT_SECRET: ${OAUTH2_PROXY_CLIENT_SECRET}

# security settings
OAUTH2_PROXY_COOKIE_DOMAINS: .${IP}.nip.io
OAUTH2_PROXY_WHITELIST_DOMAINS: .${IP}.nip.io
OAUTH2_PROXY_COOKIE_SECRET: ${OAUTH2_PROXY_COOKIE_SECRET}

labels:
"traefik.enable": "true"
traefik.http.routers.oauth2-proxy.rule: Host(`oauth2-proxy.${IP:-128.208.230.197}.nip.io`) || PathPrefix(`/oauth2`)
traefik.http.routers.oauth2-proxy.middlewares: auth-headers
traefik.http.routers.oauth2-proxy.tls: 'true'
traefik.http.routers.oauth2-proxy.tls.certresolver: letsencrypt
traefik.http.routers.oauth2-proxy.entrypoints: "websecure"

traefik.http.middlewares.auth-headers.headers.sslRedirect: 'true'
traefik.http.middlewares.auth-headers.headers.stsSeconds: '315360000'
traefik.http.middlewares.auth-headers.headers.browserXssFilter: 'true'
traefik.http.middlewares.auth-headers.headers.contentTypeNosniff: 'true'
traefik.http.middlewares.auth-headers.headers.forceSTSHeader: 'true'
traefik.http.middlewares.auth-headers.headers.sslHost: oauth2-proxy.${IP:-128.208.230.197}.nip.io
traefik.http.middlewares.auth-headers.headers.stsIncludeSubdomains: 'true'
traefik.http.middlewares.auth-headers.headers.stsPreload: 'true'
traefik.http.middlewares.auth-headers.headers.frameDeny: 'true'

traefik.http.middlewares.github-auth.forwardAuth.address: http://oauth2-proxy:4180/
traefik.http.middlewares.github-auth.forwardAuth.trustForwardHeader: 'true'
traefik.http.middlewares.github-auth.forwardAuth.authResponseHeaders: X-Auth-Request-User,X-Auth-Request-Email,X-Auth-Request-Access-Token,Authorization
traefik.enable: "true"
traefik.http.routers.auth-proxy.rule: Host(`auth-proxy.${IP}.nip.io`) || PathPrefix(`/oauth2`)
traefik.http.routers.auth-proxy.middlewares: auth-headers
traefik.http.routers.auth-proxy.entrypoints: websecure
traefik.http.routers.auth-proxy.tls.certresolver: letsencrypt
traefik.http.routers.auth-proxy.tls: "true"

# oauth2-proxy does not EXPOSE the ports it listens on, requiring explicit traefik configuration
traefik.http.services.auth-proxy.loadbalancer.server.port: 4180

traefik.http.middlewares.oidc-auth.forwardAuth.address: http://auth-proxy:4180/
traefik.http.middlewares.oidc-auth.forwardAuth.trustForwardHeader: "true"
# TODO block Authorization header for postgrest using custom middleware
traefik.http.middlewares.oidc-auth.forwardAuth.authResponseHeaders: X-Auth-Request-User,X-Auth-Request-Email,X-Auth-Request-Access-Token,Authorization


# TODO move to common config
# best-practice HTTPS headers
traefik.http.middlewares.auth-headers.headers.forceSTSHeader: "true"
traefik.http.middlewares.auth-headers.headers.sslHost: auth-proxy.${IP}.nip.io
traefik.http.middlewares.auth-headers.headers.stsIncludeSubdomains: "true"
traefik.http.middlewares.auth-headers.headers.stsPreload: "true"
traefik.http.middlewares.auth-headers.headers.sslRedirect: "true"
traefik.http.middlewares.auth-headers.headers.stsSeconds: 315360000
traefik.http.middlewares.auth-headers.headers.browserXssFilter: "true"
traefik.http.middlewares.auth-headers.headers.contentTypeNosniff: "true"
traefik.http.middlewares.auth-headers.headers.frameDeny: "true"
networks:
ingress:
internal:

whoami:
image: "containous/whoami"
image: containous/whoami
labels:
"traefik.enable": "true"
traefik.http.routers.whoami.rule: Host(`whoami.${IP:-128.208.230.197}.nip.io`)
traefik.http.routers.whoami.middlewares: github-auth
"traefik.http.services.whoami.loadbalancer.server.port": "80"
"traefik.http.routers.whoami.entrypoints": "websecure"
"traefik.http.routers.whoami.tls": "true"
traefik.enable: "true"
traefik.http.routers.whoami.rule: Host(`whoami.${IP}.nip.io`)
traefik.http.routers.whoami.middlewares: oidc-auth
traefik.http.routers.whoami.entrypoints: websecure
traefik.http.routers.whoami.tls.certresolver: letsencrypt
traefik.http.routers.whoami.tls: "true"
networks:
ingress:
internal:
####


postgrest:
image: postgrest/postgrest
Expand All @@ -134,46 +95,46 @@ services:
depends_on:
- postgres
labels:
"traefik.enable": "true"
"traefik.http.routers.postgrest.rule": "Host(`postgrest.${IP:-128.208.230.197}.nip.io`)"
"traefik.http.routers.postgrest.entrypoints": "websecure"
"traefik.http.routers.postgrest.tls": "true"
traefik.http.routers.postgrest.middlewares: github-auth
"traefik.http.services.postgrest.loadbalancer.server.port": "3000"
traefik.enable: "true"
traefik.http.routers.postgrest.rule: Host(`postgrest.${IP}.nip.io`)
# chain postgrest-specifc middleware, after oidc-auth
traefik.http.routers.postgrest.middlewares: oidc-auth,remove-authz-header
traefik.http.routers.postgrest.entrypoints: websecure
traefik.http.routers.postgrest.tls.certresolver: letsencrypt
traefik.http.routers.postgrest.tls: "true"

# unset Authorization header on all requests to postgrest
# postgrest implements its own auth which conflicts with oauth2-proxy
traefik.http.middlewares.remove-authz-header.headers.customrequestheaders.Authorization: ""

networks:
ingress:
internal:
ports:
- 3000:3000

postgres:
image: postgres:${POSTGRES_IMAGE_TAG:-12}
image: postgres:${POSTGRES_IMAGE_TAG:-15}
restart: unless-stopped
environment:
POSTGRES_DB: app_db
POSTGRES_USER: app_user
POSTGRES_PASSWORD: secret
volumes:
- postgres-data:/var/lib/postgresql/data
- ./sql/init.sql:/docker-entrypoint-initdb.d/init.sql
- ./sql/init.sql:/docker-entrypoint-initdb.d/init.sql:ro
networks:
# ingress:
internal:

volumes:
postgres-data: {}





networks:
# internal network for backing services
internal:



# ingress network
ingress:
name: external_web
# TODO find fix
# `docker-compose` expects string, `docker compose` expects boolean
# external: "true"
external: true
20 changes: 20 additions & 0 deletions sql/init.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
-- create schema used for API
create schema api;

-- create table used for log events
create table api.events (
id serial primary key,
event jsonb not null
);

-- create web user w/ read only auth
create role web_anon nologin;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

as we plan to discontinue sending a customized, pre-baked JWT with the claim "role": "event_logger", I would expect we need to bless the web_anon role with grants like those given to event_logger below?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we need to bless the web_anon role with grants like those given to event_logger

Thanks for the reminder! I hadn't tested writing values into logserver (only viewing existing data). I'll make sure to test that the web_anon role can write events without authentication

grant usage on schema api to web_anon;
grant select on api.events to web_anon;

-- create privileged user to write events
create role event_logger nologin;
grant usage on schema api to event_logger;
grant all on api.events to event_logger;
grant usage, select on sequence api.events_id_seq to event_logger;