Skip to content
This repository was archived by the owner on Apr 26, 2024. It is now read-only.

Commit 6d97843

Browse files
Config templating (#5900)
Template config files * Imagine a system composed entirely of x, y, z etc and the basic operations.. Wait George, why XOR? Why not just neq? George: Eh, I didn't think of that.. Co-Authored-By: Erik Johnston <erik@matrix.org>
1 parent 7dc3985 commit 6d97843

File tree

9 files changed

+366
-46
lines changed

9 files changed

+366
-46
lines changed

changelog.d/5900.feature

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Add support for config templating.

docs/sample_config.yaml

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -205,9 +205,9 @@ listeners:
205205
#
206206
- port: 8008
207207
tls: false
208-
bind_addresses: ['::1', '127.0.0.1']
209208
type: http
210209
x_forwarded: true
210+
bind_addresses: ['::1', '127.0.0.1']
211211

212212
resources:
213213
- names: [client, federation]
@@ -392,10 +392,10 @@ listeners:
392392
# permission to listen on port 80.
393393
#
394394
acme:
395-
# ACME support is disabled by default. Uncomment the following line
396-
# (and tls_certificate_path and tls_private_key_path above) to enable it.
395+
# ACME support is disabled by default. Set this to `true` and uncomment
396+
# tls_certificate_path and tls_private_key_path above to enable it.
397397
#
398-
#enabled: true
398+
enabled: False
399399

400400
# Endpoint to use to request certificates. If you only want to test,
401401
# use Let's Encrypt's staging url:
@@ -406,17 +406,17 @@ acme:
406406
# Port number to listen on for the HTTP-01 challenge. Change this if
407407
# you are forwarding connections through Apache/Nginx/etc.
408408
#
409-
#port: 80
409+
port: 80
410410

411411
# Local addresses to listen on for incoming connections.
412412
# Again, you may want to change this if you are forwarding connections
413413
# through Apache/Nginx/etc.
414414
#
415-
#bind_addresses: ['::', '0.0.0.0']
415+
bind_addresses: ['::', '0.0.0.0']
416416

417417
# How many days remaining on a certificate before it is renewed.
418418
#
419-
#reprovision_threshold: 30
419+
reprovision_threshold: 30
420420

421421
# The domain that the certificate should be for. Normally this
422422
# should be the same as your Matrix domain (i.e., 'server_name'), but,
@@ -430,7 +430,7 @@ acme:
430430
#
431431
# If not set, defaults to your 'server_name'.
432432
#
433-
#domain: matrix.example.com
433+
domain: matrix.example.com
434434

435435
# file to use for the account key. This will be generated if it doesn't
436436
# exist.

synapse/config/_base.py

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -181,6 +181,11 @@ def generate_config(
181181
generate_secrets=False,
182182
report_stats=None,
183183
open_private_ports=False,
184+
listeners=None,
185+
database_conf=None,
186+
tls_certificate_path=None,
187+
tls_private_key_path=None,
188+
acme_domain=None,
184189
):
185190
"""Build a default configuration file
186191
@@ -207,6 +212,33 @@ def generate_config(
207212
open_private_ports (bool): True to leave private ports (such as the non-TLS
208213
HTTP listener) open to the internet.
209214
215+
listeners (list(dict)|None): A list of descriptions of the listeners
216+
synapse should start with each of which specifies a port (str), a list of
217+
resources (list(str)), tls (bool) and type (str). For example:
218+
[{
219+
"port": 8448,
220+
"resources": [{"names": ["federation"]}],
221+
"tls": True,
222+
"type": "http",
223+
},
224+
{
225+
"port": 443,
226+
"resources": [{"names": ["client"]}],
227+
"tls": False,
228+
"type": "http",
229+
}],
230+
231+
232+
database (str|None): The database type to configure, either `psycog2`
233+
or `sqlite3`.
234+
235+
tls_certificate_path (str|None): The path to the tls certificate.
236+
237+
tls_private_key_path (str|None): The path to the tls private key.
238+
239+
acme_domain (str|None): The domain acme will try to validate. If
240+
specified acme will be enabled.
241+
210242
Returns:
211243
str: the yaml config file
212244
"""
@@ -220,6 +252,11 @@ def generate_config(
220252
generate_secrets=generate_secrets,
221253
report_stats=report_stats,
222254
open_private_ports=open_private_ports,
255+
listeners=listeners,
256+
database_conf=database_conf,
257+
tls_certificate_path=tls_certificate_path,
258+
tls_private_key_path=tls_private_key_path,
259+
acme_domain=acme_domain,
223260
)
224261
)
225262

synapse/config/database.py

Lines changed: 19 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,9 @@
1313
# See the License for the specific language governing permissions and
1414
# limitations under the License.
1515
import os
16+
from textwrap import indent
17+
18+
import yaml
1619

1720
from ._base import Config
1821

@@ -38,20 +41,28 @@ def read_config(self, config, **kwargs):
3841

3942
self.set_databasepath(config.get("database_path"))
4043

41-
def generate_config_section(self, data_dir_path, **kwargs):
42-
database_path = os.path.join(data_dir_path, "homeserver.db")
43-
return (
44-
"""\
45-
## Database ##
46-
47-
database:
48-
# The database engine name
44+
def generate_config_section(self, data_dir_path, database_conf, **kwargs):
45+
if not database_conf:
46+
database_path = os.path.join(data_dir_path, "homeserver.db")
47+
database_conf = (
48+
"""# The database engine name
4949
name: "sqlite3"
5050
# Arguments to pass to the engine
5151
args:
5252
# Path to the database
5353
database: "%(database_path)s"
54+
"""
55+
% locals()
56+
)
57+
else:
58+
database_conf = indent(yaml.dump(database_conf), " " * 10).lstrip()
59+
60+
return (
61+
"""\
62+
## Database ##
5463
64+
database:
65+
%(database_conf)s
5566
# Number of events to cache in memory.
5667
#
5768
#event_cache_size: 10K

synapse/config/server.py

Lines changed: 67 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -17,8 +17,11 @@
1717

1818
import logging
1919
import os.path
20+
import re
21+
from textwrap import indent
2022

2123
import attr
24+
import yaml
2225
from netaddr import IPSet
2326

2427
from synapse.api.room_versions import KNOWN_ROOM_VERSIONS
@@ -352,7 +355,7 @@ def has_tls_listener(self):
352355
return any(l["tls"] for l in self.listeners)
353356

354357
def generate_config_section(
355-
self, server_name, data_dir_path, open_private_ports, **kwargs
358+
self, server_name, data_dir_path, open_private_ports, listeners, **kwargs
356359
):
357360
_, bind_port = parse_and_validate_server_name(server_name)
358361
if bind_port is not None:
@@ -366,11 +369,68 @@ def generate_config_section(
366369
# Bring DEFAULT_ROOM_VERSION into the local-scope for use in the
367370
# default config string
368371
default_room_version = DEFAULT_ROOM_VERSION
372+
secure_listeners = []
373+
unsecure_listeners = []
374+
private_addresses = ["::1", "127.0.0.1"]
375+
if listeners:
376+
for listener in listeners:
377+
if listener["tls"]:
378+
secure_listeners.append(listener)
379+
else:
380+
# If we don't want open ports we need to bind the listeners
381+
# to some address other than 0.0.0.0. Here we chose to use
382+
# localhost.
383+
# If the addresses are already bound we won't overwrite them
384+
# however.
385+
if not open_private_ports:
386+
listener.setdefault("bind_addresses", private_addresses)
387+
388+
unsecure_listeners.append(listener)
389+
390+
secure_http_bindings = indent(
391+
yaml.dump(secure_listeners), " " * 10
392+
).lstrip()
393+
394+
unsecure_http_bindings = indent(
395+
yaml.dump(unsecure_listeners), " " * 10
396+
).lstrip()
397+
398+
if not unsecure_listeners:
399+
unsecure_http_bindings = (
400+
"""- port: %(unsecure_port)s
401+
tls: false
402+
type: http
403+
x_forwarded: true"""
404+
% locals()
405+
)
406+
407+
if not open_private_ports:
408+
unsecure_http_bindings += (
409+
"\n bind_addresses: ['::1', '127.0.0.1']"
410+
)
411+
412+
unsecure_http_bindings += """
413+
414+
resources:
415+
- names: [client, federation]
416+
compress: false"""
417+
418+
if listeners:
419+
# comment out this block
420+
unsecure_http_bindings = "#" + re.sub(
421+
"\n {10}",
422+
lambda match: match.group(0) + "#",
423+
unsecure_http_bindings,
424+
)
369425

370-
unsecure_http_binding = "port: %i\n tls: false" % (unsecure_port,)
371-
if not open_private_ports:
372-
unsecure_http_binding += (
373-
"\n bind_addresses: ['::1', '127.0.0.1']"
426+
if not secure_listeners:
427+
secure_http_bindings = (
428+
"""#- port: %(bind_port)s
429+
# type: http
430+
# tls: true
431+
# resources:
432+
# - names: [client, federation]"""
433+
% locals()
374434
)
375435

376436
return (
@@ -556,25 +616,15 @@ def generate_config_section(
556616
# will also need to give Synapse a TLS key and certificate: see the TLS section
557617
# below.)
558618
#
559-
#- port: %(bind_port)s
560-
# type: http
561-
# tls: true
562-
# resources:
563-
# - names: [client, federation]
619+
%(secure_http_bindings)s
564620
565621
# Unsecure HTTP listener: for when matrix traffic passes through a reverse proxy
566622
# that unwraps TLS.
567623
#
568624
# If you plan to use a reverse proxy, please see
569625
# https://github.com/matrix-org/synapse/blob/master/docs/reverse_proxy.rst.
570626
#
571-
- %(unsecure_http_binding)s
572-
type: http
573-
x_forwarded: true
574-
575-
resources:
576-
- names: [client, federation]
577-
compress: false
627+
%(unsecure_http_bindings)s
578628
579629
# example additional_resources:
580630
#

synapse/config/tls.py

Lines changed: 38 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -239,12 +239,38 @@ def read_certificate_from_disk(self, require_cert_and_key):
239239
self.tls_fingerprints.append({"sha256": sha256_fingerprint})
240240

241241
def generate_config_section(
242-
self, config_dir_path, server_name, data_dir_path, **kwargs
242+
self,
243+
config_dir_path,
244+
server_name,
245+
data_dir_path,
246+
tls_certificate_path,
247+
tls_private_key_path,
248+
acme_domain,
249+
**kwargs
243250
):
251+
"""If the acme_domain is specified acme will be enabled.
252+
If the TLS paths are not specified the default will be certs in the
253+
config directory"""
254+
244255
base_key_name = os.path.join(config_dir_path, server_name)
245256

246-
tls_certificate_path = base_key_name + ".tls.crt"
247-
tls_private_key_path = base_key_name + ".tls.key"
257+
if bool(tls_certificate_path) != bool(tls_private_key_path):
258+
raise ConfigError(
259+
"Please specify both a cert path and a key path or neither."
260+
)
261+
262+
tls_enabled = (
263+
"" if tls_certificate_path and tls_private_key_path or acme_domain else "#"
264+
)
265+
266+
if not tls_certificate_path:
267+
tls_certificate_path = base_key_name + ".tls.crt"
268+
if not tls_private_key_path:
269+
tls_private_key_path = base_key_name + ".tls.key"
270+
271+
acme_enabled = bool(acme_domain)
272+
acme_domain = "matrix.example.com"
273+
248274
default_acme_account_file = os.path.join(data_dir_path, "acme_account.key")
249275

250276
# this is to avoid the max line length. Sorrynotsorry
@@ -269,11 +295,11 @@ def generate_config_section(
269295
# instance, if using certbot, use `fullchain.pem` as your certificate,
270296
# not `cert.pem`).
271297
#
272-
#tls_certificate_path: "%(tls_certificate_path)s"
298+
%(tls_enabled)stls_certificate_path: "%(tls_certificate_path)s"
273299
274300
# PEM-encoded private key for TLS
275301
#
276-
#tls_private_key_path: "%(tls_private_key_path)s"
302+
%(tls_enabled)stls_private_key_path: "%(tls_private_key_path)s"
277303
278304
# Whether to verify TLS server certificates for outbound federation requests.
279305
#
@@ -340,10 +366,10 @@ def generate_config_section(
340366
# permission to listen on port 80.
341367
#
342368
acme:
343-
# ACME support is disabled by default. Uncomment the following line
344-
# (and tls_certificate_path and tls_private_key_path above) to enable it.
369+
# ACME support is disabled by default. Set this to `true` and uncomment
370+
# tls_certificate_path and tls_private_key_path above to enable it.
345371
#
346-
#enabled: true
372+
enabled: %(acme_enabled)s
347373
348374
# Endpoint to use to request certificates. If you only want to test,
349375
# use Let's Encrypt's staging url:
@@ -354,17 +380,17 @@ def generate_config_section(
354380
# Port number to listen on for the HTTP-01 challenge. Change this if
355381
# you are forwarding connections through Apache/Nginx/etc.
356382
#
357-
#port: 80
383+
port: 80
358384
359385
# Local addresses to listen on for incoming connections.
360386
# Again, you may want to change this if you are forwarding connections
361387
# through Apache/Nginx/etc.
362388
#
363-
#bind_addresses: ['::', '0.0.0.0']
389+
bind_addresses: ['::', '0.0.0.0']
364390
365391
# How many days remaining on a certificate before it is renewed.
366392
#
367-
#reprovision_threshold: 30
393+
reprovision_threshold: 30
368394
369395
# The domain that the certificate should be for. Normally this
370396
# should be the same as your Matrix domain (i.e., 'server_name'), but,
@@ -378,7 +404,7 @@ def generate_config_section(
378404
#
379405
# If not set, defaults to your 'server_name'.
380406
#
381-
#domain: matrix.example.com
407+
domain: %(acme_domain)s
382408
383409
# file to use for the account key. This will be generated if it doesn't
384410
# exist.

0 commit comments

Comments
 (0)