Skip to content

Add MSC3861 config options admin_token_path and client_secret_path#18004

Merged
MadLittleMods merged 6 commits into
element-hq:developfrom
V02460:msc3861-secrets
Feb 4, 2025
Merged

Add MSC3861 config options admin_token_path and client_secret_path#18004
MadLittleMods merged 6 commits into
element-hq:developfrom
V02460:msc3861-secrets

Conversation

@V02460
Copy link
Copy Markdown
Contributor

@V02460 V02460 commented Dec 6, 2024

Another PR on my quest to a *_path variant for every secret. Adds two config options admin_token_path and client_secret_path to the experimental config under experimental_features.msc3861. Also includes tests.

I tried to be a good citizen here by following attrs conventions and not rewriting the corresponding non-path variants in the class, but instead adding methods to retrieve the value.

It is noteworthy that in this patch each access to the client secret or the admin token will re-read it from file. In the current state a ConfigError will be raised at runtime when the file can not be read anymore. You might prefer another form of error handling than the current behavior.

A big pro of directly using the secret contents from file is that it enables secret rotation without having to restart Synapse.

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.

Pull Request Checklist

  • Pull request is based on the develop branch
  • Pull request includes a changelog file. The entry should:
    • Be a short description of your change which makes sense to users. "Fixed a bug that prevented receiving messages from other servers." instead of "Moved X method from EventStore to EventWorkerStore.".
    • Use markdown where necessary, mostly for code blocks.
    • End with either a period (.) or an exclamation mark (!).
    • Start with a capital letter.
    • Feel free to credit yourself, by adding a sentence "Contributed by @github_username." or "Contributed by [Your Name]." to the end of the entry.
  • Code style is correct
    (run the linters)

@V02460 V02460 requested a review from a team as a code owner December 6, 2024 12:36
Comment on lines +228 to +238
def client_secret(self) -> Optional[str]:
"""Returns the client secret.

If client_secret_path is given, the secret is always read from file.
"""
if self._client_secret_path:
return read_file(
self._client_secret_path,
("experimental_features", "msc3861", "client_secret_path"),
).strip()
return self._client_secret
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It is noteworthy that in this patch each access to the client secret or the admin token will re-read it from file. In the current state a ConfigError will be raised at runtime when the file can not be read anymore.

Is there a different idiomatic pattern with attrs where we can just read from the file once?

It looks like we just access config.client_secret() once in __init__() in the usage but I don't think we should make downstream consumers think about having to cache this value because of the expensive/slow file read each time.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The context in which I made this design decision is the following: I’ve started thinking about secret rotation in Synapse and would like to have it supported eventually. For that to work, the consumers of secrets have to know about updates to the secret in use (and sometimes previous secrets as well).

I find the small scope of this change (together with the unwieldiness of attrs1) a good place to get a first taste of how updating secrets might work.

Regarding your concern about performance: I’d expect such a small file to be cached by the OS and very quick to be accessed. If that would not be the case, memory mapping or the use of inotify would be a more full-fledged solution. But does this MR actually cause any performance regression?

Footnotes

  1. I don’t blame you, attrs, it’s simply not the task you were made for.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I’ve started thinking about secret rotation in Synapse and would like to have it supported eventually

This feels like a pre-optimization and something that needs discussion on whether we even want to support it and strategies for how best to tackle.

Regarding your concern about performance:

I don't think it's a performance problem in its current form. I just don't think that downstream consumers using config.client_secret() should have to think about it. It should just always be an in-memory thing after startup.

For example, if we wanted to support secret rotation, we could have a watcher on the file that would only update when the file changes (not read the file each time). But this is just another example of a strategy that should be discussed in a separate, dedicated secret rotation discussion. And why I don't think we should attempt to figure it out with this PR.


In any case, I think we should figure out how best to read the file once on startup.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The updated patch set now only reads in the files once at startup.

To allow consumers access to the raw values close to what is written in the config, the attributes _{client_secret,admin_token}{,_path} are part of the dataclass – with leading underscore to underline their lower-level nature. The methods client_secret and admin_token on the other hand provide convenient access for a consumer to the secret, independently of where it initially came from.

You are right, I think those maybe-future plans should not impede the best solution to the problem at hand. It was not my intention to overload the PR with a discussion about secret rotation. In the meantime I had some helpful and structuring conversations about it at FOSDEM, and I will write up an issue to discuss it further.

Comment thread synapse/api/auth/msc3861_delegated.py
Comment thread changelog.d/18004.feature
@MadLittleMods MadLittleMods merged commit e41174c into element-hq:develop Feb 4, 2025
@MadLittleMods
Copy link
Copy Markdown
Contributor

Thanks for the contribution @V02460 🐫

Sorry for the review noise at the end 🙇

devonh pushed a commit that referenced this pull request Feb 25, 2025
Adds the `--no-secrets-in-config` command line option that makes Synapse
reject all configurations containing keys with in-line secret values.
Currently this rejects

- `turn_shared_secret`
- `registration_shared_secret`
- `macaroon_secret_key`
- `recaptcha_private_key`
- `recaptcha_public_key`
- `experimental_features.msc3861.client_secret`
- `experimental_features.msc3861.jwk`
- `experimental_features.msc3861.admin_token`
- `form_secret`
- `redis.password`
- `worker_replication_secret`

> [!TIP]
> Hey, you! Yes, you! 😊 If you think this list is missing an item,
please leave a comment below. Thanks :)

This PR complements my other PRs[^1] that add the corresponding `_path`
variants for this class of config options. It enables admins to enforce
a policy of no secrets in configuration files and guards against
accident and malice.

Because I consider the flag `--no-secrets-in-config` to be
security-relevant, I did not add a corresponding `--secrets-in-config`
flag; this way, if Synapse command line options are appended at various
places, there is no way to weaken the once-set setting with a succeeding
flag.

[^1]: [#17690](#17690),
[#17717](#17717),
[#17983](#17983),
[#17984](#17984),
[#18004](#18004),
[#18090](#18090)


### Pull Request Checklist

<!-- Please read
https://element-hq.github.io/synapse/latest/development/contributing_guide.html
before submitting your pull request -->

* [x] Pull request is based on the develop branch
* [x] Pull request includes a [changelog
file](https://element-hq.github.io/synapse/latest/development/contributing_guide.html#changelog).
The entry should:
- Be a short description of your change which makes sense to users.
"Fixed a bug that prevented receiving messages from other servers."
instead of "Moved X method from `EventStore` to `EventWorkerStore`.".
  - Use markdown where necessary, mostly for `code blocks`.
  - End with either a period (.) or an exclamation mark (!).
  - Start with a capital letter.
- Feel free to credit yourself, by adding a sentence "Contributed by
@github_username." or "Contributed by [Your Name]." to the end of the
entry.
* [x] [Code
style](https://element-hq.github.io/synapse/latest/code_style.html) is
correct
(run the
[linters](https://element-hq.github.io/synapse/latest/development/contributing_guide.html#run-the-linters))
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants