1
0
mirror of https://github.com/gristlabs/grist-core.git synced 2024-10-27 20:44:07 +00:00

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'. ## 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'. ## 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. ## 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. ## Set the path on disk to Authelia assets.
## Useful to allow overriding of specific static assets. ## Useful to allow overriding of specific static assets.
@ -1137,37 +1137,34 @@ notifier:
## ##
## Identity Providers ## Identity Providers
## ##
# identity_providers: identity_providers:
## ##
## OpenID Connect (Identity Provider) ## OpenID Connect (Identity Provider)
## ##
## It's recommended you read the documentation before configuration of this section: ## It's recommended you read the documentation before configuration of this section:
## https://www.authelia.com/c/oidc ## https://www.authelia.com/c/oidc
# oidc: oidc:
## The hmac_secret is used to sign OAuth2 tokens (authorization code, access tokens and refresh tokens). ## 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 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 ## 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. ## 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. ## 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. ## This value is automatically generated if not provided. It's recommended to not configure this.
# key_id: 'example' # key_id: 'example'
## The key algorithm used with this key. ## The key algorithm used with this key.
# algorithm: 'RS256' algorithm: 'RS256'
## The key use expected with this key. Currently only 'sig' is supported. ## The key use expected with this key. Currently only 'sig' is supported.
# use: 'sig' use: 'sig'
## Required Private Key in PEM DER form. ## Required Private Key in PEM DER form.
# key: | key: {{ secret (mustEnv "JWT_PRIVATE_KEY_FILE") | mindent 10 "|" | msquote }}
# -----BEGIN RSA PRIVATE KEY-----
# ...
# -----END RSA PRIVATE KEY-----
## Optional matching certificate chain in PEM DER form that matches the key. All certificates within the chain ## 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 # allowed_origins_from_client_redirect_uris: false
## Clients is a list of known clients and their configuration. ## 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 ## 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. ## 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. ## 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. ## The client secret is a shared secret between Authelia and the consumer of this client.
# yamllint disable-line rule:line-length # 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 ## 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. ## necessary. It is critical to read the documentation for more information.
@ -1264,8 +1261,8 @@ notifier:
# public: false # public: false
## Redirect URI's specifies a list of valid case-sensitive callbacks for this client. ## Redirect URI's specifies a list of valid case-sensitive callbacks for this client.
# redirect_uris: redirect_uris:
# - 'https://oidc.example.com:8080/oauth2/callback' - {{ 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 ## Request URI's specifies a list of valid case-sensitive TLS-secured URIs for this client for use as
## URIs to fetch Request Objects. ## URIs to fetch Request Objects.
@ -1276,11 +1273,11 @@ notifier:
# audience: [] # audience: []
## Scopes this client is allowed to request. ## Scopes this client is allowed to request.
# scopes: scopes:
# - 'openid' - 'openid'
# - 'groups' - 'groups'
# - 'email' - 'email'
# - 'profile' - 'profile'
## Grant Types configures which grants this client can obtain. ## Grant Types configures which grants this client can obtain.
## It's not recommended to define this unless you know what you're doing. ## 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 ## The policy to require for this client; one_factor or two_factor. Can also be the key names for the
## authorization policies section. ## 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 ## 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. ## 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: providers:
# Enables reading docker label config values # Enables reading docker label config values
docker: {} docker: {}
# Read additional config from this file.
file:
directory: "/etc/traefik/dynamic"
entrypoints: entrypoints:
# Defines a secure entrypoint using TLS encryption # Defines a secure entrypoint using TLS encryption
@ -29,7 +26,5 @@ certificatesResolvers:
storage: /acme/acme.json storage: /acme/acme.json
tlschallenge: true tlschallenge: true
# Enables the web UI
# This is disabled by default for security, but can be useful to debugging traefik.
api: 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: secrets:
# These secrets are used by Authelia # These secrets are used by Authelia
JWT_SECRET: JWT_SECRET:
@ -19,17 +9,27 @@ secrets:
# These secrets are for using Authelia as an OIDC provider # These secrets are for using Authelia as an OIDC provider
HMAC_SECRET: HMAC_SECRET:
file: ./secrets/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: services:
grist: grist:
image: gristlabs/grist:latest image: gristlabs/grist:latest
ports:
- 8484:8484
environment: environment:
GRIST_OIDC_IDP_ISSUER: http://dex:5556 # The URL of given OIDC provider. Used for redirects, among other things.
GRIST_OIDC_IDP_CLIENT_ID: grist-client GRIST_OIDC_IDP_ISSUER: https://auth.grist.localhost
GRIST_OIDC_IDP_CLIENT_SECRET: app-secret # Client ID, as configured with the OIDC provider.
GRIST_OIDC_IDP_SKIP_END_SESSION_ENDPOINT: true 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' # 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 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 APP_HOME_URL: https://grist.localhost
# Default email for the "Admin" account # Default email for the "Admin" account
GRIST_DEFAULT_EMAIL: test@example.org GRIST_DEFAULT_EMAIL: test@example.org
restart: always
volumes: volumes:
# Where to store persistent data, such as documents. # Where to store persistent data, such as documents.
- ./grist_local_data:/persist - ./grist_local_data:/persist
@ -46,74 +47,71 @@ services:
- "traefik.http.services.grist.loadbalancer.server.port=8484" - "traefik.http.services.grist.loadbalancer.server.port=8484"
- "traefik.http.routers.grist.rule=Host(`grist.localhost`)" - "traefik.http.routers.grist.rule=Host(`grist.localhost`)"
- "traefik.http.routers.grist.service=grist" - "traefik.http.routers.grist.service=grist"
- "traefik.http.routers.grist.tls.certresolver=letsencrypt" # Uncomment and configure in traefik-config.yml to enable automatic HTTPS certificate setup.
# #- "traefik.http.routers.grist.tls.certresolver=letsencrypt"
# traefik: depends_on:
# image: traefik:latest # Grist attempts to setup OIDC when it starts, making a request to the OIDC service.
# ports: # This will fail if Authelia isn't ready and reachable.
# # HTTP Ports # Traefik will only start routing to Authelia when it's registered as healthy.
# - "80:80" # Making Grist wait for Authelia to be healthy should avoid this issue.
# - "443:443" authelia:
# # The Web UI (enabled by --api.insecure=true) condition: service_healthy
# # - "8080:8080" traefik:
# volumes: condition: service_started
# # 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
# authelia: traefik:
# image: authelia/authelia:4 image: traefik:latest
# 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
ports: ports:
- 5556:5556 # HTTP Ports
- 5557:5557 - "80:80"
environment: - "443:443"
DEX_ISSUER: http://auth.grist.localhost:5556/ # The Web UI (enabled by --api.insecure=true)
DEX_STORAGE_SQLITE3_CONFIG_FILE: /dex_db/dex.db - "8080:8080"
DEX_ENABLE_PASSWORD_DB: true - "8082:8082"
DEX_OAUTH2_PASSWORD_CONNECTOR: local
volumes: volumes:
- ./configs/dex:/config # Set the config file for traefik - this is loaded automatically.
- ./dex_db:/dex_db - ./configs/traefik/config.yml:/etc/traefik/traefik.yml
command: # Certificate location, if automatic certificate setup is enabled.
- dex - ./configs/traefik/acme:/acme
- serve # Traefik needs docker access when configured via docker labels.
- /config/config.yaml - /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. # 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) SCRIPT_DIR=$(dirname $0)
function generateSecureString { function getSecret {
xxd -l"$1" -ps /dev/urandom | xxd -r -ps | base64 \ cut -d ":" -f 2 <<< "$1" | tr -d '[:blank:]'
| tr -d = | tr + - | tr / _ | tr -d \\n
} }
generateSecureString 64 > "$SCRIPT_DIR/secrets/JWT_SECRET" function generateSecureString {
generateSecureString 64 > "$SCRIPT_DIR/secrets/SESSION_SECRET" getSecret "$(docker run authelia/authelia:4 authelia crypto rand --charset=rfc3986 --length="$1")"
generateSecureString 64 > "$SCRIPT_DIR/secrets/STORAGE_ENCRYPTION_KEY" }
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