Skip to content

Commit

Permalink
[Filebeat][httpjson] httpjson oauth2 authentication mechanism for sal…
Browse files Browse the repository at this point in the history
…esforce 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 <sunny.chaudhari@elastic.co>
  • Loading branch information
kush-elastic and sunny-elastic committed Dec 2, 2021
1 parent fab2197 commit 008182a
Show file tree
Hide file tree
Showing 4 changed files with 111 additions and 8 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.next.asciidoc
Original file line number Diff line number Diff line change
Expand Up @@ -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*

Expand Down
29 changes: 29 additions & 0 deletions x-pack/filebeat/docs/inputs/input-httpjson.asciidoc
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down Expand Up @@ -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`

Expand Down
47 changes: 39 additions & 8 deletions x-pack/filebeat/input/httpjson/config_auth.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"`
Expand Down Expand Up @@ -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"`
Expand All @@ -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...)
Expand Down Expand Up @@ -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())
}
Expand Down
42 changes: 42 additions & 0 deletions x-pack/filebeat/input/httpjson/config_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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'",
Expand Down

0 comments on commit 008182a

Please sign in to comment.