Skip to content

Conversation

@hsmatulis
Copy link

@hsmatulis hsmatulis commented Jun 27, 2025

This PR introduces a new package, secrets, to prometheus/common. This package provides a unified way to handle secrets within configuration files for Prometheus and its ecosystem components. It is designed to be extensible and observable. See the proposal here

@hsmatulis hsmatulis force-pushed the main branch 3 times, most recently from cf118e6 to 24ec0f7 Compare July 7, 2025 11:07
@hsmatulis hsmatulis force-pushed the main branch 18 times, most recently from 1882b4c to 694bf26 Compare October 2, 2025 07:35
@hsmatulis hsmatulis changed the title feat(secrets): Introduce new package for dynamic secret management feat(secrets): Add new secrets management package Oct 2, 2025
@hsmatulis hsmatulis marked this pull request as ready for review October 2, 2025 07:42
@hsmatulis
Copy link
Author

hsmatulis commented Oct 2, 2025

@pintohutch @bernot-dev @bwplotka PTAL, should be ready!

@bwplotka bwplotka self-requested a review October 8, 2025 15:15
Copy link
Member

@bwplotka bwplotka left a comment

Choose a reason for hiding this comment

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

Thanks, amazing work!

Generally, it's great - maybe first big question is if we want SecretField to store so much state or do we want to move some of this state to manager (or and providers). This might be more composable and easier to reason about. Prometheus discovery is doing some of this. I mentioned one idea (A) 1 and 2 in comments.

However, it's not a blocker, even in the current state, I would say we could try this out on Prometheus (and someone might try on AM side!). What matters is that I see a clean YAML surface format, clean code, idiomatic struct reflection to find fields and healthy amount of test, so amazing! With this we can iterate.

No matter if you want to try (A) or skip it for now, I think I would try to check before merging:

Then I would like to review in more depth the manager code, but generally... we could start with this! 🎉 Thanks!

}

type SecretFieldSettings struct {
RefreshInterval time.Duration `yaml:"refreshInterval,omitempty"`
Copy link
Member

Choose a reason for hiding this comment

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

question: Hm, good question if refresh is really relevant to all providers... E.g it's not for inline and.. also not for file based? Refreshing in general is a key concept here so we at least needs a good commentary in the code for this field on how it suppose to be implemented and consumed?

Copy link
Author

Choose a reason for hiding this comment

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

Hmm I guess it is not relevant to inline but could be relevant to file. Although we might want to think about listening to filesystem changes for a file based provider instead, but I found that to complicate the API quite a bit...


In your configuration struct, use the `secrets.SecretField` type for any fields that should contain secrets.

```go
Copy link
Member

Choose a reason for hiding this comment

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

Sustainability suggestion (non-blocking):

This is well documented, thanks!

However, it might be even better if we would replace the "How to use" section with a single or set of buildable examples as per https://go.dev/blog/examples .. and commentary in the key public code structures.

secrets/field.go Outdated
}
}

func (s *SecretField) Get() string {
Copy link
Member

Choose a reason for hiding this comment

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

Nice! This make caller unaware of errors which can be both great and bad, depending what we do. I think it would be great to test out in this state 👍🏽

One alternative to make things leaner on SecretField (decompose responsibilities and state), would be if SecretField would allow storing (atomically) and getting secret value string. This way we could have common package global free and SecretField manager free (manager could be responsible to set correct things in config).

It could be even provider agnostic/free combined with https://github.com/prometheus/common/pull/797/files#r2414589581 idea (and avoid globals in this package)

return manager, nil
}

func (m *Manager) registerMetrics(r prometheus.Registerer) {
Copy link
Member

Choose a reason for hiding this comment

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

Thanks for metrics, super important!

return secret.secret
}

func (m *Manager) triggerRefresh(s *SecretField) {
Copy link
Member

Choose a reason for hiding this comment

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

note to myself: Manager code needs deeper review, quite many locks 🙈 prone to errors, but perhaps needed.

Copy link
Author

Choose a reason for hiding this comment

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

yeah it was very complicated before with the verify logic, I think it should be a bit simpler now.. Still need some cleanup there, but should be more readable

secrets/field.go Outdated
return nil
}

func (s *SecretField) UnmarshalYAML(unmarshal func(interface{}) error) error {
Copy link
Member

Choose a reason for hiding this comment

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

Have we considered doing plainSecret thingy, but then unmarshal to any or map[string]any and let manager.ApplyConfig do the final parsing? This would make SecretField leaner and manager/provider agnostic (together with setter/getter for secret values https://github.com/prometheus/common/pull/797/files#r2414554264)

Remove all verifier logic, as it is not needed for now.

This removes the  interface and all related logic for validating secrets before they are activated.

This simplifies the secret management lifecycle by removing the pending/verification state. Fetched secrets will now become active immediately.
Copy link
Member

@bwplotka bwplotka left a comment

Choose a reason for hiding this comment

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

Looks solid, thanks!

Still some few comments to address, but overall looks close to be merged!

type Field struct {
providerName string
providerConfig ProviderConfig
manager *Manager
Copy link
Member

Choose a reason for hiding this comment

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

idea during 1:1, it would be nice to remove bi-direction and have lazy refresh perhaps

Copy link
Member

Choose a reason for hiding this comment

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

Alternatively just use interface for Refresh()

* `ProviderConfig`: An interface for configuring a secret provider. It includes methods to create a new `Provider` instance and to clone the configuration.
* `ProviderConfigId`: An interface for uniquely identifying a `ProviderConfig` instance.
* `Provider`: An interface for fetching secrets from a specific source (e.g., inline string, file on disk). The package comes with built-in providers, and new ones can be registered.
* `Manager`: A component that discovers all `SecretField` instances within a configuration struct, manages their lifecycle, and handles periodic refreshing of secrets.
Copy link
Member

Choose a reason for hiding this comment

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

Can we put those into comments? Especially for library, unlikely anyone will notice README. In go it's either doc.go or code structure for godocs.

Copy link
Member

Choose a reason for hiding this comment

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

Copy link
Member

Choose a reason for hiding this comment

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

Signed-off-by: Henrique Matulis <[email protected]>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants