Skip to content

Commit f88562c

Browse files
authored
Add oauth2authextension from upstream with no code changes (#249)
1 parent 4ee04fc commit f88562c

22 files changed

+1291
-1
lines changed
Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
# Authenticator - OAuth2 Client Credentials
2+
<!-- status autogenerated section -->
3+
| Status | |
4+
| ------------- |-----------|
5+
| Stability | [beta] |
6+
| Distributions | [contrib], [k8s] |
7+
| Issues | [![Open issues](https://img.shields.io/github/issues-search/open-telemetry/opentelemetry-collector-contrib?query=is%3Aissue%20is%3Aopen%20label%3Aextension%2Foauth2clientauth%20&label=open&color=orange&logo=opentelemetry)](https://github.com/open-telemetry/opentelemetry-collector-contrib/issues?q=is%3Aopen+is%3Aissue+label%3Aextension%2Foauth2clientauth) [![Closed issues](https://img.shields.io/github/issues-search/open-telemetry/opentelemetry-collector-contrib?query=is%3Aissue%20is%3Aclosed%20label%3Aextension%2Foauth2clientauth%20&label=closed&color=blue&logo=opentelemetry)](https://github.com/open-telemetry/opentelemetry-collector-contrib/issues?q=is%3Aclosed+is%3Aissue+label%3Aextension%2Foauth2clientauth) |
8+
| [Code Owners](https://github.com/open-telemetry/opentelemetry-collector-contrib/blob/main/CONTRIBUTING.md#becoming-a-code-owner) | [@pavankrish123](https://www.github.com/pavankrish123), [@jpkrohling](https://www.github.com/jpkrohling) |
9+
10+
[beta]: https://github.com/open-telemetry/opentelemetry-collector/blob/main/docs/component-stability.md#beta
11+
[contrib]: https://github.com/open-telemetry/opentelemetry-collector-releases/tree/main/distributions/otelcol-contrib
12+
[k8s]: https://github.com/open-telemetry/opentelemetry-collector-releases/tree/main/distributions/otelcol-k8s
13+
<!-- end autogenerated section -->
14+
15+
Copy of the goauth2clientauthextension.
16+
17+
This extension provides OAuth2 Client Credentials flow authenticator for HTTP and gRPC based exporters. The extension
18+
fetches and refreshes the token after expiry automatically. For further details about OAuth2 Client Credentials flow (2-legged workflow)
19+
refer https://datatracker.ietf.org/doc/html/rfc6749#section-4.4.
20+
21+
The authenticator type has to be set to `oauth2client`.
22+
23+
## Configuration
24+
25+
```yaml
26+
extensions:
27+
oauth2client:
28+
client_id: someclientid
29+
client_secret: someclientsecret
30+
endpoint_params:
31+
audience: someaudience
32+
token_url: https://example.com/oauth2/default/v1/token
33+
scopes: ["api.metrics"]
34+
# tls settings for the token client
35+
tls:
36+
insecure: true
37+
ca_file: /var/lib/mycert.pem
38+
cert_file: certfile
39+
key_file: keyfile
40+
# timeout for the token client
41+
timeout: 2s
42+
# buffer time before token expiry to refresh
43+
expiry_buffer: 10s
44+
45+
receivers:
46+
hostmetrics:
47+
scrapers:
48+
memory:
49+
otlp:
50+
protocols:
51+
grpc:
52+
53+
exporters:
54+
otlphttp/withauth:
55+
endpoint: http://localhost:9000
56+
auth:
57+
authenticator: oauth2client
58+
59+
otlp/withauth:
60+
endpoint: 0.0.0.0:5000
61+
tls:
62+
ca_file: /tmp/certs/ca.pem
63+
auth:
64+
authenticator: oauth2client
65+
66+
service:
67+
extensions: [oauth2client]
68+
pipelines:
69+
metrics:
70+
receivers: [hostmetrics]
71+
processors: []
72+
exporters: [otlphttp/withauth, otlp/withauth]
73+
```
74+
75+
Following are the configuration fields
76+
77+
- [**token_url**](https://datatracker.ietf.org/doc/html/rfc6749#section-3.2) - The resource server's token endpoint URLs.
78+
- [**client_id**](https://datatracker.ietf.org/doc/html/rfc6749#section-2.2) - The client identifier issued to the client.
79+
- **client_id_file** - The file path to retrieve the client identifier issued to the client.
80+
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 -->
81+
This setting takes precedence over `client_id`.
82+
- [**client_secret**](https://datatracker.ietf.org/doc/html/rfc6749#section-2.3.1) - The secret string associated with above identifier.
83+
- **client_secret_file** - The file path to retrieve the secret string associated with above identifier.
84+
The extension reads this file and updates the client secret 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 -->
85+
This setting takes precedence over `client_secret`.
86+
- [**endpoint_params**](https://github.com/golang/oauth2/blob/master/clientcredentials/clientcredentials.go#L44) - Additional parameters that are sent to the token endpoint.
87+
- [**scopes**](https://datatracker.ietf.org/doc/html/rfc6749#section-3.3) - **Optional** optional requested permissions associated for the client.
88+
- [**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).
89+
This is optional and not setting this configuration implies there is no timeout on the client.
90+
- **expiry_buffer** - **Optional** Specifies the time buffer to refresh the access token before it expires, preventing authentication failures due to token expiration. The default value is 5m.
91+
92+
For more information on client side TLS settings, see [configtls README](https://github.com/open-telemetry/opentelemetry-collector/tree/main/config/configtls).
Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,104 @@
1+
// Copyright The OpenTelemetry Authors
2+
// SPDX-License-Identifier: Apache-2.0
3+
4+
package oauth2clientauthextension // import "github.com/open-telemetry/opentelemetry-collector-contrib/extension/oauth2clientauthextension"
5+
6+
import (
7+
"context"
8+
"fmt"
9+
"os"
10+
"strings"
11+
"time"
12+
13+
"go.uber.org/multierr"
14+
"golang.org/x/oauth2"
15+
"golang.org/x/oauth2/clientcredentials"
16+
)
17+
18+
// clientCredentialsConfig is a clientcredentials.Config wrapper to allow
19+
// values read from files in the ClientID and ClientSecret fields.
20+
//
21+
// Values from files can be retrieved by populating the ClientIDFile or
22+
// the ClientSecretFile fields with the path to the file.
23+
//
24+
// Priority: File > Raw value
25+
//
26+
// Example - Retrieve secret from file:
27+
//
28+
// cfg := clientCredentialsConfig{
29+
// Config: clientcredentials.Config{
30+
// ClientID: "clientId",
31+
// ...
32+
// },
33+
// ClientSecretFile: "/path/to/client/secret",
34+
// }
35+
type clientCredentialsConfig struct {
36+
clientcredentials.Config
37+
38+
ClientIDFile string
39+
ClientSecretFile string
40+
ExpiryBuffer time.Duration
41+
}
42+
43+
type clientCredentialsTokenSource struct {
44+
ctx context.Context
45+
config *clientCredentialsConfig
46+
}
47+
48+
// clientCredentialsTokenSource implements TokenSource
49+
var _ oauth2.TokenSource = (*clientCredentialsTokenSource)(nil)
50+
51+
func readCredentialsFile(path string) (string, error) {
52+
f, err := os.ReadFile(path)
53+
if err != nil {
54+
return "", fmt.Errorf("failed to read credentials file %q: %w", path, err)
55+
}
56+
57+
credential := strings.TrimSpace(string(f))
58+
if credential == "" {
59+
return "", fmt.Errorf("empty credentials file %q", path)
60+
}
61+
return credential, nil
62+
}
63+
64+
func getActualValue(value, filepath string) (string, error) {
65+
if len(filepath) > 0 {
66+
return readCredentialsFile(filepath)
67+
}
68+
69+
return value, nil
70+
}
71+
72+
// createConfig creates a proper clientcredentials.Config with values retrieved
73+
// from files, if the user has specified '*_file' values
74+
func (c *clientCredentialsConfig) createConfig() (*clientcredentials.Config, error) {
75+
clientID, err := getActualValue(c.ClientID, c.ClientIDFile)
76+
if err != nil {
77+
return nil, multierr.Combine(errNoClientIDProvided, err)
78+
}
79+
80+
clientSecret, err := getActualValue(c.ClientSecret, c.ClientSecretFile)
81+
if err != nil {
82+
return nil, multierr.Combine(errNoClientSecretProvided, err)
83+
}
84+
85+
return &clientcredentials.Config{
86+
ClientID: clientID,
87+
ClientSecret: clientSecret,
88+
TokenURL: c.TokenURL,
89+
Scopes: c.Scopes,
90+
EndpointParams: c.EndpointParams,
91+
}, nil
92+
}
93+
94+
func (c *clientCredentialsConfig) TokenSource(ctx context.Context) oauth2.TokenSource {
95+
return oauth2.ReuseTokenSourceWithExpiry(nil, clientCredentialsTokenSource{ctx: ctx, config: c}, c.ExpiryBuffer)
96+
}
97+
98+
func (ts clientCredentialsTokenSource) Token() (*oauth2.Token, error) {
99+
cfg, err := ts.config.createConfig()
100+
if err != nil {
101+
return nil, err
102+
}
103+
return cfg.TokenSource(ts.ctx).Token()
104+
}
Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
// Copyright The OpenTelemetry Authors
2+
// SPDX-License-Identifier: Apache-2.0
3+
4+
package oauth2clientauthextension // import "github.com/open-telemetry/opentelemetry-collector-contrib/extension/oauth2clientauthextension"
5+
6+
import (
7+
"errors"
8+
"net/url"
9+
"time"
10+
11+
"go.opentelemetry.io/collector/component"
12+
"go.opentelemetry.io/collector/config/configopaque"
13+
"go.opentelemetry.io/collector/config/configtls"
14+
)
15+
16+
var (
17+
errNoClientIDProvided = errors.New("no ClientID provided in the OAuth2 exporter configuration")
18+
errNoTokenURLProvided = errors.New("no TokenURL provided in OAuth Client Credentials configuration")
19+
errNoClientSecretProvided = errors.New("no ClientSecret provided in OAuth Client Credentials configuration")
20+
)
21+
22+
// Config stores the configuration for OAuth2 Client Credentials (2-legged OAuth2 flow) setup.
23+
type Config struct {
24+
// ClientID is the application's ID.
25+
// See https://datatracker.ietf.org/doc/html/rfc6749#section-2.2
26+
ClientID string `mapstructure:"client_id"`
27+
28+
// ClientIDFile is the file path to read the application's ID from.
29+
ClientIDFile string `mapstructure:"client_id_file"`
30+
31+
// ClientSecret is the application's secret.
32+
// See https://datatracker.ietf.org/doc/html/rfc6749#section-2.3.1
33+
ClientSecret configopaque.String `mapstructure:"client_secret"`
34+
35+
// ClientSecretFile is the file pathg to read the application's secret from.
36+
ClientSecretFile string `mapstructure:"client_secret_file"`
37+
38+
// EndpointParams specifies additional parameters for requests to the token endpoint.
39+
EndpointParams url.Values `mapstructure:"endpoint_params"`
40+
41+
// TokenURL is the resource server's token endpoint
42+
// URL. This is a constant specific to each server.
43+
// See https://datatracker.ietf.org/doc/html/rfc6749#section-3.2
44+
TokenURL string `mapstructure:"token_url"`
45+
46+
// Scope specifies optional requested permissions.
47+
// See https://datatracker.ietf.org/doc/html/rfc6749#section-3.3
48+
Scopes []string `mapstructure:"scopes,omitempty"`
49+
50+
// TLSSetting struct exposes TLS client configuration for the underneath client to authorization server.
51+
TLSSetting configtls.ClientConfig `mapstructure:"tls,omitempty"`
52+
53+
// Timeout parameter configures `http.Client.Timeout` for the underneath client to authorization
54+
// server while fetching and refreshing tokens.
55+
Timeout time.Duration `mapstructure:"timeout,omitempty"`
56+
57+
// ExpiryBuffer specifies the time buffer before token expiry to refresh it.
58+
ExpiryBuffer time.Duration `mapstructure:"expiry_buffer,omitempty"`
59+
}
60+
61+
var _ component.Config = (*Config)(nil)
62+
63+
// Validate checks if the extension configuration is valid
64+
func (cfg *Config) Validate() error {
65+
if cfg.ClientID == "" && cfg.ClientIDFile == "" {
66+
return errNoClientIDProvided
67+
}
68+
if cfg.ClientSecret == "" && cfg.ClientSecretFile == "" {
69+
return errNoClientSecretProvided
70+
}
71+
if cfg.TokenURL == "" {
72+
return errNoTokenURLProvided
73+
}
74+
return nil
75+
}
Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
// Copyright The OpenTelemetry Authors
2+
// SPDX-License-Identifier: Apache-2.0
3+
4+
package oauth2clientauthextension
5+
6+
import (
7+
"net/url"
8+
"path/filepath"
9+
"testing"
10+
"time"
11+
12+
"github.com/stretchr/testify/assert"
13+
"github.com/stretchr/testify/require"
14+
"go.opentelemetry.io/collector/component"
15+
"go.opentelemetry.io/collector/config/configtls"
16+
"go.opentelemetry.io/collector/confmap/confmaptest"
17+
"go.opentelemetry.io/collector/confmap/xconfmap"
18+
19+
"github.com/open-telemetry/opentelemetry-collector-contrib/extension/oauth2clientauthextension/internal/metadata"
20+
)
21+
22+
func TestLoadConfig(t *testing.T) {
23+
t.Parallel()
24+
25+
tests := []struct {
26+
id component.ID
27+
expected component.Config
28+
expectedErr error
29+
}{
30+
{
31+
id: component.NewID(metadata.Type),
32+
expected: &Config{
33+
ClientSecret: "someclientsecret",
34+
ClientID: "someclientid",
35+
EndpointParams: url.Values{"audience": []string{"someaudience"}},
36+
Scopes: []string{"api.metrics"},
37+
TokenURL: "https://example.com/oauth2/default/v1/token",
38+
Timeout: time.Second,
39+
ExpiryBuffer: 5 * time.Minute,
40+
},
41+
},
42+
{
43+
id: component.NewIDWithName(metadata.Type, "withtls"),
44+
expected: &Config{
45+
ClientSecret: "someclientsecret2",
46+
ClientID: "someclientid2",
47+
Scopes: []string{"api.metrics"},
48+
TokenURL: "https://example2.com/oauth2/default/v1/token",
49+
Timeout: time.Second,
50+
TLSSetting: configtls.ClientConfig{
51+
Config: configtls.Config{
52+
CAFile: "cafile",
53+
CertFile: "certfile",
54+
KeyFile: "keyfile",
55+
},
56+
Insecure: true,
57+
InsecureSkipVerify: false,
58+
ServerName: "",
59+
},
60+
ExpiryBuffer: 15 * time.Second,
61+
},
62+
},
63+
{
64+
id: component.NewIDWithName(metadata.Type, "missingurl"),
65+
expectedErr: errNoTokenURLProvided,
66+
},
67+
{
68+
id: component.NewIDWithName(metadata.Type, "missingid"),
69+
expectedErr: errNoClientIDProvided,
70+
},
71+
{
72+
id: component.NewIDWithName(metadata.Type, "missingsecret"),
73+
expectedErr: errNoClientSecretProvided,
74+
},
75+
}
76+
for _, tt := range tests {
77+
t.Run(tt.id.String(), func(t *testing.T) {
78+
cm, err := confmaptest.LoadConf(filepath.Join("testdata", "config.yaml"))
79+
require.NoError(t, err)
80+
factory := NewFactory()
81+
cfg := factory.CreateDefaultConfig()
82+
sub, err := cm.Sub(tt.id.String())
83+
require.NoError(t, err)
84+
require.NoError(t, sub.Unmarshal(cfg))
85+
if tt.expectedErr != nil {
86+
assert.ErrorIs(t, xconfmap.Validate(cfg), tt.expectedErr)
87+
return
88+
}
89+
assert.NoError(t, xconfmap.Validate(cfg))
90+
assert.Equal(t, tt.expected, cfg)
91+
})
92+
}
93+
}
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
// Copyright The OpenTelemetry Authors
2+
// SPDX-License-Identifier: Apache-2.0
3+
4+
//go:generate mdatagen metadata.yaml
5+
6+
// Package oauth2clientauthextension implements `cauth.Client`
7+
// This extension provides OAuth2 Client Credentials flow authenticator for HTTP and gRPC based exporters.
8+
// The extension fetches and refreshes the token after expiry
9+
// For further details about OAuth2 Client Credentials flow refer https://datatracker.ietf.org/doc/html/rfc6749#section-4.4
10+
package oauth2clientauthextension // import "github.com/open-telemetry/opentelemetry-collector-contrib/extension/oauth2clientauthextension"

0 commit comments

Comments
 (0)