-
Notifications
You must be signed in to change notification settings - Fork 2.4k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[extension/oauth2clientauth] Enable dynamically reading ClientID and ClientSecret from files #26310
[extension/oauth2clientauth] Enable dynamically reading ClientID and ClientSecret from files #26310
Conversation
Can you sort out the CLA before I review this one? |
@jpkrohling CLA ✅ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I have made a few suggestions but this looks very cool! Thank you for the PR.
var err error | ||
|
||
if len(filepath) > 0 { | ||
actualValue, err = readCredentialsFile(filepath) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do you need to create a new var? Perhaps just return the results of the readCredentialsFile if the filepath's len is > 0, otherwise return value, nil ?
actualValue, err = readCredentialsFile(filepath) | |
return readCredentialsFile(filepath) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
At this point we don't create a new var, we override the actualValue
one (that's why we also define err
outside the block). This doesn't look optimal at this state (and I'm good with changing it) but it makes things more clear in my opinion when adding more ways of retrieving the "actual value" (as mentioned in the issue, get by executing a command).
I can push the commits with the command part so that you can take a look and decide how you want me to change it.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@jpkrohling this is the relevant commit adding the command case: https://github.com/elikatsis/opentelemetry-collector-contrib/commit/e453109b958265f58ca71b37701a5b7797469b18
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
as mentioned in the issue, get by executing a command
I missed that, and this specific example is unlikely to get accepted: we probably do not want the collector executing commands.
At this point we don't create a new var, we override the actualValue one
I mean something like this:
func getActualValue(value, cmd, filepath string) (string, error) {
if len(filepath) > 0 {
return readCredentialsFile(filepath)
}
if len(cmd) > 0 {
return readCommandOutput(cmd)
}
return value, nil
}
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
By the way, this is a minor improvement, optional for the approval of this PR.
Another take would be this: given that this is in the hot path and the decision will be the same during the whole lifecycle of this component, it could even be better to just use a func as a parameter to this type. Like this:
func NewClientCredentialsConfig(cfg *clientcredentials.Config, clientIDFile, clientSecretFile string) (*clientCredentialsConfig, error) {
c := &clientCredentialsConfig{
Config: *cfg,
}
if len(clientIDFile) > 0 {
c.clientIDSource = func() (string, error) {
return readCredentialsFile(clientIDFile)
}
} else {
c.clientIDSource = func() (string, error) {
return c.ClientID, nil
}
}
if len(clientSecretFile) > 0 {
c.clientSecretSource = func() (string, error) {
return readCredentialsFile(clientSecretFile)
}
} else {
c.clientSecretSource = func() (string, error) {
return c.ClientSecret, nil
}
}
return c, nil
}
Your createConfig would then call clientID, err := c.clientIDSource()
and clientSecret, err := c.clientSecretSource()
accordingly.
@jpkrohling thank you very much for your review. I've posted some replies I'd like your feedback on |
LGTM, there's a minor improvement that could be made to the hot path. The only real requirement is to improve the readme. Once that's done, ping me and I'll kick the CI. |
Implement a wrapper for clientcredentials.Config in preparation for custom behavior and logic when issuing new tokens.
Use the locally defined struct clientCredentialsConfig in favor of the upstream clientcredentials.Config object.
Extend the clientCredentailsConfig to retrieve client ID and/or secret from files by setting values to the ClientIDFile and ClientSecretFile fields respectively. The Client*File field takes precedence over the Client* field.
Leverage the clientCredentialsConfig features to read client ID and/or secret from a file to the actual extension configuration.
@jpkrohling I made the following changes
Your suggestion on using |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM for a first implementation. The next iteration could perhaps use the same approach we have in the Jaeger Remote sampling extension, by watching the file for changes instead of re-reading on every request. I think there's a big performance penalty with this, but I'll accept because:
- it's ready and tested by the author
- it's a new feature, doesn't impact current users
@jpkrohling I fixed a typo which made the lint test fail. I ran quite a few more Makefile targets trying to catch things early this time. You'll probably need to trigger the tests again :/ |
I see this error in the test
Is it relevant to my PR or it's some infra issue? Hmu if I can do anything to unblock this |
I ran it again and it's passing. |
Description:
This PR implements the feature described in detail in the issue linked below.
In a nutshell, it extends the
oauth2clientauth
extension to read ClientID and/or ClientSecret from files whenever a new token is needed for the OAuth flow.As a result, the extension can use updated credentials (when the old ones expire for example) without the need to restart the OTEL collector, as long as the file contents are in sync.
Link to tracking Issue: #26117
Testing:
Apart from the unit testing you can see in the PR, I've tested this feature in two real-life environments:
otlphttp
dataotlphttp
dataIn both cases, the collectors export the data to a service which sits behind an OIDC authentication proxy.
Using the
oauth2clientauth
extension, theotlphttp
exporter hits the authentication provider to issue tokens for the OIDC client and successfully authenticates to the service.In my cases, the ClientSecret gets rotated quite frequently and there is a stack making sure the ClientID and ClientSecret in the corresponding files are up-to-date.
Documentation:
I have extended the extension's README file. I'm open to more suggestions!
cc @jpkrohling @pavankrish123