Skip to content
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.

Commit 57bf449

Browse files
authoredDec 17, 2024··
Add macaroon_secret_key_path config option (#17983)
Another config option on my quest to a `*_path` variant for every secret. This time it’s `macaroon_secret_key_path`. Reading secrets from files has the security advantage of separating the secrets from the config. It also simplifies secrets management in Kubernetes. Also useful to NixOS users.
1 parent 3d60a58 commit 57bf449

File tree

5 files changed

+51
-15
lines changed

5 files changed

+51
-15
lines changed
 

‎changelog.d/17983.feature

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Add `macaroon_secret_key_path` config option.

‎docs/usage/configuration/config_documentation.md

+16
Original file line numberDiff line numberDiff line change
@@ -3091,6 +3091,22 @@ Example configuration:
30913091
```yaml
30923092
macaroon_secret_key: <PRIVATE STRING>
30933093
```
3094+
---
3095+
### `macaroon_secret_key_path`
3096+
3097+
An alternative to [`macaroon_secret_key`](#macaroon_secret_key):
3098+
allows the secret key to be specified in an external file.
3099+
3100+
The file should be a plain text file, containing only the secret key.
3101+
Synapse reads the secret key from the given file once at startup.
3102+
3103+
Example configuration:
3104+
```yaml
3105+
macaroon_secret_key_path: /path/to/secrets/file
3106+
```
3107+
3108+
_Added in Synapse 1.121.0._
3109+
30943110
---
30953111
### `form_secret`
30963112

‎synapse/config/key.py

+16-5
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@
4343
from synapse.types import JsonDict
4444
from synapse.util.stringutils import random_string, random_string_with_symbols
4545

46-
from ._base import Config, ConfigError
46+
from ._base import Config, ConfigError, read_file
4747

4848
if TYPE_CHECKING:
4949
from signedjson.key import VerifyKeyWithExpiry
@@ -91,6 +91,11 @@
9191
'suppress_key_server_warning' to 'true' in homeserver.yaml.
9292
--------------------------------------------------------------------------------"""
9393

94+
CONFLICTING_MACAROON_SECRET_KEY_OPTS_ERROR = """\
95+
Conflicting options 'macaroon_secret_key' and 'macaroon_secret_key_path' are
96+
both defined in config file.
97+
"""
98+
9499
logger = logging.getLogger(__name__)
95100

96101

@@ -166,10 +171,16 @@ def read_config(
166171
)
167172
)
168173

169-
macaroon_secret_key: Optional[str] = config.get(
170-
"macaroon_secret_key", self.root.registration.registration_shared_secret
171-
)
172-
174+
macaroon_secret_key = config.get("macaroon_secret_key")
175+
macaroon_secret_key_path = config.get("macaroon_secret_key_path")
176+
if macaroon_secret_key_path:
177+
if macaroon_secret_key:
178+
raise ConfigError(CONFLICTING_MACAROON_SECRET_KEY_OPTS_ERROR)
179+
macaroon_secret_key = read_file(
180+
macaroon_secret_key_path, "macaroon_secret_key_path"
181+
).strip()
182+
if not macaroon_secret_key:
183+
macaroon_secret_key = self.root.registration.registration_shared_secret
173184
if not macaroon_secret_key:
174185
# Unfortunately, there are people out there that don't have this
175186
# set. Lets just be "nice" and derive one from their secret key.

‎tests/config/test_load.py

+15-8
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@
3939

4040
class ConfigLoadingFileTestCase(ConfigFileTestCase):
4141
def test_load_fails_if_server_name_missing(self) -> None:
42-
self.generate_config_and_remove_lines_containing("server_name")
42+
self.generate_config_and_remove_lines_containing(["server_name"])
4343
with self.assertRaises(ConfigError):
4444
HomeServerConfig.load_config("", ["-c", self.config_file])
4545
with self.assertRaises(ConfigError):
@@ -76,7 +76,7 @@ def test_generates_and_loads_macaroon_secret_key(self) -> None:
7676
)
7777

7878
def test_load_succeeds_if_macaroon_secret_key_missing(self) -> None:
79-
self.generate_config_and_remove_lines_containing("macaroon")
79+
self.generate_config_and_remove_lines_containing(["macaroon"])
8080
config1 = HomeServerConfig.load_config("", ["-c", self.config_file])
8181
config2 = HomeServerConfig.load_config("", ["-c", self.config_file])
8282
config3 = HomeServerConfig.load_or_generate_config("", ["-c", self.config_file])
@@ -111,7 +111,7 @@ def test_disable_registration(self) -> None:
111111
self.assertTrue(config3.registration.enable_registration)
112112

113113
def test_stats_enabled(self) -> None:
114-
self.generate_config_and_remove_lines_containing("enable_metrics")
114+
self.generate_config_and_remove_lines_containing(["enable_metrics"])
115115
self.add_lines_to_config(["enable_metrics: true"])
116116

117117
# The default Metrics Flags are off by default.
@@ -131,6 +131,7 @@ def test_depreciated_identity_server_flag_throws_error(self) -> None:
131131
[
132132
"turn_shared_secret_path: /does/not/exist",
133133
"registration_shared_secret_path: /does/not/exist",
134+
"macaroon_secret_key_path: /does/not/exist",
134135
*["redis:\n enabled: true\n password_path: /does/not/exist"]
135136
* (hiredis is not None),
136137
]
@@ -146,16 +147,20 @@ def test_secret_files_missing(self, config_str: str) -> None:
146147
[
147148
(
148149
"turn_shared_secret_path: {}",
149-
lambda c: c.voip.turn_shared_secret,
150+
lambda c: c.voip.turn_shared_secret.encode("utf-8"),
150151
),
151152
(
152153
"registration_shared_secret_path: {}",
153-
lambda c: c.registration.registration_shared_secret,
154+
lambda c: c.registration.registration_shared_secret.encode("utf-8"),
155+
),
156+
(
157+
"macaroon_secret_key_path: {}",
158+
lambda c: c.key.macaroon_secret_key,
154159
),
155160
*[
156161
(
157162
"redis:\n enabled: true\n password_path: {}",
158-
lambda c: c.redis.redis_password,
163+
lambda c: c.redis.redis_password.encode("utf-8"),
159164
)
160165
]
161166
* (hiredis is not None),
@@ -164,11 +169,13 @@ def test_secret_files_missing(self, config_str: str) -> None:
164169
def test_secret_files_existing(
165170
self, config_line: str, get_secret: Callable[[RootConfig], str]
166171
) -> None:
167-
self.generate_config_and_remove_lines_containing("registration_shared_secret")
172+
self.generate_config_and_remove_lines_containing(
173+
["registration_shared_secret", "macaroon_secret_key"]
174+
)
168175
with tempfile.NamedTemporaryFile(buffering=0) as secret_file:
169176
secret_file.write(b"53C237")
170177

171178
self.add_lines_to_config(["", config_line.format(secret_file.name)])
172179
config = HomeServerConfig.load_config("", ["-c", self.config_file])
173180

174-
self.assertEqual(get_secret(config), "53C237")
181+
self.assertEqual(get_secret(config), b"53C237")

‎tests/config/utils.py

+3-2
Original file line numberDiff line numberDiff line change
@@ -51,12 +51,13 @@ def generate_config(self) -> None:
5151
],
5252
)
5353

54-
def generate_config_and_remove_lines_containing(self, needle: str) -> None:
54+
def generate_config_and_remove_lines_containing(self, needles: list[str]) -> None:
5555
self.generate_config()
5656

5757
with open(self.config_file) as f:
5858
contents = f.readlines()
59-
contents = [line for line in contents if needle not in line]
59+
for needle in needles:
60+
contents = [line for line in contents if needle not in line]
6061
with open(self.config_file, "w") as f:
6162
f.write("".join(contents))
6263

0 commit comments

Comments
 (0)
Please sign in to comment.