Description
Component(s)
extension/oauth2clientauth
Is your feature request related to a problem? Please describe.
It's common that the secret of an OIDC client expires and, therefore, it has to get rotated frequently. As expected, after its expiration the OIDC client will get rejected if it uses the same secret to authenticate.
Thus, I have come across the need to refresh the value of the client_secret
field while the OTEL collector is running without any downtime.
More specifically, a service stores the credentials in files somewhere in the filesystem and refreshes these files when they rotate. Then, other services have to read the files to get the up-to-date values.
Describe the solution you'd like
I can propose two solutions for this:
- Use
file:/path/to/file
values to the corresponding attributes denoting that the actual value to use should be retrieved from the/path/to/file
file.
Important: What if an actual value happens to begin withfile:
? - Add the
client_id_file
andclient_secret_file
fields in the config.
These fields take precedence over theclient_id
andclient_secret
fields respectively.
Example config:
- First solution -
file:
URIsextensions: oauth2client: client_id: someclientid client_secret: file:/path/to/client/secret endpoint_params: audience: someaudience token_url: https://example.com/oauth2/default/v1/token
- Second solution -
*_file
fieldsextensions: oauth2client: client_id: someclientid client_secret_file: /path/to/client/secret endpoint_params: audience: someaudience token_url: https://example.com/oauth2/default/v1/token
It's important to note that existing configurations will continue to work. Any of the above changes is fully backwards compatible. We will just handle the config and the values in a little different way.
The implementation I have been using so far uses the first solution (file:
URIs) and extends the logic of clientcredentials.Config
object, used in the clientAuthenticator
object.
To extend it, in this case, means to define a custom object (clientCredentialsConfig
) with the exact same attributes:
type clientCredentialsConfig clientcredentials.Config
And, then, define some custom logic during the corresponding Token()
method, called at
It eventually builds a
clientcredentials.Config
object using the same values, and replaces the ClientID
and/or ClientSecret
with the file contents (if they reference a file; i.e., contain a file:
-prefixed value).
Following the official API (from https://pkg.go.dev/golang.org/x/oauth2 and https://pkg.go.dev/golang.org/x/oauth2/clientcredentials), we need to define the following objects: Config > TokenSource > Token
A > B
denotes that B
is created by a method of A
.
The plan is to make the currently used new Config in the place of the clientcredentials.Config
with no other change when invoking its methods.
The TokenSource > Token
part is similar to what is already implemented for errorWrappingTokenSource
.
What do you think? Which of the two do you prefer? Do you have any other thoughts or suggestions?
Describe alternatives you've considered
An alternative would be to implement an independent service which watches the file for changes, updates the OTEL config, and restarts the collector.
However, apart from being odd as brutally forced and resulting in downtime, this cannot apply uniformly in any scenario. For example, I have needed this feature when using the collector both in a systemd service as well as in Kubernetes environments.
Additional context
I'm also planning on creating an issue about another, similar, feature regarding passing commands to retrieve the ClientID and/or ClientSecret. I have an implementation for this as well, introducing the extra []string
fields cliend_id_cmd
and client_secret_cmd
.
I won't go into greater detail in this comment. If you want to discuss the command case in parallel to the file one, I can open a new issue or add more comments on this one.
The important part is that although reading from file can happen with a command (so the command could be considered as a superset feature), there are cases in which the collector runs on a minimal Docker image (e.g., the official one) without any external tools that can be used to read files. I have needed both (read from file, read from command output) in the environments I've been working on.