From 6b7cdbbf881b2a23004dcb91d38a9a5b3b4465d8 Mon Sep 17 00:00:00 2001 From: Aidan Leuck Date: Tue, 26 Nov 2024 17:08:18 -0700 Subject: [PATCH 01/17] Work on adding auth so far --- .../component/otelcol/auth/basic/basic.go | 5 ++++ internal/component/otelcol/config_grpc.go | 29 +++++++++++++------ internal/component/otelcol/config_http.go | 29 ++++++++++++++----- .../component/otelcol/receiver/otlp/otlp.go | 22 +++++++++++++- 4 files changed, 68 insertions(+), 17 deletions(-) diff --git a/internal/component/otelcol/auth/basic/basic.go b/internal/component/otelcol/auth/basic/basic.go index facce80883..e00a94e809 100644 --- a/internal/component/otelcol/auth/basic/basic.go +++ b/internal/component/otelcol/auth/basic/basic.go @@ -2,6 +2,8 @@ package basic import ( + "fmt" + "github.com/grafana/alloy/internal/component" "github.com/grafana/alloy/internal/component/otelcol/auth" otelcolCfg "github.com/grafana/alloy/internal/component/otelcol/config" @@ -53,6 +55,9 @@ func (args Arguments) Convert() (otelcomponent.Config, error) { Username: args.Username, Password: configopaque.String(args.Password), }, + Htpasswd: &basicauthextension.HtpasswdSettings{ + Inline: fmt.Sprintf("%s:%s", args.Username, args.Password), + }, }, nil } diff --git a/internal/component/otelcol/config_grpc.go b/internal/component/otelcol/config_grpc.go index 3608d9ed94..5970353a76 100644 --- a/internal/component/otelcol/config_grpc.go +++ b/internal/component/otelcol/config_grpc.go @@ -30,13 +30,9 @@ type GRPCServerArguments struct { Keepalive *KeepaliveServerArguments `alloy:"keepalive,block,optional"` - // TODO(rfratto): auth - // - // Figuring out how to do authentication isn't very straightforward here. The - // auth section links to an authenticator extension. - // - // We will need to generally figure out how we want to provide common - // authentication extensions to all of our components. + // Auth is a binding to an otelcol.auth.* component extension which handles + // authentication. + Auth *auth.Handler `alloy:"auth,attr,optional"` IncludeMetadata bool `alloy:"include_metadata,attr,optional"` } @@ -47,6 +43,13 @@ func (args *GRPCServerArguments) Convert() *otelconfiggrpc.ServerConfig { return nil } + var auth *otelconfigauth.Authentication + if args.Auth != nil{ + auth = &otelconfigauth.Authentication{ + AuthenticatorID: args.Auth.ID, + } + } + return &otelconfiggrpc.ServerConfig{ NetAddr: confignet.AddrConfig{ Endpoint: args.Endpoint, @@ -59,11 +62,19 @@ func (args *GRPCServerArguments) Convert() *otelconfiggrpc.ServerConfig { MaxConcurrentStreams: args.MaxConcurrentStreams, ReadBufferSize: int(args.ReadBufferSize), WriteBufferSize: int(args.WriteBufferSize), - Keepalive: args.Keepalive.Convert(), - IncludeMetadata: args.IncludeMetadata, + Auth: auth, + } +} + +// Extensions exposes extensions used by args. +func (args *GRPCServerArguments) Extensions() map[otelcomponent.ID]otelextension.Extension { + m := make(map[otelcomponent.ID]otelextension.Extension) + if args.Auth != nil { + m[args.Auth.ID] = args.Auth.Extension } + return m } // KeepaliveServerArguments holds shared keepalive settings for components diff --git a/internal/component/otelcol/config_http.go b/internal/component/otelcol/config_http.go index 71c89ebc49..962861ca37 100644 --- a/internal/component/otelcol/config_http.go +++ b/internal/component/otelcol/config_http.go @@ -21,13 +21,9 @@ type HTTPServerArguments struct { CORS *CORSArguments `alloy:"cors,block,optional"` - // TODO(rfratto): auth - // - // Figuring out how to do authentication isn't very straightforward here. The - // auth section links to an authenticator extension. - // - // We will need to generally figure out how we want to provide common - // authentication extensions to all of our components. + // Auth is a binding to an otelcol.auth.* component extension which handles + // authentication. + Auth *auth.Handler `alloy:"auth,attr,optional"` MaxRequestBodySize units.Base2Bytes `alloy:"max_request_body_size,attr,optional"` IncludeMetadata bool `alloy:"include_metadata,attr,optional"` @@ -49,6 +45,15 @@ func (args *HTTPServerArguments) Convert() *otelconfighttp.ServerConfig { return nil } + var auth *otelconfighttp.AuthConfig + if args.Auth != nil { + auth = &otelconfighttp.AuthConfig{ + Authentication: otelconfigauth.Authentication{ + AuthenticatorID: args.Auth.ID, + }, + } + } + return &otelconfighttp.ServerConfig{ Endpoint: args.Endpoint, TLSSetting: args.TLS.Convert(), @@ -56,7 +61,17 @@ func (args *HTTPServerArguments) Convert() *otelconfighttp.ServerConfig { MaxRequestBodySize: int64(args.MaxRequestBodySize), IncludeMetadata: args.IncludeMetadata, CompressionAlgorithms: copyStringSlice(args.CompressionAlgorithms), + Auth: auth, + } +} + +// Extensions exposes extensions used by args. +func (args *HTTPServerArguments) Extensions() map[otelcomponent.ID]otelextension.Extension { + m := make(map[otelcomponent.ID]otelextension.Extension) + if args.Auth != nil { + m[args.Auth.ID] = args.Auth.Extension } + return m } // CORSArguments holds shared CORS settings for components which launch HTTP diff --git a/internal/component/otelcol/receiver/otlp/otlp.go b/internal/component/otelcol/receiver/otlp/otlp.go index 13d4c13a62..b1bbc9807a 100644 --- a/internal/component/otelcol/receiver/otlp/otlp.go +++ b/internal/component/otelcol/receiver/otlp/otlp.go @@ -3,6 +3,7 @@ package otlp import ( "fmt" + "maps" net_url "net/url" "github.com/alecthomas/units" @@ -89,7 +90,26 @@ func (args Arguments) Convert() (otelcomponent.Config, error) { // Extensions implements receiver.Arguments. func (args Arguments) Extensions() map[otelcomponent.ID]otelextension.Extension { - return nil + extensionMap := make(map[otelcomponent.ID]otelextension.Extension) + + // Gets the extensions for the HTTP server and GRPC server + if args.HTTP != nil{ + httpExtensions := (*otelcol.HTTPServerArguments)(args.HTTP.HTTPServerArguments).Extensions() + + // Copies the extensions from each server. + maps.Copy(extensionMap, httpExtensions) + } + + if args.GRPC != nil{ + grpcExtensions := (*otelcol.GRPCServerArguments)(args.GRPC).Extensions() + + // Copies the extensions from each server. + maps.Copy(extensionMap, grpcExtensions) + } + + + + return extensionMap } // Exporters implements receiver.Arguments. From d3812c2d9ade06f6cc6bf0f9b6da871ae255f6dc Mon Sep 17 00:00:00 2001 From: Aidan Leuck Date: Wed, 27 Nov 2024 09:14:42 -0700 Subject: [PATCH 02/17] Cleanup --- internal/component/otelcol/config_grpc.go | 10 +++++----- internal/component/otelcol/receiver/otlp/otlp.go | 12 +++++------- 2 files changed, 10 insertions(+), 12 deletions(-) diff --git a/internal/component/otelcol/config_grpc.go b/internal/component/otelcol/config_grpc.go index 5970353a76..196f939d80 100644 --- a/internal/component/otelcol/config_grpc.go +++ b/internal/component/otelcol/config_grpc.go @@ -44,7 +44,7 @@ func (args *GRPCServerArguments) Convert() *otelconfiggrpc.ServerConfig { } var auth *otelconfigauth.Authentication - if args.Auth != nil{ + if args.Auth != nil { auth = &otelconfigauth.Authentication{ AuthenticatorID: args.Auth.ID, } @@ -62,10 +62,10 @@ func (args *GRPCServerArguments) Convert() *otelconfiggrpc.ServerConfig { MaxConcurrentStreams: args.MaxConcurrentStreams, ReadBufferSize: int(args.ReadBufferSize), WriteBufferSize: int(args.WriteBufferSize), - Keepalive: args.Keepalive.Convert(), - IncludeMetadata: args.IncludeMetadata, - Auth: auth, - } + Keepalive: args.Keepalive.Convert(), + IncludeMetadata: args.IncludeMetadata, + Auth: auth, + } } // Extensions exposes extensions used by args. diff --git a/internal/component/otelcol/receiver/otlp/otlp.go b/internal/component/otelcol/receiver/otlp/otlp.go index b1bbc9807a..8668eb3cc5 100644 --- a/internal/component/otelcol/receiver/otlp/otlp.go +++ b/internal/component/otelcol/receiver/otlp/otlp.go @@ -93,21 +93,19 @@ func (args Arguments) Extensions() map[otelcomponent.ID]otelextension.Extension extensionMap := make(map[otelcomponent.ID]otelextension.Extension) // Gets the extensions for the HTTP server and GRPC server - if args.HTTP != nil{ - httpExtensions := (*otelcol.HTTPServerArguments)(args.HTTP.HTTPServerArguments).Extensions() + if args.HTTP != nil { + httpExtensions := (*otelcol.HTTPServerArguments)(args.HTTP.HTTPServerArguments).Extensions() - // Copies the extensions from each server. + // Copies the extensions for the HTTP server into the map maps.Copy(extensionMap, httpExtensions) } - if args.GRPC != nil{ + if args.GRPC != nil { grpcExtensions := (*otelcol.GRPCServerArguments)(args.GRPC).Extensions() - // Copies the extensions from each server. + // Copies the extensions for the GRPC server into the map. maps.Copy(extensionMap, grpcExtensions) } - - return extensionMap } From c7155c7b0b01f56ba1a0ccdbceb7719637c6253a Mon Sep 17 00:00:00 2001 From: aidanleuck Date: Thu, 28 Nov 2024 13:33:51 -0700 Subject: [PATCH 03/17] Made a ton of progress --- .../faro/receiver/receiver_otelcol_test.go | 4 +- internal/component/otelcol/auth/auth.go | 146 +++++++++++++++--- internal/component/otelcol/auth/auth_test.go | 6 +- .../component/otelcol/auth/basic/basic.go | 8 +- .../otelcol/auth/basic/basic_test.go | 46 ++++-- .../component/otelcol/auth/bearer/bearer.go | 12 +- .../otelcol/auth/bearer/bearer_test.go | 7 +- .../component/otelcol/auth/headers/headers.go | 9 +- .../otelcol/auth/headers/headers_test.go | 9 +- .../component/otelcol/auth/oauth2/oauth2.go | 7 +- .../otelcol/auth/oauth2/oauth2_test.go | 8 +- .../component/otelcol/auth/sigv4/sigv4.go | 10 +- .../otelcol/auth/sigv4/sigv4_test.go | 8 +- internal/component/otelcol/config_grpc.go | 47 ++++-- internal/component/otelcol/config_http.go | 47 ++++-- .../exporter/loadbalancing/loadbalancing.go | 17 +- .../component/otelcol/exporter/otlp/otlp.go | 7 +- .../otelcol/exporter/otlphttp/otlphttp.go | 7 +- .../jaeger_remote_sampling.go | 24 ++- .../otelcol/receiver/datadog/datadog.go | 7 +- .../otelcol/receiver/jaeger/jaeger.go | 21 ++- .../otelcol/receiver/opencensus/opencensus.go | 6 +- .../component/otelcol/receiver/otlp/otlp.go | 28 +++- .../otelcol/receiver/zipkin/zipkin.go | 6 +- 24 files changed, 390 insertions(+), 107 deletions(-) diff --git a/internal/component/faro/receiver/receiver_otelcol_test.go b/internal/component/faro/receiver/receiver_otelcol_test.go index 15d39d0e69..f8ffa8bd2e 100644 --- a/internal/component/faro/receiver/receiver_otelcol_test.go +++ b/internal/component/faro/receiver/receiver_otelcol_test.go @@ -1,4 +1,4 @@ -//go:build !race +// //go:build !race package receiver @@ -78,7 +78,7 @@ func TestWithOtelcolConsumer(t *testing.T) { err := otelcolExporter.Run(ctx, otlphttp.Arguments{ Client: otlphttp.HTTPClientArguments(otelcol.HTTPClientArguments{ Endpoint: finalOtelServer.URL, - Auth: &otelcolAuthHeaderExport.Handler, + Auth: otelcolAuthHeaderExport.Handler, TLS: otelcol.TLSClientArguments{ Insecure: true, InsecureSkipVerify: true, diff --git a/internal/component/otelcol/auth/auth.go b/internal/component/otelcol/auth/auth.go index eb20434dec..643c94d1a1 100644 --- a/internal/component/otelcol/auth/auth.go +++ b/internal/component/otelcol/auth/auth.go @@ -7,6 +7,7 @@ package auth import ( "context" + "errors" "fmt" "hash/fnv" "os" @@ -30,14 +31,31 @@ import ( "go.opentelemetry.io/otel/sdk/metric" ) +var ( + ErrNotServerExtension = errors.New("component does not support server authentication") + ErrNotClientExtension = errors.New("component does not support client authentication") + ErrInvalidExtension = errors.New("invalid extension") +) + +type ExtensionType int + +const ( + Server ExtensionType = iota // 0 + Client +) + // Arguments is an extension of component.Arguments which contains necessary // settings for OpenTelemetry Collector authentication extensions. type Arguments interface { component.Arguments - // Convert converts the Arguments into an OpenTelemetry Collector - // authentication extension configuration. - Convert() (otelcomponent.Config, error) + // ConvertClient converts the Arguments into an OpenTelemetry Collector + // client authentication extension configuration. + ConvertClient() (otelcomponent.Config, error) + + // ConvetServer converts the Arguments into an OpenTelemetry Collector + // server authentication extension configuration + ConvertServer() (otelcomponent.Config, error) // Extensions returns the set of extensions that the configured component is // allowed to use. @@ -56,11 +74,53 @@ type Arguments interface { type Exports struct { // Handler is the managed component. Handler is updated any time the // extension is updated. - Handler Handler `alloy:"handler,attr"` + Handler *Handler `alloy:"handler,attr"` } // Handler combines an extension with its ID. type Handler struct { + HandlerMap map[ExtensionType]*ExtensionHandler +} + +func NewDefaultHandler() *Handler { + return &Handler{ + HandlerMap: map[ExtensionType]*ExtensionHandler{}, + } +} + +func NewHandler(extensionMap map[ExtensionType]*ExtensionHandler) *Handler { + return &Handler{ + HandlerMap: extensionMap, + } +} + +func (h *Handler) GetExtension(et ExtensionType) (*ExtensionHandler, error) { + ext, ok := h.HandlerMap[et] + if !ok { + switch et { + case Server: + return nil, ErrNotServerExtension + case Client: + return nil, ErrNotClientExtension + default: + return nil, fmt.Errorf("%w: extension type provided %d", ErrInvalidExtension, et) + } + + } + + return ext, nil +} + +func (h *Handler) AddExtension(et ExtensionType, eh *ExtensionHandler) error { + if et != Server && et != Client { + return fmt.Errorf("invalid extension type %d", et) + } + + h.HandlerMap[et] = eh + return nil +} + +type ExtensionHandler struct { ID otelcomponent.ID Extension otelextension.Extension } @@ -172,30 +232,68 @@ func (a *Auth) Update(args component.Arguments) error { }, } - extensionConfig, err := rargs.Convert() + // Create instances of the extension from our factory. + var components []otelcomponent.Component + handlerMap := map[ExtensionType]*ExtensionHandler{} + + createClientExtension := true + clientConfig, err := rargs.ConvertClient() if err != nil { - return err + createClientExtension = false + if !errors.Is(err, ErrNotClientExtension) { + return err + } + + // Log something here? } - // Create instances of the extension from our factory. - var components []otelcomponent.Component + var clientext otelcomponent.Component + if createClientExtension { + clientext, err = a.createExtension(clientConfig, settings) + if err != nil { + return err + } - ext, err := a.factory.CreateExtension(a.ctx, settings, extensionConfig) + cTypeStr := NormalizeType(fmt.Sprintf("%s_%s", a.opts.ID, "client")) + handlerMap[Client] = &ExtensionHandler{ + ID: otelcomponent.NewID(otelcomponent.MustNewType(cTypeStr)), + Extension: clientext, + } + + components = append(components, clientext) + } + + createServerExtension := true + serverConfig, err := rargs.ConvertServer() if err != nil { - return err - } else if ext != nil { - components = append(components, ext) + createServerExtension = false + if !errors.Is(err, ErrNotServerExtension) { + return err + } + + // Log something here? } - cTypeStr := NormalizeType(a.opts.ID) + if createServerExtension { + serverext, err := a.createExtension(serverConfig, settings) + if err != nil { + return err + } + + components = append(components, serverext) + cTypeStr := NormalizeType(fmt.Sprintf("%s_%s", a.opts.ID, "server")) + handlerMap[Server] = &ExtensionHandler{ + ID: otelcomponent.NewID(otelcomponent.MustNewType(cTypeStr)), + Extension: serverext, + } + + } // Inform listeners that our handler changed. a.opts.OnStateChange(Exports{ - Handler: Handler{ - ID: otelcomponent.NewID(otelcomponent.MustNewType(cTypeStr)), - Extension: ext, - }, - }) + Handler: NewHandler(handlerMap), + }, + ) // Schedule the components to run once our component is running. a.sched.Schedule(host, components...) @@ -222,3 +320,15 @@ func NormalizeType(in string) string { return res } + +func (a *Auth) createExtension(config otelcomponent.Config, settings otelextension.Settings) (otelcomponent.Component, error) { + ext, err := a.factory.Create(a.ctx, settings, config) + if err != nil { + return nil, err + } + if ext == nil { + return nil, fmt.Errorf("extension was not created") + } + + return ext, nil +} diff --git a/internal/component/otelcol/auth/auth_test.go b/internal/component/otelcol/auth/auth_test.go index fbdb45bdaf..c1575aba57 100644 --- a/internal/component/otelcol/auth/auth_test.go +++ b/internal/component/otelcol/auth/auth_test.go @@ -85,7 +85,11 @@ type fakeAuthArgs struct { var _ auth.Arguments = fakeAuthArgs{} -func (fa fakeAuthArgs) Convert() (otelcomponent.Config, error) { +func (fa fakeAuthArgs) ConvertClient() (otelcomponent.Config, error) { + return &struct{}{}, nil +} + +func (fa fakeAuthArgs) ConvertServer() (otelcomponent.Config, error) { return &struct{}{}, nil } diff --git a/internal/component/otelcol/auth/basic/basic.go b/internal/component/otelcol/auth/basic/basic.go index e00a94e809..77a4417f77 100644 --- a/internal/component/otelcol/auth/basic/basic.go +++ b/internal/component/otelcol/auth/basic/basic.go @@ -48,13 +48,17 @@ func (args *Arguments) SetToDefault() { args.DebugMetrics.SetToDefault() } -// Convert implements auth.Arguments. -func (args Arguments) Convert() (otelcomponent.Config, error) { +func (args Arguments) ConvertClient() (otelcomponent.Config, error) { return &basicauthextension.Config{ ClientAuth: &basicauthextension.ClientAuthSettings{ Username: args.Username, Password: configopaque.String(args.Password), }, + }, nil +} + +func (args Arguments) ConvertServer() (otelcomponent.Config, error) { + return &basicauthextension.Config{ Htpasswd: &basicauthextension.HtpasswdSettings{ Inline: fmt.Sprintf("%s:%s", args.Username, args.Password), }, diff --git a/internal/component/otelcol/auth/basic/basic_test.go b/internal/component/otelcol/auth/basic/basic_test.go index 33e48a5a58..7d90037499 100644 --- a/internal/component/otelcol/auth/basic/basic_test.go +++ b/internal/component/otelcol/auth/basic/basic_test.go @@ -2,6 +2,7 @@ package basic_test import ( "context" + "encoding/base64" "net/http" "net/http/httptest" "testing" @@ -60,21 +61,38 @@ func Test(t *testing.T) { // Get the authentication extension from our component and use it to make a // request to our test server. exports := ctrl.Exports().(auth.Exports) - require.NotNil(t, exports.Handler.Extension, "handler extension is nil") + require.NotNil(t, exports.Handler) - clientAuth, ok := exports.Handler.Extension.(extauth.Client) - require.True(t, ok, "handler does not implement configauth.ClientAuthenticator") + t.Run("ClientAuth", func(t *testing.T) { + clientExtension, err := exports.Handler.GetExtension(auth.Client) + require.NoError(t, err) + require.NotNil(t, clientExtension) + clientAuth, ok := clientExtension.Extension.(extauth.Client) + require.True(t, ok, "handler does not implement configauth.ClientAuthenticator") - rt, err := clientAuth.RoundTripper(http.DefaultTransport) - require.NoError(t, err) - cli := &http.Client{Transport: rt} + rt, err := clientAuth.RoundTripper(http.DefaultTransport) + require.NoError(t, err) + cli := &http.Client{Transport: rt} + + // Wait until the request finishes. We don't assert anything else here; our + // HTTP handler won't write the response until it ensures that the basic auth + // was found. + req, err := http.NewRequestWithContext(ctx, http.MethodGet, srv.URL, nil) + require.NoError(t, err) + resp, err := cli.Do(req) + require.NoError(t, err, "HTTP request failed") + require.Equal(t, http.StatusOK, resp.StatusCode) + }) + + t.Run("ServerAuth", func(t *testing.T) { + serverExtension, err := exports.Handler.GetExtension(auth.Server) + require.NoError(t, err) + require.NotNil(t, serverExtension) + serverAuth, ok := serverExtension.Extension.(extauth.Server) + require.True(t, ok, "handler does not implement configauth.ServerAuthenticator") + b64EncodingAuth := base64.StdEncoding.EncodeToString([]byte("foo:bar")) + _, err = serverAuth.Authenticate(ctx, map[string][]string{"Authorization": {"Basic " + b64EncodingAuth}}) + require.NoError(t, err) + }) - // Wait until the request finishes. We don't assert anything else here; our - // HTTP handler won't write the response until it ensures that the basic auth - // was found. - req, err := http.NewRequestWithContext(ctx, http.MethodGet, srv.URL, nil) - require.NoError(t, err) - resp, err := cli.Do(req) - require.NoError(t, err, "HTTP request failed") - require.Equal(t, http.StatusOK, resp.StatusCode) } diff --git a/internal/component/otelcol/auth/bearer/bearer.go b/internal/component/otelcol/auth/bearer/bearer.go index 17a0816a30..20baa4d8e9 100644 --- a/internal/component/otelcol/auth/bearer/bearer.go +++ b/internal/component/otelcol/auth/bearer/bearer.go @@ -51,14 +51,22 @@ func (args *Arguments) SetToDefault() { args.DebugMetrics.SetToDefault() } -// Convert implements auth.Arguments. -func (args Arguments) Convert() (otelcomponent.Config, error) { +func (args Arguments) convert() (otelcomponent.Config, error) { return &bearertokenauthextension.Config{ Scheme: args.Scheme, BearerToken: configopaque.String(args.Token), }, nil } +// Convert implements auth.Arguments. +func (args Arguments) ConvertClient() (otelcomponent.Config, error) { + return args.convert() +} + +func (args Arguments) ConvertServer() (otelcomponent.Config, error) { + return args.convert() +} + // Extensions implements auth.Arguments. func (args Arguments) Extensions() map[otelcomponent.ID]otelextension.Extension { return nil diff --git a/internal/component/otelcol/auth/bearer/bearer_test.go b/internal/component/otelcol/auth/bearer/bearer_test.go index 3595e542b4..675693b1bd 100644 --- a/internal/component/otelcol/auth/bearer/bearer_test.go +++ b/internal/component/otelcol/auth/bearer/bearer_test.go @@ -95,9 +95,12 @@ func Test(t *testing.T) { // Get the authentication extension from our component and use it to make a // request to our test server. exports := ctrl.Exports().(auth.Exports) - require.NotNil(t, exports.Handler.Extension, "handler extension is nil") + require.NotNil(t, exports.Handler, "handler extension is nil") - clientAuth, ok := exports.Handler.Extension.(extauth.Client) + clientExtension, err := exports.Handler.GetExtension(auth.Client) + require.NoError(t, err) + + clientAuth, ok := clientExtension.Extension.(extauth.Client) require.True(t, ok, "handler does not implement configauth.ClientAuthenticator") rt, err := clientAuth.RoundTripper(http.DefaultTransport) diff --git a/internal/component/otelcol/auth/headers/headers.go b/internal/component/otelcol/auth/headers/headers.go index c2a206ff95..2b5168df5e 100644 --- a/internal/component/otelcol/auth/headers/headers.go +++ b/internal/component/otelcol/auth/headers/headers.go @@ -1,4 +1,4 @@ -// Package headers provides an otelcol.auth.headers component. +// // Package headers provides an otelcol.auth.headers component. package headers import ( @@ -48,7 +48,7 @@ func (args *Arguments) SetToDefault() { } // Convert implements auth.Arguments. -func (args Arguments) Convert() (otelcomponent.Config, error) { +func (args Arguments) ConvertClient() (otelcomponent.Config, error) { var upstreamHeaders []headerssetterextension.HeaderConfig for _, h := range args.Headers { upstreamHeader := headerssetterextension.HeaderConfig{ @@ -70,11 +70,16 @@ func (args Arguments) Convert() (otelcomponent.Config, error) { upstreamHeaders = append(upstreamHeaders, upstreamHeader) } + // OtelExtensionConfig does not implement ServerAuth return &headerssetterextension.Config{ HeadersConfig: upstreamHeaders, }, nil } +func (args Arguments) ConvertServer() (otelcomponent.Config, error) { + return nil, fmt.Errorf("%w, headers extension does not implement server authentication", auth.ErrNotServerExtension) +} + // Extensions implements auth.Arguments. func (args Arguments) Extensions() map[otelcomponent.ID]otelextension.Extension { return nil diff --git a/internal/component/otelcol/auth/headers/headers_test.go b/internal/component/otelcol/auth/headers/headers_test.go index 5e57d2803a..f488bc05cb 100644 --- a/internal/component/otelcol/auth/headers/headers_test.go +++ b/internal/component/otelcol/auth/headers/headers_test.go @@ -61,9 +61,12 @@ func Test(t *testing.T) { // Get the authentication extension from our component and use it to make a // request to our test server. exports := ctrl.Exports().(auth.Exports) - require.NotNil(t, exports.Handler.Extension, "handler extension is nil") - clientAuth, ok := exports.Handler.Extension.(extauth.Client) + ext, err := exports.Handler.GetExtension(auth.Client) + require.NoError(t, err) + require.NotNil(t, ext.Extension, "handler extension is nil") + + clientAuth, ok := ext.Extension.(extauth.Client) require.True(t, ok, "handler does not implement configauth.ClientAuthenticator") rt, err := clientAuth.RoundTripper(http.DefaultTransport) @@ -169,7 +172,7 @@ func TestArguments_UnmarshalAlloy(t *testing.T) { } require.NoError(t, err) - ext, err := args.Convert() + ext, err := args.ConvertClient() require.NoError(t, err) otelArgs, ok := (ext).(*headerssetterextension.Config) diff --git a/internal/component/otelcol/auth/oauth2/oauth2.go b/internal/component/otelcol/auth/oauth2/oauth2.go index 747dd779c1..5eb574c420 100644 --- a/internal/component/otelcol/auth/oauth2/oauth2.go +++ b/internal/component/otelcol/auth/oauth2/oauth2.go @@ -1,6 +1,7 @@ package oauth2 import ( + "fmt" "net/url" "time" @@ -54,7 +55,7 @@ func (args *Arguments) SetToDefault() { } // Convert implements auth.Arguments. -func (args Arguments) Convert() (otelcomponent.Config, error) { +func (args Arguments) ConvertClient() (otelcomponent.Config, error) { return &oauth2clientauthextension.Config{ ClientID: args.ClientID, ClientIDFile: args.ClientIDFile, @@ -68,6 +69,10 @@ func (args Arguments) Convert() (otelcomponent.Config, error) { }, nil } +func (args Arguments) ConvertServer() (otelcomponent.Config, error) { + return nil, fmt.Errorf("%w oauth2 client extension does not implement server authentication", auth.ErrNotServerExtension) +} + // Extensions implements auth.Arguments. func (args Arguments) Extensions() map[otelcomponent.ID]otelextension.Extension { return nil diff --git a/internal/component/otelcol/auth/oauth2/oauth2_test.go b/internal/component/otelcol/auth/oauth2/oauth2_test.go index bb37bb242d..01acb01818 100644 --- a/internal/component/otelcol/auth/oauth2/oauth2_test.go +++ b/internal/component/otelcol/auth/oauth2/oauth2_test.go @@ -112,9 +112,13 @@ func Test(t *testing.T) { // Get the authentication extension from our component and use it to make a // request to our test server. exports := ctrl.Exports().(auth.Exports) - require.NotNil(t, exports.Handler.Extension, "handler extension is nil") - clientAuth, ok := exports.Handler.Extension.(extauth.Client) + ext, err := exports.Handler.GetExtension(auth.Client) + require.NoError(t, err) + + require.NotNil(t, ext.Extension, "handler extension is nil") + + clientAuth, ok := ext.Extension.(extauth.Client) require.True(t, ok, "handler does not implement configauth.ClientAuthenticator") rt, err := clientAuth.RoundTripper(http.DefaultTransport) diff --git a/internal/component/otelcol/auth/sigv4/sigv4.go b/internal/component/otelcol/auth/sigv4/sigv4.go index c8060ed337..0b91365107 100644 --- a/internal/component/otelcol/auth/sigv4/sigv4.go +++ b/internal/component/otelcol/auth/sigv4/sigv4.go @@ -1,6 +1,8 @@ package sigv4 import ( + "fmt" + "github.com/grafana/alloy/internal/component" "github.com/grafana/alloy/internal/component/otelcol/auth" otelcolCfg "github.com/grafana/alloy/internal/component/otelcol/config" @@ -44,7 +46,7 @@ func (args *Arguments) SetToDefault() { } // Convert implements auth.Arguments. -func (args Arguments) Convert() (otelcomponent.Config, error) { +func (args Arguments) ConvertClient() (otelcomponent.Config, error) { res := sigv4authextension.Config{ Region: args.Region, Service: args.Service, @@ -58,9 +60,13 @@ func (args Arguments) Convert() (otelcomponent.Config, error) { return &res, nil } +func (args Arguments) ConvertServer() (otelcomponent.Config, error) { + return nil, fmt.Errorf("%w sigv4authplugin does not implement server authentication", auth.ErrNotServerExtension) +} + // Validate implements syntax.Validator. func (args Arguments) Validate() error { - _, err := args.Convert() + _, err := args.ConvertClient() return err } diff --git a/internal/component/otelcol/auth/sigv4/sigv4_test.go b/internal/component/otelcol/auth/sigv4/sigv4_test.go index 1f2ffa17ad..ebe977f1a4 100644 --- a/internal/component/otelcol/auth/sigv4/sigv4_test.go +++ b/internal/component/otelcol/auth/sigv4/sigv4_test.go @@ -173,9 +173,13 @@ func Test(t *testing.T) { // Get the authentication extension from our component and use it to make a // request to our test server. exports := ctrl.Exports().(auth.Exports) - require.NotNil(t, exports.Handler.Extension, "handler extension is nil") - clientAuth, ok := exports.Handler.Extension.(extauth.Client) + ext, err := exports.Handler.GetExtension(auth.Client) + require.NoError(t, err) + + require.NotNil(t, ext.Extension, "handler extension is nil") + + clientAuth, ok := ext.Extension.(extauth.Client) require.True(t, ok, "handler does not implement configauth.ClientAuthenticator") rt, err := clientAuth.RoundTripper(http.DefaultTransport) diff --git a/internal/component/otelcol/config_grpc.go b/internal/component/otelcol/config_grpc.go index 196f939d80..9ef824b72e 100644 --- a/internal/component/otelcol/config_grpc.go +++ b/internal/component/otelcol/config_grpc.go @@ -38,15 +38,19 @@ type GRPCServerArguments struct { } // Convert converts args into the upstream type. -func (args *GRPCServerArguments) Convert() *otelconfiggrpc.ServerConfig { +func (args *GRPCServerArguments) Convert() (*otelconfiggrpc.ServerConfig, error) { if args == nil { - return nil + return nil, nil } - var auth *otelconfigauth.Authentication + var authz *otelconfigauth.Authentication if args.Auth != nil { - auth = &otelconfigauth.Authentication{ - AuthenticatorID: args.Auth.ID, + serverExtension, err := args.Auth.GetExtension(auth.Server) + if err != nil { + return nil, err + } + authz = &otelconfigauth.Authentication{ + AuthenticatorID: serverExtension.ID, } } @@ -64,15 +68,19 @@ func (args *GRPCServerArguments) Convert() *otelconfiggrpc.ServerConfig { WriteBufferSize: int(args.WriteBufferSize), Keepalive: args.Keepalive.Convert(), IncludeMetadata: args.IncludeMetadata, - Auth: auth, - } + Auth: authz, + }, nil } // Extensions exposes extensions used by args. func (args *GRPCServerArguments) Extensions() map[otelcomponent.ID]otelextension.Extension { m := make(map[otelcomponent.ID]otelextension.Extension) if args.Auth != nil { - m[args.Auth.ID] = args.Auth.Extension + ext, err := args.Auth.GetExtension(auth.Server) + if err != nil { + return m + } + m[ext.ID] = ext.Extension } return m } @@ -165,9 +173,9 @@ type GRPCClientArguments struct { } // Convert converts args into the upstream type. -func (args *GRPCClientArguments) Convert() *otelconfiggrpc.ClientConfig { +func (args *GRPCClientArguments) Convert() (*otelconfiggrpc.ClientConfig, error) { if args == nil { - return nil + return nil, nil } opaqueHeaders := make(map[string]configopaque.String) @@ -176,9 +184,14 @@ func (args *GRPCClientArguments) Convert() *otelconfiggrpc.ClientConfig { } // Configure the authentication if args.Auth is set. - var auth *otelconfigauth.Authentication + var authz *otelconfigauth.Authentication if args.Auth != nil { - auth = &otelconfigauth.Authentication{AuthenticatorID: args.Auth.ID} + ext, err := args.Auth.GetExtension(auth.Client) + if err != nil { + return nil, err + } + + authz = &otelconfigauth.Authentication{AuthenticatorID: ext.ID} } // Set default value for `balancer_name` to sync up with upstream's @@ -202,15 +215,19 @@ func (args *GRPCClientArguments) Convert() *otelconfiggrpc.ClientConfig { BalancerName: balancerName, Authority: args.Authority, - Auth: auth, - } + Auth: authz, + }, nil } // Extensions exposes extensions used by args. func (args *GRPCClientArguments) Extensions() map[otelcomponent.ID]otelextension.Extension { m := make(map[otelcomponent.ID]otelextension.Extension) if args.Auth != nil { - m[args.Auth.ID] = args.Auth.Extension + ext, err := args.Auth.GetExtension(auth.Client) + if err != nil { + return m + } + m[ext.ID] = ext.Extension } return m } diff --git a/internal/component/otelcol/config_http.go b/internal/component/otelcol/config_http.go index 962861ca37..76abfdee5b 100644 --- a/internal/component/otelcol/config_http.go +++ b/internal/component/otelcol/config_http.go @@ -40,16 +40,21 @@ func copyStringSlice(s []string) []string { } // Convert converts args into the upstream type. -func (args *HTTPServerArguments) Convert() *otelconfighttp.ServerConfig { +func (args *HTTPServerArguments) Convert() (*otelconfighttp.ServerConfig, error) { if args == nil { - return nil + return nil, nil } - var auth *otelconfighttp.AuthConfig + var authz *otelconfighttp.AuthConfig if args.Auth != nil { - auth = &otelconfighttp.AuthConfig{ + ext, err := args.Auth.GetExtension(auth.Server) + if err != nil { + return nil, err + } + + authz = &otelconfighttp.AuthConfig{ Authentication: otelconfigauth.Authentication{ - AuthenticatorID: args.Auth.ID, + AuthenticatorID: ext.ID, }, } } @@ -61,15 +66,19 @@ func (args *HTTPServerArguments) Convert() *otelconfighttp.ServerConfig { MaxRequestBodySize: int64(args.MaxRequestBodySize), IncludeMetadata: args.IncludeMetadata, CompressionAlgorithms: copyStringSlice(args.CompressionAlgorithms), - Auth: auth, - } + Auth: authz, + }, nil } // Extensions exposes extensions used by args. func (args *HTTPServerArguments) Extensions() map[otelcomponent.ID]otelextension.Extension { m := make(map[otelcomponent.ID]otelextension.Extension) if args.Auth != nil { - m[args.Auth.ID] = args.Auth.Extension + ext, err := args.Auth.GetExtension(auth.Server) + if err != nil { + return m + } + m[ext.ID] = ext.Extension } return m } @@ -128,15 +137,19 @@ type HTTPClientArguments struct { } // Convert converts args into the upstream type. -func (args *HTTPClientArguments) Convert() *otelconfighttp.ClientConfig { +func (args *HTTPClientArguments) Convert() (*otelconfighttp.ClientConfig, error) { if args == nil { - return nil + return nil, nil } // Configure the authentication if args.Auth is set. - var auth *otelconfigauth.Authentication + var authz *otelconfigauth.Authentication if args.Auth != nil { - auth = &otelconfigauth.Authentication{AuthenticatorID: args.Auth.ID} + ext, err := args.Auth.GetExtension(auth.Client) + if err != nil { + return nil, err + } + authz = &otelconfigauth.Authentication{AuthenticatorID: ext.ID} } opaqueHeaders := make(map[string]configopaque.String) @@ -165,17 +178,21 @@ func (args *HTTPClientArguments) Convert() *otelconfighttp.ClientConfig { HTTP2ReadIdleTimeout: args.HTTP2ReadIdleTimeout, HTTP2PingTimeout: args.HTTP2PingTimeout, - Auth: auth, + Auth: authz, Cookies: args.Cookies.Convert(), - } + }, nil } // Extensions exposes extensions used by args. func (args *HTTPClientArguments) Extensions() map[otelcomponent.ID]otelextension.Extension { m := make(map[otelcomponent.ID]otelextension.Extension) if args.Auth != nil { - m[args.Auth.ID] = args.Auth.Extension + ext, err := args.Auth.GetExtension(auth.Client) + if err != nil { + return m + } + m[ext.ID] = ext.Extension } return m } diff --git a/internal/component/otelcol/exporter/loadbalancing/loadbalancing.go b/internal/component/otelcol/exporter/loadbalancing/loadbalancing.go index a6f94c43e2..ab7239de7a 100644 --- a/internal/component/otelcol/exporter/loadbalancing/loadbalancing.go +++ b/internal/component/otelcol/exporter/loadbalancing/loadbalancing.go @@ -349,9 +349,14 @@ func (args *GRPCClientArguments) Convert() *otelconfiggrpc.ClientConfig { } // Configure the authentication if args.Auth is set. - var auth *otelconfigauth.Authentication + var authz *otelconfigauth.Authentication if args.Auth != nil { - auth = &otelconfigauth.Authentication{AuthenticatorID: args.Auth.ID} + ext, err := args.Auth.GetExtension(auth.Client) + if err != nil { + return nil + } + + authz = &otelconfigauth.Authentication{AuthenticatorID: ext.ID} } balancerName := args.BalancerName @@ -372,7 +377,7 @@ func (args *GRPCClientArguments) Convert() *otelconfiggrpc.ClientConfig { BalancerName: balancerName, Authority: args.Authority, - Auth: auth, + Auth: authz, } } @@ -380,7 +385,11 @@ func (args *GRPCClientArguments) Convert() *otelconfiggrpc.ClientConfig { func (args *GRPCClientArguments) Extensions() map[otelcomponent.ID]otelextension.Extension { m := make(map[otelcomponent.ID]otelextension.Extension) if args.Auth != nil { - m[args.Auth.ID] = args.Auth.Extension + ext, err := args.Auth.GetExtension(auth.Client) + if err != nil { + return m + } + m[ext.ID] = ext.Extension } return m } diff --git a/internal/component/otelcol/exporter/otlp/otlp.go b/internal/component/otelcol/exporter/otlp/otlp.go index 30c82ee367..08bf608558 100644 --- a/internal/component/otelcol/exporter/otlp/otlp.go +++ b/internal/component/otelcol/exporter/otlp/otlp.go @@ -62,13 +62,18 @@ func (args *Arguments) SetToDefault() { // Convert implements exporter.Arguments. func (args Arguments) Convert() (otelcomponent.Config, error) { + clientArgs := *(*otelcol.GRPCClientArguments)(&args.Client) + convertedClientArgs, err := clientArgs.Convert() + if err != nil { + return nil, err + } return &otlpexporter.Config{ TimeoutConfig: otelpexporterhelper.TimeoutConfig{ Timeout: args.Timeout, }, QueueConfig: *args.Queue.Convert(), RetryConfig: *args.Retry.Convert(), - ClientConfig: *(*otelcol.GRPCClientArguments)(&args.Client).Convert(), + ClientConfig: *convertedClientArgs, }, nil } diff --git a/internal/component/otelcol/exporter/otlphttp/otlphttp.go b/internal/component/otelcol/exporter/otlphttp/otlphttp.go index 091c8361f0..93a91a6eb6 100644 --- a/internal/component/otelcol/exporter/otlphttp/otlphttp.go +++ b/internal/component/otelcol/exporter/otlphttp/otlphttp.go @@ -71,8 +71,13 @@ func (args *Arguments) SetToDefault() { // Convert implements exporter.Arguments. func (args Arguments) Convert() (otelcomponent.Config, error) { + httpClientArgs := *(*otelcol.HTTPClientArguments)(&args.Client) + convertedClientArgs, err := httpClientArgs.Convert() + if err != nil { + return nil, err + } return &otlphttpexporter.Config{ - ClientConfig: *(*otelcol.HTTPClientArguments)(&args.Client).Convert(), + ClientConfig: *convertedClientArgs, QueueConfig: *args.Queue.Convert(), RetryConfig: *args.Retry.Convert(), TracesEndpoint: args.TracesEndpoint, diff --git a/internal/component/otelcol/extension/jaeger_remote_sampling/jaeger_remote_sampling.go b/internal/component/otelcol/extension/jaeger_remote_sampling/jaeger_remote_sampling.go index 1fc6a12c59..9d2cf1aecf 100644 --- a/internal/component/otelcol/extension/jaeger_remote_sampling/jaeger_remote_sampling.go +++ b/internal/component/otelcol/extension/jaeger_remote_sampling/jaeger_remote_sampling.go @@ -68,11 +68,29 @@ func (args *Arguments) SetToDefault() { // Convert implements extension.Arguments. func (args Arguments) Convert() (otelcomponent.Config, error) { + httpServerConfig := (*otelcol.HTTPServerArguments)(args.HTTP) + httpConvertedServerConfig, err := httpServerConfig.Convert() + if err != nil { + return nil, err + } + + grpcServerConfig := (*otelcol.GRPCServerArguments)(args.GRPC) + convertedGrpcServerConfig, err := grpcServerConfig.Convert() + if err != nil { + return nil, err + } + + grpcClientConfig := (*otelcol.GRPCClientArguments)(args.Source.Remote) + convertedGrpcClientConfig, err := grpcClientConfig.Convert() + if err != nil { + return nil, err + } + return &jaegerremotesampling.Config{ - HTTPServerConfig: (*otelcol.HTTPServerArguments)(args.HTTP).Convert(), - GRPCServerConfig: (*otelcol.GRPCServerArguments)(args.GRPC).Convert(), + HTTPServerConfig: httpConvertedServerConfig, + GRPCServerConfig: convertedGrpcServerConfig, Source: jaegerremotesampling.Source{ - Remote: (*otelcol.GRPCClientArguments)(args.Source.Remote).Convert(), + Remote: convertedGrpcClientConfig, File: args.Source.File, ReloadInterval: args.Source.ReloadInterval, Contents: args.Source.Content, diff --git a/internal/component/otelcol/receiver/datadog/datadog.go b/internal/component/otelcol/receiver/datadog/datadog.go index 66abc47927..e1fc449586 100644 --- a/internal/component/otelcol/receiver/datadog/datadog.go +++ b/internal/component/otelcol/receiver/datadog/datadog.go @@ -57,8 +57,13 @@ func (args *Arguments) SetToDefault() { // Convert implements receiver.Arguments. func (args Arguments) Convert() (otelcomponent.Config, error) { + convertedHttpServer, err := args.HTTPServer.Convert() + if err != nil { + return nil, err + } + return &datadogreceiver.Config{ - ServerConfig: *args.HTTPServer.Convert(), + ServerConfig: *convertedHttpServer, ReadTimeout: args.ReadTimeout, }, nil } diff --git a/internal/component/otelcol/receiver/jaeger/jaeger.go b/internal/component/otelcol/receiver/jaeger/jaeger.go index a6b2f2b43a..c81c7550c8 100644 --- a/internal/component/otelcol/receiver/jaeger/jaeger.go +++ b/internal/component/otelcol/receiver/jaeger/jaeger.go @@ -65,10 +65,19 @@ func (args *Arguments) Validate() error { // Convert implements receiver.Arguments. func (args Arguments) Convert() (otelcomponent.Config, error) { + grpcProtocol, err := args.Protocols.GRPC.Convert() + if err != nil { + return nil, err + } + + httpProtocol, err := args.Protocols.ThriftHTTP.Convert() + if err != nil { + return nil, err + } return &jaegerreceiver.Config{ Protocols: jaegerreceiver.Protocols{ - GRPC: args.Protocols.GRPC.Convert(), - ThriftHTTP: args.Protocols.ThriftHTTP.Convert(), + GRPC: grpcProtocol, + ThriftHTTP: httpProtocol, ThriftBinary: args.Protocols.ThriftBinary.Convert(), ThriftCompact: args.Protocols.ThriftCompact.Convert(), }, @@ -114,9 +123,9 @@ func (args *GRPC) SetToDefault() { } // Convert converts proto into the upstream type. -func (args *GRPC) Convert() *otelconfiggrpc.ServerConfig { +func (args *GRPC) Convert() (*otelconfiggrpc.ServerConfig, error) { if args == nil { - return nil + return nil, nil } return args.GRPCServerArguments.Convert() @@ -137,9 +146,9 @@ func (args *ThriftHTTP) SetToDefault() { } // Convert converts proto into the upstream type. -func (args *ThriftHTTP) Convert() *otelconfighttp.ServerConfig { +func (args *ThriftHTTP) Convert() (*otelconfighttp.ServerConfig, error) { if args == nil { - return nil + return nil, nil } return args.HTTPServerArguments.Convert() diff --git a/internal/component/otelcol/receiver/opencensus/opencensus.go b/internal/component/otelcol/receiver/opencensus/opencensus.go index 5c3327ea3e..6e42a5c41e 100644 --- a/internal/component/otelcol/receiver/opencensus/opencensus.go +++ b/internal/component/otelcol/receiver/opencensus/opencensus.go @@ -58,9 +58,13 @@ func (args *Arguments) SetToDefault() { // Convert implements receiver.Arguments. func (args Arguments) Convert() (otelcomponent.Config, error) { + grpcServerConfig, err := args.GRPC.Convert() + if err != nil { + return nil, err + } return &opencensusreceiver.Config{ CorsOrigins: args.CorsAllowedOrigins, - ServerConfig: *args.GRPC.Convert(), + ServerConfig: *grpcServerConfig, }, nil } diff --git a/internal/component/otelcol/receiver/otlp/otlp.go b/internal/component/otelcol/receiver/otlp/otlp.go index 8668eb3cc5..3c5961f771 100644 --- a/internal/component/otelcol/receiver/otlp/otlp.go +++ b/internal/component/otelcol/receiver/otlp/otlp.go @@ -57,17 +57,22 @@ type HTTPConfigArguments struct { } // Convert converts args into the upstream type. -func (args *HTTPConfigArguments) Convert() *otlpreceiver.HTTPConfig { +func (args *HTTPConfigArguments) Convert() (*otlpreceiver.HTTPConfig, error) { if args == nil { - return nil + return nil, nil + } + + httpServerArgs, err := args.HTTPServerArguments.Convert() + if err != nil { + return nil, err } return &otlpreceiver.HTTPConfig{ - ServerConfig: args.HTTPServerArguments.Convert(), + ServerConfig: httpServerArgs, TracesURLPath: args.TracesURLPath, MetricsURLPath: args.MetricsURLPath, LogsURLPath: args.LogsURLPath, - } + }, nil } var _ receiver.Arguments = Arguments{} @@ -80,10 +85,21 @@ func (args *Arguments) SetToDefault() { // Convert implements receiver.Arguments. func (args Arguments) Convert() (otelcomponent.Config, error) { + grpcProtocol := (*otelcol.GRPCServerArguments)(args.GRPC) + grpcProtocolArgs, err := grpcProtocol.Convert() + if err != nil { + return nil, err + } + + httpProtocolArgs, err := args.HTTP.Convert() + if err != nil { + return nil, err + } + return &otlpreceiver.Config{ Protocols: otlpreceiver.Protocols{ - GRPC: (*otelcol.GRPCServerArguments)(args.GRPC).Convert(), - HTTP: args.HTTP.Convert(), + GRPC: grpcProtocolArgs, + HTTP: httpProtocolArgs, }, }, nil } diff --git a/internal/component/otelcol/receiver/zipkin/zipkin.go b/internal/component/otelcol/receiver/zipkin/zipkin.go index 3b3b4af359..8c5655ef7e 100644 --- a/internal/component/otelcol/receiver/zipkin/zipkin.go +++ b/internal/component/otelcol/receiver/zipkin/zipkin.go @@ -54,9 +54,13 @@ func (args *Arguments) SetToDefault() { // Convert implements receiver.Arguments. func (args Arguments) Convert() (otelcomponent.Config, error) { + httpServerConfig, err := args.HTTPServer.Convert() + if err != nil { + return nil, err + } return &zipkinreceiver.Config{ ParseStringTags: args.ParseStringTags, - ServerConfig: *args.HTTPServer.Convert(), + ServerConfig: *httpServerConfig, }, nil } From 37e5880d2fa1f337a20ad4a59e1a56d90eb041df Mon Sep 17 00:00:00 2001 From: aidanleuck Date: Thu, 28 Nov 2024 14:06:43 -0700 Subject: [PATCH 04/17] Fix test fails? --- .../exporter/loadbalancing/loadbalancing.go | 36 ++++++++++++------- 1 file changed, 24 insertions(+), 12 deletions(-) diff --git a/internal/component/otelcol/exporter/loadbalancing/loadbalancing.go b/internal/component/otelcol/exporter/loadbalancing/loadbalancing.go index ab7239de7a..3401acbc1a 100644 --- a/internal/component/otelcol/exporter/loadbalancing/loadbalancing.go +++ b/internal/component/otelcol/exporter/loadbalancing/loadbalancing.go @@ -90,8 +90,12 @@ func (args *Arguments) Validate() error { // Convert implements exporter.Arguments. func (args Arguments) Convert() (otelcomponent.Config, error) { + protocol, err := args.Protocol.Convert() + if err != nil { + return nil, err + } return &loadbalancingexporter.Config{ - Protocol: args.Protocol.Convert(), + Protocol: *protocol, Resolver: args.Resolver.Convert(), RoutingKey: args.RoutingKey, }, nil @@ -102,10 +106,14 @@ type Protocol struct { OTLP OtlpConfig `alloy:"otlp,block"` } -func (protocol Protocol) Convert() loadbalancingexporter.Protocol { - return loadbalancingexporter.Protocol{ - OTLP: protocol.OTLP.Convert(), +func (protocol Protocol) Convert() (*loadbalancingexporter.Protocol, error) { + otlp, err := protocol.OTLP.Convert() + if err != nil { + return nil, err } + return &loadbalancingexporter.Protocol{ + OTLP: *otlp, + }, nil } // OtlpConfig defines the config for an OTLP exporter @@ -127,15 +135,19 @@ func (oc *OtlpConfig) SetToDefault() { oc.Queue.SetToDefault() } -func (oc OtlpConfig) Convert() otlpexporter.Config { - return otlpexporter.Config{ +func (oc OtlpConfig) Convert() (*otlpexporter.Config, error) { + clientConfig, err := oc.Client.Convert() + if err != nil { + return nil, err + } + return &otlpexporter.Config{ TimeoutConfig: exporterhelper.TimeoutConfig{ Timeout: oc.Timeout, }, QueueConfig: *oc.Queue.Convert(), RetryConfig: *oc.Retry.Convert(), - ClientConfig: *oc.Client.Convert(), - } + ClientConfig: *clientConfig, + }, nil } // ResolverSettings defines the configurations for the backend resolver @@ -338,9 +350,9 @@ type GRPCClientArguments struct { var _ syntax.Defaulter = &GRPCClientArguments{} // Convert converts args into the upstream type. -func (args *GRPCClientArguments) Convert() *otelconfiggrpc.ClientConfig { +func (args *GRPCClientArguments) Convert() (*otelconfiggrpc.ClientConfig, error) { if args == nil { - return nil + return nil, nil } opaqueHeaders := make(map[string]configopaque.String) @@ -353,7 +365,7 @@ func (args *GRPCClientArguments) Convert() *otelconfiggrpc.ClientConfig { if args.Auth != nil { ext, err := args.Auth.GetExtension(auth.Client) if err != nil { - return nil + return nil, err } authz = &otelconfigauth.Authentication{AuthenticatorID: ext.ID} @@ -378,7 +390,7 @@ func (args *GRPCClientArguments) Convert() *otelconfiggrpc.ClientConfig { Authority: args.Authority, Auth: authz, - } + }, nil } // Extensions exposes extensions used by args. From 3732a542a6954617cbc3c36cccfd7f4f53e4c80f Mon Sep 17 00:00:00 2001 From: aidanleuck Date: Fri, 29 Nov 2024 11:07:04 -0700 Subject: [PATCH 05/17] Refactor --- internal/component/otelcol/auth/auth.go | 143 ++++++++++-------- .../component/otelcol/auth/headers/headers.go | 3 +- .../component/otelcol/auth/oauth2/oauth2.go | 4 +- .../component/otelcol/auth/sigv4/sigv4.go | 5 +- 4 files changed, 82 insertions(+), 73 deletions(-) diff --git a/internal/component/otelcol/auth/auth.go b/internal/component/otelcol/auth/auth.go index 643c94d1a1..1d574341de 100644 --- a/internal/component/otelcol/auth/auth.go +++ b/internal/component/otelcol/auth/auth.go @@ -37,11 +37,11 @@ var ( ErrInvalidExtension = errors.New("invalid extension") ) -type ExtensionType int +type ExtensionType string const ( - Server ExtensionType = iota // 0 - Client + Server ExtensionType = "server" + Client ExtensionType = "client" ) // Arguments is an extension of component.Arguments which contains necessary @@ -79,33 +79,25 @@ type Exports struct { // Handler combines an extension with its ID. type Handler struct { - HandlerMap map[ExtensionType]*ExtensionHandler + componentID string + handlerMap map[ExtensionType]*ExtensionHandler } -func NewDefaultHandler() *Handler { +func NewHandler(componentID string) *Handler { return &Handler{ - HandlerMap: map[ExtensionType]*ExtensionHandler{}, - } -} - -func NewHandler(extensionMap map[ExtensionType]*ExtensionHandler) *Handler { - return &Handler{ - HandlerMap: extensionMap, + componentID: componentID, + handlerMap: map[ExtensionType]*ExtensionHandler{}, } } func (h *Handler) GetExtension(et ExtensionType) (*ExtensionHandler, error) { - ext, ok := h.HandlerMap[et] + ext, ok := h.handlerMap[et] if !ok { - switch et { - case Server: - return nil, ErrNotServerExtension - case Client: - return nil, ErrNotClientExtension - default: - return nil, fmt.Errorf("%w: extension type provided %d", ErrInvalidExtension, et) - } + return nil, fmt.Errorf("error initializing %s auth extension. component %s was unexpectedly nil", et, h.componentID) + } + if ext.Error != nil { + return nil, ext.Error } return ext, nil @@ -113,16 +105,19 @@ func (h *Handler) GetExtension(et ExtensionType) (*ExtensionHandler, error) { func (h *Handler) AddExtension(et ExtensionType, eh *ExtensionHandler) error { if et != Server && et != Client { - return fmt.Errorf("invalid extension type %d", et) + return fmt.Errorf("invalid extension type %s", et) } - h.HandlerMap[et] = eh + h.handlerMap[et] = eh return nil } type ExtensionHandler struct { ID otelcomponent.ID Extension otelextension.Extension + // Set if the extension does not support the type of authentication + // requested + Error error } var _ syntax.Capsule = Handler{} @@ -234,64 +229,40 @@ func (a *Auth) Update(args component.Arguments) error { // Create instances of the extension from our factory. var components []otelcomponent.Component - handlerMap := map[ExtensionType]*ExtensionHandler{} - - createClientExtension := true - clientConfig, err := rargs.ConvertClient() + handler := NewHandler(a.opts.ID) + clientEh, err := a.setupExtension(Client, rargs, settings) if err != nil { - createClientExtension = false - if !errors.Is(err, ErrNotClientExtension) { - return err - } - - // Log something here? + return err } - var clientext otelcomponent.Component - if createClientExtension { - clientext, err = a.createExtension(clientConfig, settings) - if err != nil { - return err - } - - cTypeStr := NormalizeType(fmt.Sprintf("%s_%s", a.opts.ID, "client")) - handlerMap[Client] = &ExtensionHandler{ - ID: otelcomponent.NewID(otelcomponent.MustNewType(cTypeStr)), - Extension: clientext, - } + // Extension could be nil if the auth plugin does not support client auth + if clientEh.Extension != nil { + components = append(components, clientEh.Extension) + } - components = append(components, clientext) + // Register extension so it can be retrieved + if err := handler.AddExtension(Client, clientEh); err != nil { + return err } - createServerExtension := true - serverConfig, err := rargs.ConvertServer() + serverEh, err := a.setupExtension(Server, rargs, settings) if err != nil { - createServerExtension = false - if !errors.Is(err, ErrNotServerExtension) { - return err - } - - // Log something here? + return err } - if createServerExtension { - serverext, err := a.createExtension(serverConfig, settings) - if err != nil { - return err - } - - components = append(components, serverext) - cTypeStr := NormalizeType(fmt.Sprintf("%s_%s", a.opts.ID, "server")) - handlerMap[Server] = &ExtensionHandler{ - ID: otelcomponent.NewID(otelcomponent.MustNewType(cTypeStr)), - Extension: serverext, - } + // Extension could be nil if the auth plugin does not support server auth. + if serverEh.Extension != nil { + components = append(components, serverEh.Extension) + } + // Register extension so it can be retrieved + if err := handler.AddExtension(Server, serverEh); err != nil { + return err } // Inform listeners that our handler changed. a.opts.OnStateChange(Exports{ - Handler: NewHandler(handlerMap), + Handler: handler, }, ) @@ -321,6 +292,44 @@ func NormalizeType(in string) string { return res } +func (a *Auth) setupExtension(t ExtensionType, rargs Arguments, settings otelextension.Settings) (*ExtensionHandler, error) { + var otelConfig otelcomponent.Config + var err error + var notSupportedErr error + if t == Server { + otelConfig, err = rargs.ConvertServer() + notSupportedErr = ErrNotServerExtension + } + if t == Client { + otelConfig, err = rargs.ConvertClient() + notSupportedErr = ErrNotClientExtension + } + + if err != nil { + return nil, err + } + + eh := &ExtensionHandler{} + + // Auth plugins that don't support the client/server auth + // are expected to return nil, check for that error here. + if otelConfig == nil { + eh.Error = fmt.Errorf("%s %w", a.opts.ID, notSupportedErr) + return eh, nil + } + + otelExtension, err := a.createExtension(otelConfig, settings) + if err != nil { + return nil, err + } + + cTypeStr := NormalizeType(fmt.Sprintf("%s.%s", a.opts.ID, t)) + eh.ID = otelcomponent.NewID(otelcomponent.MustNewType(cTypeStr)) + eh.Extension = otelExtension + + return eh, nil +} + func (a *Auth) createExtension(config otelcomponent.Config, settings otelextension.Settings) (otelcomponent.Component, error) { ext, err := a.factory.Create(a.ctx, settings, config) if err != nil { diff --git a/internal/component/otelcol/auth/headers/headers.go b/internal/component/otelcol/auth/headers/headers.go index 2b5168df5e..75190ccdde 100644 --- a/internal/component/otelcol/auth/headers/headers.go +++ b/internal/component/otelcol/auth/headers/headers.go @@ -76,8 +76,9 @@ func (args Arguments) ConvertClient() (otelcomponent.Config, error) { }, nil } +// ConvertServer returns nil since theheaders extension does not support server authenticaiton. func (args Arguments) ConvertServer() (otelcomponent.Config, error) { - return nil, fmt.Errorf("%w, headers extension does not implement server authentication", auth.ErrNotServerExtension) + return nil, nil } // Extensions implements auth.Arguments. diff --git a/internal/component/otelcol/auth/oauth2/oauth2.go b/internal/component/otelcol/auth/oauth2/oauth2.go index 5eb574c420..d880b503ca 100644 --- a/internal/component/otelcol/auth/oauth2/oauth2.go +++ b/internal/component/otelcol/auth/oauth2/oauth2.go @@ -1,7 +1,6 @@ package oauth2 import ( - "fmt" "net/url" "time" @@ -69,8 +68,9 @@ func (args Arguments) ConvertClient() (otelcomponent.Config, error) { }, nil } +// ConvertServer returns nil since the ouath2 client extension doesn ot support serve auth func (args Arguments) ConvertServer() (otelcomponent.Config, error) { - return nil, fmt.Errorf("%w oauth2 client extension does not implement server authentication", auth.ErrNotServerExtension) + return nil, nil } // Extensions implements auth.Arguments. diff --git a/internal/component/otelcol/auth/sigv4/sigv4.go b/internal/component/otelcol/auth/sigv4/sigv4.go index 0b91365107..338e931464 100644 --- a/internal/component/otelcol/auth/sigv4/sigv4.go +++ b/internal/component/otelcol/auth/sigv4/sigv4.go @@ -1,8 +1,6 @@ package sigv4 import ( - "fmt" - "github.com/grafana/alloy/internal/component" "github.com/grafana/alloy/internal/component/otelcol/auth" otelcolCfg "github.com/grafana/alloy/internal/component/otelcol/config" @@ -60,8 +58,9 @@ func (args Arguments) ConvertClient() (otelcomponent.Config, error) { return &res, nil } +// ConvertServer returns nil since the sigv4 extension does not support server authentication func (args Arguments) ConvertServer() (otelcomponent.Config, error) { - return nil, fmt.Errorf("%w sigv4authplugin does not implement server authentication", auth.ErrNotServerExtension) + return nil, nil } // Validate implements syntax.Validator. From 8e828714e044877247497a570d4c331f51c2152f Mon Sep 17 00:00:00 2001 From: aidanleuck Date: Fri, 29 Nov 2024 13:35:34 -0700 Subject: [PATCH 06/17] Add auth blocks to implementing extensions --- .../jaeger_remote_sampling.go | 20 ++++++++++++++++++- .../otelcol/receiver/datadog/datadog.go | 2 +- .../otelcol/receiver/jaeger/jaeger.go | 20 ++++++++++++++++++- .../otelcol/receiver/opencensus/opencensus.go | 2 +- .../otelcol/receiver/zipkin/zipkin.go | 2 +- 5 files changed, 41 insertions(+), 5 deletions(-) diff --git a/internal/component/otelcol/extension/jaeger_remote_sampling/jaeger_remote_sampling.go b/internal/component/otelcol/extension/jaeger_remote_sampling/jaeger_remote_sampling.go index 9d2cf1aecf..99bc507dbf 100644 --- a/internal/component/otelcol/extension/jaeger_remote_sampling/jaeger_remote_sampling.go +++ b/internal/component/otelcol/extension/jaeger_remote_sampling/jaeger_remote_sampling.go @@ -2,6 +2,7 @@ package jaeger_remote_sampling import ( "fmt" + "maps" "time" "github.com/grafana/alloy/internal/component" @@ -100,7 +101,24 @@ func (args Arguments) Convert() (otelcomponent.Config, error) { // Extensions implements extension.Arguments. func (args Arguments) Extensions() map[otelcomponent.ID]otelextension.Extension { - return nil + extensionMap := make(map[otelcomponent.ID]otelextension.Extension) + + // Gets the extensions for the HTTP server and GRPC server + if args.HTTP != nil { + httpExtensions := (*otelcol.HTTPServerArguments)(args.HTTP).Extensions() + + // Copies the extensions for the HTTP server into the map + maps.Copy(extensionMap, httpExtensions) + } + + if args.GRPC != nil { + grpcExtensions := (*otelcol.GRPCServerArguments)(args.GRPC).Extensions() + + // Copies the extensions for the GRPC server into the map. + maps.Copy(extensionMap, grpcExtensions) + } + + return extensionMap } // Exporters implements extension.Arguments. diff --git a/internal/component/otelcol/receiver/datadog/datadog.go b/internal/component/otelcol/receiver/datadog/datadog.go index e1fc449586..5517fa6c59 100644 --- a/internal/component/otelcol/receiver/datadog/datadog.go +++ b/internal/component/otelcol/receiver/datadog/datadog.go @@ -70,7 +70,7 @@ func (args Arguments) Convert() (otelcomponent.Config, error) { // Extensions implements receiver.Arguments. func (args Arguments) Extensions() map[otelcomponent.ID]otelextension.Extension { - return nil + return args.HTTPServer.Extensions() } // Exporters implements receiver.Arguments. diff --git a/internal/component/otelcol/receiver/jaeger/jaeger.go b/internal/component/otelcol/receiver/jaeger/jaeger.go index c81c7550c8..3e50ab5afe 100644 --- a/internal/component/otelcol/receiver/jaeger/jaeger.go +++ b/internal/component/otelcol/receiver/jaeger/jaeger.go @@ -3,6 +3,7 @@ package jaeger import ( "fmt" + "maps" "github.com/alecthomas/units" "github.com/grafana/alloy/internal/component" @@ -86,7 +87,24 @@ func (args Arguments) Convert() (otelcomponent.Config, error) { // Extensions implements receiver.Arguments. func (args Arguments) Extensions() map[otelcomponent.ID]otelextension.Extension { - return nil + extensionMap := make(map[otelcomponent.ID]otelextension.Extension) + + // Gets the extensions for the HTTP server and GRPC server + if args.Protocols.ThriftHTTP != nil { + httpExtensions := (*otelcol.HTTPServerArguments)(args.Protocols.ThriftHTTP.HTTPServerArguments).Extensions() + + // Copies the extensions for the HTTP server into the map + maps.Copy(extensionMap, httpExtensions) + } + + if args.Protocols.GRPC.GRPCServerArguments != nil { + grpcExtensions := (*otelcol.GRPCServerArguments)(args.Protocols.GRPC.GRPCServerArguments).Extensions() + + // Copies the extensions for the GRPC server into the map. + maps.Copy(extensionMap, grpcExtensions) + } + + return extensionMap } // Exporters implements receiver.Arguments. diff --git a/internal/component/otelcol/receiver/opencensus/opencensus.go b/internal/component/otelcol/receiver/opencensus/opencensus.go index 6e42a5c41e..212b6980e0 100644 --- a/internal/component/otelcol/receiver/opencensus/opencensus.go +++ b/internal/component/otelcol/receiver/opencensus/opencensus.go @@ -70,7 +70,7 @@ func (args Arguments) Convert() (otelcomponent.Config, error) { // Extensions implements receiver.Arguments. func (args Arguments) Extensions() map[otelcomponent.ID]extension.Extension { - return nil + return args.GRPC.Extensions() } // Exporters implements receiver.Arguments. diff --git a/internal/component/otelcol/receiver/zipkin/zipkin.go b/internal/component/otelcol/receiver/zipkin/zipkin.go index 8c5655ef7e..5965246404 100644 --- a/internal/component/otelcol/receiver/zipkin/zipkin.go +++ b/internal/component/otelcol/receiver/zipkin/zipkin.go @@ -66,7 +66,7 @@ func (args Arguments) Convert() (otelcomponent.Config, error) { // Extensions implements receiver.Arguments. func (args Arguments) Extensions() map[otelcomponent.ID]otelextension.Extension { - return nil + return args.HTTPServer.Extensions() } // Exporters implements receiver.Arguments. From 93377d020d83059df0c4c66687d31656173a6c05 Mon Sep 17 00:00:00 2001 From: aidanleuck Date: Fri, 29 Nov 2024 18:27:52 -0700 Subject: [PATCH 07/17] Refactor to use feature flag --- internal/component/otelcol/auth/auth.go | 103 +++++++++++++++--- internal/component/otelcol/auth/auth_test.go | 51 +++++++++ .../component/otelcol/auth/basic/basic.go | 4 + .../component/otelcol/auth/bearer/bearer.go | 4 + .../component/otelcol/auth/headers/headers.go | 4 + .../component/otelcol/auth/oauth2/oauth2.go | 4 + .../component/otelcol/auth/sigv4/sigv4.go | 4 + internal/component/otelcol/config_grpc.go | 4 +- internal/component/otelcol/config_http.go | 3 + 9 files changed, 164 insertions(+), 17 deletions(-) diff --git a/internal/component/otelcol/auth/auth.go b/internal/component/otelcol/auth/auth.go index 1d574341de..6ae620f55e 100644 --- a/internal/component/otelcol/auth/auth.go +++ b/internal/component/otelcol/auth/auth.go @@ -34,12 +34,16 @@ import ( var ( ErrNotServerExtension = errors.New("component does not support server authentication") ErrNotClientExtension = errors.New("component does not support client authentication") - ErrInvalidExtension = errors.New("invalid extension") ) type ExtensionType string +type AuthFeature byte const ( + ClientAuthSupported AuthFeature = 1 << iota + ServerAuthSupported AuthFeature = 1 << iota + ClientAndServerAuthSupported AuthFeature = ClientAuthSupported | ServerAuthSupported + Server ExtensionType = "server" Client ExtensionType = "client" ) @@ -49,12 +53,18 @@ const ( type Arguments interface { component.Arguments + // AuthFeature returns the type of auth that a opentelemetry collector plugin supports + // client auth, server auth or both. + AuthFeatures() AuthFeature + // ConvertClient converts the Arguments into an OpenTelemetry Collector - // client authentication extension configuration. + // client authentication extension configuration. If the plugin does + // not support server authentication it should return nil, nil ConvertClient() (otelcomponent.Config, error) // ConvetServer converts the Arguments into an OpenTelemetry Collector - // server authentication extension configuration + // server authentication extension configuration. If the plugin does + // not support server authentication it should return nil, nil ConvertServer() (otelcomponent.Config, error) // Extensions returns the set of extensions that the configured component is @@ -83,6 +93,8 @@ type Handler struct { handlerMap map[ExtensionType]*ExtensionHandler } +// NewHandler creates a handler that can be exported +// in a capsule for otel servers to consume. func NewHandler(componentID string) *Handler { return &Handler{ componentID: componentID, @@ -90,12 +102,20 @@ func NewHandler(componentID string) *Handler { } } +// GetExtension retrieves the extension for the requested auth type, server or client. func (h *Handler) GetExtension(et ExtensionType) (*ExtensionHandler, error) { ext, ok := h.handlerMap[et] + + // This condition shouldn't happen since both extension types are set in Update(), but + // this will prevent a panic if it is somehow unset. if !ok { - return nil, fmt.Errorf("error initializing %s auth extension. component %s was unexpectedly nil", et, h.componentID) + return nil, fmt.Errorf("error getting %s auth extension. component %s was unexpectedly nil", et, h.componentID) } + // Check to make sure the extension does not have Error set. + // see SetupExtension() to see how this value is set. + // In general the error value is set if an auth extension + // does not support the type of authentication that was requested. if ext.Error != nil { return nil, ext.Error } @@ -103,11 +123,18 @@ func (h *Handler) GetExtension(et ExtensionType) (*ExtensionHandler, error) { return ext, nil } +// AddExtension registers an extension type with the handler so it can be referenced +// by another component. func (h *Handler) AddExtension(et ExtensionType, eh *ExtensionHandler) error { + // If an invalid extension is passed raise an error. if et != Server && et != Client { return fmt.Errorf("invalid extension type %s", et) } + if eh == nil { + return fmt.Errorf("extension handler must not be null") + } + h.handlerMap[et] = eh return nil } @@ -115,6 +142,7 @@ func (h *Handler) AddExtension(et ExtensionType, eh *ExtensionHandler) error { type ExtensionHandler struct { ID otelcomponent.ID Extension otelextension.Extension + // Set if the extension does not support the type of authentication // requested Error error @@ -229,29 +257,38 @@ func (a *Auth) Update(args component.Arguments) error { // Create instances of the extension from our factory. var components []otelcomponent.Component + + // Make sure the component returned a valid set of auth flags. + authFeature := rargs.AuthFeatures() + if valid := ValidateAuthFeatures(authFeature); !valid { + return fmt.Errorf("invalid auth flag %d returned by component %s", authFeature, a.opts.ID) + } + + // Registers the client extension for the otel collector plugin handler := NewHandler(a.opts.ID) - clientEh, err := a.setupExtension(Client, rargs, settings) + clientEh, err := a.SetupExtension(Client, rargs, settings) if err != nil { return err } - // Extension could be nil if the auth plugin does not support client auth - if clientEh.Extension != nil { + // If the extension supports client auth schedule it. + if HasAuthFeature(authFeature, ClientAuthSupported) { components = append(components, clientEh.Extension) } - // Register extension so it can be retrieved + // Register extension so it can be retrieved when referenced. if err := handler.AddExtension(Client, clientEh); err != nil { return err } - serverEh, err := a.setupExtension(Server, rargs, settings) + // Registers server authentication plugin. + serverEh, err := a.SetupExtension(Server, rargs, settings) if err != nil { return err } - // Extension could be nil if the auth plugin does not support server auth. - if serverEh.Extension != nil { + // If the extension supports server auth schedule it. + if HasAuthFeature(authFeature, ServerAuthSupported) { components = append(components, serverEh.Extension) } @@ -292,37 +329,54 @@ func NormalizeType(in string) string { return res } -func (a *Auth) setupExtension(t ExtensionType, rargs Arguments, settings otelextension.Settings) (*ExtensionHandler, error) { +// SetupExtension sets up the extension handler object with the appropriate fields to map the alloy +// capsule to the underlying otel auth extension. +func (a *Auth) SetupExtension(t ExtensionType, rargs Arguments, settings otelextension.Settings) (*ExtensionHandler, error) { var otelConfig otelcomponent.Config var err error var notSupportedErr error + var requiredAuthFeature AuthFeature + + // Retrieve the appropriate auth extension for the requested type. if t == Server { otelConfig, err = rargs.ConvertServer() notSupportedErr = ErrNotServerExtension + requiredAuthFeature = ServerAuthSupported } if t == Client { otelConfig, err = rargs.ConvertClient() notSupportedErr = ErrNotClientExtension + requiredAuthFeature = ClientAuthSupported } + // If there was an error converting the server/client args fail now. if err != nil { return nil, err } eh := &ExtensionHandler{} - - // Auth plugins that don't support the client/server auth - // are expected to return nil, check for that error here. - if otelConfig == nil { + extensionAuthFeatures := rargs.AuthFeatures() + + // Auth plugins return a feature flag indicating the types of authentication they support. + // If the plugin does not support the requested extension type (client or server authentication), + // the handler will set the error field. This results in an error being triggered if the unsupported + // extension is accessed via the handler. However, we do not return an error immediately because + // the user must explicitly request the invalid handler in their configuration for the error to occur. + // Refer to Handler.GetExtension() for the implementation logic. + if !HasAuthFeature(extensionAuthFeatures, requiredAuthFeature) { eh.Error = fmt.Errorf("%s %w", a.opts.ID, notSupportedErr) return eh, nil } + // Create the otel extension via its factory. otelExtension, err := a.createExtension(otelConfig, settings) if err != nil { return nil, err } + // Create an extension id based off the alloy name. For example + // auth.basic.creds will become auth.basic.creds.server/client depending on + // the type. cTypeStr := NormalizeType(fmt.Sprintf("%s.%s", a.opts.ID, t)) eh.ID = otelcomponent.NewID(otelcomponent.MustNewType(cTypeStr)) eh.Extension = otelExtension @@ -330,14 +384,31 @@ func (a *Auth) setupExtension(t ExtensionType, rargs Arguments, settings otelext return eh, nil } +// createExtension uses the otelextension factory to construct the otel auth extension. func (a *Auth) createExtension(config otelcomponent.Config, settings otelextension.Settings) (otelcomponent.Component, error) { ext, err := a.factory.Create(a.ctx, settings, config) if err != nil { return nil, err } + + // sanity check if ext == nil { return nil, fmt.Errorf("extension was not created") } return ext, nil } + +// ValidateAuthFeatures makes sure a valid auth feature was returned by a +func ValidateAuthFeatures(f AuthFeature) bool { + validFlags := ClientAuthSupported | ServerAuthSupported | ClientAndServerAuthSupported + + // bit clear any flags not set in f. + // if this is not zero then an invalid flag was passed. + return f&^validFlags == 0 +} + +func HasAuthFeature(flag AuthFeature, feature AuthFeature) bool { + // bitwise and the two features together. If not zero it has the feature + return flag&feature != 0 +} diff --git a/internal/component/otelcol/auth/auth_test.go b/internal/component/otelcol/auth/auth_test.go index c1575aba57..e6cd9cb0fa 100644 --- a/internal/component/otelcol/auth/auth_test.go +++ b/internal/component/otelcol/auth/auth_test.go @@ -93,6 +93,10 @@ func (fa fakeAuthArgs) ConvertServer() (otelcomponent.Config, error) { return &struct{}{}, nil } +func (fa fakeAuthArgs) AuthFeatures() auth.AuthFeature { + return auth.ClientAndServerAuthSupported +} + func (fa fakeAuthArgs) Extensions() map[otelcomponent.ID]otelextension.Extension { return nil } @@ -135,3 +139,50 @@ func (fe fakeAuthArgs) DebugMetricsConfig() otelcolCfg.DebugMetricsArguments { dma.SetToDefault() return dma } + +// TestValidateAuthFeature tests that it returns if the flag returned by an otel auth +// extension is correctly interpreted. +func TestValidateAuthFeature(t *testing.T) { + type tc struct { + input auth.AuthFeature + expected bool + } + testcases := []tc{ + {auth.ClientAndServerAuthSupported, true}, + {auth.ClientAuthSupported, true}, + {auth.ServerAuthSupported, true}, + {5, false}, + } + + for _, tc := range testcases { + actual := auth.ValidateAuthFeatures(tc.input) + require.Equal(t, tc.expected, actual) + } +} + +// TestHasAuthFeature tests that HasAuthFeature correctly determines +// whether the auth extension has an authentication feature, server or client. +func TestHasAuthFeature(t *testing.T) { + type input struct { + flag auth.AuthFeature + feature auth.AuthFeature + } + type tc struct { + input input + expected bool + } + + testcases := []tc{ + {input{flag: auth.ClientAuthSupported, feature: auth.ClientAuthSupported}, true}, + {input{flag: auth.ServerAuthSupported, feature: auth.ServerAuthSupported}, true}, + {input{flag: auth.ClientAndServerAuthSupported, feature: auth.ClientAuthSupported}, true}, + {input{flag: auth.ClientAndServerAuthSupported, feature: auth.ServerAuthSupported}, true}, + {input{flag: auth.ClientAuthSupported, feature: auth.ServerAuthSupported}, false}, + {input{flag: auth.ServerAuthSupported, feature: auth.ClientAuthSupported}, false}, + } + + for _, tc := range testcases { + actual := auth.HasAuthFeature(tc.input.flag, tc.input.feature) + require.Equal(t, tc.expected, actual, "flag:", tc.input.flag, "feature:", tc.input.feature) + } +} diff --git a/internal/component/otelcol/auth/basic/basic.go b/internal/component/otelcol/auth/basic/basic.go index 77a4417f77..a10f069720 100644 --- a/internal/component/otelcol/auth/basic/basic.go +++ b/internal/component/otelcol/auth/basic/basic.go @@ -65,6 +65,10 @@ func (args Arguments) ConvertServer() (otelcomponent.Config, error) { }, nil } +func (args Arguments) AuthFeatures() auth.AuthFeature { + return auth.ClientAndServerAuthSupported +} + // Extensions implements auth.Arguments. func (args Arguments) Extensions() map[otelcomponent.ID]otelextension.Extension { return nil diff --git a/internal/component/otelcol/auth/bearer/bearer.go b/internal/component/otelcol/auth/bearer/bearer.go index 20baa4d8e9..f0ce2e5b75 100644 --- a/internal/component/otelcol/auth/bearer/bearer.go +++ b/internal/component/otelcol/auth/bearer/bearer.go @@ -67,6 +67,10 @@ func (args Arguments) ConvertServer() (otelcomponent.Config, error) { return args.convert() } +func (args Arguments) AuthFeatures() auth.AuthFeature { + return auth.ClientAndServerAuthSupported +} + // Extensions implements auth.Arguments. func (args Arguments) Extensions() map[otelcomponent.ID]otelextension.Extension { return nil diff --git a/internal/component/otelcol/auth/headers/headers.go b/internal/component/otelcol/auth/headers/headers.go index 75190ccdde..3c988b912a 100644 --- a/internal/component/otelcol/auth/headers/headers.go +++ b/internal/component/otelcol/auth/headers/headers.go @@ -86,6 +86,10 @@ func (args Arguments) Extensions() map[otelcomponent.ID]otelextension.Extension return nil } +func (args Arguments) AuthFeatures() auth.AuthFeature { + return auth.ClientAuthSupported +} + // Exporters implements auth.Arguments. func (args Arguments) Exporters() map[pipeline.Signal]map[otelcomponent.ID]otelcomponent.Component { return nil diff --git a/internal/component/otelcol/auth/oauth2/oauth2.go b/internal/component/otelcol/auth/oauth2/oauth2.go index d880b503ca..38ff15e1c2 100644 --- a/internal/component/otelcol/auth/oauth2/oauth2.go +++ b/internal/component/otelcol/auth/oauth2/oauth2.go @@ -83,6 +83,10 @@ func (args Arguments) Exporters() map[pipeline.Signal]map[otelcomponent.ID]otelc return nil } +func (args Arguments) AuthFeatures() auth.AuthFeature { + return auth.ClientAuthSupported +} + // DebugMetricsConfig implements auth.Arguments. func (args Arguments) DebugMetricsConfig() otelcolCfg.DebugMetricsArguments { return args.DebugMetrics diff --git a/internal/component/otelcol/auth/sigv4/sigv4.go b/internal/component/otelcol/auth/sigv4/sigv4.go index 338e931464..c0849bbd94 100644 --- a/internal/component/otelcol/auth/sigv4/sigv4.go +++ b/internal/component/otelcol/auth/sigv4/sigv4.go @@ -74,6 +74,10 @@ func (args Arguments) Extensions() map[otelcomponent.ID]otelextension.Extension return nil } +func (args Arguments) AuthFeatures() auth.AuthFeature { + return auth.ClientAuthSupported +} + // Exporters implements auth.Arguments. func (args Arguments) Exporters() map[pipeline.Signal]map[otelcomponent.ID]otelcomponent.Component { return nil diff --git a/internal/component/otelcol/config_grpc.go b/internal/component/otelcol/config_grpc.go index 9ef824b72e..b2e4a8225e 100644 --- a/internal/component/otelcol/config_grpc.go +++ b/internal/component/otelcol/config_grpc.go @@ -43,8 +43,10 @@ func (args *GRPCServerArguments) Convert() (*otelconfiggrpc.ServerConfig, error) return nil, nil } + // If auth is set add that to the config. var authz *otelconfigauth.Authentication if args.Auth != nil { + // If a auth plugin does not implement server auth, an error will be returned here. serverExtension, err := args.Auth.GetExtension(auth.Server) if err != nil { return nil, err @@ -183,7 +185,7 @@ func (args *GRPCClientArguments) Convert() (*otelconfiggrpc.ClientConfig, error) opaqueHeaders[headerName] = configopaque.String(headerVal) } - // Configure the authentication if args.Auth is set. + // Configure authentication if args.Auth is set. var authz *otelconfigauth.Authentication if args.Auth != nil { ext, err := args.Auth.GetExtension(auth.Client) diff --git a/internal/component/otelcol/config_http.go b/internal/component/otelcol/config_http.go index 76abfdee5b..0cbbabab4e 100644 --- a/internal/component/otelcol/config_http.go +++ b/internal/component/otelcol/config_http.go @@ -45,6 +45,8 @@ func (args *HTTPServerArguments) Convert() (*otelconfighttp.ServerConfig, error) return nil, nil } + // If auth is set by the user retrieve the associated extension from the handler. + // if the extension does not support server auth an error will be returned. var authz *otelconfighttp.AuthConfig if args.Auth != nil { ext, err := args.Auth.GetExtension(auth.Server) @@ -75,6 +77,7 @@ func (args *HTTPServerArguments) Extensions() map[otelcomponent.ID]otelextension m := make(map[otelcomponent.ID]otelextension.Extension) if args.Auth != nil { ext, err := args.Auth.GetExtension(auth.Server) + // Extension will not be registered if there was an error. if err != nil { return m } From 497276a2fdfbf37a09c34869eae7f4f0b3a50d29 Mon Sep 17 00:00:00 2001 From: aidanleuck Date: Sat, 30 Nov 2024 13:35:40 -0700 Subject: [PATCH 08/17] Comments --- internal/component/otelcol/auth/auth_test.go | 133 ++++++++++++++++-- .../component/otelcol/auth/basic/basic.go | 2 - .../otelcol/auth/basic/basic_test.go | 132 +++++++++++------ .../otelcol/auth/bearer/bearer_test.go | 126 ++++++++++++++--- 4 files changed, 316 insertions(+), 77 deletions(-) diff --git a/internal/component/otelcol/auth/auth_test.go b/internal/component/otelcol/auth/auth_test.go index e6cd9cb0fa..9abe13c900 100644 --- a/internal/component/otelcol/auth/auth_test.go +++ b/internal/component/otelcol/auth/auth_test.go @@ -7,11 +7,11 @@ import ( "time" "github.com/grafana/alloy/internal/component" - "github.com/grafana/alloy/internal/component/otelcol" "github.com/grafana/alloy/internal/component/otelcol/auth" otelcolCfg "github.com/grafana/alloy/internal/component/otelcol/config" "github.com/grafana/alloy/internal/runtime/componenttest" "github.com/grafana/alloy/internal/util" + "github.com/stretchr/testify/mock" "github.com/stretchr/testify/require" otelcomponent "go.opentelemetry.io/collector/component" otelextension "go.opentelemetry.io/collector/extension" @@ -26,10 +26,13 @@ func TestAuth(t *testing.T) { } ) + fakeAuthArgs := &fakeAuthArgs{} + fakeAuthArgs.On("AuthFeatures").Return(auth.ClientAndServerAuthSupported) + // Create and start our Alloy component. We then wait for it to export a // consumer that we can send data to. te := newTestEnvironment(t, onCreated) - te.Start(fakeAuthArgs{}) + te.Start(fakeAuthArgs) require.NoError(t, waitCreated.Wait(time.Second), "extension never created") } @@ -46,7 +49,7 @@ func newTestEnvironment(t *testing.T, onCreated func()) *testEnvironment { reg := component.Registration{ Name: "testcomponent", Args: fakeAuthArgs{}, - Exports: otelcol.ConsumerExports{}, + Exports: auth.Exports{}, Build: func(opts component.Options, args component.Arguments) (component.Component, error) { factory := otelextension.NewFactory( otelcomponent.MustNewType("testcomponent"), @@ -58,7 +61,7 @@ func newTestEnvironment(t *testing.T, onCreated func()) *testEnvironment { ) (otelcomponent.Component, error) { onCreated() - return nil, nil + return fakeOtelComponent{}, nil }, otelcomponent.StabilityLevelUndefined, ) @@ -80,28 +83,41 @@ func (te *testEnvironment) Start(args component.Arguments) { }() } +type fakeOtelComponent struct { +} + +func (f fakeOtelComponent) Start(ctx context.Context, host otelcomponent.Host) error { + return nil +} + +func (f fakeOtelComponent) Shutdown(ctx context.Context) error { + return nil +} + type fakeAuthArgs struct { + mock.Mock } -var _ auth.Arguments = fakeAuthArgs{} +var _ auth.Arguments = &fakeAuthArgs{} -func (fa fakeAuthArgs) ConvertClient() (otelcomponent.Config, error) { +func (fa *fakeAuthArgs) ConvertClient() (otelcomponent.Config, error) { return &struct{}{}, nil } -func (fa fakeAuthArgs) ConvertServer() (otelcomponent.Config, error) { +func (fa *fakeAuthArgs) ConvertServer() (otelcomponent.Config, error) { return &struct{}{}, nil } -func (fa fakeAuthArgs) AuthFeatures() auth.AuthFeature { - return auth.ClientAndServerAuthSupported +func (fa *fakeAuthArgs) AuthFeatures() auth.AuthFeature { + result := fa.Called() + return result.Get(0).(auth.AuthFeature) } -func (fa fakeAuthArgs) Extensions() map[otelcomponent.ID]otelextension.Extension { +func (fa *fakeAuthArgs) Extensions() map[otelcomponent.ID]otelextension.Extension { return nil } -func (fa fakeAuthArgs) Exporters() map[pipeline.Signal]map[otelcomponent.ID]otelcomponent.Component { +func (fa *fakeAuthArgs) Exporters() map[pipeline.Signal]map[otelcomponent.ID]otelcomponent.Component { return nil } @@ -134,7 +150,7 @@ func TestNormalizeType(t *testing.T) { } } -func (fe fakeAuthArgs) DebugMetricsConfig() otelcolCfg.DebugMetricsArguments { +func (fe *fakeAuthArgs) DebugMetricsConfig() otelcolCfg.DebugMetricsArguments { var dma otelcolCfg.DebugMetricsArguments dma.SetToDefault() return dma @@ -186,3 +202,96 @@ func TestHasAuthFeature(t *testing.T) { require.Equal(t, tc.expected, actual, "flag:", tc.input.flag, "feature:", tc.input.feature) } } + +// TestAuthHandler runs a simple functional test by running the component +// against a mock auth extension. It validates the component starts correctly +// and when the handler requests either a server or client handler the correct output +// is returned +func TestAuthHandler(t *testing.T) { + var ( + waitCreated = util.NewWaitTrigger() + onCreated = func() { + waitCreated.Trigger() + } + ) + + // Test case definition + type input struct { + support auth.AuthFeature + } + + type expected struct { + clientAuthSupported bool + serverAuthSupported bool + err error + } + type tc struct { + input input + expected expected + } + + // Test cases + tcs := []tc{ + // Validates + { + input: input{support: auth.ClientAuthSupported}, expected: expected{ + clientAuthSupported: true, serverAuthSupported: false, err: auth.ErrNotServerExtension, + }, + }, + { + input: input{support: auth.ServerAuthSupported}, expected: expected{ + clientAuthSupported: false, serverAuthSupported: true, err: auth.ErrNotClientExtension, + }, + }, + { + input: input{support: auth.ClientAndServerAuthSupported}, expected: expected{ + clientAuthSupported: true, serverAuthSupported: true, err: nil, + }, + }, + } + + for _, tc := range tcs { + // Spin up a test component + te := newTestEnvironment(t, onCreated) + + // Mock the return of AuthFeatures to avoid creating test-specific implementations + // for each combination of authentication extensions supported by the OpenTelemetry collector. + fakeAuthArgs := &fakeAuthArgs{} + fakeAuthArgs.On("AuthFeatures").Return(tc.input.support) + + // Start the test environment and validate it comes up properly. + te.Start(fakeAuthArgs) + require.NoError(t, waitCreated.Wait(time.Second), "extension never created") + require.NoError(t, te.Controller.WaitRunning(time.Second), "extension never started running") + require.NoError(t, te.Controller.WaitExports(time.Second), "extension never exported anything") + + // Retrieve the exports of the component, make sure it exported a handler. + export := te.Controller.Exports() + authExport, ok := export.(auth.Exports) + require.True(t, ok, "auth component didn't export an auth export type") + require.NotNil(t, authExport.Handler) + + // Check the state of the handler and verify it is correct. + clientEh, err := authExport.Handler.GetExtension(auth.Client) + validateHandler(t, clientEh, tc.expected.clientAuthSupported, err, tc.expected.err) + + serverEh, err := authExport.Handler.GetExtension(auth.Server) + validateHandler(t, serverEh, tc.expected.serverAuthSupported, err, tc.expected.err) + } +} + +// validateHandler determines what the correct state of the extension handler should be depending +// on the test case state. If the extension supports the authentication requested it should return +// the extension. Otherwise it should return an error saying the extension does not support the requested +// type of authenticaiton. +func validateHandler(t *testing.T, eh *auth.ExtensionHandler, authSupported bool, actualErr error, expectedError error) { + t.Helper() + if authSupported { + require.NoError(t, actualErr) + require.NotNil(t, eh.Extension) + require.NotNil(t, eh.ID) + } else { + require.NotNil(t, actualErr) + require.ErrorIs(t, actualErr, expectedError) + } +} diff --git a/internal/component/otelcol/auth/basic/basic.go b/internal/component/otelcol/auth/basic/basic.go index a10f069720..42743e7361 100644 --- a/internal/component/otelcol/auth/basic/basic.go +++ b/internal/component/otelcol/auth/basic/basic.go @@ -32,8 +32,6 @@ func init() { // Arguments configures the otelcol.auth.basic component. type Arguments struct { - // TODO(rfratto): should we support htpasswd? - Username string `alloy:"username,attr"` Password alloytypes.Secret `alloy:"password,attr"` diff --git a/internal/component/otelcol/auth/basic/basic_test.go b/internal/component/otelcol/auth/basic/basic_test.go index 7d90037499..646f7ffcf3 100644 --- a/internal/component/otelcol/auth/basic/basic_test.go +++ b/internal/component/otelcol/auth/basic/basic_test.go @@ -3,6 +3,7 @@ package basic_test import ( "context" "encoding/base64" + "fmt" "net/http" "net/http/httptest" "testing" @@ -18,16 +19,28 @@ import ( extauth "go.opentelemetry.io/collector/extension/auth" ) +const ( + actualUsername = "foo" + actualPassword = "bar" +) + +var ( + cfg = fmt.Sprintf(` + username = "%s" + password = "%s" + `, actualUsername, actualPassword) +) + // Test performs a basic integration test which runs the otelcol.auth.basic // component and ensures that it can be used for authentication. -func Test(t *testing.T) { +func TestClientAuth(t *testing.T) { // Create an HTTP server which will assert that basic auth has been injected // into the request. srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { username, password, ok := r.BasicAuth() assert.True(t, ok, "no basic auth found") - assert.Equal(t, "foo", username, "basic auth username didn't match") - assert.Equal(t, "bar", password, "basic auth password didn't match") + assert.Equal(t, actualUsername, username, "basic auth username didn't match") + assert.Equal(t, actualPassword, password, "basic auth password didn't match") w.WriteHeader(http.StatusOK) })) @@ -37,62 +50,89 @@ func Test(t *testing.T) { ctx, cancel := context.WithTimeout(ctx, time.Minute) defer cancel() - l := util.TestLogger(t) + ctrl := newTestComponent(t, ctx) - // Create and run our component - ctrl, err := componenttest.NewControllerFromID(l, "otelcol.auth.basic") + require.NoError(t, ctrl.WaitRunning(time.Second), "component never started") + require.NoError(t, ctrl.WaitExports(time.Second), "component never exported anything") + + // Get the authentication extension from our component and use it to make a + // request to our test server. + exports := ctrl.Exports().(auth.Exports) + require.NotNil(t, exports.Handler) + + clientExtension, err := exports.Handler.GetExtension(auth.Client) require.NoError(t, err) + require.NotNil(t, clientExtension) + clientAuth, ok := clientExtension.Extension.(extauth.Client) + require.True(t, ok, "handler does not implement configauth.ClientAuthenticator") - cfg := ` - username = "foo" - password = "bar" - ` - var args basic.Arguments - require.NoError(t, syntax.Unmarshal([]byte(cfg), &args)) + rt, err := clientAuth.RoundTripper(http.DefaultTransport) + require.NoError(t, err) + cli := &http.Client{Transport: rt} - go func() { - err := ctrl.Run(ctx, args) - require.NoError(t, err) - }() + // Wait until the request finishes. We don't assert anything else here; our + // HTTP handler won't write the response until it ensures that the basic auth + // was found. + req, err := http.NewRequestWithContext(ctx, http.MethodGet, srv.URL, nil) + require.NoError(t, err) + resp, err := cli.Do(req) + require.NoError(t, err, "HTTP request failed") + require.Equal(t, http.StatusOK, resp.StatusCode) +} +// TestServerAuth verifies the server auth component starts up properly and we can +// authenticate with the provided credentials. +func TestServerAuth(t *testing.T) { + ctx := componenttest.TestContext(t) + ctx, cancel := context.WithTimeout(ctx, time.Minute) + defer cancel() + + ctrl := newTestComponent(t, ctx) require.NoError(t, ctrl.WaitRunning(time.Second), "component never started") require.NoError(t, ctrl.WaitExports(time.Second), "component never exported anything") - // Get the authentication extension from our component and use it to make a - // request to our test server. - exports := ctrl.Exports().(auth.Exports) + exports, ok := ctrl.Exports().(auth.Exports) + require.True(t, ok, "extension doesn't export auth exports struct") require.NotNil(t, exports.Handler) - t.Run("ClientAuth", func(t *testing.T) { - clientExtension, err := exports.Handler.GetExtension(auth.Client) - require.NoError(t, err) - require.NotNil(t, clientExtension) - clientAuth, ok := clientExtension.Extension.(extauth.Client) - require.True(t, ok, "handler does not implement configauth.ClientAuthenticator") + // There's a data race condition in this test that causes this test to fail. + // The test will pass if running without race conditions with this sleep, + // If race condition checks are enabled it will fail. I believe this is because + // the upstream starts the extension asynchronously, but the scheduler expects + // a component to be running once Run() returns. If you have any suggestions/ideas + // on how to improve this test please let me know. Otherwise I will remove this test + // since it will cause failures. + time.Sleep(time.Second) - rt, err := clientAuth.RoundTripper(http.DefaultTransport) - require.NoError(t, err) - cli := &http.Client{Transport: rt} + serverAuthExtension, err := exports.Handler.GetExtension(auth.Server) + require.NoError(t, err) + require.NotNil(t, serverAuthExtension.ID) + require.NotNil(t, serverAuthExtension.Extension) - // Wait until the request finishes. We don't assert anything else here; our - // HTTP handler won't write the response until it ensures that the basic auth - // was found. - req, err := http.NewRequestWithContext(ctx, http.MethodGet, srv.URL, nil) - require.NoError(t, err) - resp, err := cli.Do(req) - require.NoError(t, err, "HTTP request failed") - require.Equal(t, http.StatusOK, resp.StatusCode) - }) + otelServerExtension, ok := serverAuthExtension.Extension.(extauth.Server) + require.True(t, ok, "extension did not implement server authentication") - t.Run("ServerAuth", func(t *testing.T) { - serverExtension, err := exports.Handler.GetExtension(auth.Server) - require.NoError(t, err) - require.NotNil(t, serverExtension) - serverAuth, ok := serverExtension.Extension.(extauth.Server) - require.True(t, ok, "handler does not implement configauth.ServerAuthenticator") - b64EncodingAuth := base64.StdEncoding.EncodeToString([]byte("foo:bar")) - _, err = serverAuth.Authenticate(ctx, map[string][]string{"Authorization": {"Basic " + b64EncodingAuth}}) + b64EncodingAuth := base64.StdEncoding.EncodeToString([]byte(fmt.Sprintf("%s:%s", actualUsername, actualPassword))) + _, err = otelServerExtension.Authenticate(ctx, map[string][]string{"Authorization": {"Basic " + b64EncodingAuth}}) + require.NoError(t, err) +} + +// newTestComponent brings up and runs the test component. +func newTestComponent(t *testing.T, ctx context.Context) *componenttest.Controller { + t.Helper() + l := util.TestLogger(t) + + // Create and run our component + ctrl, err := componenttest.NewControllerFromID(l, "otelcol.auth.basic") + require.NoError(t, err) + + var args basic.Arguments + require.NoError(t, syntax.Unmarshal([]byte(cfg), &args)) + + go func() { + err := ctrl.Run(ctx, args) require.NoError(t, err) - }) + }() + return ctrl } diff --git a/internal/component/otelcol/auth/bearer/bearer_test.go b/internal/component/otelcol/auth/bearer/bearer_test.go index 675693b1bd..839e91487a 100644 --- a/internal/component/otelcol/auth/bearer/bearer_test.go +++ b/internal/component/otelcol/auth/bearer/bearer_test.go @@ -2,8 +2,10 @@ package bearer_test import ( "context" + "fmt" "net/http" "net/http/httptest" + "strings" "testing" "time" @@ -19,7 +21,7 @@ import ( // Test performs a basic integration test which runs the otelcol.auth.bearer // component and ensures that it can be used for authentication. -func Test(t *testing.T) { +func TestClient(t *testing.T) { type TestDefinition struct { testName string expectedHeaderVal string @@ -75,22 +77,7 @@ func Test(t *testing.T) { ctx, cancel := context.WithTimeout(ctx, time.Minute) defer cancel() - l := util.TestLogger(t) - - // Create and run our component - ctrl, err := componenttest.NewControllerFromID(l, "otelcol.auth.bearer") - require.NoError(t, err) - - var args bearer.Arguments - require.NoError(t, syntax.Unmarshal([]byte(tt.alloyConfig), &args)) - - go func() { - err := ctrl.Run(ctx, args) - require.NoError(t, err) - }() - - require.NoError(t, ctrl.WaitRunning(time.Second), "component never started") - require.NoError(t, ctrl.WaitExports(time.Second), "component never exported anything") + ctrl := newTestComponent(t, ctx, tt.alloyConfig) // Get the authentication extension from our component and use it to make a // request to our test server. @@ -117,3 +104,108 @@ func Test(t *testing.T) { require.Equal(t, http.StatusOK, resp.StatusCode) } } + +func TestServer(t *testing.T) { + type TestDefinition struct { + testName string + token string + scheme string + alloyConfig string + } + token := "123" + tokenCfg := fmt.Sprintf(`token = "%s"`, token) + tests := []TestDefinition{ + { + testName: "Test1", + token: token, + scheme: "Bearer", + alloyConfig: fmt.Sprintf(` + %s + `, tokenCfg), + }, + { + testName: "Test2", + token: token, + scheme: "Bearer", + alloyConfig: fmt.Sprintf(` + %s + scheme = "Bearer" + `, tokenCfg), + }, + { + testName: "Test3", + token: token, + scheme: "MyScheme", + alloyConfig: fmt.Sprintf(` + %s + scheme = "MyScheme" + `, tokenCfg), + }, + { + testName: "Test4", + token: token, + scheme: "", + alloyConfig: fmt.Sprintf(` + %s + scheme = "" + `, tokenCfg), + }, + } + + for _, td := range tests { + ctx := componenttest.TestContext(t) + ctx, cancel := context.WithTimeout(ctx, time.Minute) + defer cancel() + + // Spin up component + ctrl := newTestComponent(t, ctx, td.alloyConfig) + exports := ctrl.Exports() + require.NotNil(t, exports) + + authExport, ok := exports.(auth.Exports) + require.True(t, ok, "component doesn't export auth export struct") + + // Get handler from exports + handler := authExport.Handler + require.NotNil(t, handler) + + // Get the server auth extension + serverExtension, err := handler.GetExtension(auth.Server) + require.NoError(t, err) + require.NotNil(t, serverExtension.Extension) + require.NotNil(t, serverExtension.ID) + + // Convert to server auth extension + otelServerAuthExtension, ok := serverExtension.Extension.(extauth.Server) + require.True(t, ok, "extension does not implement server authentication") + + scheme := fmt.Sprintf("%s %s", td.scheme, td.token) + + // Trim the space in case bearer token is set to an empty string + scheme = strings.TrimSpace(scheme) + _, err = otelServerAuthExtension.Authenticate(ctx, map[string][]string{"Authorization": {scheme}}) + require.NoError(t, err, td.testName) + } +} + +func newTestComponent(t *testing.T, ctx context.Context, alloyConfig string) *componenttest.Controller { + t.Helper() + l := util.TestLogger(t) + + // Create and run our component + ctrl, err := componenttest.NewControllerFromID(l, "otelcol.auth.bearer") + require.NoError(t, err) + + var args bearer.Arguments + require.NoError(t, syntax.Unmarshal([]byte(alloyConfig), &args)) + + go func() { + err := ctrl.Run(ctx, args) + require.NoError(t, err) + }() + + require.NoError(t, ctrl.WaitRunning(time.Second), "component never started") + require.NoError(t, ctrl.WaitExports(time.Second), "component never exported anything") + + return ctrl +} From cdb1c86547387ff8abb46810592edd59366c0925 Mon Sep 17 00:00:00 2001 From: aidanleuck Date: Sat, 30 Nov 2024 13:52:52 -0700 Subject: [PATCH 09/17] Cleanup --- internal/component/faro/receiver/receiver_otelcol_test.go | 2 +- internal/component/otelcol/auth/basic/basic.go | 3 +++ internal/component/otelcol/auth/bearer/bearer.go | 4 +++- internal/component/otelcol/auth/headers/headers.go | 3 ++- internal/component/otelcol/auth/oauth2/oauth2.go | 5 +++-- internal/component/otelcol/auth/sigv4/sigv4.go | 5 +++-- internal/component/otelcol/receiver/jaeger/jaeger.go | 4 ++-- 7 files changed, 17 insertions(+), 9 deletions(-) diff --git a/internal/component/faro/receiver/receiver_otelcol_test.go b/internal/component/faro/receiver/receiver_otelcol_test.go index f8ffa8bd2e..2c130589f8 100644 --- a/internal/component/faro/receiver/receiver_otelcol_test.go +++ b/internal/component/faro/receiver/receiver_otelcol_test.go @@ -1,4 +1,4 @@ -// //go:build !race +//go:build !race package receiver diff --git a/internal/component/otelcol/auth/basic/basic.go b/internal/component/otelcol/auth/basic/basic.go index 42743e7361..d8afe2f3ac 100644 --- a/internal/component/otelcol/auth/basic/basic.go +++ b/internal/component/otelcol/auth/basic/basic.go @@ -46,6 +46,7 @@ func (args *Arguments) SetToDefault() { args.DebugMetrics.SetToDefault() } +// ConvertClient implements auth.Arguments. func (args Arguments) ConvertClient() (otelcomponent.Config, error) { return &basicauthextension.Config{ ClientAuth: &basicauthextension.ClientAuthSettings{ @@ -55,6 +56,7 @@ func (args Arguments) ConvertClient() (otelcomponent.Config, error) { }, nil } +// ConvertServer implements auth.Arguments. func (args Arguments) ConvertServer() (otelcomponent.Config, error) { return &basicauthextension.Config{ Htpasswd: &basicauthextension.HtpasswdSettings{ @@ -63,6 +65,7 @@ func (args Arguments) ConvertServer() (otelcomponent.Config, error) { }, nil } +// AuthFeatures implements auth.Arguments. func (args Arguments) AuthFeatures() auth.AuthFeature { return auth.ClientAndServerAuthSupported } diff --git a/internal/component/otelcol/auth/bearer/bearer.go b/internal/component/otelcol/auth/bearer/bearer.go index f0ce2e5b75..9f13ca117c 100644 --- a/internal/component/otelcol/auth/bearer/bearer.go +++ b/internal/component/otelcol/auth/bearer/bearer.go @@ -58,15 +58,17 @@ func (args Arguments) convert() (otelcomponent.Config, error) { }, nil } -// Convert implements auth.Arguments. +// ConvertClient implements auth.Arguments. func (args Arguments) ConvertClient() (otelcomponent.Config, error) { return args.convert() } +// ConvertServer implements auth.Arguments. func (args Arguments) ConvertServer() (otelcomponent.Config, error) { return args.convert() } +// AuthFeatures implements auth.Arguments. func (args Arguments) AuthFeatures() auth.AuthFeature { return auth.ClientAndServerAuthSupported } diff --git a/internal/component/otelcol/auth/headers/headers.go b/internal/component/otelcol/auth/headers/headers.go index 3c988b912a..264e73bfbe 100644 --- a/internal/component/otelcol/auth/headers/headers.go +++ b/internal/component/otelcol/auth/headers/headers.go @@ -47,7 +47,7 @@ func (args *Arguments) SetToDefault() { args.DebugMetrics.SetToDefault() } -// Convert implements auth.Arguments. +// ConvertClient implements auth.Arguments. func (args Arguments) ConvertClient() (otelcomponent.Config, error) { var upstreamHeaders []headerssetterextension.HeaderConfig for _, h := range args.Headers { @@ -86,6 +86,7 @@ func (args Arguments) Extensions() map[otelcomponent.ID]otelextension.Extension return nil } +// AuthFeatures implements auth.Arguments. func (args Arguments) AuthFeatures() auth.AuthFeature { return auth.ClientAuthSupported } diff --git a/internal/component/otelcol/auth/oauth2/oauth2.go b/internal/component/otelcol/auth/oauth2/oauth2.go index 38ff15e1c2..dd64c58dcc 100644 --- a/internal/component/otelcol/auth/oauth2/oauth2.go +++ b/internal/component/otelcol/auth/oauth2/oauth2.go @@ -53,7 +53,7 @@ func (args *Arguments) SetToDefault() { args.DebugMetrics.SetToDefault() } -// Convert implements auth.Arguments. +// ConvertClient implements auth.Arguments. func (args Arguments) ConvertClient() (otelcomponent.Config, error) { return &oauth2clientauthextension.Config{ ClientID: args.ClientID, @@ -68,7 +68,7 @@ func (args Arguments) ConvertClient() (otelcomponent.Config, error) { }, nil } -// ConvertServer returns nil since the ouath2 client extension doesn ot support serve auth +// ConvertServer returns nil since the ouath2 client extension does not support server auth. func (args Arguments) ConvertServer() (otelcomponent.Config, error) { return nil, nil } @@ -83,6 +83,7 @@ func (args Arguments) Exporters() map[pipeline.Signal]map[otelcomponent.ID]otelc return nil } +// AuthFeatures implements auth.Arguments. func (args Arguments) AuthFeatures() auth.AuthFeature { return auth.ClientAuthSupported } diff --git a/internal/component/otelcol/auth/sigv4/sigv4.go b/internal/component/otelcol/auth/sigv4/sigv4.go index c0849bbd94..3c466a628b 100644 --- a/internal/component/otelcol/auth/sigv4/sigv4.go +++ b/internal/component/otelcol/auth/sigv4/sigv4.go @@ -43,7 +43,7 @@ func (args *Arguments) SetToDefault() { args.DebugMetrics.SetToDefault() } -// Convert implements auth.Arguments. +// ConvertClient implements auth.Arguments. func (args Arguments) ConvertClient() (otelcomponent.Config, error) { res := sigv4authextension.Config{ Region: args.Region, @@ -58,7 +58,7 @@ func (args Arguments) ConvertClient() (otelcomponent.Config, error) { return &res, nil } -// ConvertServer returns nil since the sigv4 extension does not support server authentication +// ConvertServer returns nil since the sigv4 extension does not support server authentication. func (args Arguments) ConvertServer() (otelcomponent.Config, error) { return nil, nil } @@ -74,6 +74,7 @@ func (args Arguments) Extensions() map[otelcomponent.ID]otelextension.Extension return nil } +// AuthFeatures implements auth.Arguments. func (args Arguments) AuthFeatures() auth.AuthFeature { return auth.ClientAuthSupported } diff --git a/internal/component/otelcol/receiver/jaeger/jaeger.go b/internal/component/otelcol/receiver/jaeger/jaeger.go index 3e50ab5afe..1fea60e890 100644 --- a/internal/component/otelcol/receiver/jaeger/jaeger.go +++ b/internal/component/otelcol/receiver/jaeger/jaeger.go @@ -90,14 +90,14 @@ func (args Arguments) Extensions() map[otelcomponent.ID]otelextension.Extension extensionMap := make(map[otelcomponent.ID]otelextension.Extension) // Gets the extensions for the HTTP server and GRPC server - if args.Protocols.ThriftHTTP != nil { + if args.Protocols.ThriftHTTP != nil && args.Protocols.ThriftHTTP.HTTPServerArguments != nil { httpExtensions := (*otelcol.HTTPServerArguments)(args.Protocols.ThriftHTTP.HTTPServerArguments).Extensions() // Copies the extensions for the HTTP server into the map maps.Copy(extensionMap, httpExtensions) } - if args.Protocols.GRPC.GRPCServerArguments != nil { + if args.Protocols.GRPC != nil && args.Protocols.GRPC.GRPCServerArguments != nil { grpcExtensions := (*otelcol.GRPCServerArguments)(args.Protocols.GRPC.GRPCServerArguments).Extensions() // Copies the extensions for the GRPC server into the map. From a7d45c7d89ae2abd702ed1c78e86fe3e38f05f1a Mon Sep 17 00:00:00 2001 From: aidanleuck Date: Sat, 30 Nov 2024 13:58:04 -0700 Subject: [PATCH 10/17] Spacing --- internal/component/otelcol/auth/oauth2/oauth2_test.go | 1 - 1 file changed, 1 deletion(-) diff --git a/internal/component/otelcol/auth/oauth2/oauth2_test.go b/internal/component/otelcol/auth/oauth2/oauth2_test.go index 01acb01818..5af868d0d2 100644 --- a/internal/component/otelcol/auth/oauth2/oauth2_test.go +++ b/internal/component/otelcol/auth/oauth2/oauth2_test.go @@ -115,7 +115,6 @@ func Test(t *testing.T) { ext, err := exports.Handler.GetExtension(auth.Client) require.NoError(t, err) - require.NotNil(t, ext.Extension, "handler extension is nil") clientAuth, ok := ext.Extension.(extauth.Client) From 3f21422eb9abc56593d7da9ec7369e1b0d251db3 Mon Sep 17 00:00:00 2001 From: aidanleuck Date: Sat, 30 Nov 2024 15:14:13 -0700 Subject: [PATCH 11/17] Update docs --- .../otelcol/otelcol.auth.headers.md | 2 ++ .../components/otelcol/otelcol.auth.oauth2.md | 2 ++ .../components/otelcol/otelcol.auth.sigv4.md | 2 ++ .../otelcol/otelcol.receiver.datadog.md | 20 ++++++++++++++ .../otelcol/otelcol.receiver.jaeger.md | 24 +++++++++++++++++ .../otelcol/otelcol.receiver.opencensus.md | 16 ++++++++++++ .../otelcol/otelcol.receiver.otlp.md | 26 +++++++++++++++++++ .../otelcol/otelcol.receiver.zipkin.md | 16 ++++++++++++ 8 files changed, 108 insertions(+) diff --git a/docs/sources/reference/components/otelcol/otelcol.auth.headers.md b/docs/sources/reference/components/otelcol/otelcol.auth.headers.md index 526e1036f7..2ebb557f4f 100644 --- a/docs/sources/reference/components/otelcol/otelcol.auth.headers.md +++ b/docs/sources/reference/components/otelcol/otelcol.auth.headers.md @@ -11,6 +11,8 @@ title: otelcol.auth.headers `otelcol.auth.headers` exposes a `handler` that can be used by other `otelcol` components to authenticate requests using custom headers. +This extension only supports client authentication. + {{< admonition type="note" >}} `otelcol.auth.headers` is a wrapper over the upstream OpenTelemetry Collector `headerssetter` extension. Bug reports or feature requests will be redirected to the upstream repository, if necessary. diff --git a/docs/sources/reference/components/otelcol/otelcol.auth.oauth2.md b/docs/sources/reference/components/otelcol/otelcol.auth.oauth2.md index f2e8a7014b..83c1a30dfb 100644 --- a/docs/sources/reference/components/otelcol/otelcol.auth.oauth2.md +++ b/docs/sources/reference/components/otelcol/otelcol.auth.oauth2.md @@ -10,6 +10,8 @@ title: otelcol.auth.oauth2 `otelcol.auth.oauth2` exposes a `handler` that can be used by other `otelcol` components to authenticate requests using OAuth 2.0. +This extension only supports client authentication. + The authorization tokens can be used by HTTP and gRPC based OpenTelemetry exporters. This component can fetch and refresh expired tokens automatically. Refer to the [OAuth 2.0 Authorization Framework](https://datatracker.ietf.org/doc/html/rfc6749#section-4.4) for more information about the Auth 2.0 Client Credentials flow. diff --git a/docs/sources/reference/components/otelcol/otelcol.auth.sigv4.md b/docs/sources/reference/components/otelcol/otelcol.auth.sigv4.md index d8f8070a35..300d9db837 100644 --- a/docs/sources/reference/components/otelcol/otelcol.auth.sigv4.md +++ b/docs/sources/reference/components/otelcol/otelcol.auth.sigv4.md @@ -12,6 +12,8 @@ title: otelcol.auth.sigv4 components to authenticate requests to AWS services using the AWS Signature Version 4 (SigV4) protocol. For more information about SigV4 see the AWS documentation about [Signing AWS API requests][]. +This extension only supports client authentication. + [Signing AWS API requests]: https://docs.aws.amazon.com/general/latest/gr/signing-aws-api-requests.html > **NOTE**: `otelcol.auth.sigv4` is a wrapper over the upstream OpenTelemetry diff --git a/docs/sources/reference/components/otelcol/otelcol.receiver.datadog.md b/docs/sources/reference/components/otelcol/otelcol.receiver.datadog.md index d374118d86..ae5c6eeae5 100644 --- a/docs/sources/reference/components/otelcol/otelcol.receiver.datadog.md +++ b/docs/sources/reference/components/otelcol/otelcol.receiver.datadog.md @@ -38,6 +38,7 @@ Name | Type | Description `include_metadata` | `boolean` | Propagate incoming connection metadata to downstream consumers. | `false` | no `read_timeout` | `duration` | Read timeout for requests of the HTTP server. | `"60s"` | no `compression_algorithms` | `list(string)` | A list of compression algorithms the server can accept. | `["", "gzip", "zstd", "zlib", "snappy", "deflate", "lz4"]` | no +`auth` | `capsule(otelcol.Handler)` | Handler from an `otelcol.auth` component to use for authenticating requests. | | no By default, `otelcol.receiver.datadog` listens for HTTP connections on `localhost`. To expose the HTTP server to other machines on your network, configure `endpoint` with the IP address to listen on, or `0.0.0.0:8126` to listen on all network interfaces. @@ -134,6 +135,25 @@ otelcol.exporter.otlp "default" { } } ``` + +## Enabling Authentication + +You can create a `datadog` receiver that requires authentication for requests. This is useful for limiting who can push data to the server. Note that not all OpenTelemetry Collector (otelcol) authentication plugins support receiver authentication. Please refer to the documentation for each `otelcol.auth.*` plugin to determine its compatibility. + +```alloy +otelcol.receiver.datadog "default" { + output { + metrics = [otelcol.processor.batch.default.input] + traces = [otelcol.processor.batch.default.input] + } + auth = otelcol.auth.basic.creds.handler +} + +otelcol.auth.basic "creds" { + username = sys.env("USERNAME") + password = sys.env("PASSWORD") +} +``` ## Compatible components diff --git a/docs/sources/reference/components/otelcol/otelcol.receiver.jaeger.md b/docs/sources/reference/components/otelcol/otelcol.receiver.jaeger.md index b6685614cc..4349cf56f1 100644 --- a/docs/sources/reference/components/otelcol/otelcol.receiver.jaeger.md +++ b/docs/sources/reference/components/otelcol/otelcol.receiver.jaeger.md @@ -101,6 +101,7 @@ Name | Type | Description `read_buffer_size` | `string` | Size of the read buffer the gRPC server will use for reading from clients. | `"512KiB"` | no `write_buffer_size` | `string` | Size of the write buffer the gRPC server will use for writing to clients. | | no `include_metadata` | `boolean` | Propagate incoming connection metadata to downstream consumers. | | no +`auth` | `capsule(otelcol.Handler)` | Handler from an `otelcol.auth` component to use for authenticating requests. | | no ### tls block @@ -154,6 +155,7 @@ Name | Type | Description `max_request_body_size` | `string` | Maximum request body size the server will allow. | `20MiB` | no `include_metadata` | `boolean` | Propagate incoming connection metadata to downstream consumers. | | no `compression_algorithms` | `list(string)` | A list of compression algorithms the server can accept. | `["", "gzip", "zstd", "zlib", "snappy", "deflate", "lz4"]` | no +`auth` | `capsule(otelcol.Handler)` | Handler from an `otelcol.auth` component to use for authenticating requests. | | no ### cors block @@ -262,6 +264,28 @@ otelcol.exporter.otlp "default" { `otelcol.receiver.jaeger` supports [Gzip](https://en.wikipedia.org/wiki/Gzip) for compression. +## Enabling Authentication + +You can create a `jaeger` receiver that requires authentication for requests. This is useful for limiting who can push data to the server. Note that not all OpenTelemetry Collector (otelcol) authentication plugins support receiver authentication. Please refer to the documentation for each `otelcol.auth.*` plugin to determine its compatibility. This functionality is currently limited to the GRPC/HTTP blocks. + +```alloy +otelcol.receiver.jaeger "default" { + protocols { + grpc { + auth = otelcol.auth.basic.creds.handler + } + thrift_http { + auth = otelcol.auth.basic.creds.handler + } + } +} + +otelcol.auth.basic "creds" { + username = sys.env("USERNAME") + password = sys.env("PASSWORD") +} +``` + ## Compatible components diff --git a/docs/sources/reference/components/otelcol/otelcol.receiver.opencensus.md b/docs/sources/reference/components/otelcol/otelcol.receiver.opencensus.md index b628adc8cc..f3ccde9774 100644 --- a/docs/sources/reference/components/otelcol/otelcol.receiver.opencensus.md +++ b/docs/sources/reference/components/otelcol/otelcol.receiver.opencensus.md @@ -51,6 +51,7 @@ Name | Type | Description | Default | Required `cors_allowed_origins` are the allowed [CORS](https://github.com/rs/cors) origins for HTTP/JSON requests. An empty list means that CORS is not enabled at all. A wildcard (*) can be used to match any origin or one or more characters of an origin. +`auth` | `capsule(otelcol.Handler)` | Handler from an `otelcol.auth` component to use for authenticating requests. | | no The "endpoint" parameter is the same for both gRPC and HTTP/JSON, as the protocol is recognized and processed accordingly. @@ -207,6 +208,21 @@ otelcol.exporter.otlp "default" { } } ``` + +## Enabling Authentication + +You can create a `opencensus` receiver that requires authentication for requests. This is useful for limiting who can push data to the server. Note that not all OpenTelemetry Collector (otelcol) authentication plugins support receiver authentication. Please refer to the documentation for each `otelcol.auth.*` plugin to determine its compatibility. + +```alloy +otelcol.receiver.opencensus "default" { + auth = otelcol.auth.basic.creds.handler +} + +otelcol.auth.basic "creds" { + username = sys.env("USERNAME") + password = sys.env("PASSWORD") +} +``` ## Compatible components diff --git a/docs/sources/reference/components/otelcol/otelcol.receiver.otlp.md b/docs/sources/reference/components/otelcol/otelcol.receiver.otlp.md index e1ecbf79ba..c7239e7bbb 100644 --- a/docs/sources/reference/components/otelcol/otelcol.receiver.otlp.md +++ b/docs/sources/reference/components/otelcol/otelcol.receiver.otlp.md @@ -85,6 +85,7 @@ Name | Type | Description | Default | Required `read_buffer_size` | `string` | Size of the read buffer the gRPC server will use for reading from clients. | `"512KiB"` | no `write_buffer_size` | `string` | Size of the write buffer the gRPC server will use for writing to clients. | | no `include_metadata` | `boolean` | Propagate incoming connection metadata to downstream consumers. | | no +`auth` | `capsule(otelcol.Handler)` | Handler from an `otelcol.auth` component to use for authenticating requests. | | no ### tls block @@ -145,6 +146,7 @@ Name | Type | Description | Default | Required `metrics_url_path` | `string` | The URL path to receive metrics on. | `"/v1/metrics"` | no `logs_url_path` | `string` | The URL path to receive logs on. | `"/v1/logs"` | no `compression_algorithms` | `list(string)` | A list of compression algorithms the server can accept. | `["", "gzip", "zstd", "zlib", "snappy", "deflate", "lz4"]` | no +`auth` | `capsule(otelcol.Handler)` | Handler from an `otelcol.auth` component to use for authenticating requests. | | no To send telemetry signals to `otelcol.receiver.otlp` with HTTP/JSON, POST to: * `[endpoint][traces_url_path]` for traces. @@ -240,6 +242,30 @@ otelcol.exporter.otlp "default" { ## Technical details `otelcol.receiver.otlp` supports [gzip](https://en.wikipedia.org/wiki/Gzip) for compression. + +## Enabling Authentication + +You can create a `otlp` receiver that requires authentication for requests. This is useful for limiting who can push data to the server. Note that not all OpenTelemetry Collector (otelcol) authentication plugins support receiver authentication. Please refer to the documentation for each `otelcol.auth.*` plugin to determine its compatibility. + +```alloy +otelcol.receiver.otlp "default" { + http { + auth = otelcol.auth.basic.creds.handler + } + grpc { + auth = otelcol.auth.basic.creds.handler + } + + output { + ... + } +} + +otelcol.auth.basic "creds" { + username = sys.env("USERNAME") + password = sys.env("PASSWORD") +} +``` ## Compatible components diff --git a/docs/sources/reference/components/otelcol/otelcol.receiver.zipkin.md b/docs/sources/reference/components/otelcol/otelcol.receiver.zipkin.md index f5f66d4bca..de6d8558cd 100644 --- a/docs/sources/reference/components/otelcol/otelcol.receiver.zipkin.md +++ b/docs/sources/reference/components/otelcol/otelcol.receiver.zipkin.md @@ -39,6 +39,7 @@ Name | Type | Description | Default | Required `max_request_body_size` | `string` | Maximum request body size the server will allow. | `20MiB` | no `include_metadata` | `boolean` | Propagate incoming connection metadata to downstream consumers. | | no `compression_algorithms` | `list(string)` | A list of compression algorithms the server can accept. | `["", "gzip", "zstd", "zlib", "snappy", "deflate", "lz4"]` | no +`auth` | `capsule(otelcol.Handler)` | Handler from an `otelcol.auth` component to use for authenticating requests. | | no If `parse_string_tags` is `true`, string tags and binary annotations are converted to `int`, `bool`, and `float` if possible. String tags and binary @@ -141,6 +142,21 @@ otelcol.exporter.otlp "default" { } } ``` + +## Enabling Authentication + +You can create a `zipkin` receiver that requires authentication for requests. This is useful for limiting who can push data to the server. Note that not all OpenTelemetry Collector (otelcol) authentication plugins support receiver authentication. Please refer to the documentation for each `otelcol.auth.*` plugin to determine its compatibility. + +```alloy +otelcol.receiver.zipkin "default" { + auth = otelcol.auth.basic.creds.handler +} + +otelcol.auth.basic "creds" { + username = sys.env("USERNAME") + password = sys.env("PASSWORD") +} +``` ## Compatible components From 6f477ff3514dfe18f6894b4bb8d1d96bc549ae0c Mon Sep 17 00:00:00 2001 From: aidanleuck Date: Sat, 30 Nov 2024 15:20:40 -0700 Subject: [PATCH 12/17] Update CHANGELOG --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index f8a3380282..9ea97468a5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -30,6 +30,8 @@ Main (unreleased) - Add relevant golang environment variables to the support bundle (@dehaansa) +- Add support for server authentication to otelcol components. (@aidaleuc) + ### Bugfixes - Fixed an issue in the `prometheus.exporter.postgres` component that would leak goroutines when the target was not reachable (@dehaansa) From 6510731c8e30638c4c6c0e7a0d146c2bdf229b97 Mon Sep 17 00:00:00 2001 From: aidanleuck Date: Sat, 30 Nov 2024 15:45:37 -0700 Subject: [PATCH 13/17] Last auth extension missing --- ...telcol.extension.jaeger_remote_sampling.md | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/docs/sources/reference/components/otelcol/otelcol.extension.jaeger_remote_sampling.md b/docs/sources/reference/components/otelcol/otelcol.extension.jaeger_remote_sampling.md index 481789ca7b..4d5c499001 100644 --- a/docs/sources/reference/components/otelcol/otelcol.extension.jaeger_remote_sampling.md +++ b/docs/sources/reference/components/otelcol/otelcol.extension.jaeger_remote_sampling.md @@ -79,6 +79,7 @@ Name | Type | Description `max_request_body_size` | `string` | Maximum request body size the server will allow. | `20MiB` | no `include_metadata` | `boolean` | Propagate incoming connection metadata to downstream consumers. | | no `compression_algorithms` | `list(string)` | A list of compression algorithms the server can accept. | `["", "gzip", "zstd", "zlib", "snappy", "deflate", "lz4"]` | no +`auth` | `capsule(otelcol.Handler)` | Handler from an `otelcol.auth` component to use for authenticating requests. | | no ### tls block @@ -125,6 +126,7 @@ Name | Type | Description `read_buffer_size` | `string` | Size of the read buffer the gRPC server will use for reading from clients. | `"512KiB"` | no `write_buffer_size` | `string` | Size of the write buffer the gRPC server will use for writing to clients. | | no `include_metadata` | `boolean` | Propagate incoming connection metadata to downstream consumers. | | no +`auth` | `capsule(otelcol.Handler)` | Handler from an `otelcol.auth` component to use for authenticating requests. | | no ### keepalive block @@ -293,3 +295,20 @@ otelcol.extension.jaeger_remote_sampling "example" { } } ``` + +## Enabling Authentication + +You can create a `jaeger_remote_sampling` extensions that requires authentication for requests. This is useful for limiting access to the sampling document. Note that not all OpenTelemetry Collector (otelcol) authentication plugins support receiver authentication. Please refer to the documentation for each `otelcol.auth.*` plugin to determine its compatibility. + +```alloy +otelcol.extension.jaeger_remote_sampling "default" { + http { + auth = otelcol.auth.basic.creds.handler + } +} + +otelcol.auth.basic "creds" { + username = sys.env("USERNAME") + password = sys.env("PASSWORD") +} +``` From 9998e92cf9874cb62977c0a2b5f6115dfbe4391c Mon Sep 17 00:00:00 2001 From: aidanleuck Date: Sat, 30 Nov 2024 15:48:58 -0700 Subject: [PATCH 14/17] We also need grpc auth --- .../otelcol/otelcol.extension.jaeger_remote_sampling.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/docs/sources/reference/components/otelcol/otelcol.extension.jaeger_remote_sampling.md b/docs/sources/reference/components/otelcol/otelcol.extension.jaeger_remote_sampling.md index 4d5c499001..9796fbcea0 100644 --- a/docs/sources/reference/components/otelcol/otelcol.extension.jaeger_remote_sampling.md +++ b/docs/sources/reference/components/otelcol/otelcol.extension.jaeger_remote_sampling.md @@ -305,6 +305,9 @@ otelcol.extension.jaeger_remote_sampling "default" { http { auth = otelcol.auth.basic.creds.handler } + grpc { + auth = otelcol.auth.basic.creds.handler + } } otelcol.auth.basic "creds" { From dade3315027a71e1545cd6706acb97f14e22809d Mon Sep 17 00:00:00 2001 From: aidanleuck Date: Sat, 30 Nov 2024 17:37:30 -0700 Subject: [PATCH 15/17] Fix opencensus docs --- .../components/otelcol/otelcol.receiver.opencensus.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/docs/sources/reference/components/otelcol/otelcol.receiver.opencensus.md b/docs/sources/reference/components/otelcol/otelcol.receiver.opencensus.md index f3ccde9774..fe6767fce7 100644 --- a/docs/sources/reference/components/otelcol/otelcol.receiver.opencensus.md +++ b/docs/sources/reference/components/otelcol/otelcol.receiver.opencensus.md @@ -47,11 +47,12 @@ Name | Type | Description | Default | Required `read_buffer_size` | `string` | Size of the read buffer the gRPC server will use for reading from clients. | `"512KiB"` | no `write_buffer_size` | `string` | Size of the write buffer the gRPC server will use for writing to clients. | | no `include_metadata` | `boolean` | Propagate incoming connection metadata to downstream consumers. | | no +`auth` | `capsule(otelcol.Handler)` | Handler from an `otelcol.auth` component to use for authenticating requests. | | no `cors_allowed_origins` are the allowed [CORS](https://github.com/rs/cors) origins for HTTP/JSON requests. An empty list means that CORS is not enabled at all. A wildcard (*) can be used to match any origin or one or more characters of an origin. -`auth` | `capsule(otelcol.Handler)` | Handler from an `otelcol.auth` component to use for authenticating requests. | | no + The "endpoint" parameter is the same for both gRPC and HTTP/JSON, as the protocol is recognized and processed accordingly. From 055bb1bd5a975e6667b54b94cdba0ac25d100da4 Mon Sep 17 00:00:00 2001 From: aidanleuck Date: Sat, 30 Nov 2024 20:18:59 -0700 Subject: [PATCH 16/17] Fix extra comment --- internal/component/otelcol/auth/headers/headers.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/internal/component/otelcol/auth/headers/headers.go b/internal/component/otelcol/auth/headers/headers.go index 264e73bfbe..8fe5478f75 100644 --- a/internal/component/otelcol/auth/headers/headers.go +++ b/internal/component/otelcol/auth/headers/headers.go @@ -1,4 +1,4 @@ -// // Package headers provides an otelcol.auth.headers component. +// Package headers provides an otelcol.auth.headers component. package headers import ( From fd537ffd4dc0911ca0d404b7070fd8bb33b8fe8b Mon Sep 17 00:00:00 2001 From: aidanleuck Date: Sun, 1 Dec 2024 10:09:20 -0700 Subject: [PATCH 17/17] Update comment with findings --- .../otelcol/auth/basic/basic_test.go | 21 ++++++++++++------- 1 file changed, 14 insertions(+), 7 deletions(-) diff --git a/internal/component/otelcol/auth/basic/basic_test.go b/internal/component/otelcol/auth/basic/basic_test.go index 646f7ffcf3..10a4da6a24 100644 --- a/internal/component/otelcol/auth/basic/basic_test.go +++ b/internal/component/otelcol/auth/basic/basic_test.go @@ -95,16 +95,23 @@ func TestServerAuth(t *testing.T) { require.True(t, ok, "extension doesn't export auth exports struct") require.NotNil(t, exports.Handler) - // There's a data race condition in this test that causes this test to fail. - // The test will pass if running without race conditions with this sleep, - // If race condition checks are enabled it will fail. I believe this is because - // the upstream starts the extension asynchronously, but the scheduler expects - // a component to be running once Run() returns. If you have any suggestions/ideas - // on how to improve this test please let me know. Otherwise I will remove this test - // since it will cause failures. + // This test fails due to a data race condition. + // The test passes if the -race flag is not passed to the test, simulated by a sleep. + // This issue arises from both the initialization of the basic_auth extension + // and the scheduling mechanism used in the component test controller. + // The problem is related to the way componenttest is structured and the interactions + // between the Run method and the scheduler. The WaitRunning() method returns when the + // channel is closed in the Run() method, but Run is actually invoked on the following line. + // In auth.go, the component is scheduled via the scheduler, but the extension doesn't + // actually start until the component is processed. As a result, ctrl.WaitRunning() + // returns before the auth extension is fully running. + // A more reliable method for confirming the extension is running would resolve this issue. + // Otherwise, this test might need to be removed. If anyone has any feedback or thoughts + // I would appreciate it. time.Sleep(time.Second) serverAuthExtension, err := exports.Handler.GetExtension(auth.Server) + require.NoError(t, err) require.NotNil(t, serverAuthExtension.ID) require.NotNil(t, serverAuthExtension.Extension)