Skip to content

Commit 906e3e8

Browse files
authored
apikeyauthextension: propagate status code in errs (#650)
Updates the extension to return a `status.Error` type (which wraps the `status.Status` type) instead of a plain `error` type. This allows propagating the status code and message from the extension to the collector. This is similar to what the upstream OTLP receiver does: https://github.com/open-telemetry/opentelemetry-collector/blob/v0.129.0/receiver/otlpreceiver/otlphttp.go#L183-L192 --------- Signed-off-by: Marc Lopez Rubio <[email protected]>
1 parent f2fd330 commit 906e3e8

File tree

3 files changed

+30
-18
lines changed

3 files changed

+30
-18
lines changed

extension/apikeyauthextension/authenticator.go

Lines changed: 22 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,8 @@ import (
3232
"go.opentelemetry.io/collector/component"
3333
"go.opentelemetry.io/collector/extension/extensionauth"
3434
"golang.org/x/crypto/pbkdf2"
35+
"google.golang.org/grpc/codes"
36+
"google.golang.org/grpc/status"
3537

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

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

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

258264
hasPrivileges, username, err := a.hasPrivileges(ctx, authHeaderValue)
259265
if err != nil {
260-
return ctx, err
266+
return ctx, fmt.Errorf(
267+
"error checking privileges for API Key %q: %v", id, err,
268+
)
261269
}
262270
if !hasPrivileges {
263271
cacheEntry := &cacheEntry{
264272
key: derivedKey,
265273
err: fmt.Errorf("API Key %q unauthorized", id),
266274
}
267275
a.cache.Add(cacheKey, cacheEntry)
268-
return ctx, cacheEntry.err
276+
return ctx, status.Error(codes.PermissionDenied, cacheEntry.err.Error())
269277
}
270278
cacheEntry := &cacheEntry{
271279
key: derivedKey,
@@ -275,7 +283,11 @@ func (a *authenticator) Authenticate(ctx context.Context, headers map[string][]s
275283
},
276284
}
277285
a.cache.Add(cacheKey, cacheEntry)
286+
return newCtxWithAuthData(ctx, cacheEntry.data), nil
287+
}
288+
289+
func newCtxWithAuthData(ctx context.Context, authData *authData) context.Context {
278290
clientInfo := client.FromContext(ctx)
279-
clientInfo.Auth = cacheEntry.data
280-
return client.NewContext(ctx, clientInfo), nil
291+
clientInfo.Auth = authData
292+
return client.NewContext(ctx, clientInfo)
281293
}

extension/apikeyauthextension/authenticator_test.go

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -74,11 +74,11 @@ func TestAuthenticator(t *testing.T) {
7474
},
7575
Status: 400,
7676
}),
77-
expectedErr: `status: 400, failed: [a_type], reason: a_reason`,
77+
expectedErr: `error checking privileges for API Key "id": status: 400, failed: [a_type], reason: a_reason`,
7878
},
7979
"missing_privileges": {
8080
handler: newCannedHasPrivilegesHandler(hasprivileges.Response{HasAllRequested: false}),
81-
expectedErr: `API Key "id" unauthorized`,
81+
expectedErr: `rpc error: code = PermissionDenied desc = API Key "id" unauthorized`,
8282
},
8383
} {
8484
t.Run(name, func(t *testing.T) {
@@ -170,7 +170,7 @@ func TestAuthenticator_Caching(t *testing.T) {
170170
_, err = authenticator.Authenticate(context.Background(), map[string][]string{
171171
"Authorization": {"ApiKey " + base64.StdEncoding.EncodeToString([]byte("id2:secret2"))},
172172
})
173-
assert.EqualError(t, err, `API Key "id2" unauthorized`)
173+
assert.EqualError(t, err, `rpc error: code = Unauthenticated desc = API Key "id2" unauthorized`)
174174
}
175175

176176
func TestAuthenticator_CacheKeyHeaders(t *testing.T) {
@@ -259,29 +259,29 @@ func TestAuthenticator_AuthorizationHeader(t *testing.T) {
259259
},
260260
"missing_header": {
261261
headers: map[string][]string{},
262-
expectedErr: `missing header "Authorization"`,
262+
expectedErr: `rpc error: code = Unauthenticated desc = missing header "Authorization"`,
263263
},
264264
"invalid_scheme": {
265265
headers: map[string][]string{
266266
"Authorization": {
267267
"Bearer " + base64.StdEncoding.EncodeToString([]byte("id:secret")),
268268
},
269269
},
270-
expectedErr: `ApiKey prefix not found`,
270+
expectedErr: `rpc error: code = Unauthenticated desc = ApiKey prefix not found`,
271271
},
272272
"invalid_base64": {
273273
headers: map[string][]string{
274274
"Authorization": {"ApiKey not_base64"},
275275
},
276-
expectedErr: "illegal base64 data at input byte 3",
276+
expectedErr: "rpc error: code = Unauthenticated desc = illegal base64 data at input byte 3",
277277
},
278278
"invalid_encoded_apikey": {
279279
headers: map[string][]string{
280280
"Authorization": {
281281
"ApiKey " + base64.StdEncoding.EncodeToString([]byte("junk")),
282282
},
283283
},
284-
expectedErr: "invalid API Key",
284+
expectedErr: "rpc error: code = Unauthenticated desc = invalid API Key",
285285
},
286286
} {
287287
t.Run(name, func(t *testing.T) {

extension/apikeyauthextension/go.mod

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ require (
1717
go.opentelemetry.io/collector/extension/extensiontest v0.129.0
1818
go.uber.org/goleak v1.3.0
1919
golang.org/x/crypto v0.39.0
20+
google.golang.org/grpc v1.73.0
2021
)
2122

2223
require (
@@ -67,7 +68,6 @@ require (
6768
golang.org/x/sys v0.33.0 // indirect
6869
golang.org/x/text v0.26.0 // indirect
6970
google.golang.org/genproto/googleapis/rpc v0.0.0-20250324211829-b45e905df463 // indirect
70-
google.golang.org/grpc v1.73.0 // indirect
7171
google.golang.org/protobuf v1.36.6 // indirect
7272
gopkg.in/yaml.v3 v3.0.1 // indirect
7373
sigs.k8s.io/yaml v1.4.0 // indirect

0 commit comments

Comments
 (0)