Skip to content

Commit

Permalink
Merge pull request #30 from jim-wang-intel/add-access-token-for-consul
Browse files Browse the repository at this point in the history
feat(security): support ACL access token
  • Loading branch information
lenny-goodell authored Mar 25, 2021
2 parents 161d456 + 94e2211 commit 7553156
Show file tree
Hide file tree
Showing 6 changed files with 85 additions and 35 deletions.
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ type ServiceConfig struct {
Port int
Type string
BasePath string
AccessToken string
}
```

Expand All @@ -43,6 +44,7 @@ func initializeConfiguration(useConfigService bool, useProfile string) (*Configu
Port: conf.Configuration.Port,
Type: conf.Configuration.Type,
BasePath: internal.ConfigStem + internal.MyServiceKey,
AccessToken: <AccessTokenLoadedFromSecretFile>,
}
ConfigClient, err = configuration.NewConfigurationClient(serviceConfig)
Expand Down
2 changes: 1 addition & 1 deletion configuration/factory.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ import (
func NewConfigurationClient(config types.ServiceConfig) (Client, error) {

if config.Host == "" || config.Port == 0 {
return nil, fmt.Errorf("unable to create Configuation Client: Configuation service host and/or port or serviceKey not set")
return nil, fmt.Errorf("unable to create Configuration Client: Configuration service host and/or port or serviceKey not set")
}

switch config.Type {
Expand Down
3 changes: 2 additions & 1 deletion internal/pkg/consul/client.go
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
//
// Copyright (c) 2019 Intel Corporation
// Copyright (c) 2021 Intel Corporation
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
Expand Down Expand Up @@ -55,6 +55,7 @@ func NewConsulClient(config types.ServiceConfig) (*consulClient, error) {
var err error

client.consulConfig = consulapi.DefaultConfig()
client.consulConfig.Token = config.AccessToken
client.consulConfig.Address = client.consulUrl
client.consulClient, err = consulapi.NewClient(client.consulConfig)
if err != nil {
Expand Down
69 changes: 47 additions & 22 deletions internal/pkg/consul/client_test.go
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
//
// Copyright (c) 2019 Intel Corporation
// Copyright (c) 2021 Intel Corporation
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
Expand All @@ -17,6 +17,7 @@
package consul

import (
"fmt"
"log"
"net/http/httptest"
"net/url"
Expand All @@ -29,6 +30,7 @@ import (
"github.com/hashicorp/consul/api"
"github.com/pelletier/go-toml"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"

"github.com/edgexfoundry/go-mod-configuration/v2/pkg/types"
)
Expand Down Expand Up @@ -78,14 +80,14 @@ func TestMain(m *testing.M) {
}

func TestIsAlive(t *testing.T) {
client := makeConsulClient(t, getUniqueServiceName())
client := makeConsulClient(t, getUniqueServiceName(), "")
if !client.IsAlive() {
t.Fatal("Consul not running")
}
}

func TestHasConfigurationFalse(t *testing.T) {
client := makeConsulClient(t, getUniqueServiceName())
client := makeConsulClient(t, getUniqueServiceName(), "")

// Make sure the configuration doesn't already exists
reset(t, client)
Expand All @@ -101,7 +103,7 @@ func TestHasConfigurationFalse(t *testing.T) {
}

func TestHasConfigurationTrue(t *testing.T) {
client := makeConsulClient(t, getUniqueServiceName())
client := makeConsulClient(t, getUniqueServiceName(), "")

// Make sure the configuration doesn't already exists
reset(t, client)
Expand All @@ -118,7 +120,7 @@ func TestHasConfigurationTrue(t *testing.T) {
}

func TestHasConfigurationPartialServiceKey(t *testing.T) {
client := makeConsulClient(t, getUniqueServiceName())
client := makeConsulClient(t, getUniqueServiceName(), "")

// Make sure the configuration doesn't already exists
reset(t, client)
Expand Down Expand Up @@ -152,7 +154,7 @@ func TestHasConfigurationError(t *testing.T) {
port = goodPort
}()

client := makeConsulClient(t, getUniqueServiceName())
client := makeConsulClient(t, getUniqueServiceName(), "")

_, err := client.HasConfiguration()
assert.Error(t, err, "expected error checking configuration existence")
Expand All @@ -161,7 +163,7 @@ func TestHasConfigurationError(t *testing.T) {
}

func TestHasSubConfigurationFalse(t *testing.T) {
client := makeConsulClient(t, getUniqueServiceName())
client := makeConsulClient(t, getUniqueServiceName(), "")

// Make sure the configuration doesn't already exists
reset(t, client)
Expand All @@ -178,7 +180,7 @@ func TestHasSubConfigurationFalse(t *testing.T) {
}

func TestHasSubConfigurationTrue(t *testing.T) {
client := makeConsulClient(t, getUniqueServiceName())
client := makeConsulClient(t, getUniqueServiceName(), "")

// Make sure the configuration doesn't already exists
reset(t, client)
Expand All @@ -201,7 +203,7 @@ func TestHasSubConfigurationError(t *testing.T) {
port = goodPort
}()

client := makeConsulClient(t, getUniqueServiceName())
client := makeConsulClient(t, getUniqueServiceName(), "")

_, err := client.HasSubConfiguration("dummy")
assert.Error(t, err, "expected error checking configuration existence")
Expand All @@ -215,7 +217,7 @@ func TestConfigurationValueExists(t *testing.T) {
uniqueServiceName := getUniqueServiceName()
fullKey := consulBasePath + uniqueServiceName + "/" + key

client := makeConsulClient(t, uniqueServiceName)
client := makeConsulClient(t, uniqueServiceName, "")
expected := false

// Make sure the configuration doesn't already exists
Expand Down Expand Up @@ -254,7 +256,7 @@ func TestGetConfigurationValue(t *testing.T) {
expected := []byte("bar")
uniqueServiceName := getUniqueServiceName()
fullKey := consulBasePath + uniqueServiceName + "/" + key
client := makeConsulClient(t, uniqueServiceName)
client := makeConsulClient(t, uniqueServiceName, "")

// Make sure the target key/value exists
keyPair := api.KVPair{
Expand Down Expand Up @@ -282,7 +284,7 @@ func TestPutConfigurationValue(t *testing.T) {
uniqueServiceName := getUniqueServiceName()
expectedFullKey := consulBasePath + uniqueServiceName + "/" + key

client := makeConsulClient(t, uniqueServiceName)
client := makeConsulClient(t, uniqueServiceName, "")

// Make sure the configuration doesn't already exists
reset(t, client)
Expand Down Expand Up @@ -317,7 +319,7 @@ func TestGetConfiguration(t *testing.T) {
LogLevel: "debug",
}

client := makeConsulClient(t, getUniqueServiceName())
client := makeConsulClient(t, getUniqueServiceName(), "")

_ = client.PutConfigurationValue("Logging/EnableRemote", []byte(strconv.FormatBool(expected.Logging.EnableRemote)))
_ = client.PutConfigurationValue("Logging/File", []byte(expected.Logging.File))
Expand Down Expand Up @@ -355,7 +357,7 @@ func TestPutConfiguration(t *testing.T) {
LogLevel: "debug",
}

client := makeConsulClient(t, getUniqueServiceName())
client := makeConsulClient(t, getUniqueServiceName(), "")

// Make sure the tree of values doesn't exist.
_, _ = client.consulClient.KV().DeleteTree(consulBasePath, nil)
Expand All @@ -371,6 +373,7 @@ func TestPutConfiguration(t *testing.T) {
}

actual, err := client.HasConfiguration()
require.NoError(t, err)
if !assert.True(t, actual, "Failed to put configuration") {
t.Fail()
}
Expand All @@ -388,7 +391,7 @@ func configValueSet(key string, client *consulClient) bool {
}

func TestPutConfigurationTomlNoPreviousValues(t *testing.T) {
client := makeConsulClient(t, getUniqueServiceName())
client := makeConsulClient(t, getUniqueServiceName(), "")

// Make sure the tree of values doesn't exist.
_, _ = client.consulClient.KV().DeleteTree(consulBasePath, nil)
Expand Down Expand Up @@ -423,7 +426,7 @@ func TestPutConfigurationTomlNoPreviousValues(t *testing.T) {
}

func TestPutConfigurationTomlWithoutOverWrite(t *testing.T) {
client := makeConsulClient(t, getUniqueServiceName())
client := makeConsulClient(t, getUniqueServiceName(), "")

// Make sure the tree of values doesn't exist.
_, _ = client.consulClient.KV().DeleteTree(consulBasePath, nil)
Expand Down Expand Up @@ -470,7 +473,7 @@ func TestPutConfigurationTomlWithoutOverWrite(t *testing.T) {
}

func TestPutConfigurationTomlOverWrite(t *testing.T) {
client := makeConsulClient(t, getUniqueServiceName())
client := makeConsulClient(t, getUniqueServiceName(), "")

// Make sure the tree of values doesn't exist.
_, _ = client.consulClient.KV().DeleteTree(consulBasePath, nil)
Expand Down Expand Up @@ -527,7 +530,7 @@ func TestWatchForChanges(t *testing.T) {

expectedChange := "random"

client := makeConsulClient(t, getUniqueServiceName())
client := makeConsulClient(t, getUniqueServiceName(), "")

// Make sure the tree of values doesn't exist.
_, _ = client.consulClient.KV().DeleteTree(consulBasePath, nil)
Expand Down Expand Up @@ -581,11 +584,33 @@ func TestWatchForChanges(t *testing.T) {
}
}

func makeConsulClient(t *testing.T, serviceName string) *consulClient {
func TestAccessToken(t *testing.T) {
uniqueServiceName := getUniqueServiceName()
client := makeConsulClient(t, uniqueServiceName, "")

valueName := "testAccess"
// Test if have access to endpoint w/o access token set

_, err := client.GetConfigurationValue(valueName)
require.NoError(t, err)

expectedToken := "MyAccessToken"
mockConsul.SetExpectedAccessToken(expectedToken)
defer mockConsul.ClearExpectedAccessToken()

// Now verify get error w/o providing the expected access token
_, err = client.GetConfigurationValue(valueName)
require.Error(t, err)
expectedErrMsg := fmt.Sprintf("unable to get value for %s from Consul: Unexpected response code: 401", client.fullPath(valueName))
require.EqualError(t, err, expectedErrMsg)
}

func makeConsulClient(t *testing.T, serviceName string, accessToken string) *consulClient {
config := types.ServiceConfig{
Host: testHost,
Port: port,
BasePath: "edgex/core/1.0/" + serviceName,
Host: testHost,
Port: port,
BasePath: "edgex/core/1.0/" + serviceName,
AccessToken: accessToken,
}

client, err := NewConsulClient(config)
Expand Down
40 changes: 30 additions & 10 deletions internal/pkg/consul/mock_consul.go
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
//
// Copyright (c) 2019 Intel Corporation
// Copyright (c) 2021 Intel Corporation
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
Expand Down Expand Up @@ -29,20 +29,24 @@ import (
consulapi "github.com/hashicorp/consul/api"
)

const verbose = false
const (
verbose = false
TokenKey = "X-Consul-Token"
)

type MockConsul struct {
keyValueStore map[string]*consulapi.KVPair
serviceStore map[string]consulapi.AgentService
serviceCheckStore map[string]consulapi.AgentCheck
keyValueStore map[string]*consulapi.KVPair
serviceStore map[string]consulapi.AgentService
serviceCheckStore map[string]consulapi.AgentCheck
expectedAccessToken string
}

func NewMockConsul() *MockConsul {
mock := MockConsul{}
mock.keyValueStore = make(map[string]*consulapi.KVPair)
mock.serviceStore = make(map[string]consulapi.AgentService)
mock.serviceCheckStore = make(map[string]consulapi.AgentCheck)
return &mock
return &MockConsul{
keyValueStore: make(map[string]*consulapi.KVPair),
serviceStore: make(map[string]consulapi.AgentService),
serviceCheckStore: make(map[string]consulapi.AgentCheck),
}
}

var keyChannels map[string]chan bool
Expand All @@ -60,6 +64,14 @@ func (mock *MockConsul) Start() *httptest.Server {
PrefixChannels = make(map[string]chan bool)

testMockServer := httptest.NewServer(http.HandlerFunc(func(writer http.ResponseWriter, request *http.Request) {
if len(mock.expectedAccessToken) > 0 {
token := request.Header.Get(TokenKey)
if token != mock.expectedAccessToken {
writer.WriteHeader(http.StatusUnauthorized)
return
}
}

if strings.Contains(request.URL.Path, "/v1/kv/") {
key := strings.Replace(request.URL.Path, "/v1/kv/", "", 1)

Expand Down Expand Up @@ -221,3 +233,11 @@ func (mock *MockConsul) checkForPrefix(prefix string) (consulapi.KVPairs, bool)
return pairs, true

}

func (mock *MockConsul) SetExpectedAccessToken(token string) {
mock.expectedAccessToken = token
}

func (mock *MockConsul) ClearExpectedAccessToken() {
mock.expectedAccessToken = ""
}
4 changes: 3 additions & 1 deletion pkg/types/config.go
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
//
// Copyright (c) 2019 Intel Corporation
// Copyright (c) 2021 Intel Corporation
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
Expand Down Expand Up @@ -38,6 +38,8 @@ type ServiceConfig struct {
Type string
// BasePath is the base path with in the Configuration service where the your service's configuration is stored
BasePath string
// AccessToken is the token that is used to access the service configuration
AccessToken string
}

//
Expand Down

0 comments on commit 7553156

Please sign in to comment.