Working OIDC with Authelia

This commit is contained in:
Spoffy 2024-07-26 15:57:46 +01:00
parent 3b8da882ab
commit 08ad5a510d
9 changed files with 139 additions and 189 deletions

View File

@ -0,0 +1,15 @@
This is an example of Grist with Authelia for OIDC authentication, and Traefik for HTTP encryption and routing.
OIDC enables authentication using many existing providers, including Google, Microsoft, Amazon and Okta.
This example uses Authelia, which is a locally hosted OIDC provider, so that it can work without further setup.
However, Authelia could be easily replaced by one of the providers listed above, or other self-hosted alternatives,
such as Authentik or Dex.
This example could be hosted on a dedicated server, with the following changes:
- DNS setup
- HTTPS / Certificate setup (e.g Let's encrypt)
Users are defined in ./configs/authelia/user-database.yml
See https://support.getgrist.com/install/oidc for more information on using Grist with OIDC.

View File

@ -39,7 +39,7 @@ server:
## Square brackets indicate optional portions of the format. Scheme must be 'tcp', 'tcp4', 'tcp6', or 'unix'.
## The default scheme is 'unix' if the address is an absolute path otherwise it's 'tcp'. The default port is '9091'.
## If the path is specified this configures the router to handle both the `/` path and the configured path.
address: 'tcp://:9091/authelia'
address: 'tcp://:9091/'
## Set the path on disk to Authelia assets.
## Useful to allow overriding of specific static assets.
@ -1137,37 +1137,34 @@ notifier:
##
## Identity Providers
##
# identity_providers:
identity_providers:
##
## OpenID Connect (Identity Provider)
##
## It's recommended you read the documentation before configuration of this section:
## https://www.authelia.com/c/oidc
# oidc:
oidc:
## The hmac_secret is used to sign OAuth2 tokens (authorization code, access tokens and refresh tokens).
## HMAC Secret can also be set using a secret: https://www.authelia.com/c/secrets
# hmac_secret: 'this_is_a_secret_abc123abc123abc'
hmac_secret: {{ secret (mustEnv "HMAC_SECRET_FILE") }}
## The JWK's issuer option configures multiple JSON Web Keys. It's required that at least one of the JWK's
## configured has the RS256 algorithm. For RSA keys (RS or PS) the minimum is a 2048 bit key.
# jwks:
# -
jwks:
-
## Key ID embedded into the JWT header for key matching. Must be an alphanumeric string with 7 or less characters.
## This value is automatically generated if not provided. It's recommended to not configure this.
# key_id: 'example'
## The key algorithm used with this key.
# algorithm: 'RS256'
algorithm: 'RS256'
## The key use expected with this key. Currently only 'sig' is supported.
# use: 'sig'
use: 'sig'
## Required Private Key in PEM DER form.
# key: |
# -----BEGIN RSA PRIVATE KEY-----
# ...
# -----END RSA PRIVATE KEY-----
key: {{ secret (mustEnv "JWT_PRIVATE_KEY_FILE") | mindent 10 "|" | msquote }}
## Optional matching certificate chain in PEM DER form that matches the key. All certificates within the chain
@ -1243,18 +1240,18 @@ notifier:
# allowed_origins_from_client_redirect_uris: false
## Clients is a list of known clients and their configuration.
# clients:
# -
clients:
-
## The Client ID is the OAuth 2.0 and OpenID Connect 1.0 Client ID which is used to link an application to a
## configuration.
# client_id: 'myapp'
client_id: 'grist-local'
## The description to show to users when they end up on the consent screen. Defaults to the ID above.
# client_name: 'My Application'
client_name: 'Grist'
## The client secret is a shared secret between Authelia and the consumer of this client.
# yamllint disable-line rule:line-length
# client_secret: '$pbkdf2-sha512$310000$c8p78n7pUMln0jzvd4aK4Q$JNRBzwAo0ek5qKn50cFzzvE9RXV88h1wJn5KGiHrD0YKtZaR/nCb2CJPOsKaPK0hjf.9yHxzQGZziziccp6Yng' # The digest of 'insecure_secret'.
client_secret: {{ secret (mustEnv "GRIST_CLIENT_SECRET_DIGEST_FILE") }}
## Sector Identifiers are occasionally used to generate pairwise subject identifiers. In most cases this is not
## necessary. It is critical to read the documentation for more information.
@ -1264,8 +1261,8 @@ notifier:
# public: false
## Redirect URI's specifies a list of valid case-sensitive callbacks for this client.
# redirect_uris:
# - 'https://oidc.example.com:8080/oauth2/callback'
redirect_uris:
- {{ mustEnv "GRIST_OAUTH_CALLBACK_URL" | quote }}
## Request URI's specifies a list of valid case-sensitive TLS-secured URIs for this client for use as
## URIs to fetch Request Objects.
@ -1276,11 +1273,11 @@ notifier:
# audience: []
## Scopes this client is allowed to request.
# scopes:
# - 'openid'
# - 'groups'
# - 'email'
# - 'profile'
scopes:
- 'openid'
- 'groups'
- 'email'
- 'profile'
## Grant Types configures which grants this client can obtain.
## It's not recommended to define this unless you know what you're doing.
@ -1299,7 +1296,7 @@ notifier:
## The policy to require for this client; one_factor or two_factor. Can also be the key names for the
## authorization policies section.
# authorization_policy: 'two_factor'
authorization_policy: 'one_factor'
## The custom lifespan name to use for this client. This must be configured independent of the client before
## utilization. Custom lifespans are reusable similar to authorization policies.

View File

@ -1,61 +0,0 @@
# This file uses Go template formatting.
issuer: {{ getenv "DEX_ISSUER" "http://127.0.0.1:5556/dex" }}
storage:
type: sqlite3
config:
file: {{ getenv "DEX_STORAGE_SQLITE3_CONFIG_FILE" "/var/dex/dex.db" }}
web:
{{- if getenv "DEX_WEB_HTTPS" "" }}
https: {{ .Env.DEX_WEB_HTTPS }}
tlsKey: {{ getenv "DEX_WEB_TLS_KEY" | required "$DEX_WEB_TLS_KEY in case of web.https is enabled" }}
tlsCert: {{ getenv "DEX_WEB_TLS_CERT" | required "$DEX_WEB_TLS_CERT in case of web.https is enabled" }}
{{- end }}
http: {{ getenv "DEX_WEB_HTTP" "0.0.0.0:5556" }}
{{- if getenv "DEX_TELEMETRY_HTTP" }}
telemetry:
http: {{ .Env.DEX_TELEMETRY_HTTP }}
{{- end }}
expiry:
deviceRequests: {{ getenv "DEX_EXPIRY_DEVICE_REQUESTS" "5m" }}
signingKeys: {{ getenv "DEX_EXPIRY_SIGNING_KEYS" "6h" }}
idTokens: {{ getenv "DEX_EXPIRY_ID_TOKENS" "24h" }}
authRequests: {{ getenv "DEX_EXPIRY_AUTH_REQUESTS" "24h" }}
logger:
level: {{ getenv "DEX_LOG_LEVEL" "info" }}
format: {{ getenv "DEX_LOG_FORMAT" "text" }}
oauth2:
responseTypes: {{ getenv "DEX_OAUTH2_RESPONSE_TYPES" "[code]" }}
skipApprovalScreen: {{ getenv "DEX_OAUTH2_SKIP_APPROVAL_SCREEN" "false" }}
alwaysShowLoginScreen: {{ getenv "DEX_OAUTH2_ALWAYS_SHOW_LOGIN_SCREEN" "false" }}
{{- if getenv "DEX_OAUTH2_PASSWORD_CONNECTOR" "" }}
passwordConnector: {{ .Env.DEX_OAUTH2_PASSWORD_CONNECTOR }}
{{- end }}
enablePasswordDB: {{ getenv "DEX_ENABLE_PASSWORD_DB" "true" }}
staticPasswords:
- email: "admin@example.com"
hash: "$2a$10$2b2cU8CPhOTaGrs1HRQuAueS7JTT5ZHsHSzYiFPm1leZck7Mc8T4W"
username: "admin"
userID: "08a8684b-db88-4b73-90a9-3cd1661f5466"
staticClients:
- id: grist-client
secret: app-secret
name: 'Private Client'
redirectURIs:
- 'https://example.com/oidc/callback'
connectors:
{{- if getenv "DEX_CONNECTORS_ENABLE_MOCK" }}
- type: mockCallback
id: mock
name: Example
{{- end }}

View File

@ -1,2 +0,0 @@
http:
# Declaring the user list

View File

@ -1,9 +1,6 @@
providers:
# Enables reading docker label config values
docker: {}
# Read additional config from this file.
file:
directory: "/etc/traefik/dynamic"
entrypoints:
# Defines a secure entrypoint using TLS encryption
@ -29,7 +26,5 @@ certificatesResolvers:
storage: /acme/acme.json
tlschallenge: true
# Enables the web UI
# This is disabled by default for security, but can be useful to debugging traefik.
api:
# insecure: true
insecure: true

View File

@ -1,13 +1,3 @@
# This is an example of Grist using Authelia and Traefik for OIDC authentication and https encryption.
# At a minimum, the following should be changed before hosting this example on the internet:
# - An SMTP notifier should be setup to allow Authelia to send emails, instead of logging to a file.
# - DNS should be setup appropriately
# Users are defined in ./configs/authelia/user-database.yml
# See https://support.getgrist.com for more information.
secrets:
# These secrets are used by Authelia
JWT_SECRET:
@ -19,17 +9,27 @@ secrets:
# These secrets are for using Authelia as an OIDC provider
HMAC_SECRET:
file: ./secrets/HMAC_SECRET
JWT_PRIVATE_KEY:
file: ./secrets/certs/private.pem
GRIST_CLIENT_SECRET_DIGEST:
file: ./secrets/GRIST_CLIENT_SECRET_DIGEST
services:
grist:
image: gristlabs/grist:latest
ports:
- 8484:8484
environment:
GRIST_OIDC_IDP_ISSUER: http://dex:5556
GRIST_OIDC_IDP_CLIENT_ID: grist-client
GRIST_OIDC_IDP_CLIENT_SECRET: app-secret
GRIST_OIDC_IDP_SKIP_END_SESSION_ENDPOINT: true
# The URL of given OIDC provider. Used for redirects, among other things.
GRIST_OIDC_IDP_ISSUER: https://auth.grist.localhost
# Client ID, as configured with the OIDC provider.
GRIST_OIDC_IDP_CLIENT_ID: grist-local
# Client secret, as provided by the OIDC provider.
GRIST_OIDC_IDP_CLIENT_SECRET: ${GRIST_CLIENT_SECRET}
# The URL to redirect to with the OIDC provider to log out.
# Some OIDC providers will automatically configure this.
GRIST_OIDC_IDP_END_SESSION_ENDPOINT: https://auth.grist.localhost/logout
# Allow self-signed certificates so this example behaves correctly.
# REMOVE THIS IF HOSTING ON THE INTERNET.
NODE_TLS_REJECT_UNAUTHORIZED: 0
# Forces Grist to only use a single team called 'Example'
GRIST_SINGLE_ORG: my-grist-team # alternatively, GRIST_ORG_IN_PATH: "true" for multi-team operation
@ -39,6 +39,7 @@ services:
APP_HOME_URL: https://grist.localhost
# Default email for the "Admin" account
GRIST_DEFAULT_EMAIL: test@example.org
restart: always
volumes:
# Where to store persistent data, such as documents.
- ./grist_local_data:/persist
@ -46,74 +47,71 @@ services:
- "traefik.http.services.grist.loadbalancer.server.port=8484"
- "traefik.http.routers.grist.rule=Host(`grist.localhost`)"
- "traefik.http.routers.grist.service=grist"
- "traefik.http.routers.grist.tls.certresolver=letsencrypt"
#
# traefik:
# image: traefik:latest
# ports:
# # HTTP Ports
# - "80:80"
# - "443:443"
# # The Web UI (enabled by --api.insecure=true)
# # - "8080:8080"
# volumes:
# # Set the config file for traefik - this is loaded automatically.
# - ./configs/traefik-config.yml:/etc/traefik/traefik.yml
# # Set the config file for the dynamic config, such as middleware.
# - ./configs/traefik-dynamic-config.yml:/etc/traefik/dynamic/dynamic-config.yml
# # You may want to put state somewhere other than /tmp :-)
# - /tmp/grist/acme:/acme
# # Traefik needs docker access when configured via docker labels.
# - /var/run/docker.sock:/var/run/docker.sock
# depends_on:
# grist:
# condition: service_started
# authelia:
# condition: service_started
# Uncomment and configure in traefik-config.yml to enable automatic HTTPS certificate setup.
#- "traefik.http.routers.grist.tls.certresolver=letsencrypt"
depends_on:
# Grist attempts to setup OIDC when it starts, making a request to the OIDC service.
# This will fail if Authelia isn't ready and reachable.
# Traefik will only start routing to Authelia when it's registered as healthy.
# Making Grist wait for Authelia to be healthy should avoid this issue.
authelia:
condition: service_healthy
traefik:
condition: service_started
# authelia:
# image: authelia/authelia:4
# ports:
# - 9091:9091
# secrets:
# - HMAC_SECRET
# - JWT_SECRET
# - SESSION_SECRET
# - STORAGE_ENCRYPTION_KEY
# environment:
# AUTHELIA_IDENTITY_VALIDATION_RESET_PASSWORD_JWT_SECRET_FILE: '/run/secrets/JWT_SECRET'
# AUTHELIA_SESSION_SECRET_FILE: '/run/secrets/SESSION_SECRET'
# AUTHELIA_STORAGE_ENCRYPTION_KEY_FILE: '/run/secrets/STORAGE_ENCRYPTION_KEY'
# # Domain Grist is hosted at. Custom variable that's interpolated into the Authelia config
# APP_DOMAIN: 'grist.localhost'
# volumes:
# - ./configs/authelia:/config
# command:
# - 'authelia'
# - '--config=/config/configuration.yml'
# # Enables passing environment variables down to the Authelia config.
# - '--config.experimental.filters=template'
# labels:
# - "traefik.http.services.authelia.loadbalancer.server.port=9091"
# - "traefik.http.routers.authelia.rule=Host(`auth.grist.localhost`)"
# - "traefik.http.routers.authelia.service=authelia"
# - "traefik.http.routers.authelia.tls.certresolver=letsencrypt"
dex:
image: dexidp/dex:latest
traefik:
image: traefik:latest
ports:
- 5556:5556
- 5557:5557
environment:
DEX_ISSUER: http://auth.grist.localhost:5556/
DEX_STORAGE_SQLITE3_CONFIG_FILE: /dex_db/dex.db
DEX_ENABLE_PASSWORD_DB: true
DEX_OAUTH2_PASSWORD_CONNECTOR: local
# HTTP Ports
- "80:80"
- "443:443"
# The Web UI (enabled by --api.insecure=true)
- "8080:8080"
- "8082:8082"
volumes:
- ./configs/dex:/config
- ./dex_db:/dex_db
command:
- dex
- serve
- /config/config.yaml
# Set the config file for traefik - this is loaded automatically.
- ./configs/traefik/config.yml:/etc/traefik/traefik.yml
# Certificate location, if automatic certificate setup is enabled.
- ./configs/traefik/acme:/acme
# Traefik needs docker access when configured via docker labels.
- /var/run/docker.sock:/var/run/docker.sock
networks:
default:
aliases:
# Enables Grist to resolve this domain to Traefik when doing OIDC setup.
- auth.grist.localhost
authelia:
image: authelia/authelia:4
secrets:
- HMAC_SECRET
- JWT_SECRET
- JWT_PRIVATE_KEY
- GRIST_CLIENT_SECRET_DIGEST
- SESSION_SECRET
- STORAGE_ENCRYPTION_KEY
environment:
AUTHELIA_IDENTITY_VALIDATION_RESET_PASSWORD_JWT_SECRET_FILE: '/run/secrets/JWT_SECRET'
AUTHELIA_SESSION_SECRET_FILE: '/run/secrets/SESSION_SECRET'
AUTHELIA_STORAGE_ENCRYPTION_KEY_FILE: '/run/secrets/STORAGE_ENCRYPTION_KEY'
HMAC_SECRET_FILE: '/run/secrets/HMAC_SECRET'
JWT_PRIVATE_KEY_FILE: '/run/secrets/JWT_PRIVATE_KEY'
# Domain Grist is hosted at. Custom variable that's interpolated into the Authelia config
APP_DOMAIN: 'grist.localhost'
# Where Authelia should redirect to after successful authentication.
GRIST_OAUTH_CALLBACK_URL: https://grist.localhost/oauth2/callback
# Hash of the client secret provided to Grist.
GRIST_CLIENT_SECRET_DIGEST_FILE: "/run/secrets/GRIST_CLIENT_SECRET_DIGEST"
volumes:
- ./configs/authelia:/config
command:
- 'authelia'
- '--config=/config/configuration.yml'
# Enables templating in the config file
- '--config.experimental.filters=template'
labels:
- "traefik.http.services.authelia.loadbalancer.server.port=9091"
- "traefik.http.routers.authelia.rule=Host(`auth.grist.localhost`)"
- "traefik.http.routers.authelia.service=authelia"
# Uncomment and configure in traefik-config.yml to enable automatic HTTPS certificate setup.
#- "traefik.http.routers.authelia.tls.certresolver=letsencrypt"

View File

@ -1,18 +1,26 @@
# Helper script to securely generate random secrets for Authelia.
# If this doesn't work on your platform, here are some alternate snippets for secure string generation:
# Python:
# python -c "import secrets; print(secrets.token_urlsafe(32))"
# Javascript / Node:
# node -e "console.log(crypto.randomBytes(32).toString('base64').replace(/\+/g, '-').replace(/\//g, '_').replace(/=/g, ''))"
SCRIPT_DIR=$(dirname $0)
function generateSecureString {
xxd -l"$1" -ps /dev/urandom | xxd -r -ps | base64 \
| tr -d = | tr + - | tr / _ | tr -d \\n
function getSecret {
cut -d ":" -f 2 <<< "$1" | tr -d '[:blank:]'
}
generateSecureString 64 > "$SCRIPT_DIR/secrets/JWT_SECRET"
generateSecureString 64 > "$SCRIPT_DIR/secrets/SESSION_SECRET"
generateSecureString 64 > "$SCRIPT_DIR/secrets/STORAGE_ENCRYPTION_KEY"
function generateSecureString {
getSecret "$(docker run authelia/authelia:4 authelia crypto rand --charset=rfc3986 --length="$1")"
}
generateSecureString 128 > "$SCRIPT_DIR/secrets/HMAC_SECRET"
generateSecureString 128 > "$SCRIPT_DIR/secrets/JWT_SECRET"
generateSecureString 128 > "$SCRIPT_DIR/secrets/SESSION_SECRET"
generateSecureString 128 > "$SCRIPT_DIR/secrets/STORAGE_ENCRYPTION_KEY"
# Generates the OIDC secret key for the Grist client
CLIENT_SECRET_OUTPUT="$(docker run authelia/authelia:4 authelia crypto hash generate pbkdf2 --variant sha512 --random --random.length 72 --random.charset rfc3986)"
CLIENT_SECRET=$(getSecret "$(grep 'Password' <<< $CLIENT_SECRET_OUTPUT)")
echo "GRIST_CLIENT_SECRET=$CLIENT_SECRET" >> "$SCRIPT_DIR/.env"
getSecret "$(grep 'Digest' <<< $CLIENT_SECRET_OUTPUT)" >> "$SCRIPT_DIR/secrets/GRIST_CLIENT_SECRET_DIGEST"
# Generate JWT certificates Authelia needs for OIDC
docker run -v ./secrets/certs:/certs authelia/authelia:4 authelia crypto certificate rsa generate -d /certs