From 894fd8991469dcca39b225ce326b6dfbb5a85c9e Mon Sep 17 00:00:00 2001 From: Pier-Hugues Pellerin Date: Fri, 29 Nov 2019 14:22:26 -0500 Subject: [PATCH 1/3] Change the Authentication mechanism for fleet. Fleet changed how we should authenticate with them instead of using an api key and a special header fields we now use the normal "Authorization" key and with this format: ``` Authorization: ApiKey {accessApiKey} ``` See #49639 for details --- x-pack/agent/pkg/fleetapi/client.go | 6 ++-- x-pack/agent/pkg/fleetapi/client_test.go | 38 ++++++++++++++++++--- x-pack/agent/pkg/fleetapi/round_trippers.go | 38 +++++++++++++-------- 3 files changed, 60 insertions(+), 22 deletions(-) diff --git a/x-pack/agent/pkg/fleetapi/client.go b/x-pack/agent/pkg/fleetapi/client.go index ed6cb510aacb..724f780a05ba 100644 --- a/x-pack/agent/pkg/fleetapi/client.go +++ b/x-pack/agent/pkg/fleetapi/client.go @@ -59,17 +59,17 @@ func init() { // NewAuthWithConfig returns a Kibana client that will: // -// - Send the AccessToken on every HTTP request. +// - Send the API Key on every HTTP request. // - Ensure a minimun version of Kibana is required. // - Send the Fleet User Agent on every HTTP request. -func NewAuthWithConfig(log *logger.Logger, config *config.Config, accessToken string) (*kibana.Client, error) { +func NewAuthWithConfig(log *logger.Logger, config *config.Config, apiKey string) (*kibana.Client, error) { return kibana.NewWithRawConfig(log, config, func(rt http.RoundTripper) (http.RoundTripper, error) { rt, err := baseRoundTrippers(rt) if err != nil { return nil, err } - rt, err = NewFleetAccessTokenRoundTripper(rt, accessToken) + rt, err = NewFleetAuthRoundTripper(rt, apiKey) if err != nil { return nil, err } diff --git a/x-pack/agent/pkg/fleetapi/client_test.go b/x-pack/agent/pkg/fleetapi/client_test.go index 85aebf55417b..28e383392b15 100644 --- a/x-pack/agent/pkg/fleetapi/client_test.go +++ b/x-pack/agent/pkg/fleetapi/client_test.go @@ -21,11 +21,11 @@ import ( ) func TestHTTPClient(t *testing.T) { - t.Run("Access Token is valid", withServer( + t.Run("API Key is valid", withServer( func(t *testing.T) *http.ServeMux { msg := `{ message: "hello" }` mux := http.NewServeMux() - mux.HandleFunc("/echo-hello", accessTokenHandler(func(w http.ResponseWriter, r *http.Request) { + mux.HandleFunc("/echo-hello", authHandler(func(w http.ResponseWriter, r *http.Request) { w.WriteHeader(http.StatusOK) fmt.Fprintf(w, msg) }, "abc123")) @@ -36,7 +36,7 @@ func TestHTTPClient(t *testing.T) { }) client, err := kibana.NewWithRawConfig(nil, cfg, func(wrapped http.RoundTripper) (http.RoundTripper, error) { - return NewFleetAccessTokenRoundTripper(wrapped, "abc123") + return NewFleetAuthRoundTripper(wrapped, "abc123") }) require.NoError(t, err) @@ -50,6 +50,30 @@ func TestHTTPClient(t *testing.T) { }, )) + t.Run("API Key is not valid", withServer( + func(t *testing.T) *http.ServeMux { + msg := `{ message: "hello" }` + mux := http.NewServeMux() + mux.HandleFunc("/echo-hello", authHandler(func(w http.ResponseWriter, r *http.Request) { + w.WriteHeader(http.StatusOK) + fmt.Fprintf(w, msg) + }, "secret")) + return mux + }, func(t *testing.T, host string) { + cfg := config.MustNewConfigFrom(map[string]interface{}{ + "host": host, + }) + + client, err := kibana.NewWithRawConfig(nil, cfg, func(wrapped http.RoundTripper) (http.RoundTripper, error) { + return NewFleetAuthRoundTripper(wrapped, "abc123") + }) + + require.NoError(t, err) + _, err = client.Send("GET", "/echo-hello", nil, nil, nil) + require.Error(t, err) + }, + )) + t.Run("Fleet user agent", withServer( func(t *testing.T) *http.ServeMux { msg := `{ message: "hello" }` @@ -81,9 +105,13 @@ func TestHTTPClient(t *testing.T) { )) } -func accessTokenHandler(handler http.HandlerFunc, accessToken string) http.HandlerFunc { +func authHandler(handler http.HandlerFunc, apiKey string) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { - if r.Header.Get("kbn-fleet-access-token") != accessToken { + const key = "Authorization" + const prefix = "ApiKey " + + v := strings.TrimPrefix(r.Header.Get(key), prefix) + if v != apiKey { http.Error(w, "Unauthorized", http.StatusUnauthorized) return } diff --git a/x-pack/agent/pkg/fleetapi/round_trippers.go b/x-pack/agent/pkg/fleetapi/round_trippers.go index 17898c1eb858..51bd103a7b76 100644 --- a/x-pack/agent/pkg/fleetapi/round_trippers.go +++ b/x-pack/agent/pkg/fleetapi/round_trippers.go @@ -11,6 +11,8 @@ import ( "github.com/elastic/beats/agent/kibana" ) +var InvalidCredentialsErr = errors.New("invalid credentials to connect to fleet") + // FleetUserAgentRoundTripper adds the Fleet user agent. type FleetUserAgentRoundTripper struct { rt http.RoundTripper @@ -31,27 +33,35 @@ func NewFleetUserAgentRoundTripper(wrapped http.RoundTripper, version string) ht } } -// FleetAccessTokenRoundTripper allow all calls to be authenticated using the accessToken. +// FleetAuthRoundTripper allow all calls to be authenticated using the api key. // The token is added as a header key. -type FleetAccessTokenRoundTripper struct { - rt http.RoundTripper - accessToken string +type FleetAuthRoundTripper struct { + rt http.RoundTripper + apiKey string } // RoundTrip makes all the calls to the service authenticated. -func (r *FleetAccessTokenRoundTripper) RoundTrip(req *http.Request) (*http.Response, error) { - const key = "kbn-fleet-access-token" - req.Header.Set(key, r.accessToken) - return r.rt.RoundTrip(req) +func (r *FleetAuthRoundTripper) RoundTrip(req *http.Request) (*http.Response, error) { + const key = "Authorization" + const prefix = "ApiKey " + + req.Header.Set(key, prefix+r.apiKey) + resp, err := r.rt.RoundTrip(req) + + if resp.StatusCode == http.StatusUnauthorized { + defer resp.Body.Close() + return resp, InvalidCredentialsErr + } + return resp, err } -// NewFleetAccessTokenRoundTripper wrap an existing http.RoundTripper and adds the accessToken in the header. -func NewFleetAccessTokenRoundTripper( +// NewFleetAuthRoundTripper wrap an existing http.RoundTripper and adds the API in the header. +func NewFleetAuthRoundTripper( wrapped http.RoundTripper, - accessToken string, + apiKey string, ) (http.RoundTripper, error) { - if len(accessToken) == 0 { - return nil, errors.New("empty access token received") + if len(apiKey) == 0 { + return nil, errors.New("empty api key received") } - return &FleetAccessTokenRoundTripper{rt: wrapped, accessToken: accessToken}, nil + return &FleetAuthRoundTripper{rt: wrapped, apiKey: apiKey}, nil } From f904b361ff0cda0470c0a8eff45697101f2104b6 Mon Sep 17 00:00:00 2001 From: Pier-Hugues Pellerin Date: Fri, 29 Nov 2019 14:27:10 -0500 Subject: [PATCH 2/3] rename error --- x-pack/agent/pkg/fleetapi/round_trippers.go | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/x-pack/agent/pkg/fleetapi/round_trippers.go b/x-pack/agent/pkg/fleetapi/round_trippers.go index 51bd103a7b76..1d7732877d82 100644 --- a/x-pack/agent/pkg/fleetapi/round_trippers.go +++ b/x-pack/agent/pkg/fleetapi/round_trippers.go @@ -11,7 +11,8 @@ import ( "github.com/elastic/beats/agent/kibana" ) -var InvalidCredentialsErr = errors.New("invalid credentials to connect to fleet") +// ErrInvalidAPIKey is returned when authentication fail to fleet. +var ErrInvalidAPIKey = errors.New("invalid credentials to connect to fleet") // FleetUserAgentRoundTripper adds the Fleet user agent. type FleetUserAgentRoundTripper struct { @@ -50,7 +51,7 @@ func (r *FleetAuthRoundTripper) RoundTrip(req *http.Request) (*http.Response, er if resp.StatusCode == http.StatusUnauthorized { defer resp.Body.Close() - return resp, InvalidCredentialsErr + return resp, ErrInvalidAPIKey } return resp, err } From caf56ebb615af8927c699c1b4c633bc5e553809b Mon Sep 17 00:00:00 2001 From: Pier-Hugues Pellerin Date: Fri, 29 Nov 2019 14:27:42 -0500 Subject: [PATCH 3/3] refine --- x-pack/agent/pkg/fleetapi/round_trippers.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/x-pack/agent/pkg/fleetapi/round_trippers.go b/x-pack/agent/pkg/fleetapi/round_trippers.go index 1d7732877d82..0f8a904e2484 100644 --- a/x-pack/agent/pkg/fleetapi/round_trippers.go +++ b/x-pack/agent/pkg/fleetapi/round_trippers.go @@ -12,7 +12,7 @@ import ( ) // ErrInvalidAPIKey is returned when authentication fail to fleet. -var ErrInvalidAPIKey = errors.New("invalid credentials to connect to fleet") +var ErrInvalidAPIKey = errors.New("invalid api key to authenticate with fleet") // FleetUserAgentRoundTripper adds the Fleet user agent. type FleetUserAgentRoundTripper struct {