diff --git a/CHANGELOG.asciidoc b/CHANGELOG.asciidoc index d8138e76d..3b2ff57af 100644 --- a/CHANGELOG.asciidoc +++ b/CHANGELOG.asciidoc @@ -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 diff --git a/docs/configuration.asciidoc b/docs/configuration.asciidoc index 27601b691..1e9021ed9 100644 --- a/docs/configuration.asciidoc +++ b/docs/configuration.asciidoc @@ -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 <>. Depending on the configuration -of your server, you may also need to set <> -and <>. All other -variables have usable defaults. +of your server, you may also need to set <>, +<>, and +<>. All other variables +have usable defaults. // end::setup-config[] [float] @@ -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` diff --git a/transport/http.go b/transport/http.go index 432d14add..d084a51d4 100644 --- a/transport/http.go +++ b/transport/http.go @@ -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" @@ -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 } @@ -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) @@ -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) diff --git a/transport/http_test.go b/transport/http_test.go index bf2cb19ba..bb43113b6 100644 --- a/transport/http_test.go +++ b/transport/http_test.go @@ -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) { @@ -122,10 +122,41 @@ 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() @@ -133,7 +164,7 @@ func TestHTTPTransportNoSecretToken(t *testing.T) { 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) { diff --git a/transport/util_test.go b/transport/util_test.go index 5c42a9362..3858efd41 100644 --- a/transport/util_test.go +++ b/transport/util_test.go @@ -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) }