Skip to content

Commit 61c9b6d

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

File tree

12 files changed

+332
-149
lines changed

12 files changed

+332
-149
lines changed

config/configauth/configauth.go

+141-8
Original file line numberDiff line numberDiff line change
@@ -10,16 +10,24 @@ 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"
1319

1420
"go.opentelemetry.io/collector/component"
1521
"go.opentelemetry.io/collector/extension/extensionauth"
22+
"go.opentelemetry.io/collector/internal/grpcutil"
1623
)
1724

1825
var (
1926
errAuthenticatorNotFound = errors.New("authenticator not found")
2027
errNotHTTPClient = errors.New("requested authenticator is not a HTTP client authenticator")
2128
errNotGRPCClient = errors.New("requested authenticator is not a gRPC client authenticator")
2229
errNotServer = errors.New("requested authenticator is not a server authenticator")
30+
errMetadataNotFound = errors.New("no request metadata found")
2331
)
2432

2533
// Authentication defines the auth settings for the receiver.
@@ -28,8 +36,48 @@ type Authentication struct {
2836
AuthenticatorID component.ID `mapstructure:"authenticator,omitempty"`
2937
}
3038

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

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.
92+
// Deprecated: [v0.123.0] use GetHTTPRoundTripper.
4793
func (a Authentication) GetHTTPClientAuthenticator(_ context.Context, extensions map[component.ID]component.Component) (extensionauth.HTTPClient, error) {
4894
if ext, found := extensions[a.AuthenticatorID]; found {
4995
if client, ok := ext.(extensionauth.HTTPClient); ok {
@@ -54,9 +100,25 @@ func (a Authentication) GetHTTPClientAuthenticator(_ context.Context, extensions
54100
return nil, fmt.Errorf("failed to resolve authenticator %q: %w", a.AuthenticatorID, errAuthenticatorNotFound)
55101
}
56102

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

0 commit comments

Comments
 (0)