Skip to content

Commit

Permalink
configauth: add ServerAuthenticator interfaces for HTTP receivers (o…
Browse files Browse the repository at this point in the history
…pen-telemetry#4506)

* Add ServerAuthenticator interfaces for HTTP receivers.

Fixes open-telemetry#4440

Signed-off-by: Juraci Paixão Kröhling <[email protected]>

* Update changelog

Signed-off-by: Juraci Paixão Kröhling <[email protected]>
  • Loading branch information
jpkrohling authored Dec 10, 2021
1 parent 265a06a commit 0332d7e
Show file tree
Hide file tree
Showing 5 changed files with 108 additions and 1 deletion.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
## 💡 Enhancements 💡

- Allow more zap logger configs: `disable_caller`, `disable_stacktrace`, `output_paths`, `error_output_paths`, `initial_fields` (#1048)
- `configauth`: add ServerAuthenticator interfaces for HTTP receivers. (#4506)

## v0.41.0 Beta

Expand Down
12 changes: 12 additions & 0 deletions config/configauth/mock_serverauth.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ package configauth // import "go.opentelemetry.io/collector/config/configauth"

import (
"context"
"net/http"

"google.golang.org/grpc"

Expand All @@ -31,6 +32,9 @@ var (
type MockServerAuthenticator struct {
// AuthenticateFunc to use during the authentication phase of this mock. Optional.
AuthenticateFunc AuthenticateFunc

// HTTPInterceptor to use in the test
HTTPInterceptorFunc HTTPInterceptorFunc
// TODO: implement the other funcs
}

Expand All @@ -52,6 +56,14 @@ func (m *MockServerAuthenticator) GRPCStreamServerInterceptor(interface{}, grpc.
return nil
}

// HTTPInterceptor isn't currently implemented and always returns nil.
func (m *MockServerAuthenticator) HTTPInterceptor(next http.Handler) http.Handler {
if m.HTTPInterceptorFunc == nil {
return next
}
return m.HTTPInterceptorFunc(next, m.AuthenticateFunc)
}

// Start isn't currently implemented and always returns nil.
func (m *MockServerAuthenticator) Start(context.Context, component.Host) error {
return nil
Expand Down
24 changes: 24 additions & 0 deletions config/configauth/serverauth.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ package configauth // import "go.opentelemetry.io/collector/config/configauth"
import (
"context"
"errors"
"net/http"

"google.golang.org/grpc"
"google.golang.org/grpc/metadata"
Expand Down Expand Up @@ -60,6 +61,11 @@ type ServerAuthenticator interface {
// Once the authentication succeeds, the interceptor is expected to call the handler.
// See https://pkg.go.dev/google.golang.org/grpc#StreamServerInterceptor.
GRPCStreamServerInterceptor(srv interface{}, stream grpc.ServerStream, srvInfo *grpc.StreamServerInfo, handler grpc.StreamHandler) error

// HTTPInterceptor is a helper method to provide an HTTP handler responsible for intercepting the incoming HTTP requests, using the
// request's meta data as source of data for the authentication. Once the authentication succeeds, the interceptor is expected to call
// the next handler.
HTTPInterceptor(next http.Handler) http.Handler
}

// AuthenticateFunc defines the signature for the function responsible for performing the authentication based on the given headers map.
Expand All @@ -76,6 +82,10 @@ type GRPCUnaryInterceptorFunc func(ctx context.Context, req interface{}, info *g
// See ServerAuthenticator.GRPCStreamServerInterceptor.
type GRPCStreamInterceptorFunc func(srv interface{}, stream grpc.ServerStream, info *grpc.StreamServerInfo, handler grpc.StreamHandler, authenticate AuthenticateFunc) error

// HTTPInterceptorFunc defines the signature for the function intercepting HTTP calls, useful for authenticators to use as
// types for internal structs, making it easier to mock them in tests.
type HTTPInterceptorFunc func(handler http.Handler, authenticate AuthenticateFunc) http.Handler

// DefaultGRPCUnaryServerInterceptor provides a default implementation of GRPCUnaryInterceptorFunc, useful for most authenticators.
// It extracts the headers from the incoming request, under the assumption that the credentials will be part of the resulting map.
func DefaultGRPCUnaryServerInterceptor(ctx context.Context, req interface{}, _ *grpc.UnaryServerInfo, handler grpc.UnaryHandler, authenticate AuthenticateFunc) (interface{}, error) {
Expand Down Expand Up @@ -110,3 +120,17 @@ func DefaultGRPCStreamServerInterceptor(srv interface{}, stream grpc.ServerStrea
wrapped.WrappedContext = ctx
return handler(srv, wrapped)
}

// DefaultHTTPInterceptor provides a default implementation of HTTPInterceptorFunc, useful for most authenticators.
// It passes the headers from the incoming request as it is, under the assumption that the credentials are part of it.
func DefaultHTTPInterceptor(next http.Handler, authenticate AuthenticateFunc) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
ctx, err := authenticate(r.Context(), r.Header)
if err != nil {
http.Error(w, http.StatusText(http.StatusUnauthorized), http.StatusUnauthorized)
return
}

next.ServeHTTP(w, r.WithContext(ctx))
})
}
14 changes: 13 additions & 1 deletion config/confighttp/confighttp.go
Original file line number Diff line number Diff line change
Expand Up @@ -187,6 +187,9 @@ type HTTPServerSettings struct {

// CORS configures the server for HTTP cross-origin resource sharing (CORS).
CORS *CORSSettings `mapstructure:"cors,omitempty"`

// Auth for this receiver
Auth *configauth.Authentication `mapstructure:"auth,omitempty"`
}

// ToListener creates a net.Listener.
Expand Down Expand Up @@ -226,7 +229,7 @@ func WithErrorHandler(e middleware.ErrorHandler) ToServerOption {
}

// ToServer creates an http.Server from settings object.
func (hss *HTTPServerSettings) ToServer(_ component.Host, settings component.TelemetrySettings, handler http.Handler, opts ...ToServerOption) (*http.Server, error) {
func (hss *HTTPServerSettings) ToServer(host component.Host, settings component.TelemetrySettings, handler http.Handler, opts ...ToServerOption) (*http.Server, error) {
serverOpts := &toServerOptions{}
for _, o := range opts {
o(serverOpts)
Expand All @@ -248,6 +251,15 @@ func (hss *HTTPServerSettings) ToServer(_ component.Host, settings component.Tel
}
// TODO: emit a warning when non-empty CorsHeaders and empty CorsOrigins.

if hss.Auth != nil {
authenticator, err := hss.Auth.GetServerAuthenticator(host.GetExtensions())
if err != nil {
return nil, err
}

handler = authenticator.HTTPInterceptor(handler)
}

// Enable OpenTelemetry observability plugin.
// TODO: Consider to use component ID string as prefix for all the operations.
handler = otelhttp.NewHandler(
Expand Down
58 changes: 58 additions & 0 deletions config/confighttp/confighttp_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
package confighttp

import (
"context"
"errors"
"fmt"
"io/ioutil"
Expand Down Expand Up @@ -724,3 +725,60 @@ func TestContextWithClient(t *testing.T) {
})
}
}

func TestServerAuth(t *testing.T) {
// prepare
authCalled := false
hss := HTTPServerSettings{
Auth: &configauth.Authentication{
AuthenticatorID: config.NewComponentID("mock"),
},
}
host := &mockHost{
ext: map[config.ComponentID]component.Extension{
config.NewComponentID("mock"): &configauth.MockServerAuthenticator{
AuthenticateFunc: func(ctx context.Context, headers map[string][]string) (context.Context, error) {
authCalled = true
return ctx, nil
},
HTTPInterceptorFunc: configauth.DefaultHTTPInterceptor,
},
},
}

handlerCalled := false
handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
handlerCalled = true
})

srv, err := hss.ToServer(host, componenttest.NewNopTelemetrySettings(), handler)
require.NoError(t, err)

// test
srv.Handler.ServeHTTP(&httptest.ResponseRecorder{}, httptest.NewRequest("GET", "/", nil))

// verify
assert.True(t, handlerCalled)
assert.True(t, authCalled)
}

func TestInvalidServerAuth(t *testing.T) {
hss := HTTPServerSettings{
Auth: &configauth.Authentication{
AuthenticatorID: config.NewComponentID("non-existing"),
},
}

srv, err := hss.ToServer(componenttest.NewNopHost(), componenttest.NewNopTelemetrySettings(), http.NewServeMux())
require.Error(t, err)
require.Nil(t, srv)
}

type mockHost struct {
component.Host
ext map[config.ComponentID]component.Extension
}

func (nh *mockHost) GetExtensions() map[config.ComponentID]component.Extension {
return nh.ext
}

0 comments on commit 0332d7e

Please sign in to comment.