Skip to content

Commit

Permalink
feat:Added support for listing secrets edgexfoundry#348
Browse files Browse the repository at this point in the history
Signed-off-by: Kyle Morton <[email protected]>
  • Loading branch information
Kyle Morton committed Sep 7, 2022
1 parent 8679ad0 commit e270154
Show file tree
Hide file tree
Showing 6 changed files with 179 additions and 1 deletion.
40 changes: 39 additions & 1 deletion bootstrap/interfaces/mocks/SecretProvider.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 3 additions & 0 deletions bootstrap/interfaces/secret.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,4 +22,7 @@ type SecretProvider interface {
// GetAccessToken return an access token for the specified token type and service key.
// Service key is use as the access token role which must have be previously setup.
GetAccessToken(tokenType string, serviceKey string) (string, error)

// ListSecretsAtPath returns a list of secret keys from an insecure/secure secrets secret store.
ListSecretsAtPath(path string) ([]string, error)
}
31 changes: 31 additions & 0 deletions bootstrap/secret/insecure.go
Original file line number Diff line number Diff line change
Expand Up @@ -112,3 +112,34 @@ func (p *InsecureProvider) SecretsLastUpdated() time.Time {
func (p *InsecureProvider) GetAccessToken(_ string, _ string) (string, error) {
return "", nil
}

// ListSecretsAtPath retrieves a list of secret keys from an insecure secrets secret store.
// Path specifies the type or location of the secrets to retrieve.
// If no path is provided then all keys at the specified path will be returned.
func (p *InsecureProvider) ListSecretsAtPath(path string) ([]string, error) {
var results []string
pathExists := false

insecureSecrets := p.configuration.GetInsecureSecrets()
if insecureSecrets == nil {
err := fmt.Errorf("InsecureSecrets missing from configuration")
return nil, err
}

for _, insecureSecret := range insecureSecrets {
if insecureSecret.Path == path {
pathExists = true
for k, _ := range insecureSecret.Secrets {
results = append(results, k)
}
}
}

if !pathExists {
// if path is not in secret store
err := fmt.Errorf("Error, path (%v) doesn't exist in secret store", path)
return nil, err
}

return results, nil
}
47 changes: 47 additions & 0 deletions bootstrap/secret/insecure_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,8 @@ import (
"github.com/stretchr/testify/require"
)

var expectedSecretsKeys = []string{UsernameKey, PasswordKey}

func TestInsecureProvider_GetSecrets(t *testing.T) {
configAllSecrets := TestConfig{
InsecureSecrets: map[string]bootstrapConfig.InsecureSecretsInfo{
Expand Down Expand Up @@ -93,3 +95,48 @@ func TestInsecureProvider_GetAccessToken(t *testing.T) {
require.NoError(t, err)
assert.Len(t, actualToken, 0)
}

func TestInsecureProvider_ListPaths(t *testing.T) {
configAllSecrets := TestConfig{
InsecureSecrets: map[string]bootstrapConfig.InsecureSecretsInfo{
"DB": {
Path: expectedPath,
Secrets: expectedSecrets,
},
},
}

configMissingSecrets := TestConfig{
InsecureSecrets: map[string]bootstrapConfig.InsecureSecretsInfo{
"DB": {
Path: expectedPath,
},
},
}

tests := []struct {
Name string
Path string
ExpectedKeys []string
Config TestConfig
ExpectError bool
}{
{"Valid", expectedPath, expectedSecretsKeys, configAllSecrets, false},
{"Invalid - No secrets", expectedPath, nil, configMissingSecrets, false},
{"Invalid - Bad Path", "bogus", nil, configAllSecrets, true},
}

for _, tc := range tests {
t.Run(tc.Name, func(t *testing.T) {
target := NewInsecureProvider(tc.Config, logger.MockLogger{})
actual, err := target.ListSecretsAtPath(tc.Path)
if tc.ExpectError {
require.Error(t, err)
return
}

require.NoError(t, err)
assert.Equal(t, tc.ExpectedKeys, actual)
})
}
}
24 changes: 24 additions & 0 deletions bootstrap/secret/secure.go
Original file line number Diff line number Diff line change
Expand Up @@ -349,3 +349,27 @@ func prepareSecret(secret ServiceSecret) (string, map[string]string) {

return path, secretsKV
}

// ListSecretsAtPath retrieves a list of secret keys from a secure secrets secret store.
// Path specifies the type or location of the secrets to retrieve.
// If no path is provided then all keys at the specified path will be returned.
func (p *SecureProvider) ListSecretsAtPath(path string) ([]string, error) {

if p.secretClient == nil {
return nil, errors.New("can't get secrets. Secure secret provider is not properly initialized")
}

secureSecrets, err := p.secretClient.GetKeys(path)

retry, err := p.reloadTokenOnAuthError(err)
if retry {
// Retry with potential new token
secureSecrets, err = p.secretClient.GetKeys(path)
}

if err != nil {
return nil, err
}

return secureSecrets, nil
}
35 changes: 35 additions & 0 deletions bootstrap/secret/secure_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -348,3 +348,38 @@ func TestSecureProvider_seedSecrets(t *testing.T) {
})
}
}

func TestSecureProvider_ListSecretPathsSecrets(t *testing.T) {
expectedKeys := []string{"username", "password", "config/"}
mock := &mocks.SecretClient{}
mock.On("GetKeys", "mysql").Return(expectedKeys, nil)
notfound := []string{"username", "password"}
mock.On("GetKeys", "missing").Return(nil, pkg.NewErrSecretsNotFound(notfound))

tests := []struct {
Name string
Path string
Config TestConfig
Client secrets.SecretClient
ExpectError bool
}{
{"Valid Secure", "mysql", TestConfig{}, mock, false},
{"Invalid Secure", "missing", TestConfig{}, mock, true},
{"Invalid No Client", "mysql", TestConfig{}, nil, true},
}

for _, tc := range tests {
t.Run(tc.Name, func(t *testing.T) {
target := NewSecureProvider(context.Background(), tc.Config, logger.MockLogger{}, nil, nil, "testService")
target.SetClient(tc.Client)
actual, err := target.ListSecretsAtPath(tc.Path)
if tc.ExpectError {
require.Error(t, err)
return
}

require.NoError(t, err)
assert.Equal(t, expectedKeys, actual)
})
}
}

0 comments on commit e270154

Please sign in to comment.