Skip to content

Commit e7ef35d

Browse files
committed
feat: added keeper secrets manager backend
Signed-off-by: John Rowley <[email protected]>
1 parent 8043640 commit e7ef35d

File tree

8 files changed

+766
-876
lines changed

8 files changed

+766
-876
lines changed

docs/backends.md

+41
Original file line numberDiff line numberDiff line change
@@ -599,3 +599,44 @@ type: Opaque
599599
data:
600600
password: <path:vaults/vault-uuid/items/item-uuid#key>
601601
```
602+
603+
### Keeper Secrets Manager
604+
605+
**Note**: The Keeper Secrets Manager backend does not support versioning, or annotations. It does not support injecting attached files.
606+
607+
#### Keeper Authentication
608+
609+
These are the parameters for Keeper:
610+
```
611+
AVP_TYPE: keepersecretsmanager
612+
AVP_KEEPER_CONFIG_PATH: the path to the keeper configuration file on disk.
613+
```
614+
615+
##### Examples
616+
Examples assume that the secrets are not saved base64 encoded in the Secret Server.
617+
618+
###### Path Annotation
619+
620+
```yaml
621+
kind: Secret
622+
apiVersion: v1
623+
metadata:
624+
name: test-secret
625+
annotations:
626+
avp.kubernetes.io/path: "secret-id"
627+
type: Opaque
628+
stringData:
629+
password: <key>
630+
```
631+
632+
###### Inline Path
633+
634+
```yaml
635+
kind: Secret
636+
apiVersion: v1
637+
metadata:
638+
name: test-secret
639+
type: Opaque
640+
data:
641+
password: <path:secret-id#key | base64encode>
642+
```

go.mod

+1
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ require (
3434
github.com/hashicorp/vault-plugin-secrets-kv v0.11.0
3535
github.com/hashicorp/vault/api v1.5.0
3636
github.com/hashicorp/vault/sdk v0.4.2-0.20220721224803-6e72b150730c
37+
github.com/keeper-security/secrets-manager-go/core v1.5.0
3738
github.com/leodido/go-urn v1.2.1 // indirect
3839
github.com/pierrec/lz4 v2.6.1+incompatible // indirect
3940
github.com/spf13/cobra v1.4.0

go.sum

+2-854
Large diffs are not rendered by default.

pkg/backends/keepersecretsmanager.go

+158
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,158 @@
1+
package backends
2+
3+
import (
4+
"fmt"
5+
6+
"github.com/argoproj-labs/argocd-vault-plugin/pkg/utils"
7+
ksm "github.com/keeper-security/secrets-manager-go/core"
8+
)
9+
10+
// KeeperClient is an interface containing the aspects of the keeper api that the backend needs.
11+
type KeeperClient interface {
12+
GetSecrets(ids []string) ([]*ksm.Record, error)
13+
}
14+
15+
// KeeperSecretsManager is a struct for working with a Keeper Secrets Manager backend
16+
type KeeperSecretsManager struct {
17+
client KeeperClient
18+
}
19+
20+
// NewKeeperSecretsManagerBackend returns a new Keeper Secrets Manager backend.
21+
func NewKeeperSecretsManagerBackend(client KeeperClient) *KeeperSecretsManager {
22+
return &KeeperSecretsManager{
23+
client: client,
24+
}
25+
}
26+
27+
// Login currently does nothing.
28+
func (k *KeeperSecretsManager) Login() error {
29+
return nil
30+
}
31+
32+
func buildSecretsMap(secretsMap map[string]interface{}, fieldMap map[string]interface{}) error {
33+
var label string
34+
var fieldType string
35+
36+
// we start by getting the label from the type,
37+
if fieldMapType, ok := fieldMap["type"]; ok {
38+
if typeString, ok := fieldMapType.(string); ok {
39+
label = typeString
40+
fieldType = typeString
41+
}
42+
}
43+
44+
// if there is a more specific label, then we use that instead
45+
if labelField, ok := fieldMap["label"]; ok {
46+
if labelStr, ok := labelField.(string); ok {
47+
label = labelStr
48+
}
49+
}
50+
51+
if label == "" {
52+
utils.VerboseToStdErr("secret's field does not have a \"label\"")
53+
return nil
54+
}
55+
56+
if fieldType == "" {
57+
utils.VerboseToStdErr("secret's field does not have a \"type\"")
58+
return nil
59+
}
60+
61+
var value interface{}
62+
63+
switch fieldType {
64+
case "note":
65+
fallthrough
66+
case "url":
67+
fallthrough
68+
case "text":
69+
fallthrough
70+
case "login":
71+
fallthrough
72+
case "password":
73+
fallthrough
74+
default:
75+
if fieldValues, ok := fieldMap["value"]; ok {
76+
sliceValues, ok := fieldValues.([]interface{})
77+
if !ok {
78+
utils.VerboseToStdErr("secret's field value is not a []interface %T for field %s", fieldValues, label)
79+
return nil
80+
}
81+
82+
if len(sliceValues) == 1 {
83+
value = sliceValues[0]
84+
}
85+
86+
if len(sliceValues) == 0 {
87+
value = nil
88+
}
89+
}
90+
}
91+
92+
if value != nil {
93+
secretsMap[label] = value
94+
}
95+
return nil
96+
}
97+
98+
// GetSecrets gets secrets from Keeper Secrets Manager. It does not currently
99+
// implement anything related to versions or annotations.
100+
func (a *KeeperSecretsManager) GetSecrets(path string, version string, annotations map[string]string) (map[string]interface{}, error) {
101+
records, err := a.client.GetSecrets([]string{
102+
path,
103+
})
104+
if err != nil {
105+
return nil, fmt.Errorf("could not access secret %s, error: %s", path, err)
106+
}
107+
utils.VerboseToStdErr("Keeper Secrets Manager getting path %s", path)
108+
109+
if len(records) == 0 {
110+
return nil, fmt.Errorf("no secrets could be found with the given path: %s", path)
111+
}
112+
113+
if len(records) > 1 {
114+
return nil, fmt.Errorf("unexpectedly multiple secrets were found with the given path: %s", path)
115+
}
116+
117+
utils.VerboseToStdErr("Keeper Secrets Manager decoding record %s", records[0].Title())
118+
119+
dict := records[0].RecordDict
120+
121+
secretMap := map[string]interface{}{}
122+
123+
if fields, ok := dict["custom_fields"]; ok {
124+
if fieldsSlice, ok := fields.([]interface{}); ok {
125+
for _, field := range fieldsSlice {
126+
if fieldMap, ok := field.(map[string]interface{}); ok {
127+
buildSecretsMap(secretMap, fieldMap)
128+
}
129+
}
130+
}
131+
}
132+
133+
if fields, ok := dict["fields"]; ok {
134+
if fieldsSlice, ok := fields.([]interface{}); ok {
135+
for _, field := range fieldsSlice {
136+
if fieldMap, ok := field.(map[string]interface{}); ok {
137+
buildSecretsMap(secretMap, fieldMap)
138+
}
139+
}
140+
}
141+
}
142+
143+
utils.VerboseToStdErr("Keeper Secrets Manager constructed map %s", secretMap)
144+
145+
return secretMap, nil
146+
147+
}
148+
149+
// GetIndividualSecret returns the specified secret. It simply wraps the
150+
// GetSecrets call, and currently ignores the version parameter.
151+
func (v *KeeperSecretsManager) GetIndividualSecret(kvpath, secretName, version string, annotations map[string]string) (interface{}, error) {
152+
secrets, err := v.GetSecrets(kvpath, version, annotations)
153+
if err != nil {
154+
return nil, err
155+
}
156+
157+
return secrets[secretName], nil
158+
}

0 commit comments

Comments
 (0)