Skip to content

Commit

Permalink
feat: Added logic for secret update callback (#373)
Browse files Browse the repository at this point in the history
Signed-off-by: Kyle Morton <[email protected]>
  • Loading branch information
drkfmorton authored Sep 29, 2022
1 parent 3935c8c commit f58aa0b
Show file tree
Hide file tree
Showing 9 changed files with 461 additions and 26 deletions.
40 changes: 39 additions & 1 deletion bootstrap/config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import (
"context"
"errors"
"fmt"
"github.com/edgexfoundry/go-mod-bootstrap/v2/config"
"io/ioutil"
"math"
"reflect"
Expand Down Expand Up @@ -511,7 +512,11 @@ func (cp *Processor) listenForChanges(serviceConfig interfaces.Configuration, co
lc.Info("Insecure Secrets have been updated")
secretProvider := container.SecretProviderFrom(cp.dic.Get)
if secretProvider != nil {
secretProvider.SecretsUpdated()
// Find the updated secret's path and perform call backs.
updatedSecrets := getSecretPathsChanged(previousInsecureSecrets, currentInsecureSecrets)
for _, v := range updatedSecrets {
secretProvider.SecretUpdatedAtPath(v)
}
}

case currentTelemetryInterval != previousTelemetryInterval:
Expand Down Expand Up @@ -550,3 +555,36 @@ func (cp *Processor) listenForChanges(serviceConfig interfaces.Configuration, co
func (cp *Processor) logConfigInfo(message string, overrideCount int) {
cp.lc.Infof("%s (%d envVars overrides applied)", message, overrideCount)
}

// getSecretPathsChanged returns a slice of paths that have changed secrets or are new.
func getSecretPathsChanged(prevVals config.InsecureSecrets, curVals config.InsecureSecrets) []string {
var updatedPaths []string
for key, prevVal := range prevVals {
curVal := curVals[key]

// Catches removed secrets
if curVal.Secrets == nil {
updatedPaths = append(updatedPaths, prevVal.Path)
continue
}

// Catches changes to secret data or to the path name
if !reflect.DeepEqual(prevVal, curVal) {
updatedPaths = append(updatedPaths, curVal.Path)

// Catches path name changes, so also include the previous path
if prevVal.Path != curVal.Path {
updatedPaths = append(updatedPaths, prevVal.Path)
}
}
}

for key, curVal := range curVals {
// Catches new secrets added
if prevVals[key].Secrets == nil {
updatedPaths = append(updatedPaths, curVal.Path)
}
}

return updatedPaths
}
94 changes: 94 additions & 0 deletions bootstrap/config/config_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
/*******************************************************************************
* Copyright 2022 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 config

import (
"github.com/edgexfoundry/go-mod-bootstrap/v2/config"
"github.com/stretchr/testify/assert"
"testing"
)

const (
expectedUsername = "admin"
expectedPassword = "password"
expectedPath = "redisdb"
UsernameKey = "username"
PasswordKey = "password"
)

func TestGetSecretPathsChanged(t *testing.T) {
prevVals := config.InsecureSecrets{
"DB": config.InsecureSecretsInfo{
Path: expectedPath,
Secrets: map[string]string{
UsernameKey: "edgex",
PasswordKey: expectedPassword,
}}}

curVals := config.InsecureSecrets{
"DB": config.InsecureSecretsInfo{
Path: expectedPath,
Secrets: map[string]string{
UsernameKey: expectedUsername,
PasswordKey: expectedPassword,
}}}

tests := []struct {
Name string
UpdatedPaths []string
curVals config.InsecureSecrets
prevVals config.InsecureSecrets
}{
{"Valid - No updates", nil, curVals, curVals},
{"Valid - Secret update", []string{expectedPath}, prevVals, curVals},
{"Valid - New Secret", []string{expectedPath}, prevVals, config.InsecureSecrets{
"DB": config.InsecureSecretsInfo{
Path: expectedPath,
Secrets: map[string]string{
UsernameKey: expectedUsername,
PasswordKey: expectedPassword,
"attempts": "1",
}}}},
{"Valid - Deleted Secret", []string{expectedPath}, prevVals, config.InsecureSecrets{
"DB": config.InsecureSecretsInfo{
Path: expectedPath,
Secrets: map[string]string{
UsernameKey: expectedUsername,
}}}},
{"Valid - Path update", []string{"redisdb", "message-bus"}, curVals,
config.InsecureSecrets{
"DB": config.InsecureSecretsInfo{
Path: "message-bus",
Secrets: map[string]string{
UsernameKey: expectedUsername,
PasswordKey: expectedPassword,
}}}},
{"Valid - Path delete", []string{expectedPath}, config.InsecureSecrets{
"DB": config.InsecureSecretsInfo{}}, prevVals},
{"Valid - No updates, unsorted keys", nil, curVals, config.InsecureSecrets{
"DB": config.InsecureSecretsInfo{
Path: expectedPath,
Secrets: map[string]string{
PasswordKey: expectedPassword,
UsernameKey: expectedUsername,
}}}},
}

for _, tc := range tests {
t.Run(tc.Name, func(t *testing.T) {
updatedPaths := getSecretPathsChanged(tc.prevVals, tc.curVals)
assert.Equal(t, tc.UpdatedPaths, updatedPaths)
})
}
}
5 changes: 3 additions & 2 deletions bootstrap/handlers/httpserver.go
Original file line number Diff line number Diff line change
Expand Up @@ -116,8 +116,9 @@ func (b *HttpServer) BootstrapHandler(
}).HandlerFunc(HandlePreflight(bootstrapConfig.Service.CORSConfiguration))

server := &http.Server{
Addr: addr,
Handler: b.router,
Addr: addr,
Handler: b.router,
ReadHeaderTimeout: 5 * time.Second, // G112: A configured ReadHeaderTimeout in the http.Server averts a potential Slowloris Attack
}

wg.Add(1)
Expand Down
24 changes: 24 additions & 0 deletions bootstrap/interfaces/mocks/SecretProvider.go

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

9 changes: 9 additions & 0 deletions bootstrap/interfaces/secret.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,4 +28,13 @@ type SecretProvider interface {

// HasSecret returns true if the service's SecretStore contains a secret at the specified path.
HasSecret(path string) (bool, error)

// RegisteredSecretUpdatedCallback registers a callback for a secret.
RegisteredSecretUpdatedCallback(path string, callback func(path string)) error

// SecretUpdatedAtPath performs updates and callbacks for an updated secret or path.
SecretUpdatedAtPath(path string)

// DeregisterSecretUpdatedCallback removes a secret's registered callback path.
DeregisterSecretUpdatedCallback(path string)
}
47 changes: 41 additions & 6 deletions bootstrap/secret/insecure.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,17 +27,19 @@ import (

// InsecureProvider implements the SecretProvider interface for insecure secrets
type InsecureProvider struct {
lc logger.LoggingClient
configuration interfaces.Configuration
lastUpdated time.Time
lc logger.LoggingClient
configuration interfaces.Configuration
lastUpdated time.Time
registeredSecretCallbacks map[string]func(path string)
}

// 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(),
configuration: config,
lc: lc,
lastUpdated: time.Now(),
registeredSecretCallbacks: make(map[string]func(path string)),
}
}

Expand Down Expand Up @@ -146,3 +148,36 @@ func (p *InsecureProvider) ListSecretPaths() ([]string, error) {

return results, nil
}

// RegisteredSecretUpdatedCallback registers a callback for a secret.
func (p *InsecureProvider) RegisteredSecretUpdatedCallback(path string, callback func(path string)) error {
if _, ok := p.registeredSecretCallbacks[path]; ok {
return fmt.Errorf("there is a callback already registered for path '%v'", path)
}

// Register new call back for path.
p.registeredSecretCallbacks[path] = callback

return nil
}

// SecretUpdatedAtPath performs updates and callbacks for an updated secret or path.
func (p *InsecureProvider) SecretUpdatedAtPath(path string) {
p.lastUpdated = time.Now()
if p.registeredSecretCallbacks != nil {
// Execute Callback for provided path.
for k, v := range p.registeredSecretCallbacks {
if k == path {
p.lc.Debugf("invoking callback registered for path: '%s'", path)
v(path)
return
}
}
}
}

// DeregisterSecretUpdatedCallback removes a secret's registered callback path.
func (p *InsecureProvider) DeregisterSecretUpdatedCallback(path string) {
// Remove path from map.
delete(p.registeredSecretCallbacks, path)
}
Loading

0 comments on commit f58aa0b

Please sign in to comment.