Skip to content

Commit

Permalink
feat(security): Enable Vault's Consul secrets engine (#3179)
Browse files Browse the repository at this point in the history
* feat(security): Enable Vault's consul secrets engine

- Add Secret Engine Enabler so that it can be re-used for both of Consul and KV secrets engines
- Hookup the code in secretstore-setup so that both KV and Consul secret engines are enabled

Closes: #3154

* refactor: move secretsengine's enabler to secretstore-setup and resovled merging conflicts

Refactor to move enabler to secretstore-setup in edgex-go
Rebased and resolved the merging conflicts
Use the secret client from go-mod-secret

Signed-off-by: Jim Wang <yutsung.jim.wang@intel.com>
  • Loading branch information
jim-wang-intel authored Feb 22, 2021
1 parent a8b212a commit 13b869e
Show file tree
Hide file tree
Showing 4 changed files with 250 additions and 29 deletions.
6 changes: 6 additions & 0 deletions internal/security/secretstore/constants.go
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
/*******************************************************************************
* Copyright 2021 Intel Corporation
* Copyright 2019 Dell Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
Expand All @@ -18,6 +19,11 @@
package secretstore

const (
// KVSecretsEngineMountPoint is the name of the mount point base for Vault's key-value secrets engine
KVSecretsEngineMountPoint = "secret"
// ConsulSecretEngineMountPoint is the name of the mount point base for Vault's Consul secrets engine
ConsulSecretEngineMountPoint = "consul"

VaultToken = "X-Vault-Token"
TokenCreatorPolicyName = "privileged-token-creator"

Expand Down
43 changes: 14 additions & 29 deletions internal/security/secretstore/init.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
/*******************************************************************************
* Copyright 2021 Intel Corporation
* Copyright 2019 Dell Inc.
* Copyright 2021 Intel Inc.
*
* Licensed 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
Expand Down Expand Up @@ -34,17 +34,18 @@ import (
"github.com/edgexfoundry/edgex-go/internal/security/pipedhexreader"
"github.com/edgexfoundry/edgex-go/internal/security/secretstore/config"
"github.com/edgexfoundry/edgex-go/internal/security/secretstore/container"
"github.com/edgexfoundry/go-mod-secrets/v2/pkg"
"github.com/edgexfoundry/go-mod-secrets/v2/pkg/types"
"github.com/edgexfoundry/go-mod-secrets/v2/secrets"
"github.com/edgexfoundry/edgex-go/internal/security/secretstore/secretsengine"

bootstrapContainer "github.com/edgexfoundry/go-mod-bootstrap/v2/bootstrap/container"
"github.com/edgexfoundry/go-mod-bootstrap/v2/bootstrap/startup"
"github.com/edgexfoundry/go-mod-bootstrap/v2/di"

"github.com/edgexfoundry/go-mod-core-contracts/v2/clients/logger"

"github.com/edgexfoundry/go-mod-secrets/v2/pkg"
"github.com/edgexfoundry/go-mod-secrets/v2/pkg/token/fileioperformer"
"github.com/edgexfoundry/go-mod-secrets/v2/pkg/types"
"github.com/edgexfoundry/go-mod-secrets/v2/secrets"
)

type Bootstrap struct {
Expand Down Expand Up @@ -317,11 +318,19 @@ func (b *Bootstrap) BootstrapHandler(ctx context.Context, _ *sync.WaitGroup, _ s
}

// Enable KV secret engine
if err := enableKVSecretsEngine(lc, client, rootToken); err != nil {
if err := secretsengine.New(KVSecretsEngineMountPoint, secretsengine.KeyValue).
Enable(&rootToken, lc, client); err != nil {
lc.Errorf("failed to enable KV secrets engine: %s", err.Error())
os.Exit(1)
}

// Enable Consul secret engine
if err := secretsengine.New(ConsulSecretEngineMountPoint, secretsengine.Consul).
Enable(&rootToken, lc, client); err != nil {
lc.Errorf("failed to enable Consul secrets engine: %s", err.Error())
os.Exit(1)
}

// credential creation
gen := NewPasswordGenerator(lc, secretStoreConfig.PasswordProvider, secretStoreConfig.PasswordProviderArgs)
cred := NewCred(httpCaller, rootToken, gen, secretStoreConfig.GetBaseURL(), lc)
Expand Down Expand Up @@ -528,30 +537,6 @@ func makeTokenIssuingToken(
return revokeIssuingTokenFuc, nil
}

func enableKVSecretsEngine(
lc logger.LoggingClient,
client secrets.SecretStoreClient,
rootToken string) error {

installed, err := client.CheckSecretEngineInstalled(rootToken, "secret/", "kv")
if err != nil {
lc.Errorf("failed call to check if kv secrets engine is installed: %s", err.Error())
return err
}
if !installed {
lc.Info("enabling KV secrets engine for the first time...")
// Enable KV version 1 at /v1/secret path (/v1 prefix supplied by Vault)
err := client.EnableKVSecretEngine(rootToken, "secret", "1")
if err != nil {
lc.Errorf("failed call to enable KV secrets engine: %s", err.Error())
return err
}
} else {
lc.Info("KV secrets engine already enabled...")
}
return nil
}

func loadInitResponse(
lc logger.LoggingClient,
fileOpener fileioperformer.FileIoPerformer,
Expand Down
93 changes: 93 additions & 0 deletions internal/security/secretstore/secretsengine/enabler.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
/*******************************************************************************
* Copyright 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. 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 secretsengine

import (
"fmt"

"github.com/edgexfoundry/go-mod-core-contracts/v2/clients/logger"
"github.com/edgexfoundry/go-mod-secrets/v2/secrets"
)

const (
// Vault's secrets engine type related constants
KeyValue = "kv"
Consul = "consul"

// kvVersion is the version of key-value secret storage used
// currently we use version 1 from Vault
kvVersion = "1"

// defaultConsulTokenLeaseTtl is the default time-to-live value for consul token
// currently we don't set any lease time-to-live limit for Consul tokens
// this will be changed in future for phase 3 based on the ADR
defaultConsulTokenLeaseTtl = "0"
)

// SecretsEngine is the metadata for secretstore secret engine enabler
type SecretsEngine struct {
mountPoint string
engineType string
}

// New creates an instance for SecretsEngine with mountPoint and engineType
func New(mountPoint string, engineType string) SecretsEngine {
return SecretsEngine{mountPoint: mountPoint, engineType: engineType}
}

// Enable enables the specified secrets engine for the secretstore
// the rootToken is required and returns error if not provided or invalid token provided
// also returns error if unsupported / unknown secretsEngineType is used
func (eng SecretsEngine) Enable(rootToken *string,
lc logger.LoggingClient,
client secrets.SecretStoreClient) error {
if rootToken == nil {
return fmt.Errorf("rootToken is required")
}

// the data returned from GET of check installed secrets engine API of Vault is
// the mountPoint with trailing slash(/), eg. "secret/" for kv's mountPoint "secret"
checkMountPoint := eng.mountPoint + "/"
installed, err := client.CheckSecretEngineInstalled(*rootToken, checkMountPoint, eng.engineType)
if err != nil {
return fmt.Errorf("failed call to check if %s secrets engine is installed: %s",
eng.engineType, err.Error())
}

if !installed {
lc.Infof("enabling %s secrets engine for the first time...", eng.engineType)
switch eng.engineType {
case KeyValue:
// Enable KV storage version 1 at /v1/{eng.path} path (/v1 prefix supplied by Vault)
if err := client.EnableKVSecretEngine(*rootToken, eng.mountPoint, kvVersion); err != nil {
return fmt.Errorf("failed to enable KV version %s secrets engine: %s", kvVersion, err.Error())
}
lc.Infof("KeyValue secrets engine with version %s enabled", kvVersion)
case Consul:
// Enable Consul secrets storage at /consul path
if err := client.EnableConsulSecretEngine(*rootToken,
eng.mountPoint, defaultConsulTokenLeaseTtl); err != nil {
return fmt.Errorf("failed to enable Consul secrets engine: %s", err.Error())
}
lc.Infof("Consul secrets engine with config default_ttl = %s enabled", defaultConsulTokenLeaseTtl)
default:
return fmt.Errorf("Unsupported secrets engine type: %s", eng.engineType)
}
} else {
lc.Infof("%s secrets engine already enabled...", eng.engineType)
}
return nil
}
137 changes: 137 additions & 0 deletions internal/security/secretstore/secretsengine/enabler_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,137 @@
/*******************************************************************************
* Copyright 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. 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 secretsengine

import (
"errors"
"testing"

"github.com/stretchr/testify/mock"
"github.com/stretchr/testify/require"

"github.com/edgexfoundry/go-mod-core-contracts/v2/clients/logger"
"github.com/edgexfoundry/go-mod-secrets/v2/secrets/mocks"
)

func TestNewSecretsEngine(t *testing.T) {
tests := []struct {
name string
mountPath string
engineType string
}{
{"New kv type of secrets engine", "kv-1-test/", KeyValue},
{"New consul type of secrets engine", "consul-test/", Consul},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
instance := New(tt.mountPath, tt.engineType)
require.Equal(t, tt.mountPath, instance.mountPoint)
require.Equal(t, tt.engineType, instance.engineType)
})
}
}

func TestEnableSecretsEngine(t *testing.T) {
lc := logger.MockLogger{}
testToken := "fake-token"
unsupportedEngTypeErr := errors.New("Unsupported secrets engine type")

tests := []struct {
name string
rootToken *string
mountPoint string
engineType string
kvInstalled bool
consulInstalled bool
clientCallFailed bool
expectError bool
}{
{"Ok:Enable kv secrets engine not installed yet with client call ok", &testToken, "kv-1-test",
KeyValue, false, false, false, false},
{"Ok:Enable consul secrets engine not installed yet with client call ok", &testToken, "consul-test",
Consul, false, false, false, false},
{"Ok:Enable kv secrets engine already installed with client call ok (1)", &testToken, "kv-1-test",
KeyValue, true, false, false, false},
{"Ok:Enable consul secrets engine already installed with client call ok (1)", &testToken, "consul-test",
Consul, false, true, false, false},
{"Ok:Enable kv secrets engine already installed with client call ok (2)", &testToken, "kv-1-test",
KeyValue, true, true, false, false},
{"Ok:Enable consul secrets engine already installed with client call ok (2)", &testToken, "consul-test",
Consul, true, true, false, false},
{"Bad:Enable kv secrets engine not installed yet but client call failed", &testToken, "kv-1-test",
KeyValue, false, false, true, true},
{"Bad:Enable consul secrets engine not installed yet but client call failed", &testToken, "consul-test",
Consul, false, false, true, true},
{"Bad:Enable kv secrets engine already installed but client call failed (1)", &testToken, "kv-1-test",
KeyValue, true, false, true, true},
{"Bad:Enable consul secrets engine already installed but client call failed (1)", &testToken, "consul-test",
Consul, false, true, true, true},
{"Bad:Enable kv secrets engine already installed but client call failed (2)", &testToken, "kv-1-test",
KeyValue, true, true, true, true},
{"Bad:Enable consul secrets engine already installed but client call failed (2)", &testToken, "consul-test",
Consul, true, true, true, true},
{"Bad:Enable kv secrets engine with nil token", nil, "kv-1-test",
KeyValue, false, true, false, true},
{"Bad:Enable consul secrets engine with nil token", nil, "consul-test",
Consul, true, false, false, true},
{"Bad:Unsupported secrets engine type", &testToken, "whatever",
"unsupported", false, false, false, true},
}

for _, test := range tests {
// this local copy is to ensure test is thread-safe as we are running in parallel
localTest := test
t.Run(localTest.name, func(t *testing.T) {
// run all tests in parallel
t.Parallel()

var chkErr error
var enableClientErr error

// to simplify testing, assume both errors when client calls failed
if localTest.clientCallFailed {
chkErr = errors.New("CheckSecretEngineInstalled called failed")
enableClientErr = errors.New("EnableKVSecretEngine called failed")
}

mockClient := &mocks.SecretStoreClient{}
mockClient.On("CheckSecretEngineInstalled", mock.Anything, mock.Anything, KeyValue).
Return(localTest.kvInstalled, chkErr)
mockClient.On("CheckSecretEngineInstalled", mock.Anything, mock.Anything, Consul).
Return(localTest.consulInstalled, chkErr)
mockClient.On("CheckSecretEngineInstalled", mock.Anything, mock.Anything, mock.Anything).
Return(false, chkErr)
mockClient.On("EnableKVSecretEngine", mock.Anything, localTest.mountPoint, kvVersion).
Return(enableClientErr)
mockClient.On("EnableKVSecretEngine", mock.Anything, mock.Anything, mock.Anything).
Return(unsupportedEngTypeErr)
mockClient.On("EnableConsulSecretEngine", mock.Anything, localTest.mountPoint, defaultConsulTokenLeaseTtl).
Return(enableClientErr)
mockClient.On("EnableConsulSecretEngine", mock.Anything, mock.Anything, mock.Anything).
Return(unsupportedEngTypeErr)

err := New(localTest.mountPoint, localTest.engineType).
Enable(localTest.rootToken, lc, mockClient)

if localTest.expectError {
require.Error(t, err)
} else {
require.NoError(t, err)
}
})
}
}

0 comments on commit 13b869e

Please sign in to comment.