diff --git a/bootstrap/bootstrap.go b/bootstrap/bootstrap.go index 4575f9d3..e08be7e2 100644 --- a/bootstrap/bootstrap.go +++ b/bootstrap/bootstrap.go @@ -101,7 +101,7 @@ func RunAndReturnWaitGroup( envVars := environment.NewVariables() - configProcessor := config.NewProcessor(lc, commonFlags, envVars, startupTimer, ctx, &wg, configUpdated) + configProcessor := config.NewProcessor(lc, commonFlags, envVars, startupTimer, ctx, &wg, configUpdated, dic) if err := configProcessor.Process(serviceKey, configStem, serviceConfig); err != nil { fatalError(err, lc) } diff --git a/bootstrap/config/config.go b/bootstrap/config/config.go index c2995cd7..1cf223a0 100644 --- a/bootstrap/config/config.go +++ b/bootstrap/config/config.go @@ -24,6 +24,8 @@ import ( "sync" "github.com/BurntSushi/toml" + "github.com/edgexfoundry/go-mod-bootstrap/bootstrap/container" + "github.com/edgexfoundry/go-mod-bootstrap/di" "github.com/edgexfoundry/go-mod-configuration/configuration" configTypes "github.com/edgexfoundry/go-mod-configuration/pkg/types" "github.com/edgexfoundry/go-mod-core-contracts/clients/logger" @@ -48,6 +50,7 @@ type Processor struct { ctx context.Context wg *sync.WaitGroup configUpdated UpdatedStream + dic *di.Container overwriteConfig bool } @@ -60,6 +63,7 @@ func NewProcessor( ctx context.Context, wg *sync.WaitGroup, configUpdated UpdatedStream, + dic *di.Container, ) *Processor { return &Processor{ Logger: lc, @@ -69,6 +73,7 @@ func NewProcessor( ctx: ctx, wg: wg, configUpdated: configUpdated, + dic: dic, } } @@ -283,16 +288,40 @@ func (cp *Processor) listenForChanges(serviceConfig interfaces.Configuration, co continue } + previousInsecureSecrets := serviceConfig.GetInsecureSecrets() + previousLogLevel := serviceConfig.GetLogLevel() + if !serviceConfig.UpdateWritableFromRaw(raw) { lc.Error("ListenForChanges() type check failed") return } + currentInsecureSecrets := serviceConfig.GetInsecureSecrets() + currentLogLevel := serviceConfig.GetLogLevel() + lc.Info("Writeable configuration has been updated from the Configuration Provider") - _ = lc.SetLogLevel(serviceConfig.GetLogLevel()) - if cp.configUpdated != nil { - cp.configUpdated <- struct{}{} + // Note: Updates occur one setting at a time so only have to look for single changes + switch { + case currentLogLevel != previousLogLevel: + _ = lc.SetLogLevel(serviceConfig.GetLogLevel()) + lc.Info(fmt.Sprintf("Logging level changed to %s", currentLogLevel)) + + // InsecureSecrets (map) will be nil if not in the original TOML used to seed the Config Provider, + // so ignore it if this is the case. + case currentInsecureSecrets != nil && + !reflect.DeepEqual(currentInsecureSecrets, previousInsecureSecrets): + lc.Info("Insecure Secrets have been updated") + secretProvider := container.SecretProviderFrom(cp.dic.Get) + if secretProvider != nil { + secretProvider.SecretsUpdated() + } + + default: + // Signal that configuration updates exists that have not already been processed. + if cp.configUpdated != nil { + cp.configUpdated <- struct{}{} + } } } } diff --git a/bootstrap/container/certificate.go b/bootstrap/container/certificate.go deleted file mode 100644 index 8f62ea77..00000000 --- a/bootstrap/container/certificate.go +++ /dev/null @@ -1,29 +0,0 @@ -/******************************************************************************** - * Copyright 2020 Dell 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 - * - * 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 container - -import ( - "github.com/edgexfoundry/go-mod-bootstrap/bootstrap/interfaces" - "github.com/edgexfoundry/go-mod-bootstrap/di" -) - -// CertificateProviderName contains the name of the interfaces.CertificateProvider implementation in the DIC. -var CertificateProviderName = di.TypeInstanceToName((*interfaces.CertificateProvider)(nil)) - -// CertificateProviderFrom helper function queries the DIC and returns the interfaces.CertificateProviderName -// implementation. -func CertificateProviderFrom(get di.Get) interfaces.CertificateProvider { - return get(CertificateProviderName).(interfaces.CertificateProvider) -} diff --git a/bootstrap/container/configuration.go b/bootstrap/container/configuration.go index 3dbd661d..2deb0ffb 100644 --- a/bootstrap/container/configuration.go +++ b/bootstrap/container/configuration.go @@ -24,5 +24,10 @@ var ConfigurationInterfaceName = di.TypeInstanceToName((*interfaces.Configuratio // ConfigurationFrom helper function queries the DIC and returns the interfaces.Configuration implementation. func ConfigurationFrom(get di.Get) interfaces.Configuration { - return get(ConfigurationInterfaceName).(interfaces.Configuration) + configuration, ok := get(ConfigurationInterfaceName).(interfaces.Configuration) + if !ok { + return nil + } + + return configuration } diff --git a/bootstrap/container/credentials.go b/bootstrap/container/credentials.go deleted file mode 100644 index 31f248a3..00000000 --- a/bootstrap/container/credentials.go +++ /dev/null @@ -1,29 +0,0 @@ -/******************************************************************************** - * Copyright 2019 Dell 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 - * - * 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 container - -import ( - "github.com/edgexfoundry/go-mod-bootstrap/bootstrap/interfaces" - "github.com/edgexfoundry/go-mod-bootstrap/di" -) - -// CredentialsProviderName contains the name of the interfaces.CredentialsProvider implementation in the DIC. -var CredentialsProviderName = di.TypeInstanceToName((*interfaces.CredentialsProvider)(nil)) - -// CredentialsProviderFrom helper function queries the DIC and returns the interfaces.CredentialsProviderName -// implementation. -func CredentialsProviderFrom(get di.Get) interfaces.CredentialsProvider { - return get(CredentialsProviderName).(interfaces.CredentialsProvider) -} diff --git a/bootstrap/container/logging.go b/bootstrap/container/logging.go index ce7eda5d..b37f3081 100644 --- a/bootstrap/container/logging.go +++ b/bootstrap/container/logging.go @@ -15,9 +15,9 @@ package container import ( - "github.com/edgexfoundry/go-mod-bootstrap/di" - "github.com/edgexfoundry/go-mod-core-contracts/clients/logger" + + "github.com/edgexfoundry/go-mod-bootstrap/di" ) // LoggingClientInterfaceName contains the name of the logger.LoggingClient implementation in the DIC. @@ -25,9 +25,10 @@ var LoggingClientInterfaceName = di.TypeInstanceToName((*logger.LoggingClient)(n // LoggingClientFrom helper function queries the DIC and returns the logger.loggingClient implementation. func LoggingClientFrom(get di.Get) logger.LoggingClient { - if loggingClient, ok := get(LoggingClientInterfaceName).(logger.LoggingClient); ok { - return loggingClient - } else { + loggingClient, ok := get(LoggingClientInterfaceName).(logger.LoggingClient) + if !ok { return nil } + + return loggingClient } diff --git a/bootstrap/container/registry.go b/bootstrap/container/registry.go index 4dc4f787..ba683124 100644 --- a/bootstrap/container/registry.go +++ b/bootstrap/container/registry.go @@ -25,9 +25,10 @@ var RegistryClientInterfaceName = di.TypeInstanceToName((*registry.Client)(nil)) // RegistryFrom helper function queries the DIC and returns the registry.Client implementation. func RegistryFrom(get di.Get) registry.Client { - registryClient := get(RegistryClientInterfaceName) - if registryClient != nil { - return registryClient.(registry.Client) + registryClient, ok := get(RegistryClientInterfaceName).(registry.Client) + if !ok { + return nil } - return (registry.Client)(nil) + + return registryClient } diff --git a/bootstrap/container/secret.go b/bootstrap/container/secret.go index bbff7617..d9fd96df 100644 --- a/bootstrap/container/secret.go +++ b/bootstrap/container/secret.go @@ -1,5 +1,6 @@ /******************************************************************************* * Copyright 2019 Dell Inc. + * Copyright 2020 Intel Corp. * * 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 @@ -15,15 +16,20 @@ package container import ( + "github.com/edgexfoundry/go-mod-bootstrap/bootstrap/interfaces" "github.com/edgexfoundry/go-mod-bootstrap/di" - - "github.com/edgexfoundry/go-mod-secrets/pkg" ) -// SecretClientName contains the name of the registry.Client implementation in the DIC. -var SecretClientName = di.TypeInstanceToName((*pkg.SecretClient)(nil)) +// SecretProviderName contains the name of the interfaces.SecretProvider implementation in the DIC. +var SecretProviderName = di.TypeInstanceToName((*interfaces.SecretProvider)(nil)) + +// SecretProviderFrom helper function queries the DIC and returns the interfaces.SecretProvider +// implementation. +func SecretProviderFrom(get di.Get) interfaces.SecretProvider { + provider, ok := get(SecretProviderName).(interfaces.SecretProvider) + if !ok { + return nil + } -// SecretClientFrom helper function queries the DIC and returns the pkg.SecretClient implementation. -func SecretClientFrom(get di.Get) pkg.SecretClient { - return get(SecretClientName).(pkg.SecretClient) + return provider } diff --git a/bootstrap/container/token.go b/bootstrap/container/token.go new file mode 100644 index 00000000..7ca83b9c --- /dev/null +++ b/bootstrap/container/token.go @@ -0,0 +1,46 @@ +/******************************************************************************* + * Copyright 2020 Intel Corp. + * + * 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 container + +import ( + "github.com/edgexfoundry/go-mod-secrets/pkg/token/authtokenloader" + + "github.com/edgexfoundry/go-mod-bootstrap/di" +) + +//// FileIoPerformerInterfaceName contains the name of the fileioperformer.FileIoPerformer implementation in the DIC. +//var FileIoPerformerInterfaceName = di.TypeInstanceToName((*fileioperformer.FileIoPerformer)(nil)) +// +//// FileIoPerformerFrom helper function queries the DIC and returns the fileioperformer.FileIoPerformer implementation. +//func FileIoPerformerFrom(get di.Get) fileioperformer.FileIoPerformer { +// fileIo := get(FileIoPerformerInterfaceName) +// if fileIo != nil { +// return fileIo.(fileioperformer.FileIoPerformer) +// } +// return (fileioperformer.FileIoPerformer)(nil) +//} + +// AuthTokenLoaderInterfaceName contains the name of the authtokenloader.AuthTokenLoader implementation in the DIC. +var AuthTokenLoaderInterfaceName = di.TypeInstanceToName((*authtokenloader.AuthTokenLoader)(nil)) + +// AuthTokenLoaderFrom helper function queries the DIC and returns the authtokenloader.AuthTokenLoader implementation. +func AuthTokenLoaderFrom(get di.Get) authtokenloader.AuthTokenLoader { + loader, ok := get(AuthTokenLoaderInterfaceName).(authtokenloader.AuthTokenLoader) + if !ok { + return nil + } + + return loader +} diff --git a/bootstrap/environment/variables_test.go b/bootstrap/environment/variables_test.go index 2965030b..a13593c0 100644 --- a/bootstrap/environment/variables_test.go +++ b/bootstrap/environment/variables_test.go @@ -21,16 +21,14 @@ import ( "strconv" "testing" - "github.com/edgexfoundry/go-mod-secrets/pkg/providers/vault" - "github.com/stretchr/testify/require" + "github.com/edgexfoundry/go-mod-bootstrap/config" "github.com/edgexfoundry/go-mod-configuration/pkg/types" - "github.com/edgexfoundry/go-mod-core-contracts/clients/logger" + secretsTypes "github.com/edgexfoundry/go-mod-secrets/pkg/types" "github.com/stretchr/testify/assert" - - "github.com/edgexfoundry/go-mod-bootstrap/config" + "github.com/stretchr/testify/require" ) const ( @@ -327,7 +325,7 @@ func TestOverrideConfigurationExactCase(t *testing.T) { List: []string{"val1"}, FloatVal: float32(11.11), SecretStore: config.SecretStoreInfo{ - Authentication: vault.AuthenticationInfo{ + Authentication: secretsTypes.AuthenticationInfo{ AuthType: "none", }, }, @@ -383,7 +381,7 @@ func TestOverrideConfigurationUppercase(t *testing.T) { List: []string{"val1"}, FloatVal: float32(11.11), SecretStore: config.SecretStoreInfo{ - Authentication: vault.AuthenticationInfo{ + Authentication: secretsTypes.AuthenticationInfo{ AuthType: "none", AuthToken: expectedAuthToken, }, @@ -432,7 +430,7 @@ func TestOverrideConfigurationWithBlankValue(t *testing.T) { List: []string{"val1"}, FloatVal: float32(11.11), SecretStore: config.SecretStoreInfo{ - Authentication: vault.AuthenticationInfo{ + Authentication: secretsTypes.AuthenticationInfo{ AuthType: "none", AuthToken: expectedAuthToken, }, @@ -463,7 +461,7 @@ func TestOverrideConfigurationWithEqualInValue(t *testing.T) { SecretStore config.SecretStoreInfo }{ SecretStore: config.SecretStoreInfo{ - Authentication: vault.AuthenticationInfo{ + Authentication: secretsTypes.AuthenticationInfo{ AuthType: "none", AuthToken: expectedAuthToken, }, diff --git a/bootstrap/handlers/httpserver/httpserver.go b/bootstrap/handlers/httpserver.go similarity index 94% rename from bootstrap/handlers/httpserver/httpserver.go rename to bootstrap/handlers/httpserver.go index 576e89b3..f003026d 100644 --- a/bootstrap/handlers/httpserver/httpserver.go +++ b/bootstrap/handlers/httpserver.go @@ -12,7 +12,7 @@ * the License. *******************************************************************************/ -package httpserver +package handlers import ( "context" @@ -35,8 +35,8 @@ type HttpServer struct { doListenAndServe bool } -// NewBootstrap is a factory method that returns an initialized HttpServer receiver struct. -func NewBootstrap(router *mux.Router, doListenAndServe bool) *HttpServer { +// NewHttpServer is a factory method that returns an initialized HttpServer receiver struct. +func NewHttpServer(router *mux.Router, doListenAndServe bool) *HttpServer { return &HttpServer{ router: router, isRunning: false, diff --git a/bootstrap/handlers/message/message.go b/bootstrap/handlers/message.go similarity index 91% rename from bootstrap/handlers/message/message.go rename to bootstrap/handlers/message.go index 2679fba7..8b912027 100644 --- a/bootstrap/handlers/message/message.go +++ b/bootstrap/handlers/message.go @@ -12,7 +12,7 @@ * the License. *******************************************************************************/ -package message +package handlers import ( "context" @@ -30,8 +30,8 @@ type StartMessage struct { version string } -// NewBootstrap is a factory method that returns an initialized StartMessage receiver struct. -func NewBootstrap(serviceKey, version string) *StartMessage { +// NewStartMessage is a factory method that returns an initialized StartMessage receiver struct. +func NewStartMessage(serviceKey, version string) *StartMessage { return &StartMessage{ serviceKey: serviceKey, version: version, diff --git a/bootstrap/handlers/testing/ready.go b/bootstrap/handlers/ready.go similarity index 92% rename from bootstrap/handlers/testing/ready.go rename to bootstrap/handlers/ready.go index 040b644a..400de994 100644 --- a/bootstrap/handlers/testing/ready.go +++ b/bootstrap/handlers/ready.go @@ -12,7 +12,7 @@ * the License. *******************************************************************************/ -package testing +package handlers import ( "context" @@ -33,8 +33,8 @@ type Ready struct { stream chan<- bool } -// NewBootstrap is a factory method that returns an initialized Ready receiver struct. -func NewBootstrap(httpServer httpServer, stream chan<- bool) *Ready { +// NewReady is a factory method that returns an initialized Ready receiver struct. +func NewReady(httpServer httpServer, stream chan<- bool) *Ready { return &Ready{ httpServer: httpServer, stream: stream, diff --git a/bootstrap/handlers/secret/secret.go b/bootstrap/handlers/secret.go similarity index 54% rename from bootstrap/handlers/secret/secret.go rename to bootstrap/handlers/secret.go index 471d02f2..b555de64 100644 --- a/bootstrap/handlers/secret/secret.go +++ b/bootstrap/handlers/secret.go @@ -12,68 +12,66 @@ * the License. *******************************************************************************/ -package secret +package handlers import ( "context" "fmt" - "os" "sync" "github.com/edgexfoundry/go-mod-bootstrap/bootstrap/container" - "github.com/edgexfoundry/go-mod-bootstrap/bootstrap/handlers/secret/client" + "github.com/edgexfoundry/go-mod-bootstrap/bootstrap/interfaces" + "github.com/edgexfoundry/go-mod-bootstrap/bootstrap/secret" "github.com/edgexfoundry/go-mod-bootstrap/bootstrap/startup" "github.com/edgexfoundry/go-mod-bootstrap/config" "github.com/edgexfoundry/go-mod-bootstrap/di" + "github.com/edgexfoundry/go-mod-secrets/pkg/types" + "github.com/edgexfoundry/go-mod-secrets/secrets" - "github.com/edgexfoundry/go-mod-secrets/pkg" - "github.com/edgexfoundry/go-mod-secrets/pkg/providers/vault" "github.com/edgexfoundry/go-mod-secrets/pkg/token/authtokenloader" "github.com/edgexfoundry/go-mod-secrets/pkg/token/fileioperformer" ) -type SecretProvider struct { - secretClient pkg.SecretClient -} - -func NewSecret() *SecretProvider { - return &SecretProvider{} -} - -// BootstrapHandler creates a secretClient to be used for obtaining secrets from a SecretProvider store manager. -// NOTE: This BootstrapHandler is responsible for creating a utility that will most likely be used by other -// BootstrapHandlers to obtain sensitive data, such as database credentials. This BootstrapHandler should be processed -// before other BootstrapHandlers, possibly even first since it has not other dependencies. -func (s *SecretProvider) BootstrapHandler( +// SecureProviderBootstrapHandler full initializes the Secret Provider. +func SecureProviderBootstrapHandler( ctx context.Context, _ *sync.WaitGroup, startupTimer startup.Timer, dic *di.Container) bool { - lc := container.LoggingClientFrom(dic.Get) + configuration := container.ConfigurationFrom(dic.Get) + + var provider interfaces.SecretProvider - // attempt to create a new SecretProvider client only if security is enabled. - if s.isSecurityEnabled() { + switch secret.IsSecurityEnabled() { + case true: + // attempt to create a new Secure client only if security is enabled. var err error lc.Info("Creating SecretClient") - configuration := container.ConfigurationFrom(dic.Get) secretStoreConfig := configuration.GetBootstrap().SecretStore for startupTimer.HasNotElapsed() { - var secretConfig vault.SecretConfig + var secretConfig types.SecretConfig lc.Info("Reading secret store configuration and authentication token") - secretConfig, err = s.getSecretConfig(secretStoreConfig) + tokenLoader := container.AuthTokenLoaderFrom(dic.Get) + if tokenLoader == nil { + tokenLoader = authtokenloader.NewAuthTokenLoader(fileioperformer.NewDefaultFileIoPerformer()) + } + + secretConfig, err = getSecretConfig(secretStoreConfig, tokenLoader) if err == nil { - var secretClient pkg.SecretClient + secureProvider := secret.NewSecureProvider(configuration, lc, tokenLoader) + var secretClient secrets.SecretClient - lc.Info("Attempting to create secretclient") - secretClient, err = client.NewVault(ctx, secretConfig, lc).Get(configuration.GetBootstrap().SecretStore) + lc.Info("Attempting to create secret client") + secretClient, err = secrets.NewClient(ctx, secretConfig, lc, secureProvider.DefaultTokenExpiredCallback) if err == nil { - s.secretClient = secretClient + secureProvider.SetClient(secretClient) + provider = secureProvider lc.Info("Created SecretClient") break } @@ -87,14 +85,14 @@ func (s *SecretProvider) BootstrapHandler( lc.Error(fmt.Sprintf("unable to create SecretClient: %s", err.Error())) return false } + + case false: + provider = secret.NewInsecureProvider(configuration, lc) } dic.Update(di.ServiceConstructorMap{ - container.CredentialsProviderName: func(get di.Get) interface{} { - return s - }, - container.CertificateProviderName: func(get di.Get) interface{} { - return s + container.SecretProviderName: func(get di.Get) interface{} { + return provider }, }) @@ -102,9 +100,9 @@ func (s *SecretProvider) BootstrapHandler( } // getSecretConfig creates a SecretConfig based on the SecretStoreInfo configuration properties. -// If a tokenfile is present it will override the Authentication.AuthToken value. -func (s *SecretProvider) getSecretConfig(secretStoreInfo config.SecretStoreInfo) (vault.SecretConfig, error) { - secretConfig := vault.SecretConfig{ +// If a token file is present it will override the Authentication.AuthToken value. +func getSecretConfig(secretStoreInfo config.SecretStoreInfo, tokenLoader authtokenloader.AuthTokenLoader) (types.SecretConfig, error) { + secretConfig := types.SecretConfig{ Host: secretStoreInfo.Host, Port: secretStoreInfo.Port, Path: secretStoreInfo.Path, @@ -117,24 +115,15 @@ func (s *SecretProvider) getSecretConfig(secretStoreInfo config.SecretStoreInfo) RetryWaitPeriod: secretStoreInfo.RetryWaitPeriod, } - if !s.isSecurityEnabled() || secretStoreInfo.TokenFile == "" { + if !secret.IsSecurityEnabled() || secretStoreInfo.TokenFile == "" { return secretConfig, nil } - // only bother getting a token if security is enabled and the configuration-provided tokenfile is not empty. - fileIoPerformer := fileioperformer.NewDefaultFileIoPerformer() - authTokenLoader := authtokenloader.NewAuthTokenLoader(fileIoPerformer) - - token, err := authTokenLoader.Load(secretStoreInfo.TokenFile) + token, err := tokenLoader.Load(secretStoreInfo.TokenFile) if err != nil { return secretConfig, err } + secretConfig.Authentication.AuthToken = token return secretConfig, nil } - -// isSecurityEnabled determines if security has been enabled. -func (s *SecretProvider) isSecurityEnabled() bool { - env := os.Getenv("EDGEX_SECURITY_SECRET_STORE") - return env != "false" -} diff --git a/bootstrap/handlers/secret/client/mockserver_test.go b/bootstrap/handlers/secret/client/mockserver_test.go deleted file mode 100644 index ae014f86..00000000 --- a/bootstrap/handlers/secret/client/mockserver_test.go +++ /dev/null @@ -1,81 +0,0 @@ -// -// Copyright (c) 2019 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. -// -// SPDX-License-Identifier: Apache-2.0 -// - -package client - -import ( - "encoding/json" - "fmt" - "net/http" - "net/http/httptest" - "sync" - - "github.com/edgexfoundry/go-mod-secrets/pkg/providers/vault" -) - -// getMockTokenServer is a test helper for unit tests of vaultclient -func getMockTokenServer(tokenDataMap *sync.Map) *httptest.Server { - server := httptest.NewServer(http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) { - urlPath := req.URL.String() - if req.Method == http.MethodGet && urlPath == "/v1/auth/token/lookup-self" { - token := req.Header.Get(vault.AuthTypeHeader) - sampleTokenLookup, exists := tokenDataMap.Load(token) - if !exists { - rw.WriteHeader(403) - _, _ = rw.Write([]byte("permission denied")) - } else { - resp := &vault.TokenLookupResponse{ - Data: sampleTokenLookup.(vault.TokenLookupMetadata), - } - if ret, err := json.Marshal(resp); err != nil { - rw.WriteHeader(500) - _, _ = rw.Write([]byte(err.Error())) - } else { - rw.WriteHeader(200) - _, _ = rw.Write(ret) - } - } - } else if req.Method == http.MethodPost && urlPath == "/v1/auth/token/renew-self" { - token := req.Header.Get(vault.AuthTypeHeader) - sampleTokenLookup, exists := tokenDataMap.Load(token) - if !exists { - rw.WriteHeader(403) - _, _ = rw.Write([]byte("permission denied")) - } else { - currentTTL := sampleTokenLookup.(vault.TokenLookupMetadata).Ttl - if currentTTL <= 0 { - // already expired - rw.WriteHeader(403) - _, _ = rw.Write([]byte("permission denied")) - } else { - tokenPeriod := sampleTokenLookup.(vault.TokenLookupMetadata).Period - - tokenDataMap.Store(token, vault.TokenLookupMetadata{ - Renewable: true, - Ttl: tokenPeriod, - Period: tokenPeriod, - }) - rw.WriteHeader(200) - _, _ = rw.Write([]byte("token renewed")) - } - } - } else { - rw.WriteHeader(404) - _, _ = rw.Write([]byte(fmt.Sprintf("Unknown urlPath: %s", urlPath))) - } - })) - return server -} diff --git a/bootstrap/handlers/secret/client/testdata/replacement.json b/bootstrap/handlers/secret/client/testdata/replacement.json deleted file mode 100644 index dbe73832..00000000 --- a/bootstrap/handlers/secret/client/testdata/replacement.json +++ /dev/null @@ -1,9 +0,0 @@ -{ - "keys": [ - "test-keys" - ], - "keys_base64": [ - "test-keys-base64" - ], - "root_token": "replacement-token" -} \ No newline at end of file diff --git a/bootstrap/handlers/secret/client/testdata/testToken.json b/bootstrap/handlers/secret/client/testdata/testToken.json deleted file mode 100644 index 20aaa9a9..00000000 --- a/bootstrap/handlers/secret/client/testdata/testToken.json +++ /dev/null @@ -1,9 +0,0 @@ -{ - "keys": [ - "test-keys" - ], - "keys_base64": [ - "test-keys-base64" - ], - "root_token": "test-token" -} \ No newline at end of file diff --git a/bootstrap/handlers/secret/client/vaultclient.go b/bootstrap/handlers/secret/client/vaultclient.go deleted file mode 100644 index 9f668cc0..00000000 --- a/bootstrap/handlers/secret/client/vaultclient.go +++ /dev/null @@ -1,87 +0,0 @@ -// -// Copyright (c) 2019 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. -// -// SPDX-License-Identifier: Apache-2.0 -// - -package client - -import ( - "context" - "fmt" - - "github.com/edgexfoundry/go-mod-bootstrap/config" - - "github.com/edgexfoundry/go-mod-core-contracts/clients/logger" - - "github.com/edgexfoundry/go-mod-secrets/pkg" - "github.com/edgexfoundry/go-mod-secrets/pkg/providers/vault" - "github.com/edgexfoundry/go-mod-secrets/pkg/token/authtokenloader" - "github.com/edgexfoundry/go-mod-secrets/pkg/token/fileioperformer" -) - -// Vault is the structure to get the secret client from go-mod-secrets vault package -type Vault struct { - ctx context.Context - config vault.SecretConfig - lc logger.LoggingClient -} - -// NewVault is the constructor for Vault in order to get the Vault secret client -func NewVault(ctx context.Context, config vault.SecretConfig, lc logger.LoggingClient) Vault { - return Vault{ - ctx: ctx, - config: config, - lc: lc, - } -} - -// Get is the getter for Vault secret client from go-mod-secrets -func (c Vault) Get(secretStoreInfo config.SecretStoreInfo) (pkg.SecretClient, error) { - return vault.NewSecretClientFactory().NewSecretClient( - c.ctx, - c.config, - c.lc, - c.getDefaultTokenExpiredCallback(secretStoreInfo)) -} - -// getDefaultTokenExpiredCallback is the default implementation of tokenExpiredCallback function -// It utilizes the tokenFile to re-read the token and enable retry if any update from the expired token -func (c Vault) getDefaultTokenExpiredCallback( - secretStoreInfo config.SecretStoreInfo) func(expiredToken string) (replacementToken string, retry bool) { - // if there is no tokenFile, then no replacement token can be used and hence no callback - if secretStoreInfo.TokenFile == "" { - return nil - } - - tokenFile := secretStoreInfo.TokenFile - return func(expiredToken string) (replacementToken string, retry bool) { - // during the callback, we want to re-read the token from the disk - // specified by tokenFile and set the retry to true if a new token - // is different from the expiredToken - fileIoPerformer := fileioperformer.NewDefaultFileIoPerformer() - authTokenLoader := authtokenloader.NewAuthTokenLoader(fileIoPerformer) - reReadToken, err := authTokenLoader.Load(tokenFile) - if err != nil { - c.lc.Error(fmt.Sprintf("fail to load auth token from tokenFile %s: %v", tokenFile, err)) - return "", false - } - - if reReadToken == expiredToken { - c.lc.Error("No new replacement token found for the expired token") - return reReadToken, false - } - - return reReadToken, true - } -} diff --git a/bootstrap/handlers/secret/client/vaultclient_test.go b/bootstrap/handlers/secret/client/vaultclient_test.go deleted file mode 100644 index b4c10a42..00000000 --- a/bootstrap/handlers/secret/client/vaultclient_test.go +++ /dev/null @@ -1,253 +0,0 @@ -// -// Copyright (c) 2019 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. -// -// SPDX-License-Identifier: Apache-2.0 -// - -package client - -import ( - "context" - "fmt" - "net" - "net/url" - "strconv" - "sync" - "testing" - "time" - - "github.com/edgexfoundry/go-mod-bootstrap/bootstrap/logging" - "github.com/edgexfoundry/go-mod-bootstrap/config" - - "github.com/edgexfoundry/go-mod-secrets/pkg/providers/vault" -) - -func TestGetVaultClient(t *testing.T) { - // setup - tokenPeriod := 6 - tokenDataMap := initTokenData(tokenPeriod) - server := getMockTokenServer(tokenDataMap) - defer server.Close() - - serverURL, err := url.Parse(server.URL) - if err != nil { - t.Errorf("error on parsing server url %s: %s", server.URL, err) - } - host, port, _ := net.SplitHostPort(serverURL.Host) - portNum, _ := strconv.Atoi(port) - - bkgCtx := context.Background() - lc := logging.FactoryToStdout("clientTest") - - testSecretStoreInfo := config.SecretStoreInfo{ - Host: host, - Port: portNum, - Path: "/test", - Protocol: "http", - ServerName: "mockVaultServer", - } - - tests := []struct { - name string - authToken string - tokenFile string - expectedNewToken string - expectedNilCallback bool - expectedRetry bool - expectError bool - }{ - { - name: "New secret client with testToken1, more than half of TTL remaining", - authToken: "testToken1", - tokenFile: "testdata/replacement.json", - expectedNilCallback: false, - expectedNewToken: "replacement-token", - expectedRetry: true, - expectError: false, - }, - { - name: "New secret client with the same first token again", - authToken: "testToken1", - tokenFile: "testdata/replacement.json", - expectedNilCallback: false, - expectedNewToken: "replacement-token", - expectedRetry: true, - expectError: false, - }, - { - name: "New secret client with testToken2, half of TTL remaining", - authToken: "testToken2", - expectedNilCallback: false, - tokenFile: "testdata/replacement.json", - expectedNewToken: "replacement-token", - expectedRetry: true, - expectError: false, - }, - { - name: "New secret client with testToken3, less than half of TTL remaining", - authToken: "testToken3", - tokenFile: "testdata/replacement.json", - expectedNilCallback: false, - expectedNewToken: "replacement-token", - expectedRetry: true, - expectError: false, - }, - { - name: "New secret client with expired token, no TTL remaining", - authToken: "expiredToken", - tokenFile: "testdata/replacement.json", - expectedNilCallback: false, - expectedNewToken: "replacement-token", - expectedRetry: true, - expectError: true, - }, - { - name: "New secret client with expired token, non-existing TokenFile path", - authToken: "expiredToken", - tokenFile: "testdata/non-existing.json", - expectedNilCallback: false, - expectedNewToken: "", - expectedRetry: false, - expectError: true, - }, - { - name: "New secret client with expired test token, but same expired replacement token", - authToken: "test-token", - tokenFile: "testdata/testToken.json", - expectedNilCallback: false, - expectedNewToken: "test-token", - expectedRetry: false, - expectError: true, - }, - { - name: "New secret client with unauthenticated token", - authToken: "test-token", - expectedNilCallback: true, - expectedNewToken: "", - expectedRetry: false, - expectError: true, - }, - { - name: "New secret client with unrenewable token", - authToken: "unrenewableToken", - expectedNilCallback: true, - expectedNewToken: "", - expectedRetry: true, - expectError: false, - }, - { - name: "New secret client with no TokenFile", - authToken: "testToken1", - tokenFile: "", - expectedNilCallback: true, - expectedNewToken: "", - expectedRetry: false, - expectError: false, - }, - } - - for _, test := range tests { - testSecretStoreInfo.TokenFile = test.tokenFile - // pinned local variables to avoid scopelint warnings - testToken := test.authToken - cfgHTTP := vault.SecretConfig{ - Host: host, - Port: portNum, - Protocol: "http", - Authentication: vault.AuthenticationInfo{AuthToken: testToken}, - } - expectError := test.expectError - expectedCallbackNil := test.expectedNilCallback - expectedNewToken := test.expectedNewToken - expectedRetry := test.expectedRetry - - t.Run(test.name, func(t *testing.T) { - sclient := NewVault(bkgCtx, cfgHTTP, lc) - _, err := sclient.Get(testSecretStoreInfo) - - if expectError && err == nil { - t.Error("Expected error but none was received") - } - - if !expectError && err != nil { - t.Errorf("Unexpected error: %s", err.Error()) - } - - tokenCallback := sclient.getDefaultTokenExpiredCallback(testSecretStoreInfo) - if expectedCallbackNil && tokenCallback != nil { - t.Error("expected nil token expired callback func, but found not nil") - } - if !expectedCallbackNil { - if tokenCallback == nil { - t.Error("expected some non-nil token expired callback func, but found nil") - } else { - repToken, retry := tokenCallback(testToken) - - if repToken != expectedNewToken { - t.Errorf("expected a new token [%s] from callback but got [%s]", expectedNewToken, repToken) - } - - if retry != expectedRetry { - t.Errorf("expected retry %v for a default token expired callback but got %v", expectedRetry, retry) - } - - lc.Debug(fmt.Sprintf("repToken = %s, retry = %v", repToken, retry)) - } - } - }) - } - // wait for some time to allow renewToken to be run if any - time.Sleep(7 * time.Second) -} - -func initTokenData(tokenPeriod int) *sync.Map { - var tokenDataMap sync.Map - // ttl > half of period - tokenDataMap.Store("testToken1", vault.TokenLookupMetadata{ - Renewable: true, - Ttl: tokenPeriod * 7 / 10, - Period: tokenPeriod, - }) - // ttl = half of period - tokenDataMap.Store("testToken2", vault.TokenLookupMetadata{ - Renewable: true, - Ttl: tokenPeriod / 2, - Period: tokenPeriod, - }) - // ttl < half of period - tokenDataMap.Store("testToken3", vault.TokenLookupMetadata{ - Renewable: true, - Ttl: tokenPeriod * 3 / 10, - Period: tokenPeriod, - }) - // to be expired token - tokenDataMap.Store("toToExpiredToken", vault.TokenLookupMetadata{ - Renewable: true, - Ttl: 1, - Period: tokenPeriod, - }) - // expired token - tokenDataMap.Store("expiredToken", vault.TokenLookupMetadata{ - Renewable: true, - Ttl: 0, - Period: tokenPeriod, - }) - // not renewable token - tokenDataMap.Store("unrenewableToken", vault.TokenLookupMetadata{ - Renewable: false, - Ttl: 0, - Period: tokenPeriod, - }) - - return &tokenDataMap -} diff --git a/bootstrap/handlers/secret/credentials.go b/bootstrap/handlers/secret/credentials.go deleted file mode 100644 index 107fb74c..00000000 --- a/bootstrap/handlers/secret/credentials.go +++ /dev/null @@ -1,40 +0,0 @@ -/******************************************************************************** - * Copyright 2019 Dell 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 - * - * 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 secret - -import "github.com/edgexfoundry/go-mod-bootstrap/config" - -const RedisDB = "redisdb" - -func (s *SecretProvider) GetDatabaseCredentials(database config.Database) (config.Credentials, error) { - // If security is disabled then we are to use the credentials supplied by the configuration. - if !s.isSecurityEnabled() { - return config.Credentials{ - Username: database.Username, - Password: database.Password, - }, nil - } - - secrets, err := s.secretClient.GetSecrets(database.Type, "username", "password") - if err != nil { - return config.Credentials{}, err - } - - return config.Credentials{ - Username: secrets["username"], - Password: secrets["password"], - }, nil - -} diff --git a/bootstrap/handlers/secret_test.go b/bootstrap/handlers/secret_test.go new file mode 100644 index 00000000..e645d3d7 --- /dev/null +++ b/bootstrap/handlers/secret_test.go @@ -0,0 +1,173 @@ +/******************************************************************************* + * Copyright 2020 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 + * + * 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 handlers + +import ( + "context" + "encoding/json" + "net/http" + "net/http/httptest" + "net/url" + "os" + "strconv" + "sync" + "testing" + + "github.com/edgexfoundry/go-mod-bootstrap/bootstrap/container" + "github.com/edgexfoundry/go-mod-bootstrap/bootstrap/secret" + "github.com/edgexfoundry/go-mod-bootstrap/bootstrap/startup" + bootstrapConfig "github.com/edgexfoundry/go-mod-bootstrap/config" + "github.com/edgexfoundry/go-mod-bootstrap/di" + "github.com/edgexfoundry/go-mod-core-contracts/clients/logger" + "github.com/edgexfoundry/go-mod-secrets/pkg/token/authtokenloader/mocks" + "github.com/edgexfoundry/go-mod-secrets/pkg/types" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +const ( + expectedUsername = "admin" + expectedPassword = "password" + expectedPath = "/redisdb" +) + +var testTokenResponse = `{"auth":{"accessor":"9OvxnrjgV0JTYMeBreak7YJ9","client_token":"s.oPJ8uuJCkTRb2RDdcNova8wg","entity_id":"","lease_duration":3600,"metadata":{"edgex-service-name":"edgex-core-data"},"orphan":true,"policies":["default","edgex-service-edgex-core-data"],"renewable":true,"token_policies":["default","edgex-service-edgex-core-data"],"token_type":"service"},"data":null,"lease_duration":0,"lease_id":"","renewable":false,"request_id":"ee749ee1-c8bf-6fa9-3ed5-644181fc25b0","warnings":null,"wrap_info":null}` +var expectedSecrets = map[string]string{secret.UsernameKey: expectedUsername, secret.PasswordKey: expectedPassword} + +func TestProvider_BootstrapHandler(t *testing.T) { + tests := []struct { + Name string + Secure string + }{ + {"Valid Secure", "true"}, + {"Valid Insecure", "false"}, + } + + for _, tc := range tests { + t.Run(tc.Name, func(t *testing.T) { + _ = os.Setenv(secret.EnvSecretStore, tc.Secure) + timer := startup.NewStartUpTimer("UnitTest") + + dic := di.NewContainer(di.ServiceConstructorMap{ + container.LoggingClientInterfaceName: func(get di.Get) interface{} { + return logger.MockLogger{} + }, + container.ConfigurationInterfaceName: func(get di.Get) interface{} { + return TestConfig{} + }, + }) + + if tc.Secure == "true" { + testServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + switch r.RequestURI { + case "/v1/auth/token/lookup-self": + w.WriteHeader(http.StatusOK) + _, _ = w.Write([]byte(testTokenResponse)) + case "/redisdb": + w.WriteHeader(http.StatusOK) + data := make(map[string]interface{}) + data["data"] = expectedSecrets + response, _ := json.Marshal(data) + _, _ = w.Write(response) + default: + w.WriteHeader(http.StatusNotFound) + } + })) + defer testServer.Close() + + serverUrl, _ := url.Parse(testServer.URL) + port, _ := strconv.Atoi(serverUrl.Port()) + config := NewTestConfig(port) + + mockTokenLoader := &mocks.AuthTokenLoader{} + mockTokenLoader.On("Load", "token.json").Return("Test Token", nil) + dic.Update(di.ServiceConstructorMap{ + container.AuthTokenLoaderInterfaceName: func(get di.Get) interface{} { + return mockTokenLoader + }, + container.ConfigurationInterfaceName: func(get di.Get) interface{} { + return config + }, + }) + } + + actual := SecureProviderBootstrapHandler(context.Background(), &sync.WaitGroup{}, timer, dic) + require.True(t, actual) + actualProvider := container.SecretProviderFrom(dic.Get) + assert.NotNil(t, actualProvider) + + actualSecrets, err := actualProvider.GetSecrets(expectedPath) + require.NoError(t, err) + assert.Equal(t, expectedUsername, actualSecrets[secret.UsernameKey]) + assert.Equal(t, expectedPassword, actualSecrets[secret.PasswordKey]) + }) + } +} + +type TestConfig struct { + InsecureSecrets bootstrapConfig.InsecureSecrets + SecretStore bootstrapConfig.SecretStoreInfo +} + +func NewTestConfig(port int) TestConfig { + return TestConfig{ + SecretStore: bootstrapConfig.SecretStoreInfo{ + Host: "localhost", + Port: port, + Protocol: "http", + ServerName: "localhost", + TokenFile: "token.json", + Authentication: types.AuthenticationInfo{ + AuthType: "Dummy-Token", + AuthToken: "myToken", + }, + }, + } +} + +func (t TestConfig) UpdateFromRaw(_ interface{}) bool { + panic("implement me") +} + +func (t TestConfig) EmptyWritablePtr() interface{} { + panic("implement me") +} + +func (t TestConfig) UpdateWritableFromRaw(_ interface{}) bool { + panic("implement me") +} + +func (t TestConfig) GetBootstrap() bootstrapConfig.BootstrapConfiguration { + return bootstrapConfig.BootstrapConfiguration{ + SecretStore: t.SecretStore, + } +} + +func (t TestConfig) GetLogLevel() string { + panic("implement me") +} + +func (t TestConfig) GetRegistryInfo() bootstrapConfig.RegistryInfo { + panic("implement me") +} + +func (t TestConfig) GetInsecureSecrets() bootstrapConfig.InsecureSecrets { + return map[string]bootstrapConfig.InsecureSecretsInfo{ + "DB": { + Path: expectedPath, + Secrets: expectedSecrets, + }, + } +} diff --git a/bootstrap/interfaces/configuration.go b/bootstrap/interfaces/configuration.go index 94a7cb78..7d940fca 100644 --- a/bootstrap/interfaces/configuration.go +++ b/bootstrap/interfaces/configuration.go @@ -39,4 +39,7 @@ type Configuration interface { // GetRegistryInfo gets the config.RegistryInfo field from the ConfigurationStruct. GetRegistryInfo() config.RegistryInfo + + // GetInsecureSecrets gets the config.InsecureSecrets field from the ConfigurationStruct. + GetInsecureSecrets() config.InsecureSecrets } diff --git a/bootstrap/interfaces/mocks/SecretProvider.go b/bootstrap/interfaces/mocks/SecretProvider.go new file mode 100644 index 00000000..455e33bc --- /dev/null +++ b/bootstrap/interfaces/mocks/SecretProvider.go @@ -0,0 +1,77 @@ +// Code generated by mockery v0.0.0-dev. DO NOT EDIT. + +package mocks + +import ( + time "time" + + mock "github.com/stretchr/testify/mock" +) + +// SecretProvider is an autogenerated mock type for the SecretProvider type +type SecretProvider struct { + mock.Mock +} + +// GetSecrets provides a mock function with given fields: path, keys +func (_m *SecretProvider) GetSecrets(path string, keys ...string) (map[string]string, error) { + _va := make([]interface{}, len(keys)) + for _i := range keys { + _va[_i] = keys[_i] + } + var _ca []interface{} + _ca = append(_ca, path) + _ca = append(_ca, _va...) + ret := _m.Called(_ca...) + + var r0 map[string]string + if rf, ok := ret.Get(0).(func(string, ...string) map[string]string); ok { + r0 = rf(path, keys...) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(map[string]string) + } + } + + var r1 error + if rf, ok := ret.Get(1).(func(string, ...string) error); ok { + r1 = rf(path, keys...) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// SecretsLastUpdated provides a mock function with given fields: +func (_m *SecretProvider) SecretsLastUpdated() time.Time { + ret := _m.Called() + + var r0 time.Time + if rf, ok := ret.Get(0).(func() time.Time); ok { + r0 = rf() + } else { + r0 = ret.Get(0).(time.Time) + } + + return r0 +} + +// SecretsUpdated provides a mock function with given fields: +func (_m *SecretProvider) SecretsUpdated() { + _m.Called() +} + +// StoreSecrets provides a mock function with given fields: path, secrets +func (_m *SecretProvider) StoreSecrets(path string, secrets map[string]string) error { + ret := _m.Called(path, secrets) + + var r0 error + if rf, ok := ret.Get(0).(func(string, map[string]string) error); ok { + r0 = rf(path, secrets) + } else { + r0 = ret.Error(0) + } + + return r0 +} diff --git a/bootstrap/interfaces/secret.go b/bootstrap/interfaces/secret.go new file mode 100644 index 00000000..2143bcc1 --- /dev/null +++ b/bootstrap/interfaces/secret.go @@ -0,0 +1,19 @@ +package interfaces + +import "time" + +// SecretProvider defines the contract for secret provider implementations that +// allow secrets to be retrieved/stored from/to a services Secret Store. +type SecretProvider interface { + // StoreSecrets stores new secrets into the service's SecretStore at the specified path. + StoreSecrets(path string, secrets map[string]string) error + + // GetSecrets retrieves secrets from the service's SecretStore at the specified path. + GetSecrets(path string, keys ...string) (map[string]string, error) + + // SecretsUpdated sets the secrets last updated time to current time. + SecretsUpdated() + + // SecretsLastUpdated returns the last time secrets were updated + SecretsLastUpdated() time.Time +} diff --git a/bootstrap/registration/registry_test.go b/bootstrap/registration/registry_test.go index e55cc9f3..99a93e91 100644 --- a/bootstrap/registration/registry_test.go +++ b/bootstrap/registration/registry_test.go @@ -140,6 +140,10 @@ type unitTestConfiguration struct { Registry config.RegistryInfo } +func (ut unitTestConfiguration) GetInsecureSecrets() config.InsecureSecrets { + return nil +} + func (ut unitTestConfiguration) UpdateFromRaw(rawConfig interface{}) bool { panic("should not be called") } diff --git a/bootstrap/handlers/secret/certkeypair.go b/bootstrap/secret/helper.go similarity index 64% rename from bootstrap/handlers/secret/certkeypair.go rename to bootstrap/secret/helper.go index 146ba706..bc1632ae 100644 --- a/bootstrap/handlers/secret/certkeypair.go +++ b/bootstrap/secret/helper.go @@ -1,5 +1,5 @@ -/******************************************************************************** - * Copyright 2020 Dell Inc. +/******************************************************************************* + * Copyright 2020 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 @@ -14,17 +14,16 @@ package secret -import "github.com/edgexfoundry/go-mod-bootstrap/config" +import "os" -func (s *SecretProvider) GetCertificateKeyPair(path string) (config.CertKeyPair, error) { - secrets, err := s.secretClient.GetSecrets(path, "cert", "key") - if err != nil { - return config.CertKeyPair{}, err - } - - return config.CertKeyPair{ - Cert: secrets["cert"], - Key: secrets["key"], - }, nil +const ( + EnvSecretStore = "EDGEX_SECURITY_SECRET_STORE" + UsernameKey = "username" + PasswordKey = "password" +) +// IsSecurityEnabled determines if security has been enabled. +func IsSecurityEnabled() bool { + env := os.Getenv(EnvSecretStore) + return env != "false" // Any other value is considered secure mode enabled } diff --git a/bootstrap/secret/insecure.go b/bootstrap/secret/insecure.go new file mode 100644 index 00000000..9da95a9c --- /dev/null +++ b/bootstrap/secret/insecure.go @@ -0,0 +1,108 @@ +/******************************************************************************* + * Copyright 2020 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 + * + * 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 secret + +import ( + "errors" + "fmt" + + "strings" + "time" + + "github.com/edgexfoundry/go-mod-bootstrap/bootstrap/interfaces" + "github.com/edgexfoundry/go-mod-core-contracts/clients/logger" +) + +// InsecureProvider implements the SecretProvider interface for insecure secrets +type InsecureProvider struct { + lc logger.LoggingClient + configuration interfaces.Configuration + lastUpdated time.Time +} + +// NewInsecureProvider creates, initializes Provider for insecure secrets. +func NewInsecureProvider(config interfaces.Configuration, lc logger.LoggingClient) *InsecureProvider { + return &InsecureProvider{ + configuration: config, + lc: lc, + lastUpdated: time.Now(), + } +} + +// GetSecrets retrieves secrets from a Insecure Secrets secret store. +// path specifies the type or location of the secrets to retrieve. +// keys specifies the secrets which to retrieve. If no keys are provided then all the keys associated with the +// specified path will be returned. +func (p *InsecureProvider) GetSecrets(path string, keys ...string) (map[string]string, error) { + results := make(map[string]string) + pathExists := false + var missingKeys []string + + 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 { + if len(keys) == 0 { + // If no keys are provided then all the keys associated with the specified path will be returned + for k, v := range insecureSecret.Secrets { + results[k] = v + } + return results, nil + } + + pathExists = true + for _, key := range keys { + value, keyExists := insecureSecret.Secrets[key] + if !keyExists { + missingKeys = append(missingKeys, key) + continue + } + results[key] = value + } + } + } + + if len(missingKeys) > 0 { + err := fmt.Errorf("No value for the keys: [%s] exists", strings.Join(missingKeys, ",")) + return nil, err + } + + 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 +} + +// StoreSecrets stores the secrets, but is not supported for Insecure Secrets +func (p *InsecureProvider) StoreSecrets(_ string, _ map[string]string) error { + return errors.New("storing secrets is not supported when running in insecure mode") +} + +// SecretsUpdated resets LastUpdate time for the Insecure Secrets. +func (p *InsecureProvider) SecretsUpdated() { + p.lastUpdated = time.Now() +} + +// SecretsLastUpdated returns the last time insecure secrets were updated +func (p *InsecureProvider) SecretsLastUpdated() time.Time { + return p.lastUpdated +} diff --git a/bootstrap/secret/insecure_test.go b/bootstrap/secret/insecure_test.go new file mode 100644 index 00000000..2d917a12 --- /dev/null +++ b/bootstrap/secret/insecure_test.go @@ -0,0 +1,88 @@ +/******************************************************************************* + * Copyright 2020 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 + * + * 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 secret + +import ( + "testing" + "time" + + bootstrapConfig "github.com/edgexfoundry/go-mod-bootstrap/config" + "github.com/edgexfoundry/go-mod-core-contracts/clients/logger" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestInsecureProvider_GetSecrets(t *testing.T) { + expected := map[string]string{"username": "admin", "password": "sam123!"} + + configAllSecrets := TestConfig{ + InsecureSecrets: map[string]bootstrapConfig.InsecureSecretsInfo{ + "DB": { + Path: "redis", + Secrets: expected, + }, + }, + } + + configMissingSecrets := TestConfig{ + InsecureSecrets: map[string]bootstrapConfig.InsecureSecretsInfo{ + "DB": { + Path: "redis", + }, + }, + } + + tests := []struct { + Name string + Path string + Keys []string + Config TestConfig + ExpectError bool + }{ + {"Valid", "redis", []string{"username", "password"}, configAllSecrets, false}, + {"Valid just path", "redis", nil, configAllSecrets, false}, + {"Invalid - No secrets", "redis", []string{"username", "password"}, configMissingSecrets, true}, + {"Invalid - Bad Path", "bogus", []string{"username", "password"}, configAllSecrets, true}, + } + + for _, tc := range tests { + t.Run(tc.Name, func(t *testing.T) { + target := NewInsecureProvider(tc.Config, logger.MockLogger{}) + actual, err := target.GetSecrets(tc.Path, tc.Keys...) + if tc.ExpectError { + require.Error(t, err) + return + } + + require.NoError(t, err) + assert.Equal(t, expected, actual) + }) + } +} + +func TestInsecureProvider_StoreSecrets_Secure(t *testing.T) { + target := NewInsecureProvider(nil, nil) + err := target.StoreSecrets("myPath", map[string]string{"Key": "value"}) + require.Error(t, err) +} + +func TestInsecureProvider_SecretsUpdated_SecretsLastUpdated(t *testing.T) { + target := NewInsecureProvider(nil, logger.MockLogger{}) + previous := target.SecretsLastUpdated() + time.Sleep(1 * time.Second) + target.SecretsUpdated() + current := target.SecretsLastUpdated() + assert.True(t, current.After(previous)) +} diff --git a/bootstrap/secret/secure.go b/bootstrap/secret/secure.go new file mode 100644 index 00000000..8b1cb52d --- /dev/null +++ b/bootstrap/secret/secure.go @@ -0,0 +1,180 @@ +/******************************************************************************* + * Copyright 2018 Dell Inc. + * Copyright 2020 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 + * + * 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 secret + +import ( + "errors" + "fmt" + + "github.com/edgexfoundry/go-mod-bootstrap/bootstrap/interfaces" + "github.com/edgexfoundry/go-mod-core-contracts/clients/logger" + "github.com/edgexfoundry/go-mod-secrets/pkg/token/authtokenloader" + "github.com/edgexfoundry/go-mod-secrets/secrets" + + "sync" + "time" +) + +// SecureProvider implements the SecretProvider interface +type SecureProvider struct { + secretClient secrets.SecretClient + lc logger.LoggingClient + loader authtokenloader.AuthTokenLoader + configuration interfaces.Configuration + secretsCache map[string]map[string]string // secret's path, key, value + cacheMutex *sync.RWMutex + lastUpdated time.Time +} + +// NewSecureProvider creates & initializes Provider instance for secure secrets. +func NewSecureProvider(config interfaces.Configuration, lc logger.LoggingClient, loader authtokenloader.AuthTokenLoader) *SecureProvider { + provider := &SecureProvider{ + configuration: config, + lc: lc, + loader: loader, + secretsCache: make(map[string]map[string]string), + cacheMutex: &sync.RWMutex{}, + lastUpdated: time.Now(), + } + return provider +} + +// SetClient sets the secret client that is used to access the secure secrets +func (p *SecureProvider) SetClient(client secrets.SecretClient) { + p.secretClient = client +} + +// GetSecrets retrieves secrets from a secret store. +// path specifies the type or location of the secrets to retrieve. +// keys specifies the secrets which to retrieve. If no keys are provided then all the keys associated with the +// specified path will be returned. +func (p *SecureProvider) GetSecrets(path string, keys ...string) (map[string]string, error) { + if cachedSecrets := p.getSecretsCache(path, keys...); cachedSecrets != nil { + return cachedSecrets, nil + } + + if p.secretClient == nil { + return nil, errors.New("can't get secrets. Secure secret provider is not properly initialized") + } + + secureSecrets, err := p.secretClient.GetSecrets(path, keys...) + if err != nil { + return nil, err + } + + p.updateSecretsCache(path, secureSecrets) + return secureSecrets, nil +} + +func (p *SecureProvider) getSecretsCache(path string, keys ...string) map[string]string { + secureSecrets := make(map[string]string) + + // Synchronize cache access + p.cacheMutex.RLock() + defer p.cacheMutex.RUnlock() + + // check cache for keys + allKeysExistInCache := false + cachedSecrets, cacheExists := p.secretsCache[path] + value := "" + + if cacheExists { + for _, key := range keys { + value, allKeysExistInCache = cachedSecrets[key] + if !allKeysExistInCache { + return nil + } + secureSecrets[key] = value + } + + // return secureSecrets if the requested keys exist in cache + if allKeysExistInCache { + return secureSecrets + } + } + + return nil +} + +func (p *SecureProvider) updateSecretsCache(path string, secrets map[string]string) { + // Synchronize cache access + p.cacheMutex.Lock() + defer p.cacheMutex.Unlock() + + if _, cacheExists := p.secretsCache[path]; !cacheExists { + p.secretsCache[path] = secrets + } + + for key, value := range secrets { + p.secretsCache[path][key] = value + } +} + +// StoreSecrets stores the secrets to a secret store. +// it sets the values requested at provided keys +// path specifies the type or location of the secrets to store +// secrets map specifies the "key": "value" pairs of secrets to store +func (p *SecureProvider) StoreSecrets(path string, secrets map[string]string) error { + if p.secretClient == nil { + return errors.New("can't store secrets. Secure secret provider is not properly initialized") + } + + err := p.secretClient.StoreSecrets(path, secrets) + if err != nil { + return err + } + + // Synchronize cache access before clearing + p.cacheMutex.Lock() + // Clearing cache because adding a new secret(p) possibly invalidates the previous cache + p.secretsCache = make(map[string]map[string]string) + p.cacheMutex.Unlock() + //indicate to the SDK that the cache has been invalidated + p.lastUpdated = time.Now() + return nil +} + +// SecretsUpdated is not need for secure secrets as this is handled when secrets are stored. +func (p *SecureProvider) SecretsUpdated() { + // Do nothing +} + +// SecretsLastUpdated returns the last time secure secrets were updated +func (p *SecureProvider) SecretsLastUpdated() time.Time { + return p.lastUpdated +} + +// defaultTokenExpiredCallback is the default implementation of tokenExpiredCallback function +// It utilizes the tokenFile to re-read the token and enable retry if any update from the expired token +func (p *SecureProvider) DefaultTokenExpiredCallback(expiredToken string) (replacementToken string, retry bool) { + tokenFile := p.configuration.GetBootstrap().SecretStore.TokenFile + + // during the callback, we want to re-read the token from the disk + // specified by tokenFile and set the retry to true if a new token + // is different from the expiredToken + reReadToken, err := p.loader.Load(tokenFile) + if err != nil { + p.lc.Error(fmt.Sprintf("fail to load auth token from tokenFile %s: %v", tokenFile, err)) + return "", false + } + + if reReadToken == expiredToken { + p.lc.Error("No new replacement token found for the expired token") + return reReadToken, false + } + + return reReadToken, true +} diff --git a/bootstrap/secret/secure_test.go b/bootstrap/secret/secure_test.go new file mode 100644 index 00000000..0267dcde --- /dev/null +++ b/bootstrap/secret/secure_test.go @@ -0,0 +1,254 @@ +/******************************************************************************* + * Copyright 2020 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 + * + * 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 secret + +import ( + "errors" + "testing" + "time" + + bootstrapConfig "github.com/edgexfoundry/go-mod-bootstrap/config" + "github.com/edgexfoundry/go-mod-core-contracts/clients/logger" + "github.com/edgexfoundry/go-mod-secrets/pkg" + mocks2 "github.com/edgexfoundry/go-mod-secrets/pkg/token/authtokenloader/mocks" + "github.com/edgexfoundry/go-mod-secrets/secrets" + "github.com/edgexfoundry/go-mod-secrets/secrets/mocks" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestSecureProvider_GetSecrets(t *testing.T) { + expected := map[string]string{"username": "admin", "password": "sam123!"} + + mock := &mocks.SecretClient{} + mock.On("GetSecrets", "redis", "username", "password").Return(expected, nil) + mock.On("GetSecrets", "redis").Return(expected, nil) + notfound := []string{"username", "password"} + mock.On("GetSecrets", "missing", "username", "password").Return(nil, pkg.NewErrSecretsNotFound(notfound)) + + tests := []struct { + Name string + Path string + Keys []string + Config TestConfig + Client secrets.SecretClient + ExpectError bool + }{ + {"Valid Secure", "redis", []string{"username", "password"}, TestConfig{}, mock, false}, + {"Invalid Secure", "missing", []string{"username", "password"}, TestConfig{}, mock, true}, + {"Invalid No Client", "redis", []string{"username", "password"}, TestConfig{}, nil, true}, + } + + for _, tc := range tests { + t.Run(tc.Name, func(t *testing.T) { + target := NewSecureProvider(tc.Config, logger.MockLogger{}, nil) + target.SetClient(tc.Client) + actual, err := target.GetSecrets(tc.Path, tc.Keys...) + if tc.ExpectError { + require.Error(t, err) + return + } + + require.NoError(t, err) + assert.Equal(t, expected, actual) + }) + } +} + +func TestSecureProvider_GetSecrets_Cached(t *testing.T) { + expected := map[string]string{"username": "admin", "password": "sam123!"} + + mock := &mocks.SecretClient{} + // Use the Once method so GetSecrets can be changed below + mock.On("GetSecrets", "redis", "username", "password").Return(expected, nil).Once() + + target := NewSecureProvider(nil, logger.MockLogger{}, nil) + target.SetClient(mock) + + actual, err := target.GetSecrets("redis", "username", "password") + require.NoError(t, err) + assert.Equal(t, expected, actual) + + // Now have mock return error if it is called which should not happen of secrets are cached + mock.On("GetSecrets", "redis", "username", "password").Return(nil, errors.New("No Cached")) + actual, err = target.GetSecrets("redis", "username", "password") + require.NoError(t, err) + assert.Equal(t, expected, actual) + + // Now check for error when not all requested keys not in cache. + mock.On("GetSecrets", "redis", "username", "password2").Return(nil, errors.New("No Cached")) + _, err = target.GetSecrets("redis", "username", "password2") + require.Error(t, err) +} + +func TestSecureProvider_GetSecrets_Cached_Invalidated(t *testing.T) { + expected := map[string]string{"username": "admin", "password": "sam123!"} + + mock := &mocks.SecretClient{} + // Use the Once method so GetSecrets can be changed below + mock.On("GetSecrets", "redis", "username", "password").Return(expected, nil).Once() + mock.On("StoreSecrets", "redis", expected).Return(nil) + + target := NewSecureProvider(nil, logger.MockLogger{}, nil) + target.SetClient(mock) + + actual, err := target.GetSecrets("redis", "username", "password") + require.NoError(t, err) + assert.Equal(t, expected, actual) + + // Invalidate the secrets cache by storing new secrets + err = target.StoreSecrets("redis", expected) + require.NoError(t, err) + + // Now have mock return error is it is called which should now happen if the cache was properly invalidated by the above call to StoreSecrets + mock.On("GetSecrets", "redis", "username", "password").Return(nil, errors.New("No Cached")) + _, err = target.GetSecrets("redis", "username", "password") + require.Error(t, err) +} + +func TestSecureProvider_StoreSecrets_Secure(t *testing.T) { + input := map[string]string{"username": "admin", "password": "sam123!"} + mock := &mocks.SecretClient{} + mock.On("StoreSecrets", "redis", input).Return(nil) + mock.On("StoreSecrets", "error", input).Return(errors.New("Some error happened")) + + tests := []struct { + Name string + Secure string + Path string + Client secrets.SecretClient + ExpectError bool + }{ + {"Valid Secure", "true", "redis", mock, false}, + {"Invalid no client", "true", "redis", nil, true}, + {"Invalid internal error", "true", "error", mock, true}, + } + + for _, tc := range tests { + t.Run(tc.Name, func(t *testing.T) { + target := NewSecureProvider(nil, logger.MockLogger{}, nil) + target.SetClient(tc.Client) + + err := target.StoreSecrets(tc.Path, input) + if tc.ExpectError { + require.Error(t, err) + return + } + + require.NoError(t, err) + }) + } +} + +func TestSecureProvider_SecretsLastUpdated(t *testing.T) { + input := map[string]string{"username": "admin", "password": "sam123!"} + mock := &mocks.SecretClient{} + mock.On("StoreSecrets", "redis", input).Return(nil) + + target := NewSecureProvider(nil, logger.MockLogger{}, nil) + target.SetClient(mock) + + previous := target.SecretsLastUpdated() + time.Sleep(1 * time.Second) + err := target.StoreSecrets("redis", input) + require.NoError(t, err) + current := target.SecretsLastUpdated() + assert.True(t, current.After(previous)) +} + +func TestSecureProvider_SecretsUpdated(t *testing.T) { + target := NewSecureProvider(nil, logger.MockLogger{}, nil) + previous := target.SecretsLastUpdated() + time.Sleep(1 * time.Second) + target.SecretsUpdated() + current := target.SecretsLastUpdated() + // Since the SecureProvider does nothing for SecretsUpdated, LastUpdated shouldn't change + assert.Equal(t, previous, current) +} + +func TestSecureProvider_DefaultTokenExpiredCallback(t *testing.T) { + goodTokenFile := "good-token.json" + badTokenFile := "bad-token.json" + sameTokenFile := "same-token.json" + newToken := "new token" + expiredToken := "expired token" + + mockTokenLoader := &mocks2.AuthTokenLoader{} + mockTokenLoader.On("Load", goodTokenFile).Return(newToken, nil) + mockTokenLoader.On("Load", sameTokenFile).Return(expiredToken, nil) + mockTokenLoader.On("Load", badTokenFile).Return("", errors.New("Not Found")) + + tests := []struct { + Name string + TokenFile string + ExpiredToken string + ExpectedToken string + ExpectedRetry bool + }{ + {"Valid", goodTokenFile, expiredToken, "new token", true}, + {"Bad File", badTokenFile, "", "", false}, + {"Same Token", sameTokenFile, expiredToken, expiredToken, false}, + } + + for _, tc := range tests { + t.Run(tc.Name, func(t *testing.T) { + config := TestConfig{ + SecretStore: bootstrapConfig.SecretStoreInfo{ + TokenFile: tc.TokenFile, + }, + } + + target := NewSecureProvider(config, logger.MockLogger{}, mockTokenLoader) + actualToken, actualRetry := target.DefaultTokenExpiredCallback(tc.ExpiredToken) + assert.Equal(t, tc.ExpectedToken, actualToken) + assert.Equal(t, tc.ExpectedRetry, actualRetry) + }) + } +} + +type TestConfig struct { + InsecureSecrets bootstrapConfig.InsecureSecrets + SecretStore bootstrapConfig.SecretStoreInfo +} + +func (t TestConfig) UpdateFromRaw(_ interface{}) bool { + panic("implement me") +} + +func (t TestConfig) EmptyWritablePtr() interface{} { + panic("implement me") +} + +func (t TestConfig) UpdateWritableFromRaw(_ interface{}) bool { + panic("implement me") +} + +func (t TestConfig) GetBootstrap() bootstrapConfig.BootstrapConfiguration { + return bootstrapConfig.BootstrapConfiguration{ + SecretStore: t.SecretStore, + } +} + +func (t TestConfig) GetLogLevel() string { + panic("implement me") +} + +func (t TestConfig) GetRegistryInfo() bootstrapConfig.RegistryInfo { + panic("implement me") +} + +func (t TestConfig) GetInsecureSecrets() bootstrapConfig.InsecureSecrets { + return t.InsecureSecrets +} diff --git a/config/types.go b/config/types.go index a6d455dd..8ff6ad1d 100644 --- a/config/types.go +++ b/config/types.go @@ -19,8 +19,7 @@ import ( "time" "github.com/edgexfoundry/go-mod-core-contracts/clients" - - "github.com/edgexfoundry/go-mod-secrets/pkg/providers/vault" + "github.com/edgexfoundry/go-mod-secrets/pkg/types" ) // ServiceInfo contains configuration settings necessary for the basic operation of any EdgeX service. @@ -108,7 +107,7 @@ type SecretStoreInfo struct { Namespace string RootCaCertPath string ServerName string - Authentication vault.AuthenticationInfo + Authentication types.AuthenticationInfo AdditionalRetryAttempts int RetryWaitPeriod string retryWaitPeriodTime time.Duration @@ -117,13 +116,11 @@ type SecretStoreInfo struct { } type Database struct { - Username string - Password string - Type string - Timeout int - Host string - Port int - Name string + Type string + Timeout int + Host string + Port int + Name string } // Credentials encapsulates username-password attributes. @@ -138,6 +135,15 @@ type CertKeyPair struct { Key string } +// InsecureSecrets is used to hold the secrets stored in the configuration +type InsecureSecrets map[string]InsecureSecretsInfo + +// InsecureSecretsInfo encapsulates info used to retrieve insecure secrets +type InsecureSecretsInfo struct { + Path string + Secrets map[string]string +} + // BootstrapConfiguration defines the configuration elements required by the bootstrap. type BootstrapConfiguration struct { Clients map[string]ClientInfo diff --git a/go.mod b/go.mod index 08b933c5..5f5e5d77 100644 --- a/go.mod +++ b/go.mod @@ -3,12 +3,12 @@ module github.com/edgexfoundry/go-mod-bootstrap require ( github.com/BurntSushi/toml v0.3.1 github.com/edgexfoundry/go-mod-configuration v0.0.8 - github.com/edgexfoundry/go-mod-core-contracts v0.1.115 + github.com/edgexfoundry/go-mod-core-contracts v0.1.129 github.com/edgexfoundry/go-mod-registry v0.1.26 - github.com/edgexfoundry/go-mod-secrets v0.0.27 + github.com/edgexfoundry/go-mod-secrets v0.0.29 github.com/gorilla/mux v1.7.1 github.com/pelletier/go-toml v1.2.0 - github.com/stretchr/testify v1.5.1 + github.com/stretchr/testify v1.6.1 ) go 1.15