Skip to content

Commit

Permalink
Update Provider interface to use GetValues instead of GetValue
Browse files Browse the repository at this point in the history
GetValues works exactly the same as GetValue, however GetValues gives providers the ability to implement arbitrary batch retrieval mechanisms.
  • Loading branch information
doodlesbykumbi committed Sep 24, 2020
1 parent b3c42e3 commit 683407f
Show file tree
Hide file tree
Showing 18 changed files with 165 additions and 53 deletions.
4 changes: 2 additions & 2 deletions design/proposal-zeroization.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ Ideally, 2 would happen in a centralized fashion to avoid the need for repeated
It is worth noting the journey of Secrets in Secretless.

```
Resolver.#Resolve -> Provider.#GetValue |-> Handler -> Listener -> Target Backend
Resolver.#Resolve -> Provider.#GetValues |-> Handler -> Listener -> Target Backend
|
|-> EventNotifier.#ResolveSecret
```
Expand Down Expand Up @@ -87,5 +87,5 @@ The recommendation per Secret usage session is as follows:
## Further consideration

1. Audit `Listener -> Target Backend`
1. Audit `Provider.#GetValue`
1. Audit `Provider.#GetValues`
1. Reconsider `EventNotifier.#ResolveSecret`, why do we need this ?
43 changes: 31 additions & 12 deletions internal/plugin/resolver.go
Original file line number Diff line number Diff line change
Expand Up @@ -81,30 +81,49 @@ func (resolver *Resolver) Resolve(credentials []*config_v2.Credential) (map[stri
errorStrings := make([]string, 0, len(credentials))

var err error

// Group credentials by provider
var credentialsByProvider = make(map[string][]*config_v2.Credential)
for _, credential := range credentials {
var provider plugin_v1.Provider
var value []byte
credentialsByProvider[credential.From] = append(
credentialsByProvider[credential.From],
credential,
)
}

if provider, err = resolver.Provider(credential.From); err != nil {
resolver.LogFatalf("ERROR: Provider '%s' could not be used! %v", credential.From, err)
// Resolve credentials by provider
for providerID, credentialsForProvider := range credentialsByProvider {
provider, err := resolver.Provider(providerID)
if err != nil {
resolver.LogFatalf("ERROR: Provider '%s' could not be used! %v", providerID, err)
}

// This provider cannot resolve the named credential
if value, err = provider.GetValue(credential.Get); err != nil {
errInfo := fmt.Sprintf("ERROR: Resolving credential '%s' from provider '%s' failed: %v",
credential.Get,
credential.From,
// Create secretIds slice
var secretIds = make([]string, len(credentialsForProvider))
for idx, cred := range credentialsForProvider {
secretIds[idx] = cred.Get
}

// Resolves all credentials for current provider
secretValues, err := provider.GetValues(secretIds...)
if err != nil {
errInfo := fmt.Sprintf("ERROR: Resolving credentials from provider '%s' failed: %v",
provider.GetName(),
err)
log.Println(errInfo)

errorStrings = append(errorStrings, errInfo)
continue
}

result[credential.Name] = value
for idx, secretValue := range secretValues {
credential := credentialsForProvider[idx]

result[credential.Name] = secretValue

if resolver.EventNotifier != nil {
resolver.EventNotifier.ResolveCredential(provider, credential.Name, value)
if resolver.EventNotifier != nil {
resolver.EventNotifier.ResolveCredential(provider, credential.Name, secretValue)
}
}
}

Expand Down
2 changes: 1 addition & 1 deletion internal/plugin/resolver_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,7 @@ func Test_Resolver(t *testing.T) {
credentialValues, err := resolver.Resolve(credentials)
So(len(credentialValues), ShouldEqual, 0)
So(err, ShouldNotBeNil)
errorMsg := "ERROR: Resolving credential 'something-not-in-env' from provider 'env' failed: env cannot find environment variable 'something-not-in-env'"
errorMsg := "ERROR: Resolving credentials from provider 'env' failed: env cannot find environment variable 'something-not-in-env'"
So(err.Error(), ShouldEqual, errorMsg)

})
Expand Down
27 changes: 27 additions & 0 deletions internal/plugin/v1/provider.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,33 @@ type Provider interface {
// GetName returns the name that the Provider was instantiated with
GetName() string

// GetValues takes in variable ids and returns their resolved values
GetValues(ids ...string) ([][]byte, error)
}

type singleValueProvider interface {
// GetValue takes in an id of a variable and returns its resolved value
GetValue(id string) ([]byte, error)
}

// GetValues takes in variable ids and returns their resolved values by making sequential
// calls to a singleValueProvider.
// This is a convenience function since most providers with batch retrieval capabilities
// will have need the exact same code. Note: most internal providers simply use this
// function in their implementation of the Provider interface's GetValues method.
func GetValues(
p singleValueProvider,
ids ...string,
) ([][]byte, error) {
var err error
var res = make([][]byte, len(ids))

for idx, id := range ids {
res[idx], err = p.GetValue(id)
if err != nil {
return nil, err
}
}

return res, nil
}
6 changes: 6 additions & 0 deletions internal/providers/awssecrets/provider.go
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,12 @@ func (p *Provider) GetName() string {
return p.Name
}

// GetValues takes in variable ids and returns their resolved values. This method is
// needed to the Provider interface
func (p *Provider) GetValues(ids ...string) ([][]byte, error) {
return plugin_v1.GetValues(p, ids...)
}

// GetValue obtains a secret value by id.
func (p *Provider) GetValue(id string) ([]byte, error) {
client := p.Client
Expand Down
6 changes: 6 additions & 0 deletions internal/providers/conjur/provider.go
Original file line number Diff line number Diff line change
Expand Up @@ -139,6 +139,12 @@ func (p *Provider) GetName() string {
return p.Name
}

// GetValues takes in variable ids and returns their resolved values. This method is
// needed to the Provider interface
func (p *Provider) GetValues(ids ...string) ([][]byte, error) {
return plugin_v1.GetValues(p, ids...)
}

// GetValue obtains a value by ID. The recognized IDs are:
// * "accessToken"
// * Any Conjur variable ID
Expand Down
6 changes: 6 additions & 0 deletions internal/providers/env/provider.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,12 @@ func (p *EnvironmentProvider) GetName() string {
return p.Name
}

// GetValues takes in variable ids and returns their resolved values. This method is
// needed to the Provider interface
func (p *EnvironmentProvider) GetValues(ids ...string) ([][]byte, error) {
return plugin_v1.GetValues(p, ids...)
}

// GetValue obtains a value by ID. Any environment is a recognized ID.
func (p *EnvironmentProvider) GetValue(id string) (result []byte, err error) {
var found bool
Expand Down
6 changes: 6 additions & 0 deletions internal/providers/file/provider.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,12 @@ func (p *Provider) GetName() string {
return p.Name
}

// GetValues takes in variable ids and returns their resolved values. This method is
// needed to the Provider interface
func (p *Provider) GetValues(ids ...string) ([][]byte, error) {
return plugin_v1.GetValues(p, ids...)
}

// GetValue reads the contents of the identified file.
func (p *Provider) GetValue(id string) ([]byte, error) {
return ioutil.ReadFile(id)
Expand Down
6 changes: 6 additions & 0 deletions internal/providers/keychain/provider.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,12 @@ func (p *Provider) GetName() string {
return p.Name
}

// GetValues takes in variable ids and returns their resolved values. This method is
// needed to the Provider interface
func (p *Provider) GetValues(ids ...string) ([][]byte, error) {
return plugin_v1.GetValues(p, ids...)
}

// GetValue reads the contents of the identified file.
func (p *Provider) GetValue(id string) ([]byte, error) {
tokens := strings.Split(id, "#")
Expand Down
6 changes: 6 additions & 0 deletions internal/providers/kubernetessecrets/provider.go
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,12 @@ func (p *Provider) GetName() string {
return p.Name
}

// GetValues takes in variable ids and returns their resolved values. This method is
// needed to the Provider interface
func (p *Provider) GetValues(ids ...string) ([][]byte, error) {
return plugin_v1.GetValues(p, ids...)
}

// GetValue obtains a value by id. Any secret which is stored in Kubernetes Secrets is recognized.
// The data type returned by Kubernetes Secrets is map[string][]byte. Therefore this provider needs
// to know which field to return from the map. The field to be returned is specified by appending '#fieldName' to the id argument.
Expand Down
6 changes: 6 additions & 0 deletions internal/providers/literal/provider.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,3 +24,9 @@ func (p *Provider) GetName() string {
func (p *Provider) GetValue(id string) ([]byte, error) {
return []byte(id), nil
}

// GetValues takes in variable ids and returns their resolved values. This method is
// needed to the Provider interface
func (p *Provider) GetValues(ids ...string) ([][]byte, error) {
return plugin_v1.GetValues(p, ids...)
}
6 changes: 6 additions & 0 deletions internal/providers/vault/provider.go
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,12 @@ func parseVaultID(id string) (string, string) {
}
}

// GetValues takes in variable ids and returns their resolved values. This method is
// needed to the Provider interface
func (p *Provider) GetValues(ids ...string) ([][]byte, error) {
return plugin_v1.GetValues(p, ids...)
}

// GetValue obtains a value by id. Any secret which is stored in the vault is recognized.
// The datatype returned by Vault is map[string]interface{}. Therefore this provider needs
// to know which field to return from the map. By default, it returns the 'value'.
Expand Down
27 changes: 19 additions & 8 deletions internal/summon/command/run.go
Original file line number Diff line number Diff line change
Expand Up @@ -37,20 +37,31 @@ func buildEnvironment(secrets map[string]string, secretsMap secretsyml.SecretsMa
// resolveSecrets obtains the value of each requested secret.
func resolveSecrets(provider plugin_v1.Provider, secretsMap secretsyml.SecretsMap) (result map[string]string, err error) {
result = make(map[string]string)

var varSecretsSpecKeys []string
var varSecretsSpecPaths []string

for key, spec := range secretsMap {
var value string
if spec.IsVar() {
var valueBytes []byte
if valueBytes, err = provider.GetValue(spec.Path); err != nil {
return
}
value = string(valueBytes)
varSecretsSpecKeys = append(varSecretsSpecKeys, key)
varSecretsSpecPaths = append(varSecretsSpecPaths, spec.Path)
} else {
// If the spec isn't a variable, use its value as-is
value = spec.Path
value := spec.Path
result[key] = value
}
result[key] = value
}

// Get the variable values
valuesBytes, err := provider.GetValues(varSecretsSpecPaths...)
if err != nil {
return
}
// Transform variable values to strings
for idx, key := range varSecretsSpecKeys {
result[key] = string(valuesBytes[idx])
}

return
}

Expand Down
22 changes: 11 additions & 11 deletions test/connector/http/conjur/conjur_provider_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,46 +32,46 @@ func TestConjur_Provider(t *testing.T) {
})

Convey("Can provide an access token", t, func() {
value, err := provider.GetValue("accessToken")
values, err := provider.GetValues("accessToken")
So(err, ShouldBeNil)

token := make(map[string]string)
err = json.Unmarshal(value, &token)
err = json.Unmarshal(values[0], &token)
So(err, ShouldBeNil)
So(token["protected"], ShouldNotBeNil)
So(token["payload"], ShouldNotBeNil)
})

Convey("Can provide a secret to a fully qualified variable", t, func() {
value, err := provider.GetValue("dev:variable:db/password")
values, err := provider.GetValues("dev:variable:db/password")
So(err, ShouldBeNil)

So(string(value), ShouldEqual, "secret")
So(string(values[0]), ShouldEqual, "secret")
})

Convey("Can retrieve a secret value with spaces", t, func() {
value, err := provider.GetValue("my var")
values, err := provider.GetValues("my var")
So(err, ShouldBeNil)

So(string(value), ShouldEqual, "othersecret")
So(string(values[0]), ShouldEqual, "othersecret")
})

Convey("Can provide the default Conjur account name", t, func() {
value, err := provider.GetValue("variable:db/password")
values, err := provider.GetValues("variable:db/password")
So(err, ShouldBeNil)

So(string(value), ShouldEqual, "secret")
So(string(values[0]), ShouldEqual, "secret")
})

Convey("Can provide the default Conjur account name and resource type", t, func() {
value, err := provider.GetValue("db/password")
values, err := provider.GetValues("db/password")
So(err, ShouldBeNil)

So(string(value), ShouldEqual, "secret")
So(string(values[0]), ShouldEqual, "secret")
})

Convey("Cannot provide an unknown value", t, func() {
_, err = provider.GetValue("foobar")
_, err = provider.GetValues("foobar")
So(err, ShouldNotBeNil)
So(err.Error(), ShouldEqual, "404 Not Found. Variable 'foobar' not found in account 'dev'.")
})
Expand Down
6 changes: 3 additions & 3 deletions test/providers/keychain/keychain_provider_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -40,15 +40,15 @@ func TestKeychainProvider(t *testing.T) {
Convey("Can provide a valid secret value", t, func() {
id := strings.Join([]string{service, account}, "#")

value, err := provider.GetValue(id)
values, err := provider.GetValues(id)
So(err, ShouldBeNil)
So(string(value), ShouldEqual, secret)
So(string(values[0]), ShouldEqual, secret)
})

Convey("Returns an error for an invalid secret value", t, func() {
id := "madeup#secret"

_, err := provider.GetValue(id)
_, err := provider.GetValues(id)
So(err, ShouldNotBeNil)
So(err.Error(), ShouldEqual, "The specified item could not be found in the keychain.")
})
Expand Down
20 changes: 10 additions & 10 deletions test/providers/kubernetessecrets/kubernetes_provider_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -53,36 +53,36 @@ func TestKubernetes_Provider(t *testing.T) {
})

Convey("Reports when the secret id does not contain a field name", t, func() {
value, err := provider.GetValue("foobar")
values, err := provider.GetValues("foobar")
So(err, ShouldNotBeNil)
So(err.Error(), ShouldEqual, "Kubernetes secret id must contain secret name and field name in the format secretName#fieldName, received 'foobar'")
So(value, ShouldBeNil)
So(values, ShouldBeNil)
})

Convey("Reports when the secret id has empty field name", t, func() {
value, err := provider.GetValue("foobar#")
values, err := provider.GetValues("foobar#")
So(err, ShouldNotBeNil)
So(err.Error(), ShouldEqual, "field name missing from Kubernetes secret id 'foobar#'")
So(value, ShouldBeNil)
So(values, ShouldBeNil)
})

Convey("Reports when Kubernetes is unable to find secret", t, func() {
value, err := provider.GetValue("foobar#maybe")
values, err := provider.GetValues("foobar#maybe")
So(err, ShouldNotBeNil)
So(err.Error(), ShouldEqual, "could not find Kubernetes secret from 'foobar#maybe'")
So(value, ShouldBeNil)
So(values, ShouldBeNil)
})

Convey("Reports when Kubernetes is unable to find field name in secret", t, func() {
value, err := provider.GetValue("database#missing")
values, err := provider.GetValues("database#missing")
So(err, ShouldNotBeNil)
So(err.Error(), ShouldEqual, "could not find field 'missing' in Kubernetes secret 'database'")
So(value, ShouldBeNil)
So(values, ShouldBeNil)
})

Convey("Can provide a secret", t, func() {
value, err := provider.GetValue("database#password")
values, err := provider.GetValues("database#password")
So(err, ShouldBeNil)
So(string(value), ShouldEqual, "secret")
So(string(values[0]), ShouldEqual, "secret")
})
}
Loading

0 comments on commit 683407f

Please sign in to comment.