Skip to content

[extension/oauth2clientauth] Enable dynamically reading ClientID and ClientSecret from file #26117

Closed
@elikatsis

Description

@elikatsis

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:

  1. 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 with file:?
  2. Add the client_id_file and client_secret_file fields in the config.
    These fields take precedence over the client_id and client_secret fields respectively.

Example config:

  1. First solution - file: URIs
    extensions:
      oauth2client:
        client_id: someclientid
        client_secret: file:/path/to/client/secret
        endpoint_params:
          audience: someaudience
        token_url: https://example.com/oauth2/default/v1/token
  2. Second solution - *_file fields
    extensions:
      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.

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions