diff --git a/CloudronManifest.json b/CloudronManifest.json index 6686aeb..329f511 100644 --- a/CloudronManifest.json +++ b/CloudronManifest.json @@ -9,11 +9,19 @@ "upstreamVersion": "1.128.0", "healthCheckPath": "/", "httpPort": 8008, + "httpPorts": { + "MAS_DOMAIN": { + "title": "Matrix Authentication Service Domain", + "description": "Matrix Authentication Service domain", + "containerPort": 8080, + "defaultValue": "auth" + } + }, "memoryLimit": 536870912, "addons": { "localstorage": {}, "oidc": { - "loginRedirectUri": "/_synapse/client/oidc/callback" + "loginRedirectUri": "/_synapse/client/oidc/callback, /upstream/callback/000000000000000000C10WDR0N" }, "postgresql": {}, "sendmail": { diff --git a/Dockerfile b/Dockerfile index 1e0826a..ebd21dc 100644 --- a/Dockerfile +++ b/Dockerfile @@ -13,6 +13,9 @@ ARG SYNAPSE_VERSION=1.128.0 # renovate: datasource=github-releases depName=matrix-org/synapse-s3-storage-provider versioning=semver extractVersion=^v(?.+)$ ARG S3PROVIDER_VERSION=1.5.0 +# renovate: datasource=github-releases depName=element-hq/matrix-authentication-service versioning=semver extractVersion=^v(?.+)$ +ARG MAS_VERSION=0.15.0 + # Synapse (https://github.com/matrix-org/synapse/blob/master/INSTALL.md) # lxml - required for previews RUN source /app/code/env/bin/activate && \ @@ -21,8 +24,17 @@ RUN source /app/code/env/bin/activate && \ # Updated suffix list RUN curl -L https://publicsuffix.org/list/public_suffix_list.dat -o /app/code/env/lib/python3.12/site-packages/publicsuffix2/public_suffix_list.dat +# matrix-authentication-service +RUN mkdir -p /app/code/mas && \ + curl -L https://github.com/element-hq/matrix-authentication-service/releases/download/v${MAS_VERSION}/mas-cli-x86_64-linux.tar.gz | tar zxf - --strip-components 1 -C /app/code/mas +ENV PATH=$PATH:/app/code/mas + RUN ln -sf /app/data/index.html /app/code/env/lib/python3.12/site-packages/synapse/static/index.html +# Add supervisor configs +COPY supervisor/* /etc/supervisor/conf.d/ +RUN ln -sf /run/synapse/supervisord.log /var/log/supervisor/supervisord.log + RUN chown -R cloudron:cloudron /app/code ADD index.html homeserver.yaml.template start.sh /app/pkg/ diff --git a/start.sh b/start.sh index 58bf8b9..8842ac6 100755 --- a/start.sh +++ b/start.sh @@ -2,10 +2,96 @@ set -eu -mkdir -p /app/data/data /app/data/configs /run/synapse +mkdir -p /app/data/data /app/data/configs/policies /run/synapse source /app/code/env/bin/activate +mas_client_id="0000000000000000000SYNAPSE" +cloudron_client_id="000000000000000000C10WDR0N" # a valid ULID excludes I, L, O, and U +mas_client_secret=$(openssl rand -hex 32) +matrix_secret=$(openssl rand -hex 32) + +function mas_config() { + export MAS_CONFIG=/run/synapse/mas-config.yaml + + echo "MAS configuration" + if [[ ! -f /app/data/configs/mas.yaml ]]; then + mas-cli config generate > /app/data/configs/mas.yaml + + yq eval -i ".email.from=\"${CLOUDRON_MAIL_FROM_DISPLAY_NAME:-Matrix} <${CLOUDRON_MAIL_FROM}>\"" /app/data/configs/mas.yaml + yq eval -i ".email.reply_to=\"${CLOUDRON_MAIL_FROM_DISPLAY_NAME:-Matrix} <${CLOUDRON_MAIL_FROM}>\"" /app/data/configs/mas.yaml + fi + + cat /app/data/configs/mas.yaml > ${MAS_CONFIG} + + # http + yq eval -i ".http.public_base=\"https://${MAS_DOMAIN}\"" ${MAS_CONFIG} + + # database + yq eval -i ".database.uri=\"${CLOUDRON_POSTGRESQL_URL}\"" ${MAS_CONFIG} +# yq eval -i ".database.user=\"${CLOUDRON_POSTGRESQL_USERNAME}\"" ${MAS_CONFIG} +# yq eval -i ".database.password=\"${CLOUDRON_POSTGRESQL_PASSWORD}\"" ${MAS_CONFIG} +# yq eval -i ".database.database=\"${CLOUDRON_POSTGRESQL_DATABASE}\"" ${MAS_CONFIG} +# yq eval -i ".database.host=\"${CLOUDRON_POSTGRESQL_HOST}\"" ${MAS_CONFIG} +# yq eval -i ".database.port=${CLOUDRON_POSTGRESQL_PORT}" ${MAS_CONFIG} + + # email + yq eval -i ".email.transport=\"smtp\"" ${MAS_CONFIG} + yq eval -i ".email.mode=\"plain\"" ${MAS_CONFIG} + yq eval -i ".email.hostname=\"${CLOUDRON_MAIL_SMTP_SERVER}\"" ${MAS_CONFIG} + yq eval -i ".email.port=${CLOUDRON_MAIL_SMTP_PORT}" ${MAS_CONFIG} + yq eval -i ".email.username=\"${CLOUDRON_MAIL_SMTP_USERNAME}\"" ${MAS_CONFIG} + yq eval -i ".email.password=\"${CLOUDRON_MAIL_SMTP_PASSWORD}\"" ${MAS_CONFIG} + + # provision client for the homeserver + yq eval -i ".clients[0].client_id=\"${mas_client_id}\"" ${MAS_CONFIG} + yq eval -i ".clients[0].client_auth_method=\"client_secret_basic\"" ${MAS_CONFIG} + yq eval -i ".clients[0].client_secret=\"${mas_client_secret}\"" ${MAS_CONFIG} + + # connection to the homeserver + yq eval -i ".matrix.homeserver=\"localhost:8008\"" ${MAS_CONFIG} + yq eval -i ".matrix.secret=\"${matrix_secret}\"" ${MAS_CONFIG} + yq eval -i ".matrix.endpoint=\"http://localhost:8008\"" ${MAS_CONFIG} + + # setup cloudron OIDC as upstrem SSO provider + if [[ -n "${CLOUDRON_OIDC_ISSUER:-}" ]]; then + yq eval -i ".upstream_oauth2.providers[0].id=\"${cloudron_client_id}\"" ${MAS_CONFIG} + yq eval -i ".upstream_oauth2.providers[0].human_name=\"${CLOUDRON_OIDC_PROVIDER_NAME:-Cloudron}\"" ${MAS_CONFIG} + yq eval -i ".upstream_oauth2.providers[0].issuer=\"${CLOUDRON_OIDC_ISSUER}\"" ${MAS_CONFIG} + yq eval -i ".upstream_oauth2.providers[0].client_id=\"${CLOUDRON_OIDC_CLIENT_ID}\"" ${MAS_CONFIG} + yq eval -i ".upstream_oauth2.providers[0].client_secret=\"${CLOUDRON_OIDC_CLIENT_SECRET}\"" ${MAS_CONFIG} + + yq eval -i ".upstream_oauth2.providers[0].scope=\"openid, email, profile\"" ${MAS_CONFIG} + + # How the provider configuration and endpoints should be discovered + # Possible values are: + # - `oidc`: discover the provider through OIDC discovery, + # with strict metadata validation (default) + # - `insecure`: discover through OIDC discovery, but skip metadata validation + # - `disabled`: don't discover the provider and use the endpoints below + yq eval -i ".upstream_oauth2.providers[0].discovery_mode=\"oidc\"" ${MAS_CONFIG} + + yq eval -i ".upstream_oauth2.providers[0].authorization_endpoint=\"${CLOUDRON_OIDC_AUTH_ENDPOINT}\"" ${MAS_CONFIG} + yq eval -i ".upstream_oauth2.providers[0].token_endpoint=\"${CLOUDRON_OIDC_TOKEN_ENDPOINT}\"" ${MAS_CONFIG} + yq eval -i ".upstream_oauth2.providers[0].userinfo_endpoint=\"${CLOUDRON_OIDC_PROFILE_ENDPOINT}\"" ${MAS_CONFIG} + yq eval -i ".upstream_oauth2.providers[0].jwks_uri=\"${CLOUDRON_OIDC_KEYS_ENDPOINT}\"" ${MAS_CONFIG} + yq eval -i ".upstream_oauth2.providers[0].token_endpoint_auth_method=\"client_secret_post\"" ${MAS_CONFIG} + yq eval -i ".upstream_oauth2.providers[0].response_mode=\"query\"" ${MAS_CONFIG} + + yq eval -i ".claims_imports.subject.template=\"{{ user.sub }}\"" ${MAS_CONFIG} + yq eval -i ".claims_imports.localpart.action=\"force\"" ${MAS_CONFIG} + yq eval -i ".claims_imports.localpart.template=\"{{ user.preferred_username }}\"" ${MAS_CONFIG} + yq eval -i ".claims_imports.displayname.action=\"suggest\"" ${MAS_CONFIG} + yq eval -i ".claims_imports.displayname.template=\"{{ user.name }}\"" ${MAS_CONFIG} + yq eval -i ".claims_imports.email.action=\"suggest\"" ${MAS_CONFIG} + yq eval -i ".claims_imports.email.template=\"{{ user.email }}\"" ${MAS_CONFIG} + yq eval -i ".claims_imports.set_email_verification=\"import\"" ${MAS_CONFIG} + yq eval -i ".claims_imports.account_name.template=\"@{{ user.preferred_username }}\"" ${MAS_CONFIG} + fi + + mas-cli -c ${MAS_CONFIG} database migrate +} + if [[ ! -f /app/data/configs/homeserver.yaml ]]; then echo "==> Detected first run" @@ -45,6 +131,7 @@ if [[ ! -f /app/data/configs/homeserver.yaml ]]; then yq eval -i ".password_config.pepper=\"$(pwgen -1s 12)\"" /app/data/configs/homeserver.yaml # always set this so that users can enable password login if needed fi + echo "==> Ensure we log to console" yq eval -i ".root.handlers=[\"console\"]" /app/data/configs/log.config yq eval -i ".loggers.twisted.handlers=[\"console\"]" /app/data/configs/log.config @@ -70,22 +157,23 @@ yq eval -i ".email.notif_from=\"${CLOUDRON_MAIL_FROM_DISPLAY_NAME:-Matrix} <${CL # oidc if [[ -n "${CLOUDRON_OIDC_ISSUER:-}" ]]; then yq eval -i "del(.password_providers)" /app/data/configs/homeserver.yaml # remove old ldap config - echo " ==> Configuring OIDC auth" - yq eval -i ".oidc_providers[0].idp_id=\"cloudron\"" /app/data/configs/homeserver.yaml - yq eval -i ".oidc_providers[0].idp_name=\"${CLOUDRON_OIDC_PROVIDER_NAME:-Cloudron}\"" /app/data/configs/homeserver.yaml - yq eval -i ".oidc_providers[0].issuer=\"${CLOUDRON_OIDC_ISSUER}\"" /app/data/configs/homeserver.yaml - yq eval -i ".oidc_providers[0].client_id=\"${CLOUDRON_OIDC_CLIENT_ID}\"" /app/data/configs/homeserver.yaml - yq eval -i ".oidc_providers[0].client_secret=\"${CLOUDRON_OIDC_CLIENT_SECRET}\"" /app/data/configs/homeserver.yaml - - yq eval -i ".oidc_providers[0].scopes=[\"openid\", \"email\", \"profile\"]" /app/data/configs/homeserver.yaml - yq eval -i ".oidc_providers[0].authorization_endpoint=\"${CLOUDRON_OIDC_AUTH_ENDPOINT}\"" /app/data/configs/homeserver.yaml - yq eval -i ".oidc_providers[0].token_endpoint=\"${CLOUDRON_OIDC_TOKEN_ENDPOINT}\"" /app/data/configs/homeserver.yaml - yq eval -i ".oidc_providers[0].userinfo_endpoint=\"${CLOUDRON_OIDC_PROFILE_ENDPOINT}\"" /app/data/configs/homeserver.yaml - # https://s3lph.me/ldap-to-oidc-migration-3-matrix.html - yq eval -i ".oidc_providers[0].allow_existing_users=true" /app/data/configs/homeserver.yaml - yq eval -i ".oidc_providers[0].skip_verification=true" /app/data/configs/homeserver.yaml - yq eval -i ".oidc_providers[0].user_mapping_provider.config.localpart_template=\"{{ user.sub }}\"" /app/data/configs/homeserver.yaml - yq eval -i ".oidc_providers[0].user_mapping_provider.config.display_name_template=\"{{ user.name }}\"" /app/data/configs/homeserver.yaml + yq eval -i "del(.oidc_providers[0])" /app/data/configs/homeserver.yaml # remove old oidc config +# echo " ==> Configuring OIDC auth" +# yq eval -i ".oidc_providers[0].idp_id=\"cloudron\"" /app/data/configs/homeserver.yaml +# yq eval -i ".oidc_providers[0].idp_name=\"${CLOUDRON_OIDC_PROVIDER_NAME:-Cloudron}\"" /app/data/configs/homeserver.yaml +# yq eval -i ".oidc_providers[0].issuer=\"${CLOUDRON_OIDC_ISSUER}\"" /app/data/configs/homeserver.yaml +# yq eval -i ".oidc_providers[0].client_id=\"${CLOUDRON_OIDC_CLIENT_ID}\"" /app/data/configs/homeserver.yaml +# yq eval -i ".oidc_providers[0].client_secret=\"${CLOUDRON_OIDC_CLIENT_SECRET}\"" /app/data/configs/homeserver.yaml +# +# yq eval -i ".oidc_providers[0].scopes=[\"openid\", \"email\", \"profile\"]" /app/data/configs/homeserver.yaml +# yq eval -i ".oidc_providers[0].authorization_endpoint=\"${CLOUDRON_OIDC_AUTH_ENDPOINT}\"" /app/data/configs/homeserver.yaml +# yq eval -i ".oidc_providers[0].token_endpoint=\"${CLOUDRON_OIDC_TOKEN_ENDPOINT}\"" /app/data/configs/homeserver.yaml +# yq eval -i ".oidc_providers[0].userinfo_endpoint=\"${CLOUDRON_OIDC_PROFILE_ENDPOINT}\"" /app/data/configs/homeserver.yaml +# # https://s3lph.me/ldap-to-oidc-migration-3-matrix.html +# yq eval -i ".oidc_providers[0].allow_existing_users=true" /app/data/configs/homeserver.yaml +# yq eval -i ".oidc_providers[0].skip_verification=true" /app/data/configs/homeserver.yaml +# yq eval -i ".oidc_providers[0].user_mapping_provider.config.localpart_template=\"{{ user.sub }}\"" /app/data/configs/homeserver.yaml +# yq eval -i ".oidc_providers[0].user_mapping_provider.config.display_name_template=\"{{ user.name }}\"" /app/data/configs/homeserver.yaml else yq eval -i ".password_config.localdb_enabled=true" /app/data/configs/homeserver.yaml # just setting enabled to false is not enough. see https://github.com/matrix-org/matrix-synapse-ldap3/issues/123 @@ -100,9 +188,23 @@ if [[ -n "${CLOUDRON_TURN_SERVER:-}" ]]; then yq eval -i ".turn_shared_secret=\"${CLOUDRON_TURN_SECRET}\"" /app/data/configs/homeserver.yaml fi +mas_config + +# Configure the homeserver to delegate authentication to the MAS +# https://element-hq.github.io/matrix-authentication-service/setup/homeserver.html#configure-the-homeserver-to-delegate-authentication-to-the-service +yq eval -i ".experimental_features.msc3861.enabled=true" /app/data/configs/homeserver.yaml +yq eval -i ".experimental_features.msc3861.issuer=\"http://localhost:8080/\"" /app/data/configs/homeserver.yaml +yq eval -i ".experimental_features.msc3861.client_id=\"${mas_client_id}\"" /app/data/configs/homeserver.yaml +yq eval -i ".experimental_features.msc3861.client_auth_method=\"client_secret_basic\"" /app/data/configs/homeserver.yaml +# Matches the `client_secret` in the auth service config +yq eval -i ".experimental_features.msc3861.client_secret=\"${mas_client_secret}\"" /app/data/configs/homeserver.yaml +# Matches the `matrix.secret` in the auth service config +yq eval -i ".experimental_features.msc3861.admin_token=\"${matrix_secret}\"" /app/data/configs/homeserver.yaml + # fix permissions echo "==> Fixing permissions" chown -R cloudron:cloudron /app/data /run/synapse echo "==> Starting synapse" -exec gosu cloudron:cloudron python3 -m synapse.app.homeserver --config-path /app/data/configs/homeserver.yaml -n +#exec gosu cloudron:cloudron python3 -m synapse.app.homeserver --config-path /app/data/configs/homeserver.yaml -n +exec /usr/bin/supervisord --configuration /etc/supervisor/supervisord.conf --nodaemon -i Synapse diff --git a/supervisor/homeserver.conf b/supervisor/homeserver.conf new file mode 100644 index 0000000..5bfe67e --- /dev/null +++ b/supervisor/homeserver.conf @@ -0,0 +1,11 @@ +[program:homeserver] +priority=10 +user=cloudron +directory=/app/code +command=bash -c "source /app/code/env/bin/activate && python3 -m synapse.app.homeserver --config-path /app/data/configs/homeserver.yaml -n" +autostart=true +autorestart=true +stdout_logfile=/dev/stdout +stdout_logfile_maxbytes=0 +stderr_logfile=/dev/stderr +stderr_logfile_maxbytes=0 diff --git a/supervisor/mas.conf b/supervisor/mas.conf new file mode 100644 index 0000000..4730158 --- /dev/null +++ b/supervisor/mas.conf @@ -0,0 +1,11 @@ +[program:mas] +priority=12 +directory=/app/code/mas +user=cloudron +command=mas-cli -c /run/synapse/mas-config.yaml server +autostart=true +autorestart=true +stdout_logfile=/dev/stdout +stdout_logfile_maxbytes=0 +stderr_logfile=/dev/stderr +stderr_logfile_maxbytes=0