From 008182af34d2148034c226dfa238f04ef81d124e Mon Sep 17 00:00:00 2001 From: Kush Rana <89848966+kush-elastic@users.noreply.github.com> Date: Thu, 2 Dec 2021 15:21:00 +0530 Subject: [PATCH] [Filebeat][httpjson] httpjson oauth2 authentication mechanism for salesforce events (#29087) * Add grant-type: passoword in httpjson oauth2 * refactor code and add new properties tests in config_test.go * Add grant_type: password in oAuth2ProviderDefault * Update CHANGELOG.next.asciidoc * update input-httpjson.asciidoc with new extended httpjson authentication method * add comment for authstyleparam variable * update user dummy value in the doc * Update input-httpjson.asciidoc - provider should be default only for user-passowrd method * refactor code and add new properties tests in config_test.go * Add grant_type: password in oAuth2ProviderDefault Co-authored-by: Sunny Chaudhari --- CHANGELOG.next.asciidoc | 1 + .../docs/inputs/input-httpjson.asciidoc | 29 ++++++++++++ x-pack/filebeat/input/httpjson/config_auth.go | 47 +++++++++++++++---- x-pack/filebeat/input/httpjson/config_test.go | 42 +++++++++++++++++ 4 files changed, 111 insertions(+), 8 deletions(-) diff --git a/CHANGELOG.next.asciidoc b/CHANGELOG.next.asciidoc index b8f52aba7fe..733f5f36604 100644 --- a/CHANGELOG.next.asciidoc +++ b/CHANGELOG.next.asciidoc @@ -350,6 +350,7 @@ https://github.com/elastic/beats/compare/v7.0.0-alpha2...master[Check the HEAD d - Add support in aws-s3 input for custom script parsing of s3 notifications. {pull}28946[28946] - Improve error handling in aws-s3 input for malformed s3 notifications. {issue}28828[28828] {pull}28946[28946] - Add support for parsers on journald input {pull}29070[29070] +- Add support in httpjson input for oAuth2ProviderDefault of password grant_type. {pull}29087[29087] *Heartbeat* diff --git a/x-pack/filebeat/docs/inputs/input-httpjson.asciidoc b/x-pack/filebeat/docs/inputs/input-httpjson.asciidoc index 027ef66e3e7..cbde7b8b831 100644 --- a/x-pack/filebeat/docs/inputs/input-httpjson.asciidoc +++ b/x-pack/filebeat/docs/inputs/input-httpjson.asciidoc @@ -85,6 +85,19 @@ filebeat.inputs: request.url: http://localhost ---- +["source","yaml",subs="attributes"] +---- +filebeat.inputs: +- type: httpjson + auth.oauth2: + client.id: 12345678901234567890abcdef + client.secret: abcdef12345678901234567890 + token_url: http://localhost/oauth2/token + user: user@domain.tld + password: P@$$W0₹D + request.url: http://localhost +---- + [[input-state]] ==== Input state @@ -265,6 +278,22 @@ except if using `google` as provider. Required for providers: `default`, `azure` The client secret used as part of the authentication flow. It is always required except if using `google` as provider. Required for providers: `default`, `azure`. +[float] +==== `auth.oauth2.user` + +The user used as part of the authentication flow. It is required for authentication + - grant type password. It is only available for provider `default`. + +[float] +==== `auth.oauth2.password` + +The password used as part of the authentication flow. It is required for authentication + - grant type password. It is only available for provider `default`. + +NOTE: user and password are required for grant_type password. If user and +password is not used then it will automatically use the `token_url` and +`client credential` method. + [float] ==== `auth.oauth2.scopes` diff --git a/x-pack/filebeat/input/httpjson/config_auth.go b/x-pack/filebeat/input/httpjson/config_auth.go index 88eac44edc1..7e8dae6dc0a 100644 --- a/x-pack/filebeat/input/httpjson/config_auth.go +++ b/x-pack/filebeat/input/httpjson/config_auth.go @@ -22,6 +22,9 @@ import ( "github.com/elastic/beats/v7/libbeat/common" ) +// authStyleInParams sends the "client_id" and "client_secret" in the POST body as application/x-www-form-urlencoded parameters. +const authStyleInParams = 1 + type authConfig struct { Basic *basicAuthConfig `config:"basic"` OAuth2 *oAuth2Config `config:"oauth2"` @@ -83,9 +86,11 @@ type oAuth2Config struct { ClientID string `config:"client.id"` ClientSecret string `config:"client.secret"` EndpointParams map[string][]string `config:"endpoint_params"` + Password string `config:"password"` Provider oAuth2Provider `config:"provider"` Scopes []string `config:"scopes"` TokenURL string `config:"token_url"` + User string `config:"user"` // google specific GoogleCredentialsFile string `config:"google.credentials_file"` @@ -103,20 +108,43 @@ func (o *oAuth2Config) isEnabled() bool { return o != nil && (o.Enabled == nil || *o.Enabled) } +// clientCredentialsGrant creates http client from token_url and client credentials +func (o *oAuth2Config) clientCredentialsGrant(ctx context.Context, client *http.Client) *http.Client { + creds := clientcredentials.Config{ + ClientID: o.ClientID, + ClientSecret: o.ClientSecret, + TokenURL: o.getTokenURL(), + Scopes: o.Scopes, + EndpointParams: o.getEndpointParams(), + } + return creds.Client(ctx) +} + // Client wraps the given http.Client and returns a new one that will use the oauth authentication. func (o *oAuth2Config) client(ctx context.Context, client *http.Client) (*http.Client, error) { ctx = context.WithValue(ctx, oauth2.HTTPClient, client) switch o.getProvider() { - case oAuth2ProviderAzure, oAuth2ProviderDefault: - creds := clientcredentials.Config{ - ClientID: o.ClientID, - ClientSecret: o.ClientSecret, - TokenURL: o.getTokenURL(), - Scopes: o.Scopes, - EndpointParams: o.getEndpointParams(), + case oAuth2ProviderDefault: + if o.User != "" || o.Password != "" { + conf := &oauth2.Config{ + ClientID: o.ClientID, + ClientSecret: o.ClientSecret, + Endpoint: oauth2.Endpoint{ + TokenURL: o.TokenURL, + AuthStyle: authStyleInParams, + }, + } + token, err := conf.PasswordCredentialsToken(ctx, o.User, o.Password) + if err != nil { + return nil, fmt.Errorf("oauth2 client: error loading credentials using user and password: %w", err) + } + return conf.Client(ctx, token), nil + } else { + return o.clientCredentialsGrant(ctx, client), nil } - return creds.Client(ctx), nil + case oAuth2ProviderAzure: + return o.clientCredentialsGrant(ctx, client), nil case oAuth2ProviderGoogle: if o.GoogleJWTFile != "" { cfg, err := google.JWTConfigFromJSON(o.GoogleCredentialsJSON, o.Scopes...) @@ -184,6 +212,9 @@ func (o *oAuth2Config) Validate() error { if o.TokenURL == "" || o.ClientID == "" || o.ClientSecret == "" { return errors.New("both token_url and client credentials must be provided") } + if (o.User != "" && o.Password == "") || (o.User == "" && o.Password != "") { + return errors.New("both user and password credentials must be provided") + } default: return fmt.Errorf("unknown provider %q", o.getProvider()) } diff --git a/x-pack/filebeat/input/httpjson/config_test.go b/x-pack/filebeat/input/httpjson/config_test.go index 67662b40752..71ee7edb246 100644 --- a/x-pack/filebeat/input/httpjson/config_test.go +++ b/x-pack/filebeat/input/httpjson/config_test.go @@ -152,6 +152,48 @@ func TestConfigOauth2Validation(t *testing.T) { "auth.oauth2": map[string]interface{}{}, }, }, + { + name: "if user and password is set oauth2 must use user-password authentication", + input: map[string]interface{}{ + "auth.oauth2": map[string]interface{}{ + "user": "a_client_user", + "password": "a_client_password", + "token_url": "localhost", + "client": map[string]interface{}{ + "id": "a_client_id", + "secret": "a_client_secret", + }, + }, + }, + }, + { + name: "if user is set password credentials must be set for user-password authentication", + expectedErr: "both user and password credentials must be provided accessing 'auth.oauth2'", + input: map[string]interface{}{ + "auth.oauth2": map[string]interface{}{ + "user": "a_client_user", + "token_url": "localhost", + "client": map[string]interface{}{ + "id": "a_client_id", + "secret": "a_client_secret", + }, + }, + }, + }, + { + name: "if password is set user credentials must be set for user-password authentication", + expectedErr: "both user and password credentials must be provided accessing 'auth.oauth2'", + input: map[string]interface{}{ + "auth.oauth2": map[string]interface{}{ + "password": "a_client_password", + "token_url": "localhost", + "client": map[string]interface{}{ + "id": "a_client_id", + "secret": "a_client_secret", + }, + }, + }, + }, { name: "must fail with an unknown provider", expectedErr: "unknown provider \"unknown\" accessing 'auth.oauth2'",