Support `registration_shared_secret` in a file (#13614)

A new `registration_shared_secret_path` option. This is kinda handy for k8s deployments and things.
pull/13638/head
Richard van der Hoff 2022-08-25 17:27:46 +01:00 committed by GitHub
parent a2ce614447
commit d092e6f32a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 92 additions and 5 deletions

View File

@ -0,0 +1 @@
Support setting the registration shared secret in a file, via a new `registration_shared_secret_path` configuration option.

View File

@ -2124,10 +2124,28 @@ registration_requires_token: true
If set, allows registration of standard or admin accounts by anyone who If set, allows registration of standard or admin accounts by anyone who
has the shared secret, even if registration is otherwise disabled. has the shared secret, even if registration is otherwise disabled.
See also [`registration_shared_secret_path`](#registration_shared_secret_path).
Example configuration: Example configuration:
```yaml ```yaml
registration_shared_secret: <PRIVATE STRING> registration_shared_secret: <PRIVATE STRING>
``` ```
---
### `registration_shared_secret_path`
An alternative to [`registration_shared_secret`](#registration_shared_secret):
allows the shared secret to be specified in an external file.
The file should be a plain text file, containing only the shared secret.
Example configuration:
```yaml
registration_shared_secret_file: /path/to/secrets/file
```
_Added in Synapse 1.67.0._
--- ---
### `bcrypt_rounds` ### `bcrypt_rounds`

View File

@ -1,6 +1,6 @@
# Copyright 2015, 2016 OpenMarket Ltd # Copyright 2015, 2016 OpenMarket Ltd
# Copyright 2018 New Vector # Copyright 2018 New Vector
# Copyright 2021 The Matrix.org Foundation C.I.C. # Copyright 2021-22 The Matrix.org Foundation C.I.C.
# #
# Licensed under the Apache License, Version 2.0 (the "License"); # Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License. # you may not use this file except in compliance with the License.
@ -25,6 +25,15 @@ from typing import Any, Callable, Dict, Optional
import requests import requests
import yaml import yaml
_CONFLICTING_SHARED_SECRET_OPTS_ERROR = """\
Conflicting options 'registration_shared_secret' and 'registration_shared_secret_path'
are both defined in config file.
"""
_NO_SHARED_SECRET_OPTS_ERROR = """\
No 'registration_shared_secret' or 'registration_shared_secret_path' defined in config.
"""
_DEFAULT_SERVER_URL = "http://localhost:8008" _DEFAULT_SERVER_URL = "http://localhost:8008"
@ -222,9 +231,15 @@ def main() -> None:
# argparse should check that we have either config or shared secret # argparse should check that we have either config or shared secret
assert config assert config
secret = config.get("registration_shared_secret", None) secret = config.get("registration_shared_secret")
secret_file = config.get("registration_shared_secret_path")
if secret_file:
if secret:
print(_CONFLICTING_SHARED_SECRET_OPTS_ERROR, file=sys.stderr)
sys.exit(1)
secret = _read_file(secret_file, "registration_shared_secret_path").strip()
if not secret: if not secret:
print("No 'registration_shared_secret' defined in config.") print(_NO_SHARED_SECRET_OPTS_ERROR, file=sys.stderr)
sys.exit(1) sys.exit(1)
if args.server_url: if args.server_url:
@ -254,6 +269,30 @@ def main() -> None:
) )
def _read_file(file_path: Any, config_path: str) -> str:
"""Check the given file exists, and read it into a string
If it does not, exit with an error indicating the problem
Args:
file_path: the file to be read
config_path: where in the configuration file_path came from, so that a useful
error can be emitted if it does not exist.
Returns:
content of the file.
"""
if not isinstance(file_path, str):
print(f"{config_path} setting is not a string", file=sys.stderr)
sys.exit(1)
try:
with open(file_path) as file_stream:
return file_stream.read()
except OSError as e:
print(f"Error accessing file {file_path}: {e}", file=sys.stderr)
sys.exit(1)
def _find_client_listener(config: Dict[str, Any]) -> Optional[str]: def _find_client_listener(config: Dict[str, Any]) -> Optional[str]:
# try to find a listener in the config. Returns a host:port pair # try to find a listener in the config. Returns a host:port pair
for listener in config.get("listeners", []): for listener in config.get("listeners", []):

View File

@ -13,10 +13,10 @@
# See the License for the specific language governing permissions and # See the License for the specific language governing permissions and
# limitations under the License. # limitations under the License.
import argparse import argparse
from typing import Any, Optional from typing import Any, Dict, Optional
from synapse.api.constants import RoomCreationPreset from synapse.api.constants import RoomCreationPreset
from synapse.config._base import Config, ConfigError from synapse.config._base import Config, ConfigError, read_file
from synapse.types import JsonDict, RoomAlias, UserID from synapse.types import JsonDict, RoomAlias, UserID
from synapse.util.stringutils import random_string_with_symbols, strtobool from synapse.util.stringutils import random_string_with_symbols, strtobool
@ -27,6 +27,11 @@ password resets, configure Synapse with an SMTP server via the `email` setting,
remove `account_threepid_delegates.email`. remove `account_threepid_delegates.email`.
""" """
CONFLICTING_SHARED_SECRET_OPTS_ERROR = """\
You have configured both `registration_shared_secret` and
`registration_shared_secret_path`. These are mutually incompatible.
"""
class RegistrationConfig(Config): class RegistrationConfig(Config):
section = "registration" section = "registration"
@ -53,7 +58,16 @@ class RegistrationConfig(Config):
self.enable_registration_token_3pid_bypass = config.get( self.enable_registration_token_3pid_bypass = config.get(
"enable_registration_token_3pid_bypass", False "enable_registration_token_3pid_bypass", False
) )
# read the shared secret, either inline or from an external file
self.registration_shared_secret = config.get("registration_shared_secret") self.registration_shared_secret = config.get("registration_shared_secret")
registration_shared_secret_path = config.get("registration_shared_secret_path")
if registration_shared_secret_path:
if self.registration_shared_secret:
raise ConfigError(CONFLICTING_SHARED_SECRET_OPTS_ERROR)
self.registration_shared_secret = read_file(
registration_shared_secret_path, ("registration_shared_secret_path",)
).strip()
self.bcrypt_rounds = config.get("bcrypt_rounds", 12) self.bcrypt_rounds = config.get("bcrypt_rounds", 12)
@ -218,6 +232,21 @@ class RegistrationConfig(Config):
else: else:
return "" return ""
def generate_files(self, config: Dict[str, Any], config_dir_path: str) -> None:
# if 'registration_shared_secret_path' is specified, and the target file
# does not exist, generate it.
registration_shared_secret_path = config.get("registration_shared_secret_path")
if registration_shared_secret_path and not self.path_exists(
registration_shared_secret_path
):
print(
"Generating registration shared secret file "
+ registration_shared_secret_path
)
secret = random_string_with_symbols(50)
with open(registration_shared_secret_path, "w") as f:
f.write(f"{secret}\n")
@staticmethod @staticmethod
def add_arguments(parser: argparse.ArgumentParser) -> None: def add_arguments(parser: argparse.ArgumentParser) -> None:
reg_group = parser.add_argument_group("registration") reg_group = parser.add_argument_group("registration")