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.
[extension/opamp]: Support auth extensions (open-telemetry#35508)
- Loading branch information
1 parent
b089282
commit cf25636
Showing
9 changed files
with
317 additions
and
7 deletions.
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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,13 @@ | ||
# Use this changelog template to create an entry for release notes. | ||
|
||
# One of 'breaking', 'deprecation', 'new_component', 'enhancement', 'bug_fix' | ||
change_type: enhancement | ||
|
||
# The name of the component, or a single word describing the area of concern, (e.g. filelogreceiver) | ||
component: opampextension | ||
|
||
# A brief description of the change. Surround your text with quotes ("") if it needs to start with a backtick (`). | ||
note: "Support using auth extensions for authenticating with opamp servers" | ||
|
||
# Mandatory: One or more tracking issues related to the change. You can use the PR number here if no issue exists. | ||
issues: [35507] |
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,80 @@ | ||
// Copyright The OpenTelemetry Authors | ||
// SPDX-License-Identifier: Apache-2.0 | ||
|
||
package opampextension // import "github.com/open-telemetry/opentelemetry-collector-contrib/extension/opampextension" | ||
|
||
import ( | ||
"bytes" | ||
"fmt" | ||
"io" | ||
"net/http" | ||
|
||
"go.opentelemetry.io/collector/component" | ||
"go.opentelemetry.io/collector/extension/auth" | ||
"go.uber.org/zap" | ||
) | ||
|
||
// headerCaptureRoundTripper is a RoundTripper that captures the headers of the request | ||
// that passes through it. | ||
type headerCaptureRoundTripper struct { | ||
lastHeader http.Header | ||
} | ||
|
||
func (h *headerCaptureRoundTripper) RoundTrip(req *http.Request) (*http.Response, error) { | ||
h.lastHeader = req.Header.Clone() | ||
// Dummy response is recorded here | ||
return &http.Response{ | ||
Status: "200 OK", | ||
StatusCode: 200, | ||
Proto: "HTTP/1.0", | ||
ProtoMajor: 1, | ||
ProtoMinor: 0, | ||
Body: io.NopCloser(&bytes.Buffer{}), | ||
Request: req, | ||
}, nil | ||
} | ||
|
||
func makeHeadersFunc(logger *zap.Logger, serverCfg *OpAMPServer, host component.Host) (func(http.Header) http.Header, error) { | ||
var emptyComponentID component.ID | ||
if serverCfg == nil || serverCfg.GetAuthExtensionID() == emptyComponentID { | ||
return nil, nil | ||
} | ||
|
||
extID := serverCfg.GetAuthExtensionID() | ||
ext, ok := host.GetExtensions()[extID] | ||
if !ok { | ||
return nil, fmt.Errorf("could not find auth extension %q", extID) | ||
} | ||
|
||
authExt, ok := ext.(auth.Client) | ||
if !ok { | ||
return nil, fmt.Errorf("auth extension %q is not an auth.Client", extID) | ||
} | ||
|
||
hcrt := &headerCaptureRoundTripper{} | ||
rt, err := authExt.RoundTripper(hcrt) | ||
if err != nil { | ||
return nil, fmt.Errorf("could not create roundtripper for authentication: %w", err) | ||
} | ||
|
||
return func(h http.Header) http.Header { | ||
// This is a workaround while websocket authentication is being worked on. | ||
// Currently, we are waiting on the auth module to be stabilized. | ||
// See for more info: https://github.com/open-telemetry/opentelemetry-collector/issues/10864 | ||
dummyReq, err := http.NewRequest("GET", "http://example.com", nil) | ||
if err != nil { | ||
logger.Error("Failed to create dummy request for authentication.", zap.Error(err)) | ||
return h | ||
} | ||
|
||
dummyReq.Header = h | ||
|
||
_, err = rt.RoundTrip(dummyReq) | ||
if err != nil { | ||
logger.Error("Error while performing round-trip for authentication.", zap.Error(err)) | ||
return h | ||
} | ||
|
||
return hcrt.lastHeader | ||
}, 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,135 @@ | ||
// Copyright The OpenTelemetry Authors | ||
// SPDX-License-Identifier: Apache-2.0 | ||
|
||
package opampextension | ||
|
||
import ( | ||
"context" | ||
"net/http" | ||
"testing" | ||
|
||
"github.com/stretchr/testify/require" | ||
"go.opentelemetry.io/collector/component" | ||
"go.opentelemetry.io/collector/component/componenttest" | ||
"go.uber.org/zap" | ||
"google.golang.org/grpc/credentials" | ||
) | ||
|
||
func TestMakeHeadersFunc(t *testing.T) { | ||
t.Run("Nil server config", func(t *testing.T) { | ||
headersFunc, err := makeHeadersFunc(zap.NewNop(), nil, nil) | ||
require.NoError(t, err) | ||
require.Nil(t, headersFunc) | ||
}) | ||
|
||
t.Run("No auth extension specified", func(t *testing.T) { | ||
headersFunc, err := makeHeadersFunc(zap.NewNop(), &OpAMPServer{ | ||
WS: &commonFields{}, | ||
}, nil) | ||
require.NoError(t, err) | ||
require.Nil(t, headersFunc) | ||
}) | ||
|
||
t.Run("Extension does not exist", func(t *testing.T) { | ||
nopHost := componenttest.NewNopHost() | ||
headersFunc, err := makeHeadersFunc(zap.NewNop(), &OpAMPServer{ | ||
WS: &commonFields{ | ||
Auth: component.NewID(component.MustNewType("bearerauth")), | ||
}, | ||
}, nopHost) | ||
require.EqualError(t, err, `could not find auth extension "bearerauth"`) | ||
require.Nil(t, headersFunc) | ||
}) | ||
|
||
t.Run("Extension is not an auth extension", func(t *testing.T) { | ||
authComponent := component.NewID(component.MustNewType("bearerauth")) | ||
host := &mockHost{ | ||
extensions: map[component.ID]component.Component{ | ||
authComponent: mockComponent{}, | ||
}, | ||
} | ||
headersFunc, err := makeHeadersFunc(zap.NewNop(), &OpAMPServer{ | ||
WS: &commonFields{ | ||
Auth: authComponent, | ||
}, | ||
}, host) | ||
|
||
require.EqualError(t, err, `auth extension "bearerauth" is not an auth.Client`) | ||
require.Nil(t, headersFunc) | ||
}) | ||
|
||
t.Run("Headers func extracts headers from extension", func(t *testing.T) { | ||
authComponent := component.NewID(component.MustNewType("bearerauth")) | ||
h := http.Header{} | ||
h.Set("Authorization", "Bearer user:pass") | ||
|
||
host := &mockHost{ | ||
extensions: map[component.ID]component.Component{ | ||
authComponent: mockAuthClient{ | ||
header: h, | ||
}, | ||
}, | ||
} | ||
headersFunc, err := makeHeadersFunc(zap.NewNop(), &OpAMPServer{ | ||
WS: &commonFields{ | ||
Auth: authComponent, | ||
}, | ||
}, host) | ||
|
||
require.NoError(t, err) | ||
headersOut := headersFunc(http.Header{ | ||
"OtherHeader": []string{"OtherValue"}, | ||
}) | ||
|
||
require.Equal(t, http.Header{ | ||
"OtherHeader": []string{"OtherValue"}, | ||
"Authorization": []string{"Bearer user:pass"}, | ||
}, headersOut) | ||
}) | ||
} | ||
|
||
type mockHost struct { | ||
extensions map[component.ID]component.Component | ||
} | ||
|
||
func (m mockHost) GetExtensions() map[component.ID]component.Component { | ||
return m.extensions | ||
} | ||
|
||
type mockComponent struct{} | ||
|
||
func (mockComponent) Start(_ context.Context, _ component.Host) error { return nil } | ||
func (mockComponent) Shutdown(_ context.Context) error { return nil } | ||
|
||
type mockAuthClient struct { | ||
header http.Header | ||
} | ||
|
||
func (mockAuthClient) Start(_ context.Context, _ component.Host) error { return nil } | ||
func (mockAuthClient) Shutdown(_ context.Context) error { return nil } | ||
func (m mockAuthClient) RoundTripper(base http.RoundTripper) (http.RoundTripper, error) { | ||
return mockRoundTripper{ | ||
header: m.header, | ||
base: base, | ||
}, nil | ||
} | ||
func (mockAuthClient) PerRPCCredentials() (credentials.PerRPCCredentials, error) { | ||
return nil, nil | ||
} | ||
|
||
type mockRoundTripper struct { | ||
header http.Header | ||
base http.RoundTripper | ||
} | ||
|
||
func (m mockRoundTripper) RoundTrip(req *http.Request) (*http.Response, error) { | ||
reqClone := req.Clone(req.Context()) | ||
|
||
for k, vals := range m.header { | ||
for _, val := range vals { | ||
reqClone.Header.Add(k, val) | ||
} | ||
} | ||
|
||
return m.base.RoundTrip(reqClone) | ||
} |
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
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
Oops, something went wrong.