Skip to content

Commit

Permalink
Merge pull request #698 from axw/apikey
Browse files Browse the repository at this point in the history
Add support for API Key auth
  • Loading branch information
axw committed Jan 7, 2020
2 parents 4948a81 + 71a1a25 commit c9b3406
Show file tree
Hide file tree
Showing 41 changed files with 383 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
26 changes: 23 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,25 @@ 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.

NOTE: This feature is fully supported in the APM Server versions >= 7.6.

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
18 changes: 18 additions & 0 deletions features/api_key.feature
@@ -0,0 +1,18 @@
Feature: Api Key

Scenario: A configured api key is sent in the Authorization header
Given an agent
When an api key is set to 'RTNxMjlXNEJt' in the config
Then the Authorization header is 'ApiKey RTNxMjlXNEJt'

Scenario: A configured api key takes precedence over a secret token
Given an agent
When an api key is set in the config
And a secret_token is set in the config
Then the api key is sent in the Authorization header

Scenario: A configured secret token is sent if no api key is configured
Given an agent
When a secret_token is set in the config
And an api key is not set in the config
Then the secret token is sent in the Authorization header
1 change: 1 addition & 0 deletions go.mod
@@ -1,6 +1,7 @@
module go.elastic.co/apm

require (
github.com/DATA-DOG/godog v0.7.13
github.com/armon/go-radix v1.0.0
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/elastic/go-sysinfo v1.1.1
Expand Down
2 changes: 2 additions & 0 deletions go.sum
@@ -1,3 +1,5 @@
github.com/DATA-DOG/godog v0.7.13 h1:JmgpKcra7Vf3yzI9vPsWyoQRx13tyKziHtXWDCUUgok=
github.com/DATA-DOG/godog v0.7.13/go.mod h1:z2OZ6a3X0/YAKVqLfVzYBwFt3j6uSt3Xrqa7XTtcQE0=
github.com/armon/go-radix v1.0.0 h1:F4z6KzEeeQIMeLFa97iZU6vupzoecKdU5TX24SNppXI=
github.com/armon/go-radix v1.0.0/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
Expand Down
118 changes: 118 additions & 0 deletions internal/apmgodog/formatter.go
@@ -0,0 +1,118 @@
// Licensed to Elasticsearch B.V. under one or more contributor
// license agreements. See the NOTICE file distributed with
// this work for additional information regarding copyright
// ownership. Elasticsearch B.V. licenses this file to you under
// the Apache License, Version 2.0 (the "License"); you may
// not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing,
// software distributed under the License is distributed on an
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, either express or implied. See the License for the
// specific language governing permissions and limitations
// under the License.

package apmgodog

import (
"fmt"
"io/ioutil"
"testing"

"github.com/DATA-DOG/godog"
"github.com/DATA-DOG/godog/colors"
"github.com/DATA-DOG/godog/gherkin"
)

// Run runs the Gherkin feature files specified in paths as Go subtests.
func Run(t *testing.T, paths []string) {
initContext := func(s *godog.Suite) {
var commands chan command
var scenarioFailed bool
s.BeforeFeature(func(f *gherkin.Feature) {
commands = make(chan command)
go runCommands(t, commands)
startTest(commands, f.Name)
})
s.AfterFeature(func(f *gherkin.Feature) {
endTest(commands, nil)
close(commands)
})
s.BeforeScenario(func(s interface{}) {
scenarioFailed = false
switch s := s.(type) {
case *gherkin.Scenario:
startTest(commands, s.Name)
case *gherkin.ScenarioOutline:
startTest(commands, s.Name)
}
})
s.AfterScenario(func(_ interface{}, err error) {
endTest(commands, err)
})
s.BeforeStep(func(step *gherkin.Step) {
if scenarioFailed {
fmt.Printf(colors.Yellow(" %s%s\n"), step.Keyword, step.Text)
}
})
s.AfterStep(func(step *gherkin.Step, err error) {
if err != nil {
scenarioFailed = true
fmt.Printf(colors.Red(" %s%s (%s)\n"), step.Keyword, step.Text, err)
} else {
fmt.Printf(colors.Cyan(" %s%s\n"), step.Keyword, step.Text)
}
})
InitContext(s)
}

godog.RunWithOptions("godog", initContext, godog.Options{
Format: "events", // must pick one, this will do
Paths: []string{"."},
Output: ioutil.Discard,
})
}

func startTest(commands chan command, name string) {
commands <- func(t *testing.T) error {
t.Run(name, func(t *testing.T) {
runCommands(t, commands)
})
return nil
}
}

func endTest(commands chan command, err error) {
commands <- func(t *testing.T) error {
if err != nil {
return err
}
return done{}
}
}

func runCommands(t *testing.T, commands chan command) {
for {
cmd, ok := <-commands
if !ok {
return
}
err := cmd(t)
switch err.(type) {
case nil:
case done:
return
default:
t.Fatal(err)
}
}
}

type command func(t *testing.T) error

type done struct{}

func (done) Error() string { return "done" }
122 changes: 122 additions & 0 deletions internal/apmgodog/suitecontext.go
@@ -0,0 +1,122 @@
// Licensed to Elasticsearch B.V. under one or more contributor
// license agreements. See the NOTICE file distributed with
// this work for additional information regarding copyright
// ownership. Elasticsearch B.V. licenses this file to you under
// the Apache License, Version 2.0 (the "License"); you may
// not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing,
// software distributed under the License is distributed on an
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, either express or implied. See the License for the
// specific language governing permissions and limitations
// under the License.

package apmgodog

import (
"fmt"
"net/http"
"net/http/httptest"
"os"
"strings"

"github.com/DATA-DOG/godog"

"go.elastic.co/apm"
"go.elastic.co/apm/transport"
)

const (
aSecretToken = "a_secret_token"
anAPIKey = "an_api_key"
)

type featureContext struct {
apiKey string
secretToken string
}

// InitContext initialises a godoc.Suite with step definitions.
func InitContext(s *godog.Suite) {
c := &featureContext{}
s.BeforeScenario(func(interface{}) { c.reset() })

s.Step("^an agent$", c.anAgent)
s.Step("^an api key is not set in the config$", func() error { return nil })
s.Step("^an api key is set in the config$", func() error { return c.setAPIKey(anAPIKey) })
s.Step("^an api key is set to '(.*)' in the config$", c.setAPIKey)
s.Step("^a secret_token is set in the config$", func() error { return c.setSecretToken(aSecretToken) })
s.Step("^the Authorization header is '(.*)'$", c.checkAuthorizationHeader)
s.Step("^the secret token is sent in the Authorization header$", c.secretTokenSentInAuthorizationHeader)
s.Step("^the api key is sent in the Authorization header$", c.apiKeySentInAuthorizationHeader)
}

func (c *featureContext) reset() {
c.apiKey = ""
c.secretToken = ""
for _, k := range os.Environ() {
if strings.HasPrefix(k, "ELASTIC_APM") {
os.Unsetenv(k)
}
}
}

func (c *featureContext) anAgent() error {
// No-op; we create the tracer as needed to test steps.
return nil
}

func (c *featureContext) setAPIKey(v string) error {
c.apiKey = v
return nil
}

func (c *featureContext) setSecretToken(v string) error {
c.secretToken = v
return nil
}

func (c *featureContext) secretTokenSentInAuthorizationHeader() error {
return c.checkAuthorizationHeader("Bearer " + c.secretToken)
}

func (c *featureContext) apiKeySentInAuthorizationHeader() error {
return c.checkAuthorizationHeader("ApiKey " + c.apiKey)
}

func (c *featureContext) checkAuthorizationHeader(expected string) error {
var authHeader []string
var h http.HandlerFunc = func(w http.ResponseWriter, r *http.Request) {
authHeader = r.Header["Authorization"]
}
server := httptest.NewServer(h)
defer server.Close()

os.Setenv("ELASTIC_APM_SECRET_TOKEN", c.secretToken)
os.Setenv("ELASTIC_APM_API_KEY", c.apiKey)
os.Setenv("ELASTIC_APM_SERVER_URL", server.URL)
if _, err := transport.InitDefault(); err != nil {
return err
}

tracer, err := apm.NewTracer("godog", "")
if err != nil {
return err
}
defer tracer.Close()

tracer.StartTransaction("name", "type").End()
tracer.Flush(nil)

if n := len(authHeader); n != 1 {
return fmt.Errorf("got %d Authorization headers, expected 1", n)
}
if authHeader[0] != expected {
return fmt.Errorf("got Authorization header value %q, expected %q", authHeader, expected)
}
return nil
}
1 change: 1 addition & 0 deletions internal/tracecontexttest/go.sum
@@ -1,3 +1,4 @@
github.com/DATA-DOG/godog v0.7.13/go.mod h1:z2OZ6a3X0/YAKVqLfVzYBwFt3j6uSt3Xrqa7XTtcQE0=
github.com/armon/go-radix v1.0.0 h1:F4z6KzEeeQIMeLFa97iZU6vupzoecKdU5TX24SNppXI=
github.com/armon/go-radix v1.0.0/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
Expand Down
1 change: 1 addition & 0 deletions module/apmbeego/go.sum
@@ -1,3 +1,4 @@
github.com/DATA-DOG/godog v0.7.13/go.mod h1:z2OZ6a3X0/YAKVqLfVzYBwFt3j6uSt3Xrqa7XTtcQE0=
github.com/Knetic/govaluate v3.0.0+incompatible/go.mod h1:r7JcOSlj0wfOMncg0iLm8Leh48TZaKVeNIfJntJ2wa0=
github.com/armon/go-radix v1.0.0 h1:F4z6KzEeeQIMeLFa97iZU6vupzoecKdU5TX24SNppXI=
github.com/armon/go-radix v1.0.0/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8=
Expand Down
1 change: 1 addition & 0 deletions module/apmchi/go.sum
@@ -1,3 +1,4 @@
github.com/DATA-DOG/godog v0.7.13/go.mod h1:z2OZ6a3X0/YAKVqLfVzYBwFt3j6uSt3Xrqa7XTtcQE0=
github.com/armon/go-radix v1.0.0 h1:F4z6KzEeeQIMeLFa97iZU6vupzoecKdU5TX24SNppXI=
github.com/armon/go-radix v1.0.0/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
Expand Down
1 change: 1 addition & 0 deletions module/apmecho/go.sum
@@ -1,3 +1,4 @@
github.com/DATA-DOG/godog v0.7.13/go.mod h1:z2OZ6a3X0/YAKVqLfVzYBwFt3j6uSt3Xrqa7XTtcQE0=
github.com/armon/go-radix v1.0.0 h1:F4z6KzEeeQIMeLFa97iZU6vupzoecKdU5TX24SNppXI=
github.com/armon/go-radix v1.0.0/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
Expand Down
1 change: 1 addition & 0 deletions module/apmechov4/go.sum
@@ -1,3 +1,4 @@
github.com/DATA-DOG/godog v0.7.13/go.mod h1:z2OZ6a3X0/YAKVqLfVzYBwFt3j6uSt3Xrqa7XTtcQE0=
github.com/armon/go-radix v1.0.0 h1:F4z6KzEeeQIMeLFa97iZU6vupzoecKdU5TX24SNppXI=
github.com/armon/go-radix v1.0.0/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
Expand Down
1 change: 1 addition & 0 deletions module/apmelasticsearch/go.sum
@@ -1,3 +1,4 @@
github.com/DATA-DOG/godog v0.7.13/go.mod h1:z2OZ6a3X0/YAKVqLfVzYBwFt3j6uSt3Xrqa7XTtcQE0=
github.com/armon/go-radix v1.0.0 h1:F4z6KzEeeQIMeLFa97iZU6vupzoecKdU5TX24SNppXI=
github.com/armon/go-radix v1.0.0/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
Expand Down
1 change: 1 addition & 0 deletions module/apmelasticsearch/internal/integration/go.sum
@@ -1,3 +1,4 @@
github.com/DATA-DOG/godog v0.7.13/go.mod h1:z2OZ6a3X0/YAKVqLfVzYBwFt3j6uSt3Xrqa7XTtcQE0=
github.com/armon/go-radix v1.0.0 h1:F4z6KzEeeQIMeLFa97iZU6vupzoecKdU5TX24SNppXI=
github.com/armon/go-radix v1.0.0/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
Expand Down
1 change: 1 addition & 0 deletions module/apmgin/go.sum
@@ -1,3 +1,4 @@
github.com/DATA-DOG/godog v0.7.13/go.mod h1:z2OZ6a3X0/YAKVqLfVzYBwFt3j6uSt3Xrqa7XTtcQE0=
github.com/armon/go-radix v1.0.0 h1:F4z6KzEeeQIMeLFa97iZU6vupzoecKdU5TX24SNppXI=
github.com/armon/go-radix v1.0.0/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
Expand Down
1 change: 1 addition & 0 deletions module/apmgocql/go.sum
@@ -1,3 +1,4 @@
github.com/DATA-DOG/godog v0.7.13/go.mod h1:z2OZ6a3X0/YAKVqLfVzYBwFt3j6uSt3Xrqa7XTtcQE0=
github.com/armon/go-radix v1.0.0 h1:F4z6KzEeeQIMeLFa97iZU6vupzoecKdU5TX24SNppXI=
github.com/armon/go-radix v1.0.0/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8=
github.com/bitly/go-hostpool v0.0.0-20171023180738-a3a6125de932 h1:mXoPYz/Ul5HYEDvkta6I8/rnYM5gSdSV2tJ6XbZuEtY=
Expand Down
1 change: 1 addition & 0 deletions module/apmgokit/go.sum
@@ -1,4 +1,5 @@
cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
github.com/DATA-DOG/godog v0.7.13/go.mod h1:z2OZ6a3X0/YAKVqLfVzYBwFt3j6uSt3Xrqa7XTtcQE0=
github.com/armon/go-radix v1.0.0 h1:F4z6KzEeeQIMeLFa97iZU6vupzoecKdU5TX24SNppXI=
github.com/armon/go-radix v1.0.0/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8=
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
Expand Down
1 change: 1 addition & 0 deletions module/apmgometrics/go.sum
@@ -1,3 +1,4 @@
github.com/DATA-DOG/godog v0.7.13/go.mod h1:z2OZ6a3X0/YAKVqLfVzYBwFt3j6uSt3Xrqa7XTtcQE0=
github.com/armon/go-radix v1.0.0 h1:F4z6KzEeeQIMeLFa97iZU6vupzoecKdU5TX24SNppXI=
github.com/armon/go-radix v1.0.0/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
Expand Down
1 change: 1 addition & 0 deletions module/apmgopg/go.sum
@@ -1,3 +1,4 @@
github.com/DATA-DOG/godog v0.7.13/go.mod h1:z2OZ6a3X0/YAKVqLfVzYBwFt3j6uSt3Xrqa7XTtcQE0=
github.com/armon/go-radix v1.0.0 h1:F4z6KzEeeQIMeLFa97iZU6vupzoecKdU5TX24SNppXI=
github.com/armon/go-radix v1.0.0/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
Expand Down
1 change: 1 addition & 0 deletions module/apmgoredis/go.sum
@@ -1,3 +1,4 @@
github.com/DATA-DOG/godog v0.7.13/go.mod h1:z2OZ6a3X0/YAKVqLfVzYBwFt3j6uSt3Xrqa7XTtcQE0=
github.com/armon/go-radix v1.0.0 h1:F4z6KzEeeQIMeLFa97iZU6vupzoecKdU5TX24SNppXI=
github.com/armon/go-radix v1.0.0/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
Expand Down
1 change: 1 addition & 0 deletions module/apmgorilla/go.sum
@@ -1,3 +1,4 @@
github.com/DATA-DOG/godog v0.7.13/go.mod h1:z2OZ6a3X0/YAKVqLfVzYBwFt3j6uSt3Xrqa7XTtcQE0=
github.com/armon/go-radix v1.0.0 h1:F4z6KzEeeQIMeLFa97iZU6vupzoecKdU5TX24SNppXI=
github.com/armon/go-radix v1.0.0/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
Expand Down

0 comments on commit c9b3406

Please sign in to comment.