-
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 command #32623
[extension/oauth2clientauth] Enable dynamically reading ClientID and ClientSecret from command #32623
Conversation
Friendly ping @pavankrish123 @jpkrohling for visibility. |
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 feel like some changes to the tests weren't necessary, but this looks good. I'd still like to have extra 👀 from other @open-telemetry/collector-contrib-approvers, given this code spanws arbitrary commands to get values. This could be used as vector together with another vulnerability, to send confidential data to an attacker by making a curl call to a property controlled by the attacker.
I would probably prefer to allow only one argument to the cmd, requiring admins to wrap a cmd+args in a shell script on the machine. This way, we are certain that only those commands are executed.
@@ -125,8 +131,6 @@ func TestOAuthClientSettingsCredsConfig(t *testing.T) { | |||
settings: &Config{ | |||
ClientIDFile: testCredsFile, | |||
ClientSecret: "testsecret", | |||
TokenURL: "https://example.com/v1/token", |
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.
What happened here?
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.
PTAL at my answer at your other comment where I drop all transport TLS settings -related assertions from this test.
@@ -186,13 +228,6 @@ func TestOAuthClientSettingsCredsConfig(t *testing.T) { | |||
assert.NoError(t, err) | |||
assert.Equal(t, test.expectedClientConfig.ClientID, cfg.ClientID) | |||
assert.Equal(t, test.expectedClientConfig.ClientSecret, cfg.ClientSecret) | |||
|
|||
// test tls settings | |||
transport := rc.client.Transport.(*http.Transport) |
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.
Why was this removed?
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.
The test TestOAuthClientSettingsCredsConfig
, I introduced it in my previous contribution (#26310). I included the TLS settings as a copying-pasting artifact, but I shouldn't have them since it's duplication.
We are testing the transport and TLS configuration [more extensively] in the TestOAuthClientSettings
test (current source).
I removed the // test tls settings
code to have a single place to test TLS settings (could even be split to a separate test) and, if anything changes on the TLS front, drop the need to touch this unrelated test (for example #32332, #32394).
Moreover, related to your other comment about removing TokenURL
and Scopes
assignments, my previous contribution had to assign a value to the TokenURL
field because at that point we've been validating the configuration when calling newClientAuthenticator()
, and a missing TokenURL
got this test breaking [kind of falsely].
But, later, #30469 removed this validation logic. So we don't need to configure it anymore for these tests, and I deleted the relevant lines to drop the [now] unnecessary boilerplate.
I'll bring them back if you believe they should be there.
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.
No, I think it's fine, thank you for the explanation. My concern was whether the new code introduced changes in how we handle TLS, which wasn't apparent at first.
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.
Actually, can you revert this change, and send a new PR removing it? Just so that we isolate the changes, ensuring they are not related.
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 had it in a separate commit. I pushed a reversion commit. I'll send a new PR when we move forward with this one.
cmd := exec.Command(cmdArgs[0], cmdArgs[1:]...) // #nosec G204 | ||
outBytes, err := cmd.Output() | ||
if err != nil { | ||
return "", err |
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.
return "", err | |
return "", fmt.Errorf("failed to execute the command %q to obtain credential details: %v", err) |
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.
Makes sense! I'll push a fixup commit since your suggestion is missing the strings.Join()
for %q
and also use %w
instead of %v
.
@@ -75,10 +75,16 @@ Following are the configuration fields | |||
- **client_id_file** - The file path to retrieve the client identifier issued to the client. | |||
The extension reads this file and updates the client ID used whenever it needs to issue a new token. This enables dynamically changing the client credentials by modifying the file contents when, for example, they need to rotate. <!-- Intended whitespace for compact new line --> | |||
This setting takes precedence over `client_id`. | |||
- **client_id_cmd** - The command to retrieve the client identifier issued to the client. Expects a list of strings. |
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.
Is this the only list of strings we have on this file? If so, please provide an example. Add another example in the testdata's config as well please.
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.
We also have the scopes
field as a list of strings. Nevertheless, I'll push an extra commit to showcase this.
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 thanks a lot for your review!
I pushed two extra commits to address your comments and replied on your comments for the changes in tests.
I would probably prefer to allow only one argument to the cmd, requiring admins to wrap a cmd+args in a shell script on the machine. This way, we are certain that only those commands are executed.
I understand your concerns. However, it's not trivial to do this cmd+args wrapping when deploying the collector as a container, or it would require extra effort and not to use the official community image out-of-the-box.
When deploying the OTEL collector in some other way which allows the software to access a binary from the host filesystem (e.g., a systemd service), a vulnerability which enables the attacker to modify this extension's pointer and run arbitrary commands is probably the last concern since it sounds like there's a bigger security hole in the system.
I am no security expert, though, so I could be very off here :p
I'll also take a look at the JMX receiver you mentioned in the issue.
@@ -125,8 +131,6 @@ func TestOAuthClientSettingsCredsConfig(t *testing.T) { | |||
settings: &Config{ | |||
ClientIDFile: testCredsFile, | |||
ClientSecret: "testsecret", | |||
TokenURL: "https://example.com/v1/token", |
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.
PTAL at my answer at your other comment where I drop all transport TLS settings -related assertions from this test.
@@ -186,13 +228,6 @@ func TestOAuthClientSettingsCredsConfig(t *testing.T) { | |||
assert.NoError(t, err) | |||
assert.Equal(t, test.expectedClientConfig.ClientID, cfg.ClientID) | |||
assert.Equal(t, test.expectedClientConfig.ClientSecret, cfg.ClientSecret) | |||
|
|||
// test tls settings | |||
transport := rc.client.Transport.(*http.Transport) |
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.
The test TestOAuthClientSettingsCredsConfig
, I introduced it in my previous contribution (#26310). I included the TLS settings as a copying-pasting artifact, but I shouldn't have them since it's duplication.
We are testing the transport and TLS configuration [more extensively] in the TestOAuthClientSettings
test (current source).
I removed the // test tls settings
code to have a single place to test TLS settings (could even be split to a separate test) and, if anything changes on the TLS front, drop the need to touch this unrelated test (for example #32332, #32394).
Moreover, related to your other comment about removing TokenURL
and Scopes
assignments, my previous contribution had to assign a value to the TokenURL
field because at that point we've been validating the configuration when calling newClientAuthenticator()
, and a missing TokenURL
got this test breaking [kind of falsely].
But, later, #30469 removed this validation logic. So we don't need to configure it anymore for these tests, and I deleted the relevant lines to drop the [now] unnecessary boilerplate.
I'll bring them back if you believe they should be there.
cmd := exec.Command(cmdArgs[0], cmdArgs[1:]...) // #nosec G204 | ||
outBytes, err := cmd.Output() | ||
if err != nil { | ||
return "", err |
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.
Makes sense! I'll push a fixup commit since your suggestion is missing the strings.Join()
for %q
and also use %w
instead of %v
.
@@ -75,10 +75,16 @@ Following are the configuration fields | |||
- **client_id_file** - The file path to retrieve the client identifier issued to the client. | |||
The extension reads this file and updates the client ID used whenever it needs to issue a new token. This enables dynamically changing the client credentials by modifying the file contents when, for example, they need to rotate. <!-- Intended whitespace for compact new line --> | |||
This setting takes precedence over `client_id`. | |||
- **client_id_cmd** - The command to retrieve the client identifier issued to the client. Expects a list of strings. |
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.
We also have the scopes
field as a list of strings. Nevertheless, I'll push an extra commit to showcase this.
Introduce a constructor for clientCredentialsConfig to make the object details more abstract to the rest of the extension.
In preparation for a minor design enhancement, introduce value sources for raw values and values read from files.
Extend the clientCredentialsConfig object with value sources for ClientID and ClientSecret. It does not make actual use of them yet.
Use the recent changes related to value sources to retrieve the client ID and secret during the final Config creation.
Extend the Config object to expect 'client_id_cmd' and 'client_secret_cmd' values as lists of strings to retrieve the client ID and/or secret by executing commands.
Drop parts related to TLS settings and token retrieval already verified in previous tests.
This reverts commit bc4f2c210e70df1520cd6116a9deaa5b6c29b1cf.
A successful attack is typically the result of using a few attack vectors to exploit a vulnerability. So, while this likely wouldn't introduce a vulnerability, it could be used as a vector in an attack. If other approvers are OK, with this change, I'm also OK in accepting it. |
**Description:** Drop duplicate testing of parts related to TLS settings which already verified more extensively in other tests. This led to relevant changes requiring identical modifications in multiple places (e.g., #32332, #32394). Also remove 'TokenURL' and 'Scopes' from the corresponding configs as they are not required anymore (since #30469). For more details, see also #32623 (comment) Signed-off-by: Ilias Katsakioris <ilias.katsakioris@hpe.com>
This PR was marked stale due to lack of activity. It will be closed in 14 days. |
Not stale |
This PR was marked stale due to lack of activity. It will be closed in 14 days. |
@jpkrohling any update on this? |
I haven't heard from other approvers. Would you mind joining the SIG Collector and bringing this topic there? Or perhaps ask on #otel-collector on Slack. I'm still not sure I approve this change, at least not until I have more time to think about the possible attack scenarios. If other approvers have a different opinion and want to approve this instead, I won't block it. |
This PR was marked stale due to lack of activity. It will be closed in 14 days. |
@elikatsis, I'm sorry for this taking so long, but I think we'll need to close this. I'm VERY happy about your engagement and proposal, and I think this suggestion goes in the right direction. However, I'm not 100% comfortable with allowing this component to execute arbitrary commands on the server. In general, I agree that there's a bigger concern if the Collector can be tricked into doing that, but this feature could be used as a vector in a bigger vulnerability. My recommendation for your specific case (although I suspect you are doing this already): host a version of this component on your own repository, and build your distribution with this component. This way, you can use this feature and distribute it to other users, even if it's not part of the official distribution. Yes, it's a bit more effort, but I think it's justified given the use-case. Again, I'm sorry for closing this, but I think it's the right thing for the moment. |
@jpkrohling thank you for taking the time to consider this. Unfortunately I didn't make it to start a discussion on Slack or a community meeting in time. But, no matter the outcome, it's been a great back and forth, both in this and my previous contribution. Keep up the good work! |
Description:
This PR implements the feature described in detail in the issue linked below.
In a nutshell, it extends the
oauth2clientauth
extension to retrieveClientID
and/orClientSecret
by executing a command whenever a new token is needed for the OAuth flow.As a result, the extension can use updated credentials (when the old ones expire and they get rotated for example) without the need to restart the OTEL collector, as long as the command returns a different output (e.g., fetch credentials from a secret store).
As part of this PR I also modify the design of the code which is responsible to get the actual client ID and secret values based on an old review comment. I have exposed the details in the tracking issue.
Link to tracking Issue: Closes #32602
Testing:
otlphttp
exporter)The collectors export data to a service which sits behind and OIDC authentication proxy and the
oauth2clientauth
extension successfully retrieves the credentials from a secret store and authenticates against the service.Documentation:
I have extended the extension's README file.