Skip to content

Commit b005ca1

Browse files
[extension/externalauth] Add external proxy auth extension
Signed-off-by: Bogdan Stancu <[email protected]>
1 parent 6bc0201 commit b005ca1

26 files changed

+2150
-0
lines changed

.codecov.yml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -324,6 +324,10 @@ component_management:
324324
name: extension_datadog
325325
paths:
326326
- extension/datadogextension/**
327+
- component_id: extension_externalauth
328+
name: extension_externalauth
329+
paths:
330+
- extension/externalauthextension/**
327331
- component_id: extension_googleclientauth
328332
name: extension_googleclientauth
329333
paths:

.github/CODEOWNERS

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -106,6 +106,7 @@ extension/encoding/skywalkingencodingextension/ @open-telemetry
106106
extension/encoding/textencodingextension/ @open-telemetry/collector-contrib-approvers @MovieStoreGuy @atoulme
107107
extension/encoding/zipkinencodingextension/ @open-telemetry/collector-contrib-approvers @MovieStoreGuy @dao-jun
108108
extension/googleclientauthextension/ @open-telemetry/collector-contrib-approvers @dashpole @aabmass @braydonk @jsuereth @psx95 @ridwanmsharif
109+
extension/externalauthextension/ @open-telemetry/collector-contrib-approvers @bogdan-at-adobe
109110
extension/headerssetterextension/ @open-telemetry/collector-contrib-approvers @VihasMakwana
110111
extension/healthcheckextension/ @open-telemetry/collector-contrib-approvers
111112
extension/healthcheckv2extension/ @open-telemetry/collector-contrib-approvers @mwear

.github/component_labels.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,7 @@ extension/encoding/otlpencodingextension extension/encoding/otlpencoding
8686
extension/encoding/skywalkingencodingextension extension/encoding/skywalkingencoding
8787
extension/encoding/textencodingextension extension/encoding/textencoding
8888
extension/encoding/zipkinencodingextension extension/encoding/zipkinencoding
89+
extension/externalauthextension extension/externalauth
8990
extension/googleclientauthextension extension/googleclientauth
9091
extension/headerssetterextension extension/headerssetter
9192
extension/healthcheckextension extension/healthcheck

cmd/otelcontribcol/builder-config.yaml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ extensions:
2222
- gomod: github.com/open-telemetry/opentelemetry-collector-contrib/extension/basicauthextension v0.128.0
2323
- gomod: github.com/open-telemetry/opentelemetry-collector-contrib/extension/bearertokenauthextension v0.128.0
2424
- gomod: github.com/open-telemetry/opentelemetry-collector-contrib/extension/datadogextension v0.128.0
25+
- gomod: github.com/open-telemetry/opentelemetry-collector-contrib/extension/externalauthextension v0.128.0
2526
- gomod: github.com/open-telemetry/opentelemetry-collector-contrib/extension/googleclientauthextension v0.128.0
2627
- gomod: github.com/open-telemetry/opentelemetry-collector-contrib/extension/headerssetterextension v0.128.0
2728
- gomod: github.com/open-telemetry/opentelemetry-collector-contrib/extension/healthcheckextension v0.128.0
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
include ../../Makefile.Common
Lines changed: 115 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,115 @@
1+
# External Auth Extension
2+
3+
<!-- status autogenerated section -->
4+
| Status | |
5+
| ------------- |-----------|
6+
| Stability | [alpha] |
7+
| Distributions | [contrib] |
8+
| Issues | [![Open issues](https://img.shields.io/github/issues-search/open-telemetry/opentelemetry-collector-contrib?query=is%3Aissue%20is%3Aopen%20label%3Aextension%2Fexternalauth%20&label=open&color=orange&logo=opentelemetry)](https://github.com/open-telemetry/opentelemetry-collector-contrib/issues?q=is%3Aopen+is%3Aissue+label%3Aextension%2Fexternalauth) [![Closed issues](https://img.shields.io/github/issues-search/open-telemetry/opentelemetry-collector-contrib?query=is%3Aissue%20is%3Aclosed%20label%3Aextension%2Fexternalauth%20&label=closed&color=blue&logo=opentelemetry)](https://github.com/open-telemetry/opentelemetry-collector-contrib/issues?q=is%3Aclosed+is%3Aissue+label%3Aextension%2Fexternalauth) |
9+
| Code coverage | [![codecov](https://codecov.io/github/open-telemetry/opentelemetry-collector-contrib/graph/main/badge.svg?component=extension_externalauth)](https://app.codecov.io/gh/open-telemetry/opentelemetry-collector-contrib/tree/main/?components%5B0%5D=extension_externalauth&displayType=list) |
10+
| [Code Owners](https://github.com/open-telemetry/opentelemetry-collector-contrib/blob/main/CONTRIBUTING.md#becoming-a-code-owner) | [@bogdan-at-adobe](https://www.github.com/bogdan-at-adobe) \| Seeking more code owners! |
11+
| Emeritus | |
12+
13+
[alpha]: https://github.com/open-telemetry/opentelemetry-collector/blob/main/docs/component-stability.md#alpha
14+
[contrib]: https://github.com/open-telemetry/opentelemetry-collector-releases/tree/main/distributions/otelcol-contrib
15+
<!-- end autogenerated section -->
16+
17+
This extension implements `configauth.ServerAuthenticator` to authenticate incoming requests using an external authentication service. The authenticator type has to be set to `externalauth`.
18+
19+
The `externalauthextension` is an extension for the OpenTelemetry Collector that allows for external authentication of incoming requests. This extension can be used to integrate with external authentication services to validate credentials and enforce access control policies.
20+
21+
## Problem Statement
22+
23+
In distributed collector architectures where multiple collectors are chained together, authentication failures often go unnoticed until they reach the final collector in the chain. This creates several issues:
24+
25+
- **Silent failures**: Intermediate collectors show successful processing logs while authentication actually fails
26+
- **Debugging complexity**: Authentication issues are only visible at the last collector, making troubleshooting difficult
27+
- **Misleading metrics**: Success metrics from intermediate collectors don't reflect actual authentication status
28+
- **Delayed error detection**: Authentication problems are discovered late in the pipeline
29+
30+
The externalauth extension solves this by performing authentication validation at each collector in the chain, ensuring that authentication failures are detected and logged immediately where they occur.
31+
32+
## Features
33+
34+
- Supports integration with external authentication services
35+
- Validates incoming requests based on external authentication responses
36+
- Enforces access control policies
37+
- Token caching for performance optimization
38+
39+
## Configuration
40+
41+
The following settings are available:
42+
43+
- `endpoint` (required): The URL of the external authentication service
44+
- `refresh_interval` (default = "1h"): Specifies the time that a newly checked token will be valid for
45+
- `header` (default = "Authorization"): Specifies the header to use for the token
46+
- `scheme` (default = "Bearer"): Specifies the authentication scheme used for the request
47+
- `expected_codes` (default = [200]): Specifies the list of expected HTTP codes
48+
- `method` (default = "POST"): Specifies the HTTP method to use for the authentication request
49+
- `http_client_timeout` (default = "10s"): Specifies the timeout for the HTTP client when making requests to the external authentication service
50+
- `telemetry_type` (default = "traces"): Specifies the telemetry type for this endpoint. Options: `traces`, `metrics`, `logs`. Only used to be added as a label to metrics
51+
52+
### Example Configuration
53+
54+
```yaml
55+
extensions:
56+
externalauth:
57+
endpoint: "https://auth-endpoint"
58+
telemetry_type: "metrics" # This endpoint handles metrics
59+
60+
receivers:
61+
otlp:
62+
protocols:
63+
grpc:
64+
endpoint: "localhost:4317"
65+
auth:
66+
authenticator: externalauth
67+
68+
service:
69+
extensions: [externalauth]
70+
pipelines:
71+
traces:
72+
receivers: [otlp]
73+
processors: [batch]
74+
exporters: [otlp]
75+
```
76+
77+
### Advanced Configuration
78+
79+
```yaml
80+
extensions:
81+
externalauth/custom:
82+
endpoint: "https://custom-auth.example.com"
83+
refresh_interval: "30m"
84+
header: "X-Custom-Auth"
85+
expected_codes: [200, 201]
86+
scheme: "Custom"
87+
method: "GET"
88+
http_client_timeout: "5s"
89+
telemetry_type: "traces"
90+
91+
receivers:
92+
otlp:
93+
protocols:
94+
grpc:
95+
endpoint: "localhost:4317"
96+
auth:
97+
authenticator: externalauth/custom
98+
99+
service:
100+
extensions: [externalauth/custom]
101+
pipelines:
102+
traces:
103+
receivers: [otlp]
104+
processors: [batch]
105+
exporters: [otlp]
106+
```
107+
108+
## Usage
109+
110+
1. Configure the `externalauthextension` in your OpenTelemetry Collector configuration file
111+
2. Start the OpenTelemetry Collector
112+
3. The extension will intercept incoming requests and validate them using the configured external authentication service
113+
114+
The full list of settings exposed for this extension is documented in [config.go](./config.go)
115+
with detailed sample configurations in [testdata/config.yaml](./testdata/config.yaml).
Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
1+
package externalauthextension
2+
3+
import (
4+
"errors"
5+
"net/http"
6+
"net/url"
7+
"time"
8+
9+
"go.opentelemetry.io/collector/component"
10+
)
11+
12+
const (
13+
defaultRefreshInterval = "1h"
14+
defaultHeader = "Authorization"
15+
defaultExpectedCode = 200
16+
defaultScheme = "Bearer"
17+
defaultMethod = "POST"
18+
defaultHttpClientTimeout = 10 * time.Second
19+
defaultTelemetryType = "traces"
20+
)
21+
22+
type Config struct {
23+
// Endpoint specifies the endpoint to authenticate against. Required
24+
Endpoint string `mapstructure:"endpoint"`
25+
//RefreshInterval specifies the time that a newly checked token will be valid for. Defaults to "1h"
26+
RefreshInterval string `mapstructure:"refresh_interval,omitempty"`
27+
// Header specifies the header used in the request. Defaults to "Authorization"
28+
Header string `mapstructure:"header,omitempty"`
29+
// ExpectedCodes is a list of expected HTTP codes. Defaults to [200]
30+
ExpectedCodes []int `mapstructure:"expected_codes,omitempty"`
31+
// Scheme specifies the auth-scheme for the token. Defaults to "Bearer"
32+
Scheme string `mapstructure:"scheme,omitempty"`
33+
// Method specifies the HTTP method used in the request. Defaults to "POST"
34+
Method string `mapstructure:"method,omitempty"`
35+
// HTTPClientTimeout specifies the timeout for the HTTP client. Defaults to "10s"
36+
HTTPClientTimeout time.Duration `mapstructure:"http_client_timeout,omitempty"`
37+
// TelemetryType specifies the telemetry type for this endpoint. Options: "traces", "metrics", "logs". Defaults to "traces"
38+
TelemetryType string `mapstructure:"telemetry_type,omitempty"`
39+
}
40+
41+
var (
42+
_ component.Config = (*Config)(nil)
43+
errNoEndpointProvided = errors.New("no endpoint to authenticate against provided")
44+
errInvalidInterval = errors.New("invalid refresh interval")
45+
errInvalidEndpoint = errors.New("invalid remote endpoint")
46+
errInvalidHttpCode = errors.New("code provided is not a valid HTTP code")
47+
errInvalidTelemetryType = errors.New("telemetry_type must be one of: traces, metrics, logs")
48+
)
49+
50+
// Validate checks if the extension configuration is valid
51+
func (cfg *Config) Validate() error {
52+
if cfg.Endpoint == "" {
53+
return errNoEndpointProvided
54+
} else {
55+
_, err := url.ParseRequestURI(cfg.Endpoint)
56+
if err != nil {
57+
return errInvalidEndpoint
58+
}
59+
}
60+
if cfg.RefreshInterval == "" {
61+
cfg.RefreshInterval = defaultRefreshInterval
62+
} else {
63+
_, err := time.ParseDuration(cfg.RefreshInterval)
64+
if err != nil {
65+
return errInvalidInterval
66+
}
67+
}
68+
if cfg.Header == "" {
69+
cfg.Header = defaultHeader
70+
}
71+
if cfg.HTTPClientTimeout == 0 {
72+
cfg.HTTPClientTimeout = defaultHttpClientTimeout
73+
}
74+
if cfg.ExpectedCodes == nil {
75+
cfg.ExpectedCodes = []int{defaultExpectedCode}
76+
} else {
77+
for _, code := range cfg.ExpectedCodes {
78+
if http.StatusText(code) == "" {
79+
return errInvalidHttpCode
80+
}
81+
}
82+
}
83+
if cfg.Scheme == "" {
84+
cfg.Scheme = defaultScheme
85+
}
86+
if cfg.Method == "" {
87+
cfg.Method = defaultMethod
88+
}
89+
if cfg.TelemetryType == "" {
90+
cfg.TelemetryType = defaultTelemetryType
91+
} else {
92+
validTypes := map[string]bool{
93+
"traces": true,
94+
"metrics": true,
95+
"logs": true,
96+
}
97+
if !validTypes[cfg.TelemetryType] {
98+
return errInvalidTelemetryType
99+
}
100+
}
101+
return nil
102+
}
Lines changed: 129 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,129 @@
1+
package externalauthextension
2+
3+
import (
4+
"path/filepath"
5+
"testing"
6+
"time"
7+
8+
"github.com/stretchr/testify/assert"
9+
"github.com/stretchr/testify/require"
10+
"go.opentelemetry.io/collector/component"
11+
"go.opentelemetry.io/collector/confmap/confmaptest"
12+
"go.opentelemetry.io/collector/confmap/xconfmap"
13+
14+
"github.com/open-telemetry/opentelemetry-collector-contrib/extension/externalauthextension/internal/metadata"
15+
)
16+
17+
func TestLoadConfig(t *testing.T) {
18+
t.Parallel()
19+
20+
tests := []struct {
21+
id component.ID
22+
expected component.Config
23+
expectedErr bool
24+
}{
25+
{
26+
id: component.NewIDWithName(metadata.Type, "valid"),
27+
expected: &Config{
28+
Endpoint: "https://auth.example.com/validate",
29+
RefreshInterval: "1h",
30+
Header: "Authorization",
31+
ExpectedCodes: []int{200},
32+
Scheme: "Bearer",
33+
Method: "POST",
34+
HTTPClientTimeout: 10 * time.Second,
35+
TelemetryType: "traces",
36+
},
37+
},
38+
{
39+
id: component.NewIDWithName(metadata.Type, "metrics"),
40+
expected: &Config{
41+
Endpoint: "https://auth.example.com/validate",
42+
RefreshInterval: "1h",
43+
Header: "Authorization",
44+
ExpectedCodes: []int{200},
45+
Scheme: "Bearer",
46+
Method: "POST",
47+
HTTPClientTimeout: 10 * time.Second,
48+
TelemetryType: "metrics",
49+
},
50+
},
51+
{
52+
id: component.NewIDWithName(metadata.Type, "logs"),
53+
expected: &Config{
54+
Endpoint: "https://auth.example.com/validate",
55+
RefreshInterval: "1h",
56+
Header: "Authorization",
57+
ExpectedCodes: []int{200},
58+
Scheme: "Bearer",
59+
Method: "POST",
60+
HTTPClientTimeout: 10 * time.Second,
61+
TelemetryType: "logs",
62+
},
63+
},
64+
{
65+
id: component.NewIDWithName(metadata.Type, "custom_settings"),
66+
expected: &Config{
67+
Endpoint: "https://custom-auth.example.com",
68+
RefreshInterval: "30m",
69+
Header: "X-Custom-Auth",
70+
ExpectedCodes: []int{200, 201},
71+
Scheme: "Custom",
72+
Method: "GET",
73+
HTTPClientTimeout: 5 * time.Second,
74+
TelemetryType: "traces",
75+
},
76+
},
77+
{
78+
id: component.NewIDWithName(metadata.Type, "k8s_dns"),
79+
expected: &Config{
80+
Endpoint: "dns:///auth-service.namespace.svc.cluster.local",
81+
RefreshInterval: "1h",
82+
Header: "Authorization",
83+
ExpectedCodes: []int{200},
84+
Scheme: "Bearer",
85+
Method: "POST",
86+
HTTPClientTimeout: 10 * time.Second,
87+
TelemetryType: "traces",
88+
},
89+
},
90+
{
91+
id: component.NewIDWithName(metadata.Type, "missing_endpoint"),
92+
expectedErr: true,
93+
},
94+
{
95+
id: component.NewIDWithName(metadata.Type, "invalid_endpoint"),
96+
expectedErr: true,
97+
},
98+
{
99+
id: component.NewIDWithName(metadata.Type, "invalid_interval"),
100+
expectedErr: true,
101+
},
102+
{
103+
id: component.NewIDWithName(metadata.Type, "invalid_http_code"),
104+
expectedErr: true,
105+
},
106+
{
107+
id: component.NewIDWithName(metadata.Type, "invalid_telemetry_type"),
108+
expectedErr: true,
109+
},
110+
}
111+
112+
for _, tt := range tests {
113+
t.Run(tt.id.String(), func(t *testing.T) {
114+
cm, err := confmaptest.LoadConf(filepath.Join("testdata", "config.yaml"))
115+
require.NoError(t, err)
116+
factory := NewFactory()
117+
cfg := factory.CreateDefaultConfig()
118+
sub, err := cm.Sub(tt.id.String())
119+
require.NoError(t, err)
120+
require.NoError(t, sub.Unmarshal(cfg))
121+
if tt.expectedErr {
122+
assert.Error(t, xconfmap.Validate(cfg))
123+
return
124+
}
125+
assert.NoError(t, xconfmap.Validate(cfg))
126+
assert.Equal(t, tt.expected, cfg)
127+
})
128+
}
129+
}
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
// Copyright The OpenTelemetry Authors
2+
// SPDX-License-Identifier: Apache-2.0
3+
4+
//go:generate mdatagen metadata.yaml
5+
6+
// Package externalauthextension implements an extension offering external authentication for incoming requests.
7+
package externalauthextension // import "github.com/open-telemetry/opentelemetry-collector-contrib/extension/externalauthextension"

0 commit comments

Comments
 (0)