groupware: add OIDC authentication support between Groupware backend and Stalwart

* re-implement the auth-api service to authenticate Reva tokens
   following the OIDC Userinfo endpoint specification

 * pass the context where necessary and add an authenticator interface
   to the JMAP HTTP driver, in order to select between master
   authentication (which is used when GROUPWARE_JMAP_MASTER_USERNAME and
   GROUPWARE_JMAP_MASTER_PASSWORD are both set) and OIDC token
   forwarding through bearer auth

 * add Stalwart directory configuration "idmoidc" which uses the
   OpenCloud auth-api service API (/auth/) to validate the token it
   received as bearer auth from the Groupware backend's JMAP client,
   using it as an OIDC Userinfo endpoint

 * implement optional additional shared secret to secure the Userinfo
   service, as an additional path parameter
This commit is contained in:
Pascal Bleser
2026-02-10 16:57:24 +01:00
parent 614e2b22c0
commit db42de2a35
52 changed files with 959 additions and 3352 deletions

View File

@@ -311,11 +311,11 @@ KEYCLOAK_ADMIN_PASSWORD=
# Domain of Stalwart
# Defaults to "stalwart.opencloud.test"
STALWART_DOMAIN=
# LDAP configuration to use for Stalwart:
# Can either be either
# - idmldap: for the built-in IDP/IDM
# - ldap: when using KeyCloak and OpenLDAP
# - idmldap: for the built-in IDP/IDM, using Master Authentication between Groupware and Stalwart, and LDAP in Stalwart
# - idmoidc: built-in IDP/IDM, using OIDC Userinfo between Groupware and Stalwart
# - ldap: when using KeyCloak and OpenLDAP, with Master Authentication between Groupware and Stalwart, and LDAP in Stalwart
STALWART_AUTH_DIRECTORY=idmldap
## IMPORTANT ##

View File

@@ -0,0 +1,67 @@
authentication.fallback-admin.secret = "$6$4qPYDVhaUHkKcY7s$bB6qhcukb9oFNYRIvaDZgbwxrMa2RvF5dumCjkBFdX19lSNqrgKltf3aPrFMuQQKkZpK2YNuQ83hB1B3NiWzj."
authentication.fallback-admin.user = "mailadmin"
authentication.master.secret = "$6$4qPYDVhaUHkKcY7s$bB6qhcukb9oFNYRIvaDZgbwxrMa2RvF5dumCjkBFdX19lSNqrgKltf3aPrFMuQQKkZpK2YNuQ83hB1B3NiWzj."
authentication.master.user = "master"
directory.oidc.cache.size = 1048576
directory.oidc.cache.ttl.negative = "10m"
directory.oidc.cache.ttl.positive = "1h"
directory.oidc.endpoint.method = "userinfo"
directory.oidc.endpoint.url = "http://172.17.0.1:10000/auth/maethaR9eiXaiph8ahn8ohH6dahPiequ"
directory.oidc.fields.email = "email"
directory.oidc.fields.full-name = "name"
directory.oidc.fields.username = "preferred_username"
directory.oidc.timeout = "15s"
directory.oidc.type = "oidc"
http.allowed-endpoint = 200
http.hsts = true
http.permissive-cors = false
http.url = "'https://' + config_get('server.hostname')"
http.use-x-forwarded = true
metrics.prometheus.auth.secret = "secret"
metrics.prometheus.auth.username = "metrics"
metrics.prometheus.enable = true
server.listener.http.bind = "0.0.0.0:8080"
server.listener.http.protocol = "http"
server.listener.https.bind = "0.0.0.0:443"
server.listener.https.protocol = "http"
server.listener.https.tls.implicit = true
server.listener.imap.bind = "0.0.0.0:143"
server.listener.imap.protocol = "imap"
server.listener.imaptls.bind = "0.0.0.0:993"
server.listener.imaptls.protocol = "imap"
server.listener.imaptls.tls.implicit = true
server.listener.pop3.bind = "0.0.0.0:110"
server.listener.pop3.protocol = "pop3"
server.listener.pop3s.bind = "0.0.0.0:995"
server.listener.pop3s.protocol = "pop3"
server.listener.pop3s.tls.implicit = true
server.listener.sieve.bind = "0.0.0.0:4190"
server.listener.sieve.protocol = "managesieve"
server.listener.smtp.bind = "0.0.0.0:25"
server.listener.smtp.protocol = "smtp"
server.listener.submission.bind = "0.0.0.0:587"
server.listener.submission.protocol = "smtp"
server.listener.submissions.bind = "0.0.0.0:465"
server.listener.submissions.protocol = "smtp"
server.listener.submissions.tls.implicit = true
server.max-connections = 8192
server.socket.backlog = 1024
server.socket.nodelay = true
server.socket.reuse-addr = true
server.socket.reuse-port = true
sharing.allow-directory-query = false
storage.blob = "rocksdb"
storage.data = "rocksdb"
storage.directory = "oidc"
storage.fts = "rocksdb"
storage.lookup = "rocksdb"
store.rocksdb.compression = "lz4"
store.rocksdb.path = "/opt/stalwart/data"
store.rocksdb.type = "rocksdb"
tracer.console.ansi = true
tracer.console.buffered = true
tracer.console.enable = true
tracer.console.level = "trace"
tracer.console.lossy = false
tracer.console.multiline = false
tracer.console.type = "stdout"

View File

@@ -64,6 +64,8 @@ services:
GROUPS_LDAP_BIND_PASSWORD: "admin"
IDM_LDAPS_ADDR: 0.0.0.0:9235
GROUPWARE_JMAP_BASE_URL: https://${STALWART_DOMAIN:-stalwart.opencloud.test}
GROUPWARE_JMAP_MASTER_USERNAME: "master"
GROUPWARE_JMAP_MASTER_PASSWORD: "admin"
volumes:
- ./config/opencloud/app-registry.yaml:/etc/opencloud/app-registry.yaml
- ./config/opencloud/csp.yaml:/etc/opencloud/csp.yaml