Skip to content

Commit dc11142

Browse files
Fix bad error handling in api key auth (#3935) (#3937)
* Fix bad error handling in api key auth. * Don't return so much information. * bug-fix * Remove un-need changes. (cherry picked from commit 28e96bf) Co-authored-by: Blake Rouse <blake.rouse@elastic.co>
1 parent b9408cf commit dc11142

File tree

3 files changed

+77
-7
lines changed

3 files changed

+77
-7
lines changed
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
# Kind can be one of:
2+
# - breaking-change: a change to previously-documented behavior
3+
# - deprecation: functionality that is being removed in a later release
4+
# - bug-fix: fixes a problem in a previous version
5+
# - enhancement: extends functionality but does not break or fix existing behavior
6+
# - feature: new functionality
7+
# - known-issue: problems that we are aware of in a given version
8+
# - security: impacts on the security of a product or a user’s deployment.
9+
# - upgrade: important information for someone upgrading from a prior version
10+
# - other: does not fit into any of the other categories
11+
kind: bug-fix
12+
13+
# Change summary; a 80ish characters long description of the change.
14+
summary: Fix authentication return error code
15+
16+
# Long description; in case the summary is not enough to describe the change
17+
# this field accommodate a description without length limits.
18+
# NOTE: This field will be rendered only for breaking-change and known-issue kinds at the moment.
19+
description: |
20+
A non-401 error code from elasticsearch will no longer result in a 401 error code being returned to the caller.
21+
22+
# Affected component; usually one of "elastic-agent", "fleet-server", "filebeat", "metricbeat", "auditbeat", "all", etc.
23+
component:
24+
25+
# PR URL; optional; the PR number that added the changeset.
26+
# If not present is automatically filled by the tooling finding the PR where this changelog fragment has been added.
27+
# NOTE: the tooling supports backports, so it's able to fill the original PR number instead of the backport PR number.
28+
# Please provide it if you are adding a fragment for a different PR.
29+
pr: https://github.com/elastic/fleet-server/pull/3935
30+
31+
# Issue URL; optional; the GitHub issue related to this changeset (either closes or is part of).
32+
# If not present is automatically filled by the tooling with the issue linked to the PR number.
33+
issue: https://github.com/elastic/fleet-server/issues/3929

internal/pkg/apikey/auth.go

Lines changed: 15 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -9,9 +9,12 @@ import (
99
"encoding/json"
1010
"errors"
1111
"fmt"
12+
"net/http"
1213

1314
"github.com/elastic/go-elasticsearch/v8"
1415
"github.com/elastic/go-elasticsearch/v8/esapi"
16+
17+
"github.com/elastic/fleet-server/v7/internal/pkg/es"
1518
)
1619

1720
var (
@@ -33,15 +36,15 @@ type SecurityInfo struct {
3336

3437
// Authenticate will return the SecurityInfo associated with the APIKey (retrieved from Elasticsearch).
3538
// Note: Prefer the bulk wrapper on this API
36-
func (k APIKey) Authenticate(ctx context.Context, es *elasticsearch.Client) (*SecurityInfo, error) {
39+
func (k APIKey) Authenticate(ctx context.Context, client *elasticsearch.Client) (*SecurityInfo, error) {
3740

3841
token := fmt.Sprintf("%s%s", authPrefix, k.Token())
3942

4043
req := esapi.SecurityAuthenticateRequest{
4144
Header: map[string][]string{AuthKey: []string{token}},
4245
}
4346

44-
res, err := req.Do(ctx, es)
47+
res, err := req.Do(ctx, client)
4548

4649
if err != nil {
4750
return nil, fmt.Errorf("apikey auth request %s: %w", k.ID, err)
@@ -52,11 +55,18 @@ func (k APIKey) Authenticate(ctx context.Context, es *elasticsearch.Client) (*Se
5255
}
5356

5457
if res.IsError() {
55-
returnError := ErrUnauthorized
56-
if res.StatusCode == 429 {
58+
var returnError error
59+
switch res.StatusCode {
60+
case http.StatusUnauthorized:
61+
returnError = ErrUnauthorized
62+
case http.StatusTooManyRequests:
5763
returnError = ErrElasticsearchAuthLimit
5864
}
59-
return nil, fmt.Errorf("%w: %w", returnError, fmt.Errorf("apikey auth response %s: %s", k.ID, res.String()))
65+
if returnError != nil {
66+
return nil, fmt.Errorf("%w: %w", returnError, fmt.Errorf("apikey auth response %s: %s", k.ID, res.String()))
67+
}
68+
// body is not parsed to not give the caller too much information
69+
return nil, es.TranslateError(res.StatusCode, nil)
6070
}
6171

6272
var info SecurityInfo

internal/pkg/apikey/auth_test.go

Lines changed: 29 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ package apikey
77
import (
88
"context"
99
"encoding/base64"
10+
"fmt"
1011
"net/http"
1112
"testing"
1213

@@ -32,15 +33,41 @@ func setup(t *testing.T, statusCode int) (context.Context, *APIKey, *elasticsear
3233
}
3334

3435
func TestAuth429(t *testing.T) {
35-
ctx, apiKey, mockES := setup(t, 429)
36+
ctx, apiKey, mockES := setup(t, http.StatusTooManyRequests)
3637
_, err := apiKey.Authenticate(ctx, mockES)
3738

3839
assert.Equal(t, "elasticsearch auth limit: apikey auth response foo: [429 Too Many Requests] ", err.Error())
3940
}
4041

4142
func TestAuth401(t *testing.T) {
42-
ctx, apiKey, mockES := setup(t, 401)
43+
ctx, apiKey, mockES := setup(t, http.StatusUnauthorized)
4344
_, err := apiKey.Authenticate(ctx, mockES)
4445

4546
assert.Equal(t, "unauthorized: apikey auth response foo: [401 Unauthorized] ", err.Error())
4647
}
48+
49+
func TestAuthOtherErrors(t *testing.T) {
50+
scenarios := []struct {
51+
StatusCode int
52+
}{
53+
{StatusCode: http.StatusBadRequest},
54+
// 401 is handled in TestAuth401
55+
{StatusCode: http.StatusForbidden},
56+
{StatusCode: http.StatusNotFound},
57+
{StatusCode: http.StatusMethodNotAllowed},
58+
{StatusCode: http.StatusConflict},
59+
// 429 is handled in TestAuth429
60+
{StatusCode: http.StatusInternalServerError},
61+
{StatusCode: http.StatusBadGateway},
62+
{StatusCode: http.StatusServiceUnavailable},
63+
{StatusCode: http.StatusGatewayTimeout},
64+
}
65+
for _, scenario := range scenarios {
66+
t.Run(fmt.Sprintf("%d", scenario.StatusCode), func(t *testing.T) {
67+
ctx, apiKey, mockES := setup(t, scenario.StatusCode)
68+
_, err := apiKey.Authenticate(ctx, mockES)
69+
70+
assert.Equal(t, fmt.Sprintf("elastic fail %d", scenario.StatusCode), err.Error())
71+
})
72+
}
73+
}

0 commit comments

Comments
 (0)