-
Notifications
You must be signed in to change notification settings - Fork 480
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(security): Enable Vault's Consul secrets engine (#3179)
* 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
1 parent
a8b212a
commit 13b869e
Showing
4 changed files
with
250 additions
and
29 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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
137
internal/security/secretstore/secretsengine/enabler_test.go
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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) | ||
} | ||
}) | ||
} | ||
} |