Skip to content

Commit

Permalink
[confighttp] Add option to include query params in auth context
Browse files Browse the repository at this point in the history
This PR adds a new option to the ServerConfig's Auth option, allowing users to specify a list of query parameters to add to the sources of auth data, in addition to HTTP headers. Instead of simply adding all parameters, which might be numeruous, we require users to specify which ones to include.

Auth extensions don't need to be changed, but should document which attributes they expect to find in the context and how to configure confighttp to accomplish that.

Fixes open-telemetry#4806

Signed-off-by: Juraci Paixão Kröhling <[email protected]>
  • Loading branch information
jpkrohling committed Jul 11, 2024
1 parent 30de685 commit cc99403
Show file tree
Hide file tree
Showing 3 changed files with 80 additions and 12 deletions.
3 changes: 3 additions & 0 deletions config/confighttp/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,7 @@ will not be enabled.
- `compression_algorithms`: configures the list of compression algorithms the server can accept. Default: ["", "gzip", "zstd", "zlib", "snappy", "deflate"]
- [`tls`](../configtls/README.md)
- [`auth`](../configauth/README.md)
- `request_params`: a list of query parameter names to add to the auth context, along with the HTTP headers

You can enable [`attribute processor`][attribute-processor] to append any http header to span's attribute using custom key. You also need to enable the "include_metadata"

Expand All @@ -94,6 +95,8 @@ receivers:
http:
include_metadata: true
auth:
request_params:
- token
authenticator: some-authenticator-extension
cors:
allowed_origins:
Expand Down
23 changes: 19 additions & 4 deletions config/confighttp/confighttp.go
Original file line number Diff line number Diff line change
Expand Up @@ -291,7 +291,7 @@ type ServerConfig struct {
CORS *CORSConfig `mapstructure:"cors"`

// Auth for this receiver
Auth *configauth.Authentication `mapstructure:"auth"`
Auth *AuthConfig `mapstructure:"auth"`

// MaxRequestBodySize sets the maximum request body size in bytes. Default: 20MiB.
MaxRequestBodySize int64 `mapstructure:"max_request_body_size"`
Expand All @@ -308,6 +308,14 @@ type ServerConfig struct {
CompressionAlgorithms []string `mapstructure:"compression_algorithms"`
}

type AuthConfig struct {
// Auth for this receiver.
*configauth.Authentication `mapstructure:"-"`

// RequestParameters is a list of parameters that should be extracted from the request and added to the context.
RequestParameters []string `mapstructure:"request_params"`
}

// ToListener creates a net.Listener.
func (hss *ServerConfig) ToListener(ctx context.Context) (net.Listener, error) {
listener, err := net.Listen("tcp", hss.Endpoint)
Expand Down Expand Up @@ -387,7 +395,7 @@ func (hss *ServerConfig) ToServer(_ context.Context, host component.Host, settin
return nil, err
}

handler = authInterceptor(handler, server)
handler = authInterceptor(handler, server, hss.Auth.RequestParameters)
}

if hss.CORS != nil && len(hss.CORS.AllowedOrigins) > 0 {
Expand Down Expand Up @@ -467,9 +475,16 @@ type CORSConfig struct {
MaxAge int `mapstructure:"max_age"`
}

func authInterceptor(next http.Handler, server auth.Server) http.Handler {
func authInterceptor(next http.Handler, server auth.Server, requestParams []string) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
ctx, err := server.Authenticate(r.Context(), r.Header)
sources := r.Header
query := r.URL.Query()
for _, param := range requestParams {
if val, ok := query[param]; ok {
sources[param] = val
}
}
ctx, err := server.Authenticate(r.Context(), sources)
if err != nil {
http.Error(w, http.StatusText(http.StatusUnauthorized), http.StatusUnauthorized)
return
Expand Down
66 changes: 58 additions & 8 deletions config/confighttp/confighttp_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -865,8 +865,10 @@ func TestHttpCorsWithSettings(t *testing.T) {
CORS: &CORSConfig{
AllowedOrigins: []string{"*"},
},
Auth: &configauth.Authentication{
AuthenticatorID: mockID,
Auth: &AuthConfig{
Authentication: &configauth.Authentication{
AuthenticatorID: mockID,
},
},
}

Expand Down Expand Up @@ -1168,8 +1170,10 @@ func TestServerAuth(t *testing.T) {
authCalled := false
hss := ServerConfig{
Endpoint: "localhost:0",
Auth: &configauth.Authentication{
AuthenticatorID: mockID,
Auth: &AuthConfig{
Authentication: &configauth.Authentication{
AuthenticatorID: mockID,
},
},
}

Expand Down Expand Up @@ -1202,8 +1206,10 @@ func TestServerAuth(t *testing.T) {

func TestInvalidServerAuth(t *testing.T) {
hss := ServerConfig{
Auth: &configauth.Authentication{
AuthenticatorID: nonExistingID,
Auth: &AuthConfig{
Authentication: &configauth.Authentication{
AuthenticatorID: nonExistingID,
},
},
}

Expand All @@ -1216,8 +1222,10 @@ func TestFailedServerAuth(t *testing.T) {
// prepare
hss := ServerConfig{
Endpoint: "localhost:0",
Auth: &configauth.Authentication{
AuthenticatorID: mockID,
Auth: &AuthConfig{
Authentication: &configauth.Authentication{
AuthenticatorID: mockID,
},
},
}
host := &mockHost{
Expand Down Expand Up @@ -1390,6 +1398,48 @@ func TestDefaultMaxRequestBodySize(t *testing.T) {
}
}

func TestAuthWithQueryParams(t *testing.T) {
// prepare
authCalled := false
hss := ServerConfig{
Endpoint: "localhost:0",
Auth: &AuthConfig{
RequestParameters: []string{"auth"},
Authentication: &configauth.Authentication{
AuthenticatorID: mockID,
},
},
}

host := &mockHost{
ext: map[component.ID]component.Component{
mockID: auth.NewServer(
auth.WithServerAuthenticate(func(ctx context.Context, sources map[string][]string) (context.Context, error) {
require.Len(t, sources, 1)
assert.Equal(t, "1", sources["auth"][0])
authCalled = true
return ctx, nil
}),
),
},
}

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

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

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

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

type mockHost struct {
component.Host
ext map[component.ID]component.Component
Expand Down

0 comments on commit cc99403

Please sign in to comment.