blob: eb0893511a87bd65dccdf550971881f007f711da [file] [log] [blame]
Piotr Dobrowolski8ec86572021-01-30 13:06:07 +01001# Matrix server (synapse).
2# This needs a secret provisioned, create with:
3# ns=matrix
4#
5# SIGNING_KEY="$(kubectl run -n $ns -i --quiet --restart=Never --rm synapse-generate-config --image=matrixdotorg/synapse:v1.19.2 --env SYNAPSE_SERVER_NAME=dummy --env SYNAPSE_REPORT_STATS=no -o yaml --command -- sh -c '/start.py generate >/dev/null && cat /data/*.signing.key')"
6# kubectl -n $ns create secret generic synapse --from-literal=postgres_password=$(pwgen 24 1) --from-literal=macaroon_secret_key=$(pwgen 32 1) --from-literal=registration_shared_secret=$(pwgen 32 1) --from-literal=homeserver_signing_key="$SIGNING_KEY" --from-literal=redis_password=$(pwgen 32 1) --from-literal=worker_replication_secret=$(pwgen 32 1)
7# kubectl -n $ns create secret generic oauth2-cas-proxy --from-literal=oauth2_secret=...
8#
9# When migrating from matrix.libsonnet, instance signing key, redis passwsord
10# and worker replication secret need to be added to existing synapse secret:
11#
12# echo "homeserver_signing_key: $(kubectl -n $ns exec deploy/synapse -- sh -c 'cat /data/*.signing.key' | base64 -w0)"
13# echo "redis_password: $(pwgen 32 1 | tr -d '\n' | base64 -w0)"
14# echo "worker_replication_secret: $(pwgen 32 1 | tr -d '\n' | base64 -w0)"
15# kubectl -n $ns edit secret synapse
16# # ...add homeserver_signing_key, redis_password and worker_replication_secret keys
17#
Piotr Dobrowolskib67ae482021-01-31 10:35:38 +010018# Additionally some resources need to be explicitly removed due to
19# label/annotations changes:
20# kubectl -n $ns delete deployment riot-web oauth2-cas-proxy wellknown synapse
21#
22# Some service configuration customization fields have been renamed:
23# .riotConfig → .riot.config
24# .synapseConfig → .synapse.config
25#
Piotr Dobrowolskifb119aa2021-01-31 19:07:32 +010026# When migrating from CAS to OpenID Connect authentication scheme following need
27# to be ensured:
28# * https://{homeserver}/_synapse/oidc/callback is added to allowed callback URLs list
29# * openid scope is enabled for configured client
30#
Piotr Dobrowolski122d5e52021-09-10 23:54:34 +020031# In order to deploy matrix-media-repo as a replacement for synapse built-in
32# media workers the following steps need to be carried out:
33#
34# 1. Generate password and bootstrap extra postgres user
35# pwgen 32 1 > secrets/plain/media-repo-$ns-postgres
36# echo "create database mediarepo; create user mediarepo with password '$(cat secrets/plain/media-repo-$ns-postgres)'; grant all privileges on database mediarepo to mediarepo;" | kubectl -n $ns exec -it deploy/waw3-postgres psql
37# secretstore sync secrets
38#
39# 2. Fetch Ceph RGW credentials
40# kubectl get secrets -n ceph-waw3 rook-ceph-object-user-waw-hdd-redundant-3-object-$ns -o json | jq '.data|map_values(@base64d)' > secrets/plain/media-repo-$ns-ceph.json
41# secretstore sync secrets
42#
43# 3. Create an apropriate bucket using s3cmd
44# s3cmd --access_key="$(jq -r '.AccessKey' secrets/plain/media-repo-$ns-ceph.json)" --secret_key="$(jq -r '.SecretKey' secrets/plain/media-repo-$ns-ceph.json)" --host=object.ceph-waw3.hswaw.net --host-bucket=object.ceph-waw3.hswaw.net mb s3://media-repo-$ns
45#
46# 4. Add relevant configuration overrides in cfg.mediaRepo key for your
47# deployment configuration file:
48#
49# mediaRepo+: {
50# enable: true,
51# route: false,
52# s3+: {
53# endpoint: std.strReplace((import "secrets/plain/media-repo-$ns-ceph.json").Endpoint, "http://", ""),
54# accessKey: (import "secrets/plain/media-repo-$ns-ceph.json").AccessKey,
55# secretKey: (import "secrets/plain/media-repo-$ns-ceph.json").SecretKey,
56# bucketName: "media-repo-$ns",
57# region: "eu",
58# },
59# db+: {
60# password: std.strReplace(importstr "secrets/plain/media-repo-$ns-postgres", "\n", ""),
61# },
62# },
63#
64# 5. Additionally, when migrating from already deployed synapse media worker the
65# following command needs to be run in order to import existing media files:
66# kubectl -n $ns exec deploy/media-repo -- import_synapse -baseUrl http://synapse-media:8008 -dbHost waw3-postgres -dbPassword "$(kubectl -n $ns get secret synapse -o json | jq -r '.data.postgres_password | @base64d')" -config /config/config.yaml -serverName 'SERVER_NAME'
67#
68# 6. After migrating data over from native synapse media worker storage traffic
69# can be rerouted to matrix-media-repo by switching cfg.mediaRepo.route flag
70# to true
71#
72# 7. Run import step #5 again to make sure no media were left missing in old
73# media worker deployment - import operation is indempotent and can be ran
74# against a synapse media worker that's not handling user traffic anymore.
75#
Piotr Dobrowolski8ec86572021-01-30 13:06:07 +010076# Sequencing appservices is fun. The appservice needs to run first (for
77# instance, via a bootstrap job), and on startup it will spit out a
78# registration file. This registration file then needs to be fed to synapse -
79# this is done via specialy named secrets (appservice-X-registration, for X key
80# in the appservices object).
81#
82# For appservice-irc instances, you can use this oneliner magic to get the
83# registration YAML from logs.
84# kubectl -n matrix create secret generic appservice-irc-freenode-registration --from-file=registration.yaml=<(kubectl -n matrix logs job/appservice-irc-freenode-bootstrap | tail -n +4 | sed -r 's/(.*aliases:.*)/ group_id: "+freenode:hackerspace.pl"\n\1/')
85#
86# For appservice-telegram instances, you can use this oneliner magic:
87# kubectl -n matrix create secret generic appservice-telegram-prod-registration --from-file=registration.yaml=<(kubectl -n matrix logs job/appservice-telegram-prod-bootstrap | grep -A 100 SNIPSNIP | grep -v SNIPSNIP)
88
89local kube = import "../../../kube/kube.libsonnet";
90local postgres = import "../../../kube/postgres.libsonnet";
91local redis = import "../../../kube/redis.libsonnet";
92
Piotr Dobrowolskib67ae482021-01-31 10:35:38 +010093local riot = import "./riot.libsonnet";
94local cas = import "./cas.libsonnet";
95local wellKnown = import "./wellknown.libsonnet";
96local synapse = import "./synapse.libsonnet";
Piotr Dobrowolski122d5e52021-09-10 23:54:34 +020097local mediaRepo = import "./media-repo.libsonnet";
Piotr Dobrowolski690ed452022-05-07 11:27:24 +020098local coturn = import "./coturn.libsonnet";
Piotr Dobrowolskib67ae482021-01-31 10:35:38 +010099
Piotr Dobrowolski8ec86572021-01-30 13:06:07 +0100100{
radexc995c212023-11-24 12:01:49 +0100101 local top = self,
102 local cfg = top.cfg,
Piotr Dobrowolski8ec86572021-01-30 13:06:07 +0100103 cfg:: {
104 namespace: error "cfg.namespace must be set",
105 # webDomain is the domain name at which element will run
106 webDomain: error "cfg.webDomain must be set",
107 # serverName is the server part of the MXID this homeserver will cover
108 serverName: error "cfg.serverName must be set",
109 storageClassName: "waw-hdd-redundant-3",
110
111 images: {
Piotr Dobrowolski926252c2023-03-28 23:58:20 +0200112 synapse: "matrixdotorg/synapse:v1.79.0",
Piotr Dobrowolski1e6ae552023-03-28 22:39:46 +0200113 riot: "vectorim/element-web:v1.11.26",
Piotr Dobrowolski8ec86572021-01-30 13:06:07 +0100114 casProxy: "registry.k0.hswaw.net/q3k/oauth2-cas-proxy:0.1.4",
Piotr Dobrowolskid3c14e52022-09-27 01:47:02 +0200115 appserviceIRC: "matrixdotorg/matrix-appservice-irc:release-0.35.1",
Serge Bazanski8eae4542021-05-19 15:42:13 +0000116 appserviceTelegram: "dock.mau.dev/tulir/mautrix-telegram@sha256:c6e25cb57e1b67027069e8dc2627338df35d156315c004a6f2b34b6aeaa79f77",
Piotr Dobrowolski8ec86572021-01-30 13:06:07 +0100117 wellKnown: "registry.k0.hswaw.net/q3k/wellknown:1611960794-adbf560851a46ad0e58b42f0daad7ef19535687c",
Piotr Dobrowolski654dd802022-09-13 20:48:13 +0200118 mediaRepo: "turt2live/matrix-media-repo:v1.2.12",
Piotr Dobrowolski690ed452022-05-07 11:27:24 +0200119 coturn: "coturn/coturn:4.5.2-r11-alpine",
Piotr Dobrowolski8ec86572021-01-30 13:06:07 +0100120 },
121
122 # OpenID Connect provider configuration.
123 # Currently only client_secret can be provided as a secretKeyRef.
124 #
125 # https://${cfg.webDomain}/_synapse/oidc/callback needs to be set as
126 # allowed OAuth2/OpenID Connect callback URL
127 #
128 # See: https://github.com/matrix-org/synapse/blob/v1.25.0/docs/openid.md
129 oidc: {
130 enable: false,
131 config: {
132 issuer: error "oidc.config.issuer must be set",
133 client_id: error "oidc.config.client_id must be set",
134 client_secret: error "oidc.config.client_secret must be set",
135
136 # Set this to true when migrating from existing CAS deployment
137 allow_existing_users: false,
138 user_mapping_provider: {
139 config: {
140 localpart_template: '{{ user.sub }}',
141 display_name_template: '{{ user.sub }}',
142 },
143 },
144
145 # Extra configuration required when migrating from
146 # oauth2-cas-proxy bound to https://sso.hackerspace.pl
147 # user_profile_method: "userinfo_endpoint",
148 # client_auth_method: "client_secret_post",
149 },
150 },
151
152 # Central Authentication Scheme, a single-sign-on system. Note: this flow is now called 'SSO' in Matrix, we keep this name for legacy reasons.
153 # Refer to https://matrix.org/docs/spec/client_server/r0.6.1#sso-client-login
154 cas: {
155 # whether to enable the CAS proxy (ie. connect to hswaw sso via OAuth)
156 enable: false,
157 # generate client ID and secret in with your OAuth2 provider, refer to https://www.oauth.com/oauth2-servers/client-registration/client-id-secret/
158 oauth2: {
159 clientID: error "cas.oauth2.clientID must be set",
160 clientSecret: error "cas.oauth2.clientSecret must be set",
161 scope: error "cas.oauth2.scope must be set",
162 authorizeURL: error "cas.oauth2.authorizeURL must be set",
163 tokenURL: error "cas.oauth2.tokenURL must be set",
164 userinfoURL: error "cas.oauth2.userinfoURL must be set",
165 },
166 },
167
168 # Serve /.well-known/matrix configuration endpoints required when using
169 # cfg.webDomain directly as mxid.
170 wellKnown: false,
Piotr Dobrowolski122d5e52021-09-10 23:54:34 +0200171
172 # matrix-media-repo S3-based media storage container
173 mediaRepo: {
174 enable: false,
175
176 # Route /_matrix/media/ endpoints to matrix-media-repo. Set this
177 # to true after migrating media files to matrix-media-repo.
178 route: false,
179
180 s3: {
181 endpoint: error "mediaRepo.s3.endpoint needs to be set",
182 accessKey: error "mediaRepo.s3.accessKey needs to be set",
183 secretKey: error "mediaRepo.s3.secretKey needs to be set",
184 bucketName: error "mediaRepo.s3.bucketName needs to be set",
185 region: error "mediaRepo.s3.region needs to be set",
186 },
187
188 db: {
189 username: "mediarepo",
190 password: error "mediaRepo.db.password needs to be set",
191 database: "mediarepo",
192 host: "waw3-postgres",
Piotr Dobrowolskiad3cb5c2023-03-28 22:39:08 +0200193 port: 5432,
Piotr Dobrowolski122d5e52021-09-10 23:54:34 +0200194 },
195 },
196
197 # List of administrative users MXIDs (used in matrix-media-repo only)
198 admins: [],
Piotr Dobrowolski690ed452022-05-07 11:27:24 +0200199
200 # Deploy coturn STUN/TURN server
201 coturn: {
202 enable: false,
203 config: {
204 domain: error "coturn.config.domain must be set",
205
206 # Default to public domain - this may be adjusted when multiple
207 # turn servers are deployed.
208 realm: self.domain,
209
210 # Set this to assigned LoadBalacer IP for correct NAT resolution
211 loadBalancerIP: null,
212
213 authSecret: { secretKeyRef: { name: "coturn", key: "auth_secret" } },
214 },
215 },
Serge Bazanskif2628682023-03-26 21:56:09 +0200216
217 postgres: {
218 # Deploy on-cluster postgres: a postgres instance backed by Ceph.
219 # Okay for tiny baby synapses, not okay for chonkers. If not
220 # enabled, bring your own external postgres.
221 enable: true,
222
223 # If not deploying on-cluster postgres, the following needs to be
224 # set.
225 host: error "host must be set with off-cluster postgres",
226 username: "synapse",
227 database: "synapse",
228 port: 5432,
229 # Default to the same password secret ref that is used for
230 # on-cluster postgres.
231 password: { secretKeyRef: { name: "synapse", key: "postgres_password" } },
232 },
Piotr Dobrowolski8ec86572021-01-30 13:06:07 +0100233 },
234
Piotr Dobrowolskifb119aa2021-01-31 19:07:32 +0100235 # DEPRECATED: this needs to be removed in favor of namespace.Contain() in
236 # modules that depend on this (appservices/instance defintions)
237 metadata(component):: {
238 namespace: cfg.namespace,
239 labels: {
240 "app.kubernetes.io/name": "matrix",
241 "app.kubernetes.io/managed-by": "kubecfg",
242 "app.kubernetes.io/component": component,
243 },
244 },
245
radex99ed6a72023-11-24 11:42:55 +0100246 local ns = kube.Namespace(cfg.namespace),
Piotr Dobrowolski8ec86572021-01-30 13:06:07 +0100247
radex0e128492023-11-24 12:47:27 +0100248 postgres3: if cfg.postgres.enable then ns.Contain(postgres) {
Serge Bazanskif2628682023-03-26 21:56:09 +0200249 local psql = self,
Piotr Dobrowolski8ec86572021-01-30 13:06:07 +0100250 cfg+: {
Piotr Dobrowolski8ec86572021-01-30 13:06:07 +0100251 appName: "synapse",
252 database: "synapse",
253 username: "synapse",
254 prefix: "waw3-",
255 password: { secretKeyRef: { name: "synapse", key: "postgres_password" } },
256 storageClassName: cfg.storageClassName,
257 storageSize: "100Gi",
258 initdbArgs: "--encoding='UTF8' --lc-collate='C' --lc-ctype='C'",
Piotr Dobrowolski529e1812021-02-13 19:44:37 +0100259
Serge Bazanskif2628682023-03-26 21:56:09 +0200260 # Doesn't influence postgres instance, but used by synapse
261 # libsonnet. Do not override.
262 port: psql.svc.port,
263 host: psql.svc.host,
264
Piotr Dobrowolski529e1812021-02-13 19:44:37 +0100265 opts: {
266 max_connections: "300",
267 shared_buffers: "80MB",
Bartosz Stebel45394bf2023-03-01 21:17:25 +0100268 wal_level: "logical",
Piotr Dobrowolski529e1812021-02-13 19:44:37 +0100269 },
Piotr Dobrowolski8ec86572021-01-30 13:06:07 +0100270 },
Serge Bazanskif2628682023-03-26 21:56:09 +0200271 } else {},
Piotr Dobrowolski8ec86572021-01-30 13:06:07 +0100272
radex0e128492023-11-24 12:47:27 +0100273 redis: ns.Contain(redis) {
Piotr Dobrowolski8ec86572021-01-30 13:06:07 +0100274 cfg+: {
Piotr Dobrowolski8ec86572021-01-30 13:06:07 +0100275 appName: "synapse",
276 storageClassName: cfg.storageClassName,
277 password: { secretKeyRef: { name: "synapse", key: "redis_password" } },
Piotr Dobrowolski529e1812021-02-13 19:44:37 +0100278 persistence: false,
Piotr Dobrowolski8ec86572021-01-30 13:06:07 +0100279 },
280 },
281
Piotr Dobrowolskib67ae482021-01-31 10:35:38 +0100282 riot: riot {
radex99ed6a72023-11-24 11:42:55 +0100283 ns: ns,
Piotr Dobrowolskib67ae482021-01-31 10:35:38 +0100284 cfg+: {
285 webDomain: cfg.webDomain,
286 serverName: cfg.serverName,
287 image: cfg.images.riot,
Piotr Dobrowolski8ec86572021-01-30 13:06:07 +0100288 },
289 },
290
Piotr Dobrowolski8ec86572021-01-30 13:06:07 +0100291 cas: if cfg.cas.enable && cfg.oidc.enable then error "cfg.cas.enable and cfg.oidc.enable options are exclusive"
Piotr Dobrowolskib67ae482021-01-31 10:35:38 +0100292 else if cfg.cas.enable then cas {
radex99ed6a72023-11-24 11:42:55 +0100293 ns: ns,
Piotr Dobrowolski8ec86572021-01-30 13:06:07 +0100294 cfg+: {
Piotr Dobrowolskib67ae482021-01-31 10:35:38 +0100295 image: cfg.images.casProxy,
296 webDomain: cfg.webDomain,
297 oauth2: cfg.cas.oauth2,
Piotr Dobrowolski8ec86572021-01-30 13:06:07 +0100298 },
299 },
300
Piotr Dobrowolskib67ae482021-01-31 10:35:38 +0100301 wellKnown: if cfg.wellKnown then wellKnown {
radex99ed6a72023-11-24 11:42:55 +0100302 ns: ns,
Piotr Dobrowolski8ec86572021-01-30 13:06:07 +0100303 cfg+: {
Piotr Dobrowolskib67ae482021-01-31 10:35:38 +0100304 image: cfg.images.wellKnown,
305 webDomain: cfg.webDomain,
Piotr Dobrowolski8ec86572021-01-30 13:06:07 +0100306 },
307 } else {},
308
Piotr Dobrowolski122d5e52021-09-10 23:54:34 +0200309 mediaRepo: if cfg.mediaRepo.enable then mediaRepo {
radex99ed6a72023-11-24 11:42:55 +0100310 ns: ns,
Piotr Dobrowolski122d5e52021-09-10 23:54:34 +0200311 cfg+: {
312 image: cfg.images.mediaRepo,
313
314 homeservers: [
315 {name: cfg.serverName, csApi: "https://" + cfg.webDomain}
316 ],
317 admins: cfg.admins,
318
319 s3: cfg.mediaRepo.s3,
320 db: cfg.mediaRepo.db,
321 },
322 } else {},
323
Piotr Dobrowolski690ed452022-05-07 11:27:24 +0200324 coturn: if cfg.coturn.enable then coturn {
radex99ed6a72023-11-24 11:42:55 +0100325 ns: ns,
Piotr Dobrowolski690ed452022-05-07 11:27:24 +0200326 cfg+: {
327 storageClassName: cfg.storageClassName,
328 image: cfg.images.coturn,
329 realm: cfg.coturn.config.realm,
330 loadBalancerIP: cfg.coturn.config.loadBalancerIP,
331 authSecret: cfg.coturn.config.authSecret,
332 },
333 } else null,
334
Piotr Dobrowolskib67ae482021-01-31 10:35:38 +0100335 synapse: synapse {
radex99ed6a72023-11-24 11:42:55 +0100336 ns: ns,
radexc995c212023-11-24 12:01:49 +0100337 postgres: if cfg.postgres.enable then top.postgres3 else {
Serge Bazanskif2628682023-03-26 21:56:09 +0200338 # If not using on-cluster postgres, pass the config postgres object
339 # as the postgres object into the synapse lib. It's a bit ugly (we
340 # should have some common 'config' type instead) but it's good
341 # enough.
342 cfg: cfg.postgres,
radexc995c212023-11-24 12:01:49 +0100343 }, redis: top.redis,
344 appservices: top.appservices,
345 cfg+: top.cfg {
346 image: top.cfg.images.synapse,
Piotr Dobrowolskib67ae482021-01-31 10:35:38 +0100347
348 macaroonSecretKey: { secretKeyRef: { name: "synapse", key: "macaroon_secret_key" } },
349 registrationSharedSecret: { secretKeyRef: { name: "synapse", key: "registration_shared_secret" } },
350 workerReplicationSecret: { secretKeyRef: { name: "synapse", key: "worker_replication_secret" } },
351 },
352 },
353
Piotr Dobrowolski8ec86572021-01-30 13:06:07 +0100354 // Any appservice you add here will require an appservice-X-registration
355 // secret containing a registration.yaml file. Adding something to this
356 // dictionary will cause Synapse to not start until that secret is
357 // available - so change things carefully!
358 // If bootstrapping a new appservice, just keep it out of this dictionary
359 // until it spits you a registration YAML and you feed that to a secret.
360 appservices: {},
361
radexc995c212023-11-24 12:01:49 +0100362 ingress: top.namespace.Contain(kube.Ingress("matrix")) {
Piotr Dobrowolskib67ae482021-01-31 10:35:38 +0100363 metadata+: {
Piotr Dobrowolski8ec86572021-01-30 13:06:07 +0100364 annotations+: {
365 "kubernetes.io/tls-acme": "true",
Piotr Dobrowolski7e841062023-04-23 11:36:15 +0200366 "cert-manager.io/cluster-issuer": "letsencrypt-prod",
Piotr Dobrowolski8ec86572021-01-30 13:06:07 +0100367 "nginx.ingress.kubernetes.io/proxy-body-size": "0",
368 "nginx.ingress.kubernetes.io/use-regex": "true",
369 },
370 },
371 spec+: {
372 tls: [
373 {
374 hosts: [cfg.webDomain],
375 secretName: "synapse-tls",
376 },
377 ],
378 rules: [
379 {
380 host: cfg.webDomain,
381 http: {
382 paths: [
radexc995c212023-11-24 12:01:49 +0100383 { path: path, backend: top.synapse.genericWorker.svc.name_port }
384 for path in top.synapse.genericWorker.paths
Piotr Dobrowolski8ec86572021-01-30 13:06:07 +0100385 ] + [
radexc995c212023-11-24 12:01:49 +0100386 { path: "/", backend: top.riot.svc.name_port },
387 { path: "/_matrix/media/", backend: if cfg.mediaRepo.route then top.mediaRepo.svc.name_port else top.synapse.mediaWorker.svc.name_port },
388 { path: "/_matrix/", backend: top.synapse.main.svc.name_port },
Piotr Dobrowolski8ec86572021-01-30 13:06:07 +0100389
390 # Used by OpenID Connect login flow
radexc995c212023-11-24 12:01:49 +0100391 { path: "/_synapse/", backend: top.synapse.main.svc.name_port },
Piotr Dobrowolski8ec86572021-01-30 13:06:07 +0100392 ] + (if cfg.cas.enable then [
radexc995c212023-11-24 12:01:49 +0100393 { path: "/_cas", backend: top.cas.svc.name_port },
Piotr Dobrowolski8ec86572021-01-30 13:06:07 +0100394 ] else []) + (if cfg.wellKnown then [
radexc995c212023-11-24 12:01:49 +0100395 { path: "/.well-known/matrix", backend: top.wellKnown.svc.name_port },
Piotr Dobrowolski8ec86572021-01-30 13:06:07 +0100396 ] else [])
397 },
398 }
399 ],
400 },
401 },
402}