Skip to content

Commit 27b698f

Browse files
committed
Refactor configauth to support multiple extension auth interfaces
Signed-off-by: Bogdan Drutu <[email protected]>
1 parent ee0f0ae commit 27b698f

File tree

12 files changed

+333
-149
lines changed

12 files changed

+333
-149
lines changed

config/configauth/configauth.go

+142-8
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,14 @@ import (
1010
"context"
1111
"errors"
1212
"fmt"
13+
"net/http"
14+
15+
"google.golang.org/grpc"
16+
"google.golang.org/grpc/codes"
17+
"google.golang.org/grpc/metadata"
18+
"google.golang.org/grpc/status"
19+
20+
"go.opentelemetry.io/collector/internal/grpcutil"
1321

1422
"go.opentelemetry.io/collector/component"
1523
"go.opentelemetry.io/collector/extension/extensionauth"
@@ -20,6 +28,7 @@ var (
2028
errNotHTTPClient = errors.New("requested authenticator is not a HTTP client authenticator")
2129
errNotGRPCClient = errors.New("requested authenticator is not a gRPC client authenticator")
2230
errNotServer = errors.New("requested authenticator is not a server authenticator")
31+
errMetadataNotFound = errors.New("no request metadata found")
2332
)
2433

2534
// Authentication defines the auth settings for the receiver.
@@ -28,8 +37,48 @@ type Authentication struct {
2837
AuthenticatorID component.ID `mapstructure:"authenticator,omitempty"`
2938
}
3039

31-
// GetServerAuthenticator attempts to select the appropriate extensionauth.Server from the list of extensions,
32-
// based on the requested extension name. If an authenticator is not found, an error is returned.
40+
// GetGRPCServerOptions attempts to select the appropriate extensionauth.Server from the list of extensions,
41+
// based on the requested extension name and return the grpc.ServerOption to be used with the grpc.Server.
42+
// If an authenticator is not found, an error is returned.
43+
func (a Authentication) GetGRPCServerOptions(_ context.Context, extensions map[component.ID]component.Component) ([]grpc.ServerOption, error) {
44+
ext, found := extensions[a.AuthenticatorID]
45+
if !found {
46+
return nil, fmt.Errorf("failed to resolve authenticator %q: %w", a.AuthenticatorID, errAuthenticatorNotFound)
47+
}
48+
49+
eauth, ok := ext.(extensionauth.Server)
50+
if !ok {
51+
return nil, errNotServer
52+
}
53+
54+
uInterceptor := func(ctx context.Context, req any, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (resp any, err error) {
55+
return authServerUnaryInterceptor(ctx, req, info, handler, eauth)
56+
}
57+
sInterceptors := func(srv any, ss grpc.ServerStream, info *grpc.StreamServerInfo, handler grpc.StreamHandler) error {
58+
return authServerStreamInterceptor(srv, ss, info, handler, eauth)
59+
}
60+
61+
return []grpc.ServerOption{grpc.ChainUnaryInterceptor(uInterceptor), grpc.ChainStreamInterceptor(sInterceptors)}, nil
62+
}
63+
64+
// GetHTTPHandler attempts to select the appropriate extensionauth.Server from the list of extensions,
65+
// based on the requested extension name and return the http.Handler to be used with the http.Server.
66+
// If an authenticator is not found, an error is returned.
67+
func (a Authentication) GetHTTPHandler(_ context.Context, extensions map[component.ID]component.Component, next http.Handler, reqParams []string) (http.Handler, error) {
68+
ext, found := extensions[a.AuthenticatorID]
69+
if !found {
70+
return nil, fmt.Errorf("failed to resolve authenticator %q: %w", a.AuthenticatorID, errAuthenticatorNotFound)
71+
}
72+
73+
eauth, ok := ext.(extensionauth.Server)
74+
if !ok {
75+
return nil, errNotServer
76+
}
77+
78+
return authInterceptor(next, eauth, reqParams), nil
79+
}
80+
81+
// Deprecated: [v0.123.0] use GetGRPCServerOptions or GetHTTPServer.
3382
func (a Authentication) GetServerAuthenticator(_ context.Context, extensions map[component.ID]component.Component) (extensionauth.Server, error) {
3483
if ext, found := extensions[a.AuthenticatorID]; found {
3584
if server, ok := ext.(extensionauth.Server); ok {
@@ -41,9 +90,7 @@ func (a Authentication) GetServerAuthenticator(_ context.Context, extensions map
4190
return nil, fmt.Errorf("failed to resolve authenticator %q: %w", a.AuthenticatorID, errAuthenticatorNotFound)
4291
}
4392

44-
// GetHTTPClientAuthenticator attempts to select the appropriate extensionauth.Client from the list of extensions,
45-
// based on the component id of the extension. If an authenticator is not found, an error is returned.
46-
// This should be only used by HTTP clients.
93+
// Deprecated: [v0.123.0] use GetHTTPRoundTripper.
4794
func (a Authentication) GetHTTPClientAuthenticator(_ context.Context, extensions map[component.ID]component.Component) (extensionauth.HTTPClient, error) {
4895
if ext, found := extensions[a.AuthenticatorID]; found {
4996
if client, ok := ext.(extensionauth.HTTPClient); ok {
@@ -54,9 +101,25 @@ func (a Authentication) GetHTTPClientAuthenticator(_ context.Context, extensions
54101
return nil, fmt.Errorf("failed to resolve authenticator %q: %w", a.AuthenticatorID, errAuthenticatorNotFound)
55102
}
56103

57-
// GetGRPCClientAuthenticator attempts to select the appropriate extensionauth.Client from the list of extensions,
58-
// based on the component id of the extension. If an authenticator is not found, an error is returned.
59-
// This should be only used by gRPC clients.
104+
// GetHTTPRoundTripper attempts to select the appropriate extensionauth.Client from the list of extensions,
105+
// based on the component id of the extension and return the http.RoundTripper to be used with the http.Client.
106+
// If an authenticator is not found, an error is returned. This should be only used by HTTP clients.
107+
func (a Authentication) GetHTTPRoundTripper(_ context.Context, extensions map[component.ID]component.Component, base http.RoundTripper) (http.RoundTripper, error) {
108+
ext, found := extensions[a.AuthenticatorID]
109+
if !found {
110+
return nil, fmt.Errorf("failed to resolve authenticator %q: %w", a.AuthenticatorID, errAuthenticatorNotFound)
111+
}
112+
113+
// Currently only support `extensionauth.HTTPClient`.
114+
client, ok := ext.(extensionauth.HTTPClient)
115+
if !ok {
116+
return nil, errNotHTTPClient
117+
}
118+
119+
return client.RoundTripper(base)
120+
}
121+
122+
// Deprecated: [v0.123.0] Use GetGRPCDialOptions.
60123
func (a Authentication) GetGRPCClientAuthenticator(_ context.Context, extensions map[component.ID]component.Component) (extensionauth.GRPCClient, error) {
61124
if ext, found := extensions[a.AuthenticatorID]; found {
62125
if client, ok := ext.(extensionauth.GRPCClient); ok {
@@ -66,3 +129,74 @@ func (a Authentication) GetGRPCClientAuthenticator(_ context.Context, extensions
66129
}
67130
return nil, fmt.Errorf("failed to resolve authenticator %q: %w", a.AuthenticatorID, errAuthenticatorNotFound)
68131
}
132+
133+
// GetGRPCDialOptions attempts to select the appropriate extensionauth.Client from the list of extensions,
134+
// based on the component id of the extension and return the grpc.DialOptions to be used with grpc.ClientConn.
135+
// If an authenticator is not found, an error is returned. This should be only used by gRPC clients.
136+
func (a Authentication) GetGRPCDialOptions(_ context.Context, extensions map[component.ID]component.Component) ([]grpc.DialOption, error) {
137+
ext, found := extensions[a.AuthenticatorID]
138+
if !found {
139+
return nil, fmt.Errorf("failed to resolve authenticator %q: %w", a.AuthenticatorID, errAuthenticatorNotFound)
140+
}
141+
142+
// Currently only support `extensionauth.GRPCClient`.
143+
client, ok := ext.(extensionauth.GRPCClient)
144+
if !ok {
145+
return nil, errNotGRPCClient
146+
}
147+
148+
perRPCCredentials, err := client.PerRPCCredentials()
149+
if err != nil {
150+
return nil, err
151+
}
152+
153+
return []grpc.DialOption{grpc.WithPerRPCCredentials(perRPCCredentials)}, nil
154+
}
155+
156+
func authServerUnaryInterceptor(ctx context.Context, req any, _ *grpc.UnaryServerInfo, handler grpc.UnaryHandler, eauth extensionauth.Server) (any, error) {
157+
headers, ok := metadata.FromIncomingContext(ctx)
158+
if !ok {
159+
return nil, errMetadataNotFound
160+
}
161+
162+
ctx, err := eauth.Authenticate(ctx, headers)
163+
if err != nil {
164+
return nil, status.Error(codes.Unauthenticated, err.Error())
165+
}
166+
167+
return handler(ctx, req)
168+
}
169+
170+
func authServerStreamInterceptor(srv any, stream grpc.ServerStream, _ *grpc.StreamServerInfo, handler grpc.StreamHandler, eauth extensionauth.Server) error {
171+
ctx := stream.Context()
172+
headers, ok := metadata.FromIncomingContext(ctx)
173+
if !ok {
174+
return errMetadataNotFound
175+
}
176+
177+
ctx, err := eauth.Authenticate(ctx, headers)
178+
if err != nil {
179+
return status.Error(codes.Unauthenticated, err.Error())
180+
}
181+
182+
return handler(srv, grpcutil.WrapServerStream(ctx, stream))
183+
}
184+
185+
func authInterceptor(next http.Handler, eauth extensionauth.Server, requestParams []string) http.Handler {
186+
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
187+
sources := r.Header
188+
query := r.URL.Query()
189+
for _, param := range requestParams {
190+
if val, ok := query[param]; ok {
191+
sources[param] = val
192+
}
193+
}
194+
ctx, err := eauth.Authenticate(r.Context(), sources)
195+
if err != nil {
196+
http.Error(w, http.StatusText(http.StatusUnauthorized), http.StatusUnauthorized)
197+
return
198+
}
199+
200+
next.ServeHTTP(w, r.WithContext(ctx))
201+
})
202+
}

0 commit comments

Comments
 (0)