Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

added keeper secrets manager backend #468

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
41 changes: 41 additions & 0 deletions docs/backends.md
Original file line number Diff line number Diff line change
Expand Up @@ -599,3 +599,44 @@ type: Opaque
data:
password: <path:vaults/vault-uuid/items/item-uuid#key>
```

### Keeper Secrets Manager

**Note**: The Keeper Secrets Manager backend does not support versioning, or annotations. It does not support injecting attached files.

#### Keeper Authentication

These are the parameters for Keeper:
```
AVP_TYPE: keepersecretsmanager
AVP_KEEPER_CONFIG_PATH: the path to the keeper configuration file on disk.
```

##### Examples
Examples assume that the secrets are not saved base64 encoded in the Secret Server.

###### Path Annotation

```yaml
kind: Secret
apiVersion: v1
metadata:
name: test-secret
annotations:
avp.kubernetes.io/path: "secret-id"
type: Opaque
stringData:
password: <key>
```

###### Inline Path

```yaml
kind: Secret
apiVersion: v1
metadata:
name: test-secret
type: Opaque
data:
password: <path:secret-id#key | base64encode>
```
1 change: 1 addition & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ require (
github.com/hashicorp/vault-plugin-secrets-kv v0.11.0
github.com/hashicorp/vault/api v1.5.0
github.com/hashicorp/vault/sdk v0.4.2-0.20220721224803-6e72b150730c
github.com/keeper-security/secrets-manager-go/core v1.5.0
github.com/leodido/go-urn v1.2.1 // indirect
github.com/pierrec/lz4 v2.6.1+incompatible // indirect
github.com/spf13/cobra v1.4.0
Expand Down
856 changes: 2 additions & 854 deletions go.sum

Large diffs are not rendered by default.

158 changes: 158 additions & 0 deletions pkg/backends/keepersecretsmanager.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,158 @@
package backends

import (
"fmt"

"github.com/argoproj-labs/argocd-vault-plugin/pkg/utils"
ksm "github.com/keeper-security/secrets-manager-go/core"
)

// KeeperClient is an interface containing the aspects of the keeper api that the backend needs.
type KeeperClient interface {
GetSecrets(ids []string) ([]*ksm.Record, error)
}

// KeeperSecretsManager is a struct for working with a Keeper Secrets Manager backend
type KeeperSecretsManager struct {
client KeeperClient
}

// NewKeeperSecretsManagerBackend returns a new Keeper Secrets Manager backend.
func NewKeeperSecretsManagerBackend(client KeeperClient) *KeeperSecretsManager {
return &KeeperSecretsManager{
client: client,
}
}

// Login currently does nothing.
func (k *KeeperSecretsManager) Login() error {
return nil
}

func buildSecretsMap(secretsMap map[string]interface{}, fieldMap map[string]interface{}) error {
var label string
var fieldType string

// we start by getting the label from the type,
if fieldMapType, ok := fieldMap["type"]; ok {
if typeString, ok := fieldMapType.(string); ok {
label = typeString
fieldType = typeString
}
}

// if there is a more specific label, then we use that instead
if labelField, ok := fieldMap["label"]; ok {
if labelStr, ok := labelField.(string); ok {
label = labelStr
}
}

if label == "" {
utils.VerboseToStdErr("secret's field does not have a \"label\"")
return nil
}

if fieldType == "" {
utils.VerboseToStdErr("secret's field does not have a \"type\"")
return nil
}

var value interface{}

switch fieldType {
case "note":
fallthrough
case "url":
fallthrough
case "text":
fallthrough
case "login":
fallthrough
case "password":
fallthrough
default:
if fieldValues, ok := fieldMap["value"]; ok {
sliceValues, ok := fieldValues.([]interface{})
if !ok {
utils.VerboseToStdErr("secret's field value is not a []interface %T for field %s", fieldValues, label)
return nil
}

if len(sliceValues) == 1 {
value = sliceValues[0]
}

if len(sliceValues) == 0 {
value = nil
}
}
}

if value != nil {
secretsMap[label] = value
}
return nil
}

// GetSecrets gets secrets from Keeper Secrets Manager. It does not currently
// implement anything related to versions or annotations.
func (a *KeeperSecretsManager) GetSecrets(path string, version string, annotations map[string]string) (map[string]interface{}, error) {
records, err := a.client.GetSecrets([]string{
path,
})
if err != nil {
return nil, fmt.Errorf("could not access secret %s, error: %s", path, err)
}
utils.VerboseToStdErr("Keeper Secrets Manager getting path %s", path)

if len(records) == 0 {
return nil, fmt.Errorf("no secrets could be found with the given path: %s", path)
}

if len(records) > 1 {
return nil, fmt.Errorf("unexpectedly multiple secrets were found with the given path: %s", path)
}

utils.VerboseToStdErr("Keeper Secrets Manager decoding record %s", records[0].Title())

dict := records[0].RecordDict

secretMap := map[string]interface{}{}

if fields, ok := dict["custom"]; ok {
if fieldsSlice, ok := fields.([]interface{}); ok {
for _, field := range fieldsSlice {
if fieldMap, ok := field.(map[string]interface{}); ok {
buildSecretsMap(secretMap, fieldMap)
}
}
}
}

if fields, ok := dict["fields"]; ok {
if fieldsSlice, ok := fields.([]interface{}); ok {
for _, field := range fieldsSlice {
if fieldMap, ok := field.(map[string]interface{}); ok {
buildSecretsMap(secretMap, fieldMap)
}
}
}
}

utils.VerboseToStdErr("Keeper Secrets Manager constructed map %s", secretMap)

return secretMap, nil

}

// GetIndividualSecret returns the specified secret. It simply wraps the
// GetSecrets call, and currently ignores the version parameter.
func (v *KeeperSecretsManager) GetIndividualSecret(kvpath, secretName, version string, annotations map[string]string) (interface{}, error) {
secrets, err := v.GetSecrets(kvpath, version, annotations)
if err != nil {
return nil, err
}

return secrets[secretName], nil
}
Loading