-
Notifications
You must be signed in to change notification settings - Fork 4.9k
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
Change OAuth2 Authorization Style to Auto Detection, Avoid Client.Sec… #33342
Conversation
💚 CLA has been signed |
This pull request doesn't have a |
This pull request does not have a backport label.
To fixup this pull request, you need to add the backport labels for the needed
|
@@ -211,7 +211,7 @@ func (o *oAuth2Config) Validate() error { | |||
case oAuth2ProviderGoogle: | |||
return o.validateGoogleProvider() | |||
case oAuth2ProviderDefault: | |||
if o.TokenURL == "" || o.ClientID == "" || o.ClientSecret == "" { | |||
if o.TokenURL == "" || o.ClientID == "" { |
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.
This change troubles me. The RFC says that the secret is REQUIRED but that it may be omitted if it's the empty string. This does that, but it allows a user to omit the var completely without error. I would like to have a check that the var is set, even if it is set to ""
.
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 type of ClientSecret is string therefore the inital value is always "".
Do you have any idea to check that this var was set?
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 normal approach would be to make the field be a pointer to the value, so a *string
, but that will complicate some of the logic. If the field is not set, then it will be nil, otherwise it will be a non-nil pointer to the string, which may be ""
.
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.
Okay, thx for this hint but I think that going to make it more complicated and needs deeper changes within the logic.
It is an option to set the field as defaulted with ""
in the context of beat?
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'll have a look into how complicated it will be to make it aware of set state without using ""
as the zero value.
It is an option to set the field as defaulted with
""
in the context of beat?
I'm sorry, I don't understand this question.
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'll have a look into how complicated it will be to make it aware of set state without using "" as the zero value.
Thx in advance!
It is an option to set the field as defaulted with "" in the context of beat?
Sry, my bad. I meant it this way, to declare that the client secret is still required but has the default value ""
if not set.
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.
to declare that the client secret is still required but has the default value
""
if not set.
That is not possible. An unset string is ""
. This is why the pointer is needed. The zero value of a *string
is nil
, but the zero value of a string
is ""
.
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 following change works (includes tests).
diff --git a/x-pack/filebeat/input/httpjson/config_auth.go b/x-pack/filebeat/input/httpjson/config_auth.go
index 71911cd6f4..d4e7c4ab5c 100644
--- a/x-pack/filebeat/input/httpjson/config_auth.go
+++ b/x-pack/filebeat/input/httpjson/config_auth.go
@@ -84,7 +84,7 @@ type oAuth2Config struct {
// common oauth fields
ClientID string `config:"client.id"`
- ClientSecret string `config:"client.secret"`
+ ClientSecret *string `config:"client.secret"`
EndpointParams map[string][]string `config:"endpoint_params"`
Password string `config:"password"`
Provider oAuth2Provider `config:"provider"`
@@ -114,7 +114,7 @@ func (o *oAuth2Config) isEnabled() bool {
func (o *oAuth2Config) clientCredentialsGrant(ctx context.Context, _ *http.Client) *http.Client {
creds := clientcredentials.Config{
ClientID: o.ClientID,
- ClientSecret: o.ClientSecret,
+ ClientSecret: maybeString(o.ClientSecret),
TokenURL: o.getTokenURL(),
Scopes: o.Scopes,
EndpointParams: o.getEndpointParams(),
@@ -131,10 +131,10 @@ func (o *oAuth2Config) client(ctx context.Context, client *http.Client) (*http.C
if o.User != "" || o.Password != "" {
conf := &oauth2.Config{
ClientID: o.ClientID,
- ClientSecret: o.ClientSecret,
+ ClientSecret: maybeString(o.ClientSecret),
Endpoint: oauth2.Endpoint{
TokenURL: o.TokenURL,
- AuthStyle: authStyleInParams,
+ AuthStyle: oauth2.AuthStyleAutoDetect,
},
}
token, err := conf.PasswordCredentialsToken(ctx, o.User, o.Password)
@@ -167,6 +167,14 @@ func (o *oAuth2Config) client(ctx context.Context, client *http.Client) (*http.C
}
}
+// maybeString returns the string pointed to by p or "" if p in nil.
+func maybeString(p *string) string {
+ if p == nil {
+ return ""
+ }
+ return *p
+}
+
// getTokenURL returns the TokenURL.
func (o *oAuth2Config) getTokenURL() string {
switch o.getProvider() {
@@ -211,7 +219,7 @@ func (o *oAuth2Config) Validate() error {
case oAuth2ProviderGoogle:
return o.validateGoogleProvider()
case oAuth2ProviderDefault:
- if o.TokenURL == "" || o.ClientID == "" || o.ClientSecret == "" {
+ if o.TokenURL == "" || o.ClientID == "" || o.ClientSecret == nil {
return errors.New("both token_url and client credentials must be provided")
}
if (o.User != "" && o.Password == "") || (o.User == "" && o.Password != "") {
@@ -228,7 +236,7 @@ func (o *oAuth2Config) Validate() error {
var findDefaultGoogleCredentials = google.FindDefaultCredentials
func (o *oAuth2Config) validateGoogleProvider() error {
- if o.TokenURL != "" || o.ClientID != "" || o.ClientSecret != "" ||
+ if o.TokenURL != "" || o.ClientID != "" || o.ClientSecret != nil ||
o.AzureTenantID != "" || o.AzureResource != "" || len(o.EndpointParams) != 0 {
return errors.New("none of token_url and client credentials can be used, use google.credentials_file, google.jwt_file, google.credentials_json or ADC instead")
}
@@ -295,7 +303,7 @@ func (o *oAuth2Config) validateAzureProvider() error {
if o.TokenURL != "" && o.AzureTenantID != "" {
return errors.New("only one of token_url and tenant_id can be used")
}
- if o.ClientID == "" || o.ClientSecret == "" {
+ if o.ClientID == "" || o.ClientSecret == nil {
return errors.New("client credentials must be provided")
}
diff --git a/x-pack/filebeat/input/httpjson/config_test.go b/x-pack/filebeat/input/httpjson/config_test.go
index 2ee8c4276c..116ea617e3 100644
--- a/x-pack/filebeat/input/httpjson/config_test.go
+++ b/x-pack/filebeat/input/httpjson/config_test.go
@@ -152,6 +152,32 @@ func TestConfigOauth2Validation(t *testing.T) {
"auth.oauth2": map[string]interface{}{},
},
},
+ {
+ name: "client credential secret may be empty",
+ input: map[string]interface{}{
+ "auth.oauth2": map[string]interface{}{
+ "enabled": true,
+ "token_url": "localhost",
+ "client": map[string]interface{}{
+ "id": "a_client_id",
+ "secret": "",
+ },
+ },
+ },
+ },
+ {
+ name: "client credential secret may not be missing",
+ expectedErr: "both token_url and client credentials must be provided accessing 'auth.oauth2'",
+ input: map[string]interface{}{
+ "auth.oauth2": map[string]interface{}{
+ "enabled": true,
+ "token_url": "localhost",
+ "client": map[string]interface{}{
+ "id": "a_client_id",
+ },
+ },
+ },
+ },
{
name: "if user and password is set oauth2 must use user-password authentication",
input: map[string]interface{}{
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.
Thank you very much! I've made the changes above.
/test |
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.
After nit is addressed, this LGTM (I will need to get another approver since I wrote the changes).
Co-authored-by: Dan Kortschak <[email protected]>
/test |
/test |
This pull request is now in conflicts. Could you fix it? 🙏
|
/test |
1 similar comment
/test |
Hi @efd6 , sorry about the stupid question but what should I do next with this PR? Do have I any things to do now? |
Sorry for the delay. There is nothing for you to do here. I am waiting for a second reviewer. |
@@ -167,6 +164,14 @@ func (o *oAuth2Config) client(ctx context.Context, client *http.Client) (*http.C | |||
} | |||
} | |||
|
|||
// maybeString returns the string pointed to by p or "" if p in nil. | |||
func maybeString(p *string) string { |
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 name maybeString suggests that the type maybe a string or something else entirely suggesting we are doing a type check here, but in reality we are checking if the string is nil or not and returning a value accordingly. Maybe something like checkIfEmpty or something similar makes more sense ?
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.
It's not a check it's a weak maybe in the vein of a Rust maybe. It either is a string or it is not valid. In the case that it's not valid, an empty string suffices.
…mpty secrets (#33342) Allow automatic OAuth2 style detection and do not require a non-empty client secret, though do require it to be explicitly empty if an empty secret is needed. Co-authored-by: Dan Kortschak <[email protected]>
…ret Validation
What does this PR do?
The option is missing that the credentials going to be passed as header params as well or even better to use the automatic function from the oauth2 package. (oauth2 package - golang.org/x/oauth2 - Go Packages)
The passing of the required client credentials is to strict, because the param validator checks the client secret as well. But the client secret can be empty too and even still valid for oauth2 access process.
Why is it important?
This makes it possible to use the httpjson more generic and more flexible OAuth2 services
Checklist
I have made corresponding changes to the documentationI have made corresponding change to the default configuration filesCHANGELOG.next.asciidoc
orCHANGELOG-developer.next.asciidoc
.Related issues
Use cases
We have an oauth2 service which supports passing the parameter by header only and not by params in the body.
Furthermore the required credentials for the client contains a id and an empty secret.