diff --git a/docs/SUMMARY.md b/docs/SUMMARY.md index ddc32ac2..cce7d387 100644 --- a/docs/SUMMARY.md +++ b/docs/SUMMARY.md @@ -12,7 +12,6 @@ - [Database setup](./setup/database.md) - [Homeserver configuration](./setup/homeserver.md) - [Configuring a reverse proxy](./setup/reverse-proxy.md) -- [Email configuration](./setup/email.md) - [Running the service](./setup/running.md) # Usage diff --git a/docs/setup/README.md b/docs/setup/README.md index 5baaa97c..2c243e1f 100644 --- a/docs/setup/README.md +++ b/docs/setup/README.md @@ -1,4 +1,53 @@ -# Setup introduction +# Planning the installation This part of the documentation goes through installing the service, the important parts of the configuration file, and how to run the service. + +Before going through the installation, it is important to understand the different components of an OIDC-native Matrix homeserver, and how they interact with each other. +It is meant to complement the homeserver, replacing the internal authentication mechanism with the authentication service. + +Making a homeserver deployment OIDC-native radically shifts the authentication model: the homeserver is no longer responsible for managing user accounts and sessions. +The authentication service becomes the source of truth for user accounts and access tokens, and the homeserver only verifies the validity of the tokens it receives through the service. + +At time of writing, the authentication service is meant to be run on a standalone domain name (e.g. `auth.example.com`), and the homeserver on another (e.g. `matrix.example.com`). +This domain will be user-facing as part of the authentication flow. + +When a client initiates an authentication flow, it will discover the authentication service through the deployment `.well-known/matrix/client` endpoint. +This file will refer to an `issuer`, which is the canonical name of the authentication service instance. +Out of that issuer, it will discover the rest of the endpoints by calling the `[issuer]/.well-known/openid-configuration` endpoint. +By default, the `issuer` will match the root domain where the service is deployed (e.g. `https://auth.example.com/`), but it can be configured to be different. + +An example setup could look like this: + + - The deployment domain is `example.com`, so Matrix IDs look like `@user:example.com` + - The issuer chosen is `https://example.com/` + - The homeserver is deployed on `matrix.example.com` + - The authentication service is deployed on `auth.example.com` + - Calling `https://example.com/.well-known/matrix/client` returns the following JSON: + + ```json + { + "m.homeserver": { + "base_url": "https://matrix.example.com" + }, + "org.matrix.msc2965.authentication": { + "issuer": "https://example.com/", + "account": "https://auth.example.com/account" + } + } + ``` + + - Calling `https://example.com/.well-known/openid-configuration` returns a JSON document similar to the following: + + ```json + { + "issuer": "https://example.com/", + "authorization_endpoint": "https://auth.example.com/authorize", + "token_endpoint": "https://auth.example.com/oauth2/token", + "jwks_uri": "https://auth.example.com/oauth2/keys.json", + "registration_endpoint": "https://auth.example.com/oauth2/registration", + "//": "..." + } + ``` + +With the installation planned, it is time to go through the installation and configuration process. The first section focuses on [installing the service](./installation.md). diff --git a/docs/setup/database.md b/docs/setup/database.md index f37103a2..5ccbd0e5 100644 --- a/docs/setup/database.md +++ b/docs/setup/database.md @@ -69,6 +69,6 @@ mas-cli server --migrate Once the database is up, the remaining steps are to: - [Set up the connection to the homeserver (recommended)](./homeserver.md) - - [Setup email sending (optional)](./email.md) + - [Setup email sending (optional)](../usage/configuration.md#email) - [Configure a reverse proxy (optional)](./reverse-proxy.md) - [Run the service](./running.md) \ No newline at end of file diff --git a/docs/setup/email.md b/docs/setup/email.md deleted file mode 100644 index 7cc4a419..00000000 --- a/docs/setup/email.md +++ /dev/null @@ -1,3 +0,0 @@ -# Email configuration - -TODO: explain how to configure the email backend in the `email` section of the configuration file. \ No newline at end of file diff --git a/docs/setup/reverse-proxy.md b/docs/setup/reverse-proxy.md index 69d5b4de..0ae52977 100644 --- a/docs/setup/reverse-proxy.md +++ b/docs/setup/reverse-proxy.md @@ -1,3 +1,115 @@ # Configuring a reverse proxy -TODO: explain how to run a reverse proxy in front of the service, and which C-S API endpoints need to be proxied to the service. \ No newline at end of file +Although the service can be exposed directly to the internet, including handling the TLS termination, many deployments will want to run a reverse proxy in front of the service. + +In those configuration, the service should be configured to listen on `localhost` or Unix domain socket. + +## Example configuration + +```yaml +http: + public_base: https://auth.example.com/ + listeners: + - name: web + resources: + - name: discovery + - name: human + - name: oauth + - name: compat + - name: graphql + # Uncomment to serve the assets by the service + #- name: assets + # path: ./share/assets/ + + binds: + # Bind on a local port + - host: localhost + port: 8080 + + # OR bind on a Unix domain socket + #- path: /var/run/mas.sock + + # Optional: use the PROXY protocol + #proxy_protocol: true +``` + +## Example nginx configuration + +Note that the assets can be served directly by nginx, and the `assets` resource can be removed from the service configuration. + +```nginx +server { + listen 443 ssl http2; + listen [::]:443 ssl http2; + server_name auth.example.com; + + ssl_certificate path/to/fullchain.pem; + ssl_certificate_key path/to/privkey.pem; + + location / { + proxy_pass http://localhost:8080; + # OR via the Unix domain socket + #proxy_pass http://unix:/var/run/mas.sock; + + proxy_http_version 1.1; + + # Optional: use the PROXY protocol + #proxy_protocol on; + } + + # Optional: serve the assets directly + location /assets/ { + root /path/to/share/assets/; + + # Serve pre-compressed assets + gzip_static on; + # With the ngx_brotli module installed + # https://github.com/google/ngx_brotli + #brotli_static on; + + # Cache assets for a year + expires 365d; + } +} +``` + +For the compatibility layer, the following endpoints need to be proxied to the service: + + - `/_matrix/client/*/login` + - `/_matrix/client/*/logout` + - `/_matrix/client/*/refresh` + +For example, a nginx configuration could look like: + +```nginx +server { + listen 443 ssl http2; + listen [::]:443 ssl http2; + + server_name matrix.example.com; + + # Forward to the auth service + location ~ ^/_matrix/client/(.*)/(login|logout|refresh) { + proxy_pass http://localhost:8080; + # OR via the Unix domain socket + #proxy_pass http://unix:/var/run/mas.sock; + + proxy_http_version 1.1; + + # Optional: use the PROXY protocol + #proxy_protocol on; + } + + # Forward to Synapse + # as per https://matrix-org.github.io/synapse/latest/reverse_proxy.html#nginx + location ~ ^(/_matrix|/_synapse/client) { + proxy_pass http://localhost:8008; + proxy_set_header X-Forwarded-For $remote_addr; + proxy_set_header X-Forwarded-Proto $scheme; + proxy_set_header Host $host; + + client_max_body_size 50M; + proxy_http_version 1.1; + } +} +``` \ No newline at end of file diff --git a/docs/setup/running.md b/docs/setup/running.md index 5d3bcaf5..d37c5f6a 100644 --- a/docs/setup/running.md +++ b/docs/setup/running.md @@ -21,4 +21,31 @@ Other than the binary, the service needs a few files to run: Be sure to check the [installation instructions](./installation.md) for more information on how to get these files, and make sure the configuration file is updated accordingly. -TODO: systemd service, docker, etc. \ No newline at end of file +## Configure the HTTP server + +The service can be configured to have multiple HTTP listeners, serving different resources. +See the [`http.listeners`](../usage/configuration.md#http) configuration section for more information. + +The service needs to be aware of the public URL it is served on, regardless of the HTTP listeners configuration. +This is done using the [`http.public_base`](../usage/configuration.md#http) configuration option. +By default, the OIDC issuer advertised by the `/.well-known/openid-configuration` endpoint will be the same as the `public_base` URL, but can be configured to be different. + +## Tweak the remaining configuration + +A few configuration sections might still require some tweaking, including: + + - [`telemetry`](../usage/configuration.md#telemetry): to setup metrics, tracing and Sentry crash reporting + - [`email`](../usage/configuration.md#email): to setup email sending + - [`password`](../usage/configuration.md#password): to enable/disable password authentication + - [`upstream_oauth`](../usage/configuration.md#upstream-oauth): to configure upstream OAuth providers + + +## Run the service + +Once the configuration is done, the service can be started with the [`mas-cli server`](../usage/cli/server.md) command: + +```sh +mas-cli server +``` + +It is advised to run the service as a non-root user, using a tool like [`systemd`](https://www.freedesktop.org/wiki/Software/systemd/) to manage the service lifecycle. \ No newline at end of file diff --git a/docs/usage/configuration.md b/docs/usage/configuration.md index 0c294b24..a90828b5 100644 --- a/docs/usage/configuration.md +++ b/docs/usage/configuration.md @@ -6,34 +6,81 @@ Controls the web server. ```yaml http: - listeners: - - name: web - resources: - - name: discovery - - name: human - - name: oauth - - name: compat - - name: graphql - playground: true - - name: assets - # Path from which to serve static files - path: ./frontend/dist/ - binds: - # On what address and port the server should listen to - - address: '[::]:8080' - proxy_protocol: false - - name: internal - resources: - - name: health - binds: - - host: localhost - port: 8081 - proxy_protocol: false # Public URL base used when building absolute public URLs - public_base: http://[::]:8080/ - issuer: http://[::]:8080/ + public_base: https://auth.example.com/ + + # OIDC issuer advertised by the service. Defaults to `public_base` + issuer: https://example.com/ + + # List of HTTP listeners, see below + listeners: + # ... ``` +### `http.listeners` + +Each listener can serve multiple resources, and listen on multiple TCP ports or UNIX sockets. + +```yaml +http: + listeners: + # The name of the listener, used in logs and metrics + - name: web + + # List of resources to serve + resources: + # Serves the .well-known/openid-configuration document + - name: discovery + # Serves the human-facing pages, such as the login page + - name: human + # Serves the OAuth 2.0/OIDC endpoints + - name: oauth + # Serves the Matrix C-S API compatibility endpoints + - name: compat + # Serve the GraphQL API used by the frontend, + # and optionally the GraphQL playground + - name: graphql + playground: true + # Serve the given folder on the /assets/ path + - name: assets + path: ./share/assets/ + + # List of addresses and ports to listen to + binds: + # First option: listen to the given address + - address: '[::]:8080' + + # Second option: listen on the given host and port combination + - host: localhost + port: 8081 + + # Third option: listen on the given UNIX socket + - socket: /tmp/mas.sock + + # Fourth option: grab an already open file descriptor given by the parent process + # This is useful when using systemd socket activation + - fd: 1 + # Kind of socket that was passed, defaults to tcp + kind: tcp # or unix + + # Whether to enable the PROXY protocol on the listener + proxy_protocol: false + + # If set, makes the listener use TLS with the provided certificate and key + tls: + #certificate: + certificate_file: /path/to/cert.pem + #key: + key_file: /path/to/key.pem + #password: + #password_file: /path/to/password.txt +``` + +The following additional resources are available, although it is recommended to serve them on a separate listener, not exposed to the public internet: + + - `name: prometheus`: serves the a Prometheus-compatible metrics endpoint on `/metrics`, if the Prometheus exporter is enabled in `telemetry.metrics.exporter`. + - `name: health`: serves the health check endpoint on `/health`. + ## `database` Configure how to connect to the PostgreSQL database. @@ -61,6 +108,23 @@ database: max_lifetime: 1800 ``` +## `matrix` + +Settings related to the connection to the Matrix homeserver + +```yaml +matrix: + # The homeserver name, as per the `server_name` in the Synapse configuration file + homeserver: example.com + + # Shared secret used to authenticate the service to the homeserver + # This must be of high entropy, because leaking this secret would allow anyone to perform admin actions on the homeserver + secret: "SomeRandomSecret" + + # URL to which the homeserver is accessible from the service + endpoint: "http://localhost:8008" +``` + ## `templates` Allows loading custom templates @@ -83,7 +147,7 @@ List of OAuth 2.0/OIDC clients and their keys/secrets. Each `client_id` must be clients: # Confidential client - client_id: 000000000000000000000FIRST - client_auth_method: clent_secret_post + client_auth_method: client_secret_post client_secret: secret # List of authorized redirect URIs redirect_uris: @@ -93,6 +157,8 @@ clients: client_auth_method: none ``` +**Note:** this list is not used at runtime, and any modification of this list must be synced to the database using the [`config sync`](../usage/cli/config.md#config-sync---prune---dry-run) command. + ## `secrets` Signing and encryption secrets @@ -100,6 +166,7 @@ Signing and encryption secrets ```yaml secrets: # Encryption secret (used for encrypting cookies and database fields) + # This must be a 32-byte long hex-encoded key encryption: c7e42fb8baba8f228b2e169fdf4c8216dffd5d33ad18bafd8b928c09ca46c718 # Signing keys @@ -107,43 +174,82 @@ secrets: # It needs at least an RSA key to work properly - kid: "ahM2bien" key: | - -----BEGIN PRIVATE KEY----- - MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQC7iinu0NXjWP5/ - /4OqyqOMI5uLJIHSrYIZLUlWMldtXmNy0c/pan+gxvZogiYx0cNydO/FogNbC4oD - yj7RIF+QcWJ8wcdG94/P+Xs3HFQzIZfwF+78RWQQJ7nQFekXJ1wQSXV4giw9b4XR - YkoVhHlyxyYGBFffO//DtYVto4uHvXVL0M27bV6l1K8VKspF72gb8Vt44V8OX5hT - sEsYW8SjOD1neEoVKiY6XP63cAG9FTB4a4sKkcUqwjrKEYKio/JLujmCl96eLN18 - cuqr6XuSDKvuVJtb+ZNLJi61vIOlD8cz3wu37hr3PCUZ+Ko9Ley+QfopJ3WYFxrI - IjQKb0W5AgMBAAECggEBAK87ZfsTfwczPHn1Ed4gAbkL/GaC8hscrJdBzWiRGUfE - DkBW82IydJaR0ePM2EtsqKblxLRxsZj8qzTnYNKe4SxiBZh0p/MTlnjJr+vKuJIe - LY3VjySA4gKGXASmtGlCCa/eM7kqSJQPBIakxHxej+xDULAGluSrd0wy7D2JtvJY - 5By+2apILUujBZZU/iUyB2AqK6IrzALo2gTV9Jhun9Wz0k3DXZBGd41v42BhZ+Rx - bHHgpuUTyDQOpKqJ5g1kN4qGlN/CeoontxcE5NOSgtipWeQEuelT8t4eZKHTXBS+ - Byd+uFe8oobWRY2crLptX8TZEENH7wX7y2YgNYUbeZECgYEA95YRhDuukcrDiNuF - fOXs+99XFORsKTbtZYwrouc7PI2CYb0I9ezoQMu3dzWIwOTUQHea5qCo/dYzbeED - fNzwPb2zaWaWFbkEWVTOwRewL+NfP+Ek2Nml+dVJm3d35qdIHYV+9gAcI87iCHxA - gqc13ZS4ba+5/vV6OYwNSAeW0TcCgYEAwem1F9zhVVOIReh3JaJLpZn0xuvZs5kN - TzvFXar33LKdulk5liC3L7ZrqspGESU0JC3pANR8PwuNteEEdHnkC5UTEqMf/fxG - j5CObJl+e2CiV2CNbe+3IQ1PKSxopD+Sq65ze7aP2moVZg94mbw4dsN+uY5QEql1 - Bmq0b7Wm2I8CgYBOqlDgefIKgqlEF7O/LnLwyFKr4bP4GGqvZC0NMnkg0TmHAoAR - W3ej9tZROyI7X7mMzjPaaVuoY2Gt3Nu11aFDjL2vlJfFSSb3lzmmInepj43ZBxkl - CWpyCfG8QuZG1AnWz266jOhj/DzXQ1tf5+72e2Vp/HaVaruuAzDJHRgvWwKBgAHy - aMEOlKyYpBufk+Kq2HuXKh/9KjhlZv7OqNKh7s8mc/L1BmD9fxlZiYczdLSjXPyo - AVjiyUSQxyF2WucYejOrkX90Z9PS/ppeZy+r8tsmQzsBWyopZ/tK+Op+6aYMhVp3 - 6+zoDlWxDvnxWdKhUyfOGq2eQiuNzAD+fUVJ25z9AoGARUJ4C87X7vH4QnsIE0g+ - ni0xSs8DgSq0v8+cJ7xwyN+NnC6eeU9N2U/m/5anLEM2GFjo4kghLDkC/2urc8dD - UtiisQjWz3O88QHqOvclEAmveuxfCYr/A/CWGdkH3YoK+AXIj3fkwb2Qd//rJ9zL - dT3XPCRatoVKLNKzUGNVcFM= - -----END PRIVATE KEY----- + -----BEGIN RSA PRIVATE KEY----- + MIIEowIBAAKCAQEAuf28zPUp574jDRdX6uN0d7niZCIUpACFo+Po/13FuIGsrpze + yMX6CYWVPalgXW9FCrhxL+4toJRy5npjkgsLFsknL5/zXbWKFgt69cMwsWJ9Ra57 + bonSlI7SoCuHhtw7j+sAlHAlqTOCAVz6P039Y/AGvO6xbC7f+9XftWlbbDcjKFcb + pQilkN9qtkdEH7TLayMAFOsgNvBlwF9+oj9w5PIk3veRTdBXI4GlHjhhzqGZKiRp + oP9HnycHHveyT+C33vuhQso5a3wcUNuvDVOixSqR4kvSt4UVWNK/KmEQmlWU1/m9 + ClIwrs8Q79q0xkGaSa0iuG60nvm7tZez9TFkxwIDAQABAoIBAHA5YkppQ7fJSm0D + wNDCHeyABNJWng23IuwZAOXVNxB1bjSOAv8yNgS4zaw/Hx5BnW8yi1lYZb+W0x2u + i5X7g91j0nkyEi5g88kJdFAGTsM5ok0BUwkHsEBjTUPIACanjGjya48lfBP0OGWK + LJU2Acbjda1aeUPFpPDXw/w6bieEthQwroq3DHCMnk6i9bsxgIOXeN04ij9XBmsH + KPCP2hAUnZSlx5febYfHK7/W95aJp22qa//eHS8cKQZCJ0+dQuZwLhlGosTFqLUm + qhPlt/b1EvPPY0cq5rtUc2W31L0YayVEHVOQx1fQIkH2VIUNbAS+bfVy+o6WCRk6 + s1XDhsECgYEA30tykVTN5LncY4eQIww2mW8v1j1EG6ngVShN3GuBTuXXaEOB8Duc + yT7yJt1ZhmaJwMk4agmZ1/f/ZXBtfLREGVzVvuwqRZ+LHbqIyhi0wQJA0aezPote + uTQnFn+IveHGtpQNDYGL/UgkexuCxbc2HOZG51JpunCK0TdtVfO/9OUCgYEA1TuS + 2WAXzNudRG3xd/4OgtkLD9AvfSvyjw2LkwqCMb3A5UEqw7vubk/xgnRvqrAgJRWo + jndgRrRnikHCavDHBO0GAO/kzrFRfw+e+r4jcLl0Yadke8ndCc7VTnx4wQCrMi5H + 7HEeRwaZONoj5PAPyA5X+N/gT0NNDA7KoQT45DsCgYBt+QWa6A5jaNpPNpPZfwlg + 9e60cAYcLcUri6cVOOk9h1tYoW7cdy+XueWfGIMf+1460Z90MfhP8ncZaY6yzUGA + 0EUBO+Tx10q3wIfgKNzU9hwgZZyU4CUtx668mOEqy4iHoVDwZu4gNyiobPsyDzKa + dxtSkDc8OHNV6RtzKpJOtQKBgFoRGcwbnLH5KYqX7eDDPRnj15pMU2LJx2DJVeU8 + ERY1kl7Dke6vWNzbg6WYzPoJ/unrJhFXNyFmXj213QsSvN3FyD1pFvp/R28mB/7d + hVa93vzImdb3wxe7d7n5NYBAag9+IP8sIJ/bl6i9619uTxwvgtUqqzKPuOGY9dnh + oce1AoGBAKZyZc/NVgqV2KgAnnYlcwNn7sRSkM8dcq0/gBMNuSZkfZSuEd4wwUzR + iFlYp23O2nHWggTkzimuBPtD7Kq4jBey3ZkyGye+sAdmnKkOjNILNbpIZlT6gK3z + fBaFmJGRJinKA+BJeH79WFpYN6SBZ/c3s5BusAbEU7kE5eInyazP + -----END RSA PRIVATE KEY----- - kid: "iv1aShae" key: | - -----BEGIN PRIVATE KEY----- - MIGEAgEAMBAGByqGSM49AgEGBSuBBAAKBG0wawIBAQQgaa7KvLdq72Nb7i7pGm/6 - SCW0RAKFcVwz7P9/8Wo2TTShRANCAATlTf0uyezm7riXjZdn1XND00uf4d1tc1jc - V4CiFiDQsDX+3znAGxqhTuoOkVn/G5lwgE1cgTX57r9cyYkso9UY - -----END PRIVATE KEY----- + -----BEGIN EC PRIVATE KEY----- + MHQCAQEEIE8yeUh111Npqu2e5wXxjC/GA5lbGe0j0KVXqZP12vqioAcGBSuBBAAK + oUQDQgAESKfUtKaLqCfhK+p3z870W59yOYvd+kjGWe+tK16SmWzZJbRCgdHakHE5 + MC6tJRnvedsYoKTrYoDv/XZIBI9zlA== + -----END EC PRIVATE KEY----- ``` +### `secrets.keys` + +The service can use a number of key types for signing. +The following key types are supported: + + - RSA + - ECDSA with the P-256 (`prime256v1`) curve + - ECDSA with the P-384 (`secp384r1`) curve + - ECDSA with the K-256 (`secp256k1`) curve + +Each entry must have a unique (and arbitrary) `kid`, plus the key itself. +The key can either be specified inline (with the `key` property), or loaded from a file (with the `key_file` property). +The following key formats are supported: + + - PKCS#1 PEM or DER-encoded RSA private key + - PKCS#8 PEM or DER-encoded RSA or ECDSA private key, encrypted or not + - SEC1 PEM or DER-encoded ECDSA private key + +For PKCS#8 encoded keys, the `password` or `password_file` properties can be used to decrypt the key. + + +## `passwords` + +Settings related to the local password database + +```yaml +passwords: + # Whether to enable the password database. + # If disabled, users will only be able to log in using upstream OIDC providers + enabled: true + + # List of password hashing schemes being used + # /!\ Only change this if you know what you're doing + # TODO: document this section better + schemes: + - version: 1 + algorithm: argon2id +``` + + ## `policy` Policy settings @@ -163,7 +269,7 @@ policy: allow_insecure_uris: true # Registration using passwords - passwords: + passwords: # minimum length of a password. default: ? min_length: 8 # require at least one lowercase character in a password. default: false @@ -173,3 +279,89 @@ policy: # require at least one number in a password. default: false require_number: true ``` + +## `telemetry` + +Settings related to metrics and traces + +```yaml +telemetry: + tracing: + # List of propagators to use for extracting and injecting trace contexts + propagators: + # Propagate according to the W3C Trace Context specification + - tracecontext + # Propagate according to the W3C Baggage specification + - baggage + # Propagate trace context with Jaeger compatible headers + - jaeger + # Propagate trace context with Zipkin compatible headers (single `b3` header variant) + - b3 + # Propagate trace context with Zipkin compatible headers (multiple `x-b3-*` headers variant) + - b3multi + + # The default: don't export traces + exporter: none + + # Export traces to an OTLP-compatible endpoint + #exporter: otlp + #endpoint: https://localhost:4317 + + # Export traces to a Jaeger endpoint + #exporter: jaeger + #protocol: http/thrift.binary | udp/thrift.compact + #endpoint: http://localhost:14268/api/traces # for http/thrift.binary + #username: username # for http/thrift.binary + #password: password # for http/thrift.binary + #agent_host: localhost # for udp/thrift.compact + #agent_port: 6831 # for udp/thrift.compact + + # Export traces to a Zipkin endpoint + #exporter: zipkin + #collector_endpoint: http://localhost:9411/api/v2/spans + + metrics: + # The default: don't export metrics + exporter: none + + # Export metrics to an OTLP-compatible endpoint + #exporter: otlp + #endpoint: https://localhost:4317 + + # Export metrics by exposing a Prometheus endpoint + # This requires mounting the `prometheus` resource to an HTTP listener + #exporter: prometheus + + sentry: + # DSN to use for sending errors and crashes to Sentry + dsn: https://public@host:port/1 +``` + +### `email` + +Settings related to sending emails + +```yaml +email: + from: '"The almighty auth service" ' + reply_to: '"No reply" ' + + # Default transport: don't send any emails + transport: blackhole + + # Send emails using SMTP + #transport: smtp + #mode: plain | tls | starttls + #hostname: localhost + #port: 587 + #username: username + #password: password + + # Send emails by calling a local sendmail binary + #transport: sendmail + #command: /usr/sbin/sendmail + + # Send emails through the AWS SESv2 API + # This uses the AWS SDK, so the usual AWS environment variables are supported + #transport: aws_ses +```