Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
6 changes: 3 additions & 3 deletions x-pack/agent/pkg/fleetapi/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
Expand Down
38 changes: 33 additions & 5 deletions x-pack/agent/pkg/fleetapi/client_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"))
Expand All @@ -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)
Expand All @@ -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" }`
Expand Down Expand Up @@ -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
}
Expand Down
38 changes: 24 additions & 14 deletions x-pack/agent/pkg/fleetapi/round_trippers.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ import (
"github.com/elastic/beats/agent/kibana"
)

var InvalidCredentialsErr = errors.New("invalid credentials to connect to fleet")
Comment thread
ph marked this conversation as resolved.
Outdated

// FleetUserAgentRoundTripper adds the Fleet user agent.
type FleetUserAgentRoundTripper struct {
rt http.RoundTripper
Expand All @@ -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
}