diff --git a/internal/auth/authenticator.go b/config/configauth/authenticator.go similarity index 94% rename from internal/auth/authenticator.go rename to config/configauth/authenticator.go index df85b9148ed..62325a3e91e 100644 --- a/internal/auth/authenticator.go +++ b/config/configauth/authenticator.go @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -package auth +package configauth import ( "context" @@ -21,8 +21,6 @@ import ( "google.golang.org/grpc" "google.golang.org/grpc/metadata" - - "go.opentelemetry.io/collector/config/configauth" ) var ( @@ -52,8 +50,8 @@ type authenticateFunc func(context.Context, map[string][]string) (context.Contex type unaryInterceptorFunc func(ctx context.Context, req interface{}, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler, authenticate authenticateFunc) (interface{}, error) type streamInterceptorFunc func(srv interface{}, stream grpc.ServerStream, info *grpc.StreamServerInfo, handler grpc.StreamHandler, authenticate authenticateFunc) error -// New creates an authenticator based on the given configuration -func New(cfg configauth.Authentication) (Authenticator, error) { +// NewAuthenticator creates an authenticator based on the given configuration +func NewAuthenticator(cfg Authentication) (Authenticator, error) { if cfg.OIDC == nil { return nil, errNoOIDCProvided } diff --git a/internal/auth/authenticator_test.go b/config/configauth/authenticator_test.go similarity index 96% rename from internal/auth/authenticator_test.go rename to config/configauth/authenticator_test.go index 285c47c95cb..b148485285e 100644 --- a/internal/auth/authenticator_test.go +++ b/config/configauth/authenticator_test.go @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -package auth +package configauth import ( "context" @@ -22,14 +22,12 @@ import ( "github.com/stretchr/testify/assert" "google.golang.org/grpc" "google.golang.org/grpc/metadata" - - "go.opentelemetry.io/collector/config/configauth" ) -func TestNew(t *testing.T) { +func TestNewAuthenticator(t *testing.T) { // test - p, err := New(configauth.Authentication{ - OIDC: &configauth.OIDC{ + p, err := NewAuthenticator(Authentication{ + OIDC: &OIDC{ Audience: "some-audience", IssuerURL: "http://example.com", }, @@ -42,7 +40,7 @@ func TestNew(t *testing.T) { func TestMissingOIDC(t *testing.T) { // test - p, err := New(configauth.Authentication{}) + p, err := NewAuthenticator(Authentication{}) // verify assert.Nil(t, p) diff --git a/config/configauth/configauth.go b/config/configauth/configauth.go index 176fb91ef63..b76597ac901 100644 --- a/config/configauth/configauth.go +++ b/config/configauth/configauth.go @@ -14,6 +14,12 @@ package configauth +import ( + "context" + + "google.golang.org/grpc" +) + // Authentication defines the auth settings for the receiver type Authentication struct { // The attribute (header name) to look for auth data. Optional, default value: "authentication". @@ -47,3 +53,22 @@ type OIDC struct { // Optional. GroupsClaim string `mapstructure:"groups_claim"` } + +// ToServerOptions builds a set of server options ready to be used by the gRPC server +func (a *Authentication) ToServerOptions() ([]grpc.ServerOption, error) { + auth, err := NewAuthenticator(*a) + if err != nil { + return nil, err + } + + // perhaps we should use a timeout here? + // TODO: we need a hook to call auth.Close() + if err := auth.Start(context.Background()); err != nil { + return nil, err + } + + return []grpc.ServerOption{ + grpc.UnaryInterceptor(auth.UnaryInterceptor), + grpc.StreamInterceptor(auth.StreamInterceptor), + }, nil +} diff --git a/config/configauth/configauth_test.go b/config/configauth/configauth_test.go new file mode 100644 index 00000000000..c04efaf15d8 --- /dev/null +++ b/config/configauth/configauth_test.go @@ -0,0 +1,74 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package configauth + +import ( + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestToServerOptions(t *testing.T) { + // prepare + oidcServer, err := newOIDCServer() + require.NoError(t, err) + oidcServer.Start() + defer oidcServer.Close() + + config := Authentication{ + OIDC: &OIDC{ + IssuerURL: oidcServer.URL, + Audience: "unit-test", + GroupsClaim: "memberships", + }, + } + + // test + opts, err := config.ToServerOptions() + + // verify + assert.NoError(t, err) + assert.NotNil(t, opts) + assert.Len(t, opts, 2) // we have two interceptors +} + +func TestInvalidConfigurationFailsOnToServerOptions(t *testing.T) { + + for _, tt := range []struct { + cfg Authentication + }{ + { + Authentication{}, + }, + { + Authentication{ + OIDC: &OIDC{ + IssuerURL: "http://oidc.acme.invalid", + Audience: "unit-test", + GroupsClaim: "memberships", + }, + }, + }, + } { + // test + opts, err := tt.cfg.ToServerOptions() + + // verify + assert.Error(t, err) + assert.Nil(t, opts) + } + +} diff --git a/internal/auth/context.go b/config/configauth/context.go similarity index 98% rename from internal/auth/context.go rename to config/configauth/context.go index 2637f5d5976..a7e9eb2376c 100644 --- a/internal/auth/context.go +++ b/config/configauth/context.go @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -package auth +package configauth import "context" diff --git a/internal/auth/context_test.go b/config/configauth/context_test.go similarity index 99% rename from internal/auth/context_test.go rename to config/configauth/context_test.go index a22ae54d6e8..61dec7ab0ba 100644 --- a/internal/auth/context_test.go +++ b/config/configauth/context_test.go @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -package auth +package configauth import ( "context" diff --git a/config/configauth/empty_test.go b/config/configauth/empty_test.go deleted file mode 100644 index 1084c8a8b90..00000000000 --- a/config/configauth/empty_test.go +++ /dev/null @@ -1,15 +0,0 @@ -// Copyright The OpenTelemetry Authors -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package configauth diff --git a/internal/auth/oidc_authenticator.go b/config/configauth/oidc_authenticator.go similarity index 96% rename from internal/auth/oidc_authenticator.go rename to config/configauth/oidc_authenticator.go index 99912103643..a0deadcd03a 100644 --- a/internal/auth/oidc_authenticator.go +++ b/config/configauth/oidc_authenticator.go @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -package auth +package configauth import ( "context" @@ -29,13 +29,11 @@ import ( "github.com/coreos/go-oidc" "google.golang.org/grpc" - - "go.opentelemetry.io/collector/config/configauth" ) type oidcAuthenticator struct { attribute string - config configauth.OIDC + config OIDC provider *oidc.Provider verifier *oidc.IDTokenVerifier @@ -56,7 +54,7 @@ var ( errNotAuthenticated = errors.New("authentication didn't succeed") ) -func newOIDCAuthenticator(cfg configauth.Authentication) (*oidcAuthenticator, error) { +func newOIDCAuthenticator(cfg Authentication) (*oidcAuthenticator, error) { if cfg.OIDC.Audience == "" { return nil, errNoClientIDProvided } @@ -189,7 +187,7 @@ func getGroupsFromClaims(claims map[string]interface{}, groupsClaim string) ([]s return []string{}, nil } -func getProviderForConfig(config configauth.OIDC) (*oidc.Provider, error) { +func getProviderForConfig(config OIDC) (*oidc.Provider, error) { t := &http.Transport{ Proxy: http.ProxyFromEnvironment, DialContext: (&net.Dialer{ diff --git a/internal/auth/oidc_authenticator_test.go b/config/configauth/oidc_authenticator_test.go similarity index 91% rename from internal/auth/oidc_authenticator_test.go rename to config/configauth/oidc_authenticator_test.go index 5ca70822849..7f4879c6c28 100644 --- a/internal/auth/oidc_authenticator_test.go +++ b/config/configauth/oidc_authenticator_test.go @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -package auth +package configauth import ( "context" @@ -34,8 +34,6 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "google.golang.org/grpc" - - "go.opentelemetry.io/collector/config/configauth" ) func TestOIDCAuthenticationSucceeded(t *testing.T) { @@ -45,8 +43,8 @@ func TestOIDCAuthenticationSucceeded(t *testing.T) { oidcServer.Start() defer oidcServer.Close() - config := configauth.Authentication{ - OIDC: &configauth.OIDC{ + config := Authentication{ + OIDC: &OIDC{ IssuerURL: oidcServer.URL, Audience: "unit-test", GroupsClaim: "memberships", @@ -122,7 +120,7 @@ func TestOIDCProviderForConfigWithTLS(t *testing.T) { oidcServer.StartTLS() // prepare the processor configuration - config := configauth.OIDC{ + config := OIDC{ IssuerURL: oidcServer.URL, IssuerCAPath: caFile.Name(), Audience: "unit-test", @@ -196,7 +194,7 @@ func TestOIDCFailedToLoadIssuerCAFromPathInvalidContent(t *testing.T) { defer os.Remove(file.Name()) file.Write([]byte("foobar")) - config := configauth.OIDC{ + config := OIDC{ IssuerCAPath: file.Name(), } @@ -210,8 +208,8 @@ func TestOIDCFailedToLoadIssuerCAFromPathInvalidContent(t *testing.T) { func TestOIDCInvalidAuthHeader(t *testing.T) { // prepare - p, err := newOIDCAuthenticator(configauth.Authentication{ - OIDC: &configauth.OIDC{ + p, err := newOIDCAuthenticator(Authentication{ + OIDC: &OIDC{ Audience: "some-audience", IssuerURL: "http://example.com", }, @@ -228,8 +226,8 @@ func TestOIDCInvalidAuthHeader(t *testing.T) { func TestOIDCNotAuthenticated(t *testing.T) { // prepare - p, err := newOIDCAuthenticator(configauth.Authentication{ - OIDC: &configauth.OIDC{ + p, err := newOIDCAuthenticator(Authentication{ + OIDC: &OIDC{ Audience: "some-audience", IssuerURL: "http://example.com", }, @@ -246,8 +244,8 @@ func TestOIDCNotAuthenticated(t *testing.T) { func TestProviderNotReacheable(t *testing.T) { // prepare - p, err := newOIDCAuthenticator(configauth.Authentication{ - OIDC: &configauth.OIDC{ + p, err := newOIDCAuthenticator(Authentication{ + OIDC: &OIDC{ Audience: "some-audience", IssuerURL: "http://example.com", }, @@ -268,8 +266,8 @@ func TestFailedToVerifyToken(t *testing.T) { oidcServer.Start() defer oidcServer.Close() - p, err := newOIDCAuthenticator(configauth.Authentication{ - OIDC: &configauth.OIDC{ + p, err := newOIDCAuthenticator(Authentication{ + OIDC: &OIDC{ IssuerURL: oidcServer.URL, Audience: "unit-test", }, @@ -296,13 +294,13 @@ func TestFailedToGetGroupsClaimFromToken(t *testing.T) { for _, tt := range []struct { casename string - config configauth.Authentication + config Authentication expectedError error }{ { "groupsClaimNonExisting", - configauth.Authentication{ - OIDC: &configauth.OIDC{ + Authentication{ + OIDC: &OIDC{ IssuerURL: oidcServer.URL, Audience: "unit-test", GroupsClaim: "non-existing-claim", @@ -312,8 +310,8 @@ func TestFailedToGetGroupsClaimFromToken(t *testing.T) { }, { "usernameClaimNonExisting", - configauth.Authentication{ - OIDC: &configauth.OIDC{ + Authentication{ + OIDC: &OIDC{ IssuerURL: oidcServer.URL, Audience: "unit-test", UsernameClaim: "non-existing-claim", @@ -323,8 +321,8 @@ func TestFailedToGetGroupsClaimFromToken(t *testing.T) { }, { "usernameNotString", - configauth.Authentication{ - OIDC: &configauth.OIDC{ + Authentication{ + OIDC: &OIDC{ IssuerURL: oidcServer.URL, Audience: "unit-test", UsernameClaim: "some-non-string-field", @@ -438,8 +436,8 @@ func TestEmptyGroupsClaim(t *testing.T) { func TestMissingClient(t *testing.T) { // prepare - config := configauth.Authentication{ - OIDC: &configauth.OIDC{ + config := Authentication{ + OIDC: &OIDC{ IssuerURL: "http://example.com/", }, } @@ -454,8 +452,8 @@ func TestMissingClient(t *testing.T) { func TestMissingIssuerURL(t *testing.T) { // prepare - config := configauth.Authentication{ - OIDC: &configauth.OIDC{ + config := Authentication{ + OIDC: &OIDC{ Audience: "some-audience", }, } @@ -470,8 +468,8 @@ func TestMissingIssuerURL(t *testing.T) { func TestClose(t *testing.T) { // prepare - config := configauth.Authentication{ - OIDC: &configauth.OIDC{ + config := Authentication{ + OIDC: &OIDC{ Audience: "some-audience", IssuerURL: "http://example.com/", }, @@ -489,8 +487,8 @@ func TestClose(t *testing.T) { func TestUnaryInterceptor(t *testing.T) { // prepare - config := configauth.Authentication{ - OIDC: &configauth.OIDC{ + config := Authentication{ + OIDC: &OIDC{ Audience: "some-audience", IssuerURL: "http://example.com/", }, @@ -519,8 +517,8 @@ func TestUnaryInterceptor(t *testing.T) { func TestStreamInterceptor(t *testing.T) { // prepare - config := configauth.Authentication{ - OIDC: &configauth.OIDC{ + config := Authentication{ + OIDC: &OIDC{ Audience: "some-audience", IssuerURL: "http://example.com/", }, diff --git a/internal/auth/oidc_server_test.go b/config/configauth/oidc_server_test.go similarity index 99% rename from internal/auth/oidc_server_test.go rename to config/configauth/oidc_server_test.go index a9a533e12c1..fae066109a9 100644 --- a/internal/auth/oidc_server_test.go +++ b/config/configauth/oidc_server_test.go @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -package auth +package configauth import ( "bytes" diff --git a/config/configgrpc/configgrpc.go b/config/configgrpc/configgrpc.go index 03e98d34c4f..6d0131081c0 100644 --- a/config/configgrpc/configgrpc.go +++ b/config/configgrpc/configgrpc.go @@ -27,6 +27,7 @@ import ( "google.golang.org/grpc/encoding/gzip" "google.golang.org/grpc/keepalive" + "go.opentelemetry.io/collector/config/configauth" "go.opentelemetry.io/collector/config/confignet" "go.opentelemetry.io/collector/config/configtls" ) @@ -157,9 +158,12 @@ type GRPCServerSettings struct { // Keepalive anchor for all the settings related to keepalive. Keepalive *KeepaliveServerConfig `mapstructure:"keepalive,omitempty"` + + // Auth for this receiver + Auth *configauth.Authentication `mapstructure:"auth,omitempty"` } -// ToServerOption maps configgrpc.GRPCClientSettings to a slice of dial options for gRPC +// ToDialOptions maps configgrpc.GRPCClientSettings to a slice of dial options for gRPC func (gcs *GRPCClientSettings) ToDialOptions() ([]grpc.DialOption, error) { var opts []grpc.DialOption if gcs.Compression != "" { @@ -287,6 +291,14 @@ func (gss *GRPCServerSettings) ToServerOption() ([]grpc.ServerOption, error) { } } + if gss.Auth != nil { + authOpts, err := gss.Auth.ToServerOptions() + if err != nil { + return nil, err + } + opts = append(opts, authOpts...) + } + return opts, nil } diff --git a/config/configgrpc/configgrpc_test.go b/config/configgrpc/configgrpc_test.go index 4010db38043..035176fd6a2 100644 --- a/config/configgrpc/configgrpc_test.go +++ b/config/configgrpc/configgrpc_test.go @@ -22,8 +22,10 @@ import ( "time" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" "google.golang.org/grpc" + "go.opentelemetry.io/collector/config/configauth" "go.opentelemetry.io/collector/config/confignet" "go.opentelemetry.io/collector/config/configtls" otelcol "go.opentelemetry.io/collector/internal/data/opentelemetry-proto-gen/collector/trace/v1" @@ -74,7 +76,7 @@ func TestDefaultGrpcServerSettings(t *testing.T) { assert.Len(t, opts, 0) } -func TestAllGrpcServerSettings(t *testing.T) { +func TestAllGrpcServerSettingsExceptAuth(t *testing.T) { gss := &GRPCServerSettings{ NetAddr: confignet.NetAddr{ Endpoint: "localhost:1234", @@ -107,6 +109,25 @@ func TestAllGrpcServerSettings(t *testing.T) { assert.Len(t, opts, 7) } +func TestGrpcServerAuthSettings(t *testing.T) { + gss := &GRPCServerSettings{} + + // sanity check + _, err := gss.ToServerOption() + require.NoError(t, err) + + // test + gss.Auth = &configauth.Authentication{ + OIDC: &configauth.OIDC{}, + } + opts, err := gss.ToServerOption() + + // verify + // an error here is a positive confirmation that Auth kicked in + assert.Error(t, err) + assert.Nil(t, opts) +} + func TestGRPCClientSettingsError(t *testing.T) { tests := []struct { settings GRPCClientSettings