forked from open-telemetry/opentelemetry-collector-contrib
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Added extension to support OAuth2 client credentials authentication o…
…n HTTP and gRPC exporters (open-telemetry#3848) **This is a port of the approved PR open-telemetry/opentelemetry-collector#3369 from core to contrib per instructions from @tigrannajaryan** Link to tracking Issue: open-telemetry/opentelemetry-collector#2785 open-telemetry/opentelemetry-collector#2500 open-telemetry/opentelemetry-collector#2603 **Testing**: - Unit Tests - Manual testing by verifying the token is getting automatically refreshed after expiry time in HTTP and gRPC OTLP exporters These are Manual tests 1) To check whether the token is actually refreshed, I have set up an echo server that prints the token and I have setup a dev okta oauth2 server with access token life time for 5 mins. I could see the token being refreshed, ```yaml extensions: oauth2client: client_id: 0gI........ client_secret: _doAe0NeU....... token_url: https://dev-xxxxxxxx.XXX.com/oauth2/default/v1/token scopes: ["api.metrics"] # timeout for the token client timeout: 2s receivers: hostmetrics: collection_interval: 20s scrapers: load: exporters: logging: otlp: endpoint: localhost:5000 ca_file: /tmp/certs/ca.pem auth: authenticator: oauth2client service: extensions: [oauth2client] pipelines: metrics/agent: receivers: - hostmetrics processors: [] exporters: - logging - otlp ``` sample output from server ``` 2021/06/07 16:06:11 map[:authority:[localhost:5000] authorization:[Bearer eyJraWQiOiJfamJVQnpzX0RXMHdBUTF[...........]a4wK0VSQ0hpw] content-type:[application/grpc] user-agent:[grpc-go/1.38.0]] 2021/06/07 16:06:32 map[:authority:[localhost:5000] authorization:[Bearer eyJraWQiOiJfamJVQnpzX0RXMHdBUTF[..........]_cNz1zciIg_hJurzUD-5A1_w] content-type:[application/grpc] user-agent:[grpc-go/1.38.0]] ``` 2) For an e2e use case, I followed the steps from @jpkrohling article [here](https://medium.com/opentelemetry/securing-your-opentelemetry-collector-1a4f9fa5bd6f) and instead of directly using access token by explicitly making curl, I plugged in client secret into auth configuration as below (agent yaml) ```yaml extensions: oauth2client: client_id: agent client_secret: 0d03380f-d0f3-XXX-XXXXXXXXX token_url: http://localhost:8080/auth/realms/opentelemetry/protocol/openid-connect/token # tls settings for the token client tls: insecure: true ca_file: /tmp/certs/ca.pem cert_file: /tmp/certs/cert.pem key_file: /tmp/certs/cert-key.pem # timeout for the token client timeout: 2s receivers: hostmetrics: collection_interval: 20s scrapers: load: exporters: logging: otlp: endpoint: localhost:56680 ca_file: /tmp/certs/ca.pem auth: authenticator: oauth2client service: extensions: [oauth2client] pipelines: metrics/agent: receivers: - hostmetrics processors: [] exporters: - logging - otlp ``` I could see the metrics coming on the collector side ``` 2021-06-07T16:12:24.924-0700 INFO loggingexporter/logging_exporter.go:57 MetricsExporter {"#metrics": 3} 2021-06-07T16:12:44.838-0700 INFO loggingexporter/logging_exporter.go:57 MetricsExporter {"#metrics": 3} 2021-06-07T16:15:24.857-0700 INFO loggingexporter/logging_exporter.go:57 MetricsExporter {"#metrics": 3} ```` **Documentation:** Added README.md
- Loading branch information
1 parent
a811dbb
commit d7c94f0
Showing
20 changed files
with
2,740 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
include ../../Makefile.Common |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,65 @@ | ||
# Authenticator - OAuth2 Client Credentials | ||
|
||
This extension provides OAuth2 Client Credentials flow authenticator for HTTP and gRPC based exporters. The extension | ||
fetches and refreshes the token after expiry automatically. For further details about OAuth2 Client Credentials flow (2-legged workflow) | ||
refer https://datatracker.ietf.org/doc/html/rfc6749#section-4.4. | ||
|
||
The authenticator type has to be set to `oauth2client`. | ||
|
||
## Configuration | ||
|
||
```yaml | ||
extensions: | ||
oauth2client: | ||
client_id: someclientid | ||
client_secret: someclientsecret | ||
token_url: https://example.com/oauth2/default/v1/token | ||
scopes: ["api.metrics"] | ||
# tls settings for the token client | ||
tls: | ||
insecure: true | ||
ca_file: /var/lib/mycert.pem | ||
cert_file: certfile | ||
key_file: keyfile | ||
# timeout for the token client | ||
timeout: 2s | ||
|
||
receivers: | ||
hostmetrics: | ||
scrapers: | ||
memory: | ||
otlp: | ||
protocols: | ||
grpc: | ||
|
||
exporters: | ||
otlphttp/withauth: | ||
endpoint: http://localhost:9000 | ||
auth: | ||
authenticator: oauth2client | ||
|
||
otlp/withauth: | ||
endpoint: 0.0.0.0:5000 | ||
ca_file: /tmp/certs/ca.pem | ||
auth: | ||
authenticator: oauth2client | ||
|
||
service: | ||
extensions: [oauth2client] | ||
pipelines: | ||
metrics: | ||
receivers: [hostmetrics] | ||
processors: [] | ||
exporters: [otlphttp/withauth, otlp/withauth] | ||
``` | ||
Following are the configuration fields | ||
- [**token_url**](https://datatracker.ietf.org/doc/html/rfc6749#section-3.2) - The resource server's token endpoint URLs. | ||
- [**client_id**](https://datatracker.ietf.org/doc/html/rfc6749#section-2.2) - The client identifier issued to the client. | ||
- [**client_secret**](https://datatracker.ietf.org/doc/html/rfc6749#section-2.3.1) - The secret string associated with above identifier. | ||
- [**scopes**](https://datatracker.ietf.org/doc/html/rfc6749#section-3.3) - **Optional** optional requested permissions associated for the client. | ||
- [**timeout**](https://golang.org/src/net/http/client.go#L90) - **Optional** specifies the timeout on the underlying client to authorization server for fetching the tokens (initial and while refreshing). | ||
This is optional and not setting this configuration implies there is no timeout on the client. | ||
For more information on client side TLS settings, see [configtls README](../../config/configtls/README.md). |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,74 @@ | ||
// Copyright The OpenTelemetry Authors | ||
// | ||
// Licensed under the Apache License, Version 2.0 (the "License"); | ||
// you may not use this file except in compliance with the License. | ||
// You may obtain a copy of the License at | ||
// | ||
// http://www.apache.org/licenses/LICENSE-2.0 | ||
// | ||
// Unless required by applicable law or agreed to in writing, software | ||
// distributed under the License is distributed on an "AS IS" BASIS, | ||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
// See the License for the specific language governing permissions and | ||
// limitations under the License. | ||
|
||
package oauth2clientauthextension | ||
|
||
import ( | ||
"errors" | ||
"time" | ||
|
||
"go.opentelemetry.io/collector/config" | ||
"go.opentelemetry.io/collector/config/configtls" | ||
) | ||
|
||
var ( | ||
errNoClientIDProvided = errors.New("no ClientID provided in the OAuth2 exporter configuration") | ||
errNoTokenURLProvided = errors.New("no TokenURL provided in OAuth Client Credentials configuration") | ||
errNoClientSecretProvided = errors.New("no ClientSecret provided in OAuth Client Credentials configuration") | ||
) | ||
|
||
// Config stores the configuration for OAuth2 Client Credentials (2-legged OAuth2 flow) setup. | ||
type Config struct { | ||
config.ExtensionSettings `mapstructure:",squash"` | ||
|
||
// ClientID is the application's ID. | ||
// See https://datatracker.ietf.org/doc/html/rfc6749#section-2.2 | ||
ClientID string `mapstructure:"client_id"` | ||
|
||
// ClientSecret is the application's secret. | ||
// See https://datatracker.ietf.org/doc/html/rfc6749#section-2.3.1 | ||
ClientSecret string `mapstructure:"client_secret"` | ||
|
||
// TokenURL is the resource server's token endpoint | ||
// URL. This is a constant specific to each server. | ||
// See https://datatracker.ietf.org/doc/html/rfc6749#section-3.2 | ||
TokenURL string `mapstructure:"token_url"` | ||
|
||
// Scope specifies optional requested permissions. | ||
// See https://datatracker.ietf.org/doc/html/rfc6749#section-3.3 | ||
Scopes []string `mapstructure:"scopes,omitempty"` | ||
|
||
// TLSSetting struct exposes TLS client configuration for the underneath client to authorization server. | ||
TLSSetting configtls.TLSClientSetting `mapstructure:"tls,omitempty"` | ||
|
||
// Timeout parameter configures `http.Client.Timeout` for the underneath client to authorization | ||
// server while fetching and refreshing tokens. | ||
Timeout time.Duration `mapstructure:"timeout,omitempty"` | ||
} | ||
|
||
var _ config.Extension = (*Config)(nil) | ||
|
||
// Validate checks if the extension configuration is valid | ||
func (cfg *Config) Validate() error { | ||
if cfg.ClientID == "" { | ||
return errNoClientIDProvided | ||
} | ||
if cfg.ClientSecret == "" { | ||
return errNoClientSecretProvided | ||
} | ||
if cfg.TokenURL == "" { | ||
return errNoTokenURLProvided | ||
} | ||
return nil | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,118 @@ | ||
// Copyright The OpenTelemetry Authors | ||
// | ||
// Licensed under the Apache License, Version 2.0 (the "License"); | ||
// you may not use this file except in compliance with the License. | ||
// You may obtain a copy of the License at | ||
// | ||
// http://www.apache.org/licenses/LICENSE-2.0 | ||
// | ||
// Unless required by applicable law or agreed to in writing, software | ||
// distributed under the License is distributed on an "AS IS" BASIS, | ||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
// See the License for the specific language governing permissions and | ||
// limitations under the License. | ||
|
||
package oauth2clientauthextension | ||
|
||
import ( | ||
"path" | ||
"testing" | ||
"time" | ||
|
||
"github.com/stretchr/testify/assert" | ||
"github.com/stretchr/testify/require" | ||
"go.opentelemetry.io/collector/component/componenttest" | ||
"go.opentelemetry.io/collector/config" | ||
"go.opentelemetry.io/collector/config/configtest" | ||
"go.opentelemetry.io/collector/config/configtls" | ||
) | ||
|
||
func TestLoadConfig(t *testing.T) { | ||
factories, err := componenttest.NopFactories() | ||
assert.NoError(t, err) | ||
|
||
factory := NewFactory() | ||
factories.Extensions[typeStr] = factory | ||
cfg, err := configtest.LoadConfigAndValidate(path.Join(".", "testdata", "config.yaml"), factories) | ||
|
||
require.NoError(t, err) | ||
require.NotNil(t, cfg) | ||
|
||
expected := factory.CreateDefaultConfig().(*Config) | ||
expected.ClientSecret = "someclientsecret" | ||
expected.ClientID = "someclientid" | ||
expected.Scopes = []string{"api.metrics"} | ||
expected.TokenURL = "https://example.com/oauth2/default/v1/token" | ||
|
||
ext := cfg.Extensions[config.NewIDWithName(typeStr, "1")] | ||
assert.Equal(t, | ||
&Config{ | ||
ExtensionSettings: config.NewExtensionSettings(config.NewIDWithName(typeStr, "1")), | ||
ClientSecret: "someclientsecret", | ||
ClientID: "someclientid", | ||
Scopes: []string{"api.metrics"}, | ||
TokenURL: "https://example.com/oauth2/default/v1/token", | ||
Timeout: time.Second, | ||
}, | ||
ext) | ||
|
||
assert.Equal(t, 2, len(cfg.Service.Extensions)) | ||
assert.Equal(t, config.NewIDWithName(typeStr, "1"), cfg.Service.Extensions[0]) | ||
} | ||
|
||
func TestConfigTLSSettings(t *testing.T) { | ||
factories, err := componenttest.NopFactories() | ||
assert.NoError(t, err) | ||
|
||
factory := NewFactory() | ||
factories.Extensions[typeStr] = factory | ||
cfg, err := configtest.LoadConfigAndValidate(path.Join(".", "testdata", "config.yaml"), factories) | ||
|
||
require.NoError(t, err) | ||
require.NotNil(t, cfg) | ||
|
||
ext2 := cfg.Extensions[config.NewIDWithName(typeStr, "withtls")] | ||
|
||
cfg2 := ext2.(*Config) | ||
assert.Equal(t, cfg2.TLSSetting, configtls.TLSClientSetting{ | ||
TLSSetting: configtls.TLSSetting{ | ||
CAFile: "cafile", | ||
CertFile: "certfile", | ||
KeyFile: "keyfile", | ||
}, | ||
Insecure: true, | ||
InsecureSkipVerify: false, | ||
ServerName: "", | ||
}) | ||
} | ||
|
||
func TestLoadConfigError(t *testing.T) { | ||
factories, err := componenttest.NopFactories() | ||
assert.NoError(t, err) | ||
|
||
tests := []struct { | ||
configName string | ||
expectedErr error | ||
}{ | ||
{ | ||
"missingurl", | ||
errNoTokenURLProvided, | ||
}, | ||
{ | ||
"missingid", | ||
errNoClientIDProvided, | ||
}, | ||
{ | ||
"missingsecret", | ||
errNoClientSecretProvided, | ||
}, | ||
} | ||
for _, tt := range tests { | ||
factory := NewFactory() | ||
factories.Extensions[typeStr] = factory | ||
cfg, _ := configtest.LoadConfig(path.Join(".", "testdata", "config_bad.yaml"), factories) | ||
extension := cfg.Extensions[config.NewIDWithName(typeStr, tt.configName)] | ||
verr := extension.Validate() | ||
require.ErrorIs(t, verr, tt.expectedErr) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,19 @@ | ||
// Copyright The OpenTelemetry Authors | ||
// | ||
// Licensed under the Apache License, Version 2.0 (the "License"); | ||
// you may not use this file except in compliance with the License. | ||
// You may obtain a copy of the License at | ||
// | ||
// http://www.apache.org/licenses/LICENSE-2.0 | ||
// | ||
// Unless required by applicable law or agreed to in writing, software | ||
// distributed under the License is distributed on an "AS IS" BASIS, | ||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
// See the License for the specific language governing permissions and | ||
// limitations under the License. | ||
|
||
// Package oauth2clientcredentialsauthextension implements `configauth.ClientAuthenticator` | ||
// This extension provides OAuth2 Client Credentials flow authenticator for HTTP and gRPC based exporters. | ||
// The extension fetches and refreshes the token after expiry | ||
// For further details about OAuth2 Client Credentials flow refer https://datatracker.ietf.org/doc/html/rfc6749#section-4.4 | ||
package oauth2clientauthextension |
Oops, something went wrong.