Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
32 changes: 22 additions & 10 deletions extension/apikeyauthextension/authenticator.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,8 @@ import (
"go.opentelemetry.io/collector/component"
"go.opentelemetry.io/collector/extension/extensionauth"
"golang.org/x/crypto/pbkdf2"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/status"

"github.com/elastic/go-elasticsearch/v8"
"github.com/elastic/go-elasticsearch/v8/typedapi/security/hasprivileges"
Expand Down Expand Up @@ -222,10 +224,14 @@ func (a *authenticator) getCacheKey(id string, headers map[string][]string) (str

// Authenticate validates an ApiKey scheme Authorization header,
// passing it to Elasticsearch for checking privileges.
//
// Callers can use status.FromError(err) to get the status code
// and message from the returned error. If no status.Status is returned,
// the error should be considered an internal error.
func (a *authenticator) Authenticate(ctx context.Context, headers map[string][]string) (context.Context, error) {
authHeaderValue, id, err := a.parseAuthorizationHeader(headers)
if err != nil {
return ctx, err
return ctx, status.Error(codes.Unauthenticated, err.Error())
}

cacheKey, err := a.getCacheKey(id, headers)
Expand All @@ -245,27 +251,29 @@ func (a *authenticator) Authenticate(ctx context.Context, headers map[string][]s
// Client has specified an API Key with a colliding ID,
// but whose secret component does not match the one in
// the cache.
return ctx, fmt.Errorf("API Key %q unauthorized", id)
return ctx, status.Errorf(codes.Unauthenticated,
"API Key %q unauthorized", id,
)
}
if cacheEntry.err != nil {
return ctx, cacheEntry.err
return ctx, status.Error(codes.Unauthenticated, cacheEntry.err.Error())
}
clientInfo := client.FromContext(ctx)
clientInfo.Auth = cacheEntry.data
return client.NewContext(ctx, clientInfo), nil
return newCtxWithAuthData(ctx, cacheEntry.data), nil
}

hasPrivileges, username, err := a.hasPrivileges(ctx, authHeaderValue)
if err != nil {
return ctx, err
return ctx, fmt.Errorf(
"error checking privileges for API Key %q: %v", id, err,
)
}
if !hasPrivileges {
cacheEntry := &cacheEntry{
key: derivedKey,
err: fmt.Errorf("API Key %q unauthorized", id),
}
a.cache.Add(cacheKey, cacheEntry)
return ctx, cacheEntry.err
return ctx, status.Error(codes.PermissionDenied, cacheEntry.err.Error())
}
cacheEntry := &cacheEntry{
key: derivedKey,
Expand All @@ -275,7 +283,11 @@ func (a *authenticator) Authenticate(ctx context.Context, headers map[string][]s
},
}
a.cache.Add(cacheKey, cacheEntry)
return newCtxWithAuthData(ctx, cacheEntry.data), nil
}

func newCtxWithAuthData(ctx context.Context, authData *authData) context.Context {
clientInfo := client.FromContext(ctx)
clientInfo.Auth = cacheEntry.data
return client.NewContext(ctx, clientInfo), nil
clientInfo.Auth = authData
return client.NewContext(ctx, clientInfo)
}
14 changes: 7 additions & 7 deletions extension/apikeyauthextension/authenticator_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -74,11 +74,11 @@ func TestAuthenticator(t *testing.T) {
},
Status: 400,
}),
expectedErr: `status: 400, failed: [a_type], reason: a_reason`,
expectedErr: `error checking privileges for API Key "id": status: 400, failed: [a_type], reason: a_reason`,
},
"missing_privileges": {
handler: newCannedHasPrivilegesHandler(hasprivileges.Response{HasAllRequested: false}),
expectedErr: `API Key "id" unauthorized`,
expectedErr: `rpc error: code = PermissionDenied desc = API Key "id" unauthorized`,
},
} {
t.Run(name, func(t *testing.T) {
Expand Down Expand Up @@ -170,7 +170,7 @@ func TestAuthenticator_Caching(t *testing.T) {
_, err = authenticator.Authenticate(context.Background(), map[string][]string{
"Authorization": {"ApiKey " + base64.StdEncoding.EncodeToString([]byte("id2:secret2"))},
})
assert.EqualError(t, err, `API Key "id2" unauthorized`)
assert.EqualError(t, err, `rpc error: code = Unauthenticated desc = API Key "id2" unauthorized`)
}

func TestAuthenticator_CacheKeyHeaders(t *testing.T) {
Expand Down Expand Up @@ -259,29 +259,29 @@ func TestAuthenticator_AuthorizationHeader(t *testing.T) {
},
"missing_header": {
headers: map[string][]string{},
expectedErr: `missing header "Authorization"`,
expectedErr: `rpc error: code = Unauthenticated desc = missing header "Authorization"`,
},
"invalid_scheme": {
headers: map[string][]string{
"Authorization": {
"Bearer " + base64.StdEncoding.EncodeToString([]byte("id:secret")),
},
},
expectedErr: `ApiKey prefix not found`,
expectedErr: `rpc error: code = Unauthenticated desc = ApiKey prefix not found`,
},
"invalid_base64": {
headers: map[string][]string{
"Authorization": {"ApiKey not_base64"},
},
expectedErr: "illegal base64 data at input byte 3",
expectedErr: "rpc error: code = Unauthenticated desc = illegal base64 data at input byte 3",
},
"invalid_encoded_apikey": {
headers: map[string][]string{
"Authorization": {
"ApiKey " + base64.StdEncoding.EncodeToString([]byte("junk")),
},
},
expectedErr: "invalid API Key",
expectedErr: "rpc error: code = Unauthenticated desc = invalid API Key",
},
} {
t.Run(name, func(t *testing.T) {
Expand Down
2 changes: 1 addition & 1 deletion extension/apikeyauthextension/go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ require (
go.opentelemetry.io/collector/extension/extensiontest v0.129.0
go.uber.org/goleak v1.3.0
golang.org/x/crypto v0.39.0
google.golang.org/grpc v1.73.0
)

require (
Expand Down Expand Up @@ -67,7 +68,6 @@ require (
golang.org/x/sys v0.33.0 // indirect
golang.org/x/text v0.26.0 // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20250324211829-b45e905df463 // indirect
google.golang.org/grpc v1.73.0 // indirect
google.golang.org/protobuf v1.36.6 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
sigs.k8s.io/yaml v1.4.0 // indirect
Expand Down