Skip to content

Commit 3d25513

Browse files
pavankrish123dashpole
authored andcommitted
Support for Custom Exporter Authenticators as Extensions (open-telemetry#3128)
This PR adds support to add client side (exporter) authenticators for HTTP and gRPC clients through extension based authenticators. This is built of top of what was added for receiver (server) side authenticators via extensions in open-telemetry#2603 **Link to tracking Issue:** open-telemetry#3115 **Testing:** - Did a manual testing for static bearer token. - Added unit tests
1 parent d718ed2 commit 3d25513

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

49 files changed

+1315
-338
lines changed

config/configauth/clientauth.go

Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
// Copyright The OpenTelemetry Authors
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
package configauth
16+
17+
import (
18+
"fmt"
19+
"net/http"
20+
21+
"google.golang.org/grpc/credentials"
22+
23+
"go.opentelemetry.io/collector/component"
24+
"go.opentelemetry.io/collector/config"
25+
)
26+
27+
// ClientAuthenticator is an Extension that can be used as an authenticator for the configauth.Authentication option.
28+
// Authenticators are then included as part of OpenTelemetry Collector builds and can be referenced by their
29+
// names from the Authentication configuration.
30+
type ClientAuthenticator interface {
31+
component.Extension
32+
}
33+
34+
// HTTPClientAuthenticator is a ClientAuthenticator that can be used as an authenticator
35+
// for the configauth.Authentication option for HTTP clients.
36+
type HTTPClientAuthenticator interface {
37+
ClientAuthenticator
38+
RoundTripper(base http.RoundTripper) (http.RoundTripper, error)
39+
}
40+
41+
// GRPCClientAuthenticator is a ClientAuthenticator that can be used as an authenticator for
42+
// the configauth.Authentication option for gRPC clients.
43+
type GRPCClientAuthenticator interface {
44+
ClientAuthenticator
45+
PerRPCCredentials() (credentials.PerRPCCredentials, error)
46+
}
47+
48+
// GetHTTPClientAuthenticator attempts to select the appropriate HTTPClientAuthenticator from the list of extensions,
49+
// based on the component id of the extension. If an authenticator is not found, an error is returned.
50+
// This should be only used by HTTP clients.
51+
func GetHTTPClientAuthenticator(extensions map[config.ComponentID]component.Extension,
52+
componentID config.ComponentID) (HTTPClientAuthenticator, error) {
53+
for name, ext := range extensions {
54+
if name == componentID {
55+
if auth, ok := ext.(HTTPClientAuthenticator); ok {
56+
return auth, nil
57+
}
58+
return nil, fmt.Errorf("requested authenticator is not for HTTP clients")
59+
}
60+
}
61+
return nil, fmt.Errorf("failed to resolve authenticator %q: %w", componentID.String(), errAuthenticatorNotFound)
62+
}
63+
64+
// GetGRPCClientAuthenticator attempts to select the appropriate GRPCClientAuthenticator from the list of extensions,
65+
// based on the component id of the extension. If an authenticator is not found, an error is returned.
66+
// This should only be used by gRPC clients.
67+
func GetGRPCClientAuthenticator(extensions map[config.ComponentID]component.Extension,
68+
componentID config.ComponentID) (GRPCClientAuthenticator, error) {
69+
for name, ext := range extensions {
70+
if name == componentID {
71+
if auth, ok := ext.(GRPCClientAuthenticator); ok {
72+
return auth, nil
73+
}
74+
return nil, fmt.Errorf("requested authenticator is not for gRPC clients")
75+
}
76+
}
77+
return nil, fmt.Errorf("failed to resolve authenticator %q: %w", componentID.String(), errAuthenticatorNotFound)
78+
}

config/configauth/configauth.go

Lines changed: 7 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -23,35 +23,25 @@ import (
2323
)
2424

2525
var (
26-
errAuthenticatorNotFound = errors.New("authenticator not found")
27-
errAuthenticatorNotProvided = errors.New("authenticator not provided")
26+
errAuthenticatorNotFound = errors.New("authenticator not found")
2827
)
2928

3029
// Authentication defines the auth settings for the receiver
3130
type Authentication struct {
32-
// Authenticator specifies the name of the extension to use in order to authenticate the incoming data point.
31+
// AuthenticatorName specifies the name of the extension to use in order to authenticate the incoming data point.
3332
AuthenticatorName string `mapstructure:"authenticator"`
3433
}
3534

36-
// GetAuthenticator attempts to select the appropriate from the list of extensions, based on the requested extension name.
35+
// GetServerAuthenticator attempts to select the appropriate from the list of extensions, based on the requested extension name.
3736
// If an authenticator is not found, an error is returned.
38-
func GetAuthenticator(extensions map[config.ComponentID]component.Extension, requested string) (Authenticator, error) {
39-
if requested == "" {
40-
return nil, errAuthenticatorNotProvided
41-
}
42-
43-
reqID, err := config.NewIDFromString(requested)
44-
if err != nil {
45-
return nil, err
46-
}
47-
37+
func GetServerAuthenticator(extensions map[config.ComponentID]component.Extension, componentID config.ComponentID) (ServerAuthenticator, error) {
4838
for name, ext := range extensions {
49-
if auth, ok := ext.(Authenticator); ok {
50-
if name == reqID {
39+
if auth, ok := ext.(ServerAuthenticator); ok {
40+
if name == componentID {
5141
return auth, nil
5242
}
5343
}
5444
}
5545

56-
return nil, fmt.Errorf("failed to resolve authenticator %q: %w", requested, errAuthenticatorNotFound)
46+
return nil, fmt.Errorf("failed to resolve authenticator %q: %w", componentID.String(), errAuthenticatorNotFound)
5747
}

config/configauth/configauth_test.go

Lines changed: 8 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,10 @@ func TestGetAuthenticator(t *testing.T) {
3333
}
3434

3535
// test
36-
authenticator, err := GetAuthenticator(ext, cfg.AuthenticatorName)
36+
componentID, err := config.NewIDFromString(cfg.AuthenticatorName)
37+
assert.NoError(t, err)
38+
39+
authenticator, err := GetServerAuthenticator(ext, componentID)
3740

3841
// verify
3942
assert.NoError(t, err)
@@ -48,13 +51,7 @@ func TestGetAuthenticatorFails(t *testing.T) {
4851
expected error
4952
}{
5053
{
51-
desc: "Authenticator not provided",
52-
cfg: &Authentication{},
53-
ext: map[config.ComponentID]component.Extension{},
54-
expected: errAuthenticatorNotProvided,
55-
},
56-
{
57-
desc: "Authenticator not found",
54+
desc: "ServerAuthenticator not found",
5855
cfg: &Authentication{
5956
AuthenticatorName: "does-not-exist",
6057
},
@@ -64,7 +61,9 @@ func TestGetAuthenticatorFails(t *testing.T) {
6461
}
6562
for _, tC := range testCases {
6663
t.Run(tC.desc, func(t *testing.T) {
67-
authenticator, err := GetAuthenticator(tC.ext, tC.cfg.AuthenticatorName)
64+
componentID, err := config.NewIDFromString(tC.cfg.AuthenticatorName)
65+
assert.NoError(t, err)
66+
authenticator, err := GetServerAuthenticator(tC.ext, componentID)
6867
assert.ErrorIs(t, err, tC.expected)
6968
assert.Nil(t, authenticator)
7069
})
Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
// Copyright The OpenTelemetry Authors
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
package configauth
16+
17+
import (
18+
"context"
19+
"errors"
20+
"net/http"
21+
22+
"google.golang.org/grpc/credentials"
23+
24+
"go.opentelemetry.io/collector/component"
25+
)
26+
27+
var (
28+
_ HTTPClientAuthenticator = (*MockClientAuthenticator)(nil)
29+
_ GRPCClientAuthenticator = (*MockClientAuthenticator)(nil)
30+
errMockError = errors.New("mock Error")
31+
)
32+
33+
// MockClientAuthenticator provides a mock implementation of GRPCClientAuthenticator and HTTPClientAuthenticator interfaces
34+
type MockClientAuthenticator struct {
35+
ResultRoundTripper http.RoundTripper
36+
ResultPerRPCCredentials credentials.PerRPCCredentials
37+
MustError bool
38+
}
39+
40+
// Start for the MockClientAuthenticator does nothing
41+
func (m *MockClientAuthenticator) Start(ctx context.Context, host component.Host) error {
42+
return nil
43+
}
44+
45+
// Shutdown for the MockClientAuthenticator does nothing
46+
func (m *MockClientAuthenticator) Shutdown(ctx context.Context) error {
47+
return nil
48+
}
49+
50+
// RoundTripper for the MockClientAuthenticator either returns error if the mock authenticator is forced to or
51+
// returns the supplied resultRoundTripper.
52+
func (m *MockClientAuthenticator) RoundTripper(base http.RoundTripper) (http.RoundTripper, error) {
53+
if m.MustError {
54+
return nil, errMockError
55+
}
56+
return m.ResultRoundTripper, nil
57+
}
58+
59+
// PerRPCCredentials for the MockClientAuthenticator either returns error if the mock authenticator is forced to or
60+
// returns the supplied resultPerRPCCredentials.
61+
func (m *MockClientAuthenticator) PerRPCCredentials() (credentials.PerRPCCredentials, error) {
62+
if m.MustError {
63+
return nil, errMockError
64+
}
65+
return m.ResultPerRPCCredentials, nil
66+
}
Lines changed: 139 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,139 @@
1+
// Copyright The OpenTelemetry Authors
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
package configauth
16+
17+
import (
18+
"context"
19+
"net/http"
20+
"testing"
21+
22+
"github.com/stretchr/testify/assert"
23+
"google.golang.org/grpc/credentials"
24+
)
25+
26+
func TestNilStartAndShutdown(t *testing.T) {
27+
// prepare
28+
m := &MockClientAuthenticator{}
29+
30+
// test and verify
31+
origCtx := context.Background()
32+
33+
err := m.Start(origCtx, nil)
34+
assert.NoError(t, err)
35+
36+
err = m.Shutdown(origCtx)
37+
assert.NoError(t, err)
38+
}
39+
40+
type customRoundTripper struct{}
41+
42+
func (c *customRoundTripper) RoundTrip(request *http.Request) (*http.Response, error) {
43+
return nil, nil
44+
}
45+
46+
func TestMockRoundTripper(t *testing.T) {
47+
testcases := []struct {
48+
name string
49+
expectedErr bool
50+
clientAuth MockClientAuthenticator
51+
}{
52+
{
53+
name: "no_error",
54+
expectedErr: false,
55+
clientAuth: MockClientAuthenticator{
56+
ResultRoundTripper: &customRoundTripper{},
57+
MustError: false,
58+
},
59+
},
60+
{
61+
name: "error",
62+
expectedErr: true,
63+
clientAuth: MockClientAuthenticator{
64+
ResultRoundTripper: &customRoundTripper{},
65+
MustError: true,
66+
},
67+
},
68+
}
69+
70+
for _, testcase := range testcases {
71+
t.Run(testcase.name, func(t *testing.T) {
72+
tripper, err := testcase.clientAuth.RoundTripper(nil)
73+
if testcase.expectedErr {
74+
assert.Error(t, err)
75+
return
76+
}
77+
assert.NotNil(t, tripper)
78+
assert.NoError(t, err)
79+
// check if the resultant tripper is indeed the one provided
80+
_, ok := tripper.(*customRoundTripper)
81+
assert.True(t, ok)
82+
})
83+
}
84+
}
85+
86+
type customPerRPCCredentials struct{}
87+
88+
var _ credentials.PerRPCCredentials = (*customPerRPCCredentials)(nil)
89+
90+
func (c *customPerRPCCredentials) GetRequestMetadata(context.Context, ...string) (map[string]string, error) {
91+
return nil, nil
92+
}
93+
94+
func (c *customPerRPCCredentials) RequireTransportSecurity() bool {
95+
return true
96+
}
97+
98+
func TestMockPerRPCCredential(t *testing.T) {
99+
testcases := []struct {
100+
name string
101+
expectedErr bool
102+
clientAuth MockClientAuthenticator
103+
}{
104+
{
105+
name: "no_error",
106+
expectedErr: false,
107+
clientAuth: MockClientAuthenticator{
108+
ResultPerRPCCredentials: &customPerRPCCredentials{},
109+
MustError: false,
110+
},
111+
},
112+
{
113+
name: "error",
114+
expectedErr: true,
115+
clientAuth: MockClientAuthenticator{
116+
ResultPerRPCCredentials: &customPerRPCCredentials{},
117+
MustError: true,
118+
},
119+
},
120+
}
121+
122+
for _, testcase := range testcases {
123+
t.Run(testcase.name, func(t *testing.T) {
124+
credential, err := testcase.clientAuth.PerRPCCredentials()
125+
if err != nil {
126+
return
127+
}
128+
if testcase.expectedErr {
129+
assert.Error(t, err)
130+
return
131+
}
132+
assert.NotNil(t, credential)
133+
assert.NoError(t, err)
134+
// check if the resultant tripper is indeed the one provided
135+
_, ok := credential.(*customPerRPCCredentials)
136+
assert.True(t, ok)
137+
})
138+
}
139+
}
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ import (
2323
)
2424

2525
var (
26-
_ Authenticator = (*MockAuthenticator)(nil)
26+
_ ServerAuthenticator = (*MockAuthenticator)(nil)
2727
_ component.Extension = (*MockAuthenticator)(nil)
2828
)
2929

0 commit comments

Comments
 (0)