Skip to content

Commit

Permalink
transport: add support for API Key auth
Browse files Browse the repository at this point in the history
  • Loading branch information
axw committed Jan 6, 2020
1 parent 7b20331 commit a0dcfc9
Show file tree
Hide file tree
Showing 5 changed files with 85 additions and 19 deletions.
2 changes: 2 additions & 0 deletions CHANGELOG.asciidoc
Expand Up @@ -23,6 +23,8 @@ endif::[]
https://github.com/elastic/apm-agent-go/compare/v1.6.0...master[View commits]
- Add support for API Key auth {pull}698[(#698)]
[[release-notes-1.x]]
=== Go Agent version 1.x
Expand Down
24 changes: 21 additions & 3 deletions docs/configuration.asciidoc
Expand Up @@ -19,9 +19,10 @@ To simplify development and testing,
the agent defaults to sending data to the Elastic APM Server at `http://localhost:8200`.
To send data to an alternative location, you must configure
<<config-server-url, ELASTIC_APM_SERVER_URL>>. Depending on the configuration
of your server, you may also need to set <<config-secret-token, ELASTIC_APM_SECRET_TOKEN>>
and <<config-verify-server-cert, ELASTIC_APM_VERIFY_SERVER_CERT>>. All other
variables have usable defaults.
of your server, you may also need to set <<config-api-key, ELASTIC_APM_API_KEY>>,
<<config-secret-token, ELASTIC_APM_SECRET_TOKEN>>, and
<<config-verify-server-cert, ELASTIC_APM_VERIFY_SERVER_CERT>>. All other variables
have usable defaults.
// end::setup-config[]

[float]
Expand Down Expand Up @@ -106,6 +107,23 @@ WARNING: the secret token is sent as plain-text in every request to the server,
should also secure your communications using HTTPS. Unless you do so, your secret token
could be observed by an attacker.

[float]
[[config-api-key]]
==== `ELASTIC_APM_API_KEY`

[options="header"]
|============
| Environment | Default | Example
| `ELASTIC_APM_API_KEY` | | "A base64-encoded string"
|============

This base64-encoded string is used to ensure that only your agents can send data to your APM server.
You must have created the API key using the APM server command line tool. Please see the APM server
documentation for details on how to do that.

WARNING: the API Key is sent as plain-text in every request to the server, so you should also secure
your communications using HTTPS. Unless you do so, your API Key could be observed by an attacker.

[float]
[[config-service-name]]
=== `ELASTIC_APM_SERVICE_NAME`
Expand Down
24 changes: 21 additions & 3 deletions transport/http.go
Expand Up @@ -52,6 +52,7 @@ const (
profilePath = "/intake/v2/profile"
configPath = "/config/v1/agents"

envAPIKey = "ELASTIC_APM_API_KEY"
envSecretToken = "ELASTIC_APM_SECRET_TOKEN"
envServerURLs = "ELASTIC_APM_SERVER_URLS"
envServerURL = "ELASTIC_APM_SERVER_URL"
Expand Down Expand Up @@ -174,7 +175,11 @@ func NewHTTPTransport() (*HTTPTransport, error) {
intakeHeaders: intakeHeaders,
profileHeaders: profileHeaders,
}
t.SetSecretToken(os.Getenv(envSecretToken))
if apiKey := os.Getenv(envAPIKey); apiKey != "" {
t.SetAPIKey(apiKey)
} else if secretToken := os.Getenv(envSecretToken); secretToken != "" {
t.SetSecretToken(secretToken)
}
t.SetServerURL(serverURLs...)
return t, nil
}
Expand Down Expand Up @@ -217,8 +222,9 @@ func (t *HTTPTransport) SetUserAgent(ua string) {
}

// SetSecretToken sets the Authorization header with the given secret token.
// This overrides the value specified via the ELASTIC_APM_SECRET_TOKEN
// environment variable, if any.
//
// This overrides the value specified via the ELASTIC_APM_SECRET_TOKEN or
// ELASTIC_APM_API_KEY environment variables, if either are set.
func (t *HTTPTransport) SetSecretToken(secretToken string) {
if secretToken != "" {
t.setCommonHeader("Authorization", "Bearer "+secretToken)
Expand All @@ -227,6 +233,18 @@ func (t *HTTPTransport) SetSecretToken(secretToken string) {
}
}

// SetAPIKey sets the Authorization header with the given API Key.
//
// This overrides the value specified via the ELASTIC_APM_SECRET_TOKEN or
// ELASTIC_APM_API_KEY environment variables, if either are set.
func (t *HTTPTransport) SetAPIKey(apiKey string) {
if apiKey != "" {
t.setCommonHeader("Authorization", "ApiKey "+apiKey)
} else {
t.deleteCommonHeader("Authorization")
}
}

func (t *HTTPTransport) setCommonHeader(key, value string) {
t.configHeaders.Set(key, value)
t.intakeHeaders.Set(key, value)
Expand Down
39 changes: 35 additions & 4 deletions transport/http_test.go
Expand Up @@ -107,7 +107,7 @@ func TestHTTPTransportSecretToken(t *testing.T) {
transport.SendStream(context.Background(), strings.NewReader(""))

assert.Len(t, h.requests, 1)
assertAuthorization(t, h.requests[0], "hunter2")
assertAuthorization(t, h.requests[0], "Bearer hunter2")
}

func TestHTTPTransportEnvSecretToken(t *testing.T) {
Expand All @@ -122,18 +122,49 @@ func TestHTTPTransportEnvSecretToken(t *testing.T) {
transport.SendStream(context.Background(), strings.NewReader(""))

assert.Len(t, h.requests, 1)
assertAuthorization(t, h.requests[0], "hunter2")
assertAuthorization(t, h.requests[0], "Bearer hunter2")
}

func TestHTTPTransportNoSecretToken(t *testing.T) {
func TestHTTPTransportAPIKey(t *testing.T) {
var h recordingHandler
server := httptest.NewServer(&h)
defer server.Close()
defer patchEnv("ELASTIC_APM_SERVER_URLS", server.URL)()

transport, err := transport.NewHTTPTransport()
transport.SetAPIKey("hunter2")
assert.NoError(t, err)
transport.SendStream(context.Background(), strings.NewReader(""))

assert.Len(t, h.requests, 1)
assertAuthorization(t, h.requests[0], "ApiKey hunter2")
}

func TestHTTPTransportEnvAPIKey(t *testing.T) {
var h recordingHandler
server := httptest.NewServer(&h)
defer server.Close()
defer patchEnv("ELASTIC_APM_SERVER_URLS", server.URL)()
defer patchEnv("ELASTIC_APM_API_KEY", "api_key_wins")()
defer patchEnv("ELASTIC_APM_SECRET_TOKEN", "secret_token_loses")()

transport, err := transport.NewHTTPTransport()
assert.NoError(t, err)
transport.SendStream(context.Background(), strings.NewReader(""))

assert.Len(t, h.requests, 1)
assertAuthorization(t, h.requests[0], "ApiKey api_key_wins")
}

func TestHTTPTransportNoAuthorization(t *testing.T) {
var h recordingHandler
transport, server := newHTTPTransport(t, &h)
defer server.Close()

transport.SendStream(context.Background(), strings.NewReader(""))

assert.Len(t, h.requests, 1)
assertAuthorization(t, h.requests[0], "")
assertAuthorization(t, h.requests[0])
}

func TestHTTPTransportTLS(t *testing.T) {
Expand Down
15 changes: 6 additions & 9 deletions transport/util_test.go
Expand Up @@ -51,18 +51,15 @@ func (h *recordingHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) {
h.requests = append(h.requests, req)
}

func assertAuthorization(t *testing.T, req *http.Request, token string) {
func assertAuthorization(t *testing.T, req *http.Request, expect ...string) {
values, ok := req.Header["Authorization"]
if !ok {
if token == "" {
return
}
t.Errorf("missing Authorization header")
if ok && len(expect) == 0 {
t.Errorf("unexpected Authorization header")
return
}
var expect []string
if token != "" {
expect = []string{"Bearer " + token}
if !ok && len(expect) != 0 {
t.Errorf("missing Authorization header")
return
}
assert.Equal(t, expect, values)
}
Expand Down

0 comments on commit a0dcfc9

Please sign in to comment.