Browse Source

Feat/allow keeper to work with complex types (#3016)

* update dependencies (#3005)

Signed-off-by: External Secrets Operator <ExternalSecretsOperator@users.noreply.github.com>
Co-authored-by: External Secrets Operator <ExternalSecretsOperator@users.noreply.github.com>
Co-authored-by: Moritz Johner <moolen@users.noreply.github.com>
Signed-off-by: Pedro Parra Ortega <parraortega.pedro@gmail.com>

* feat: allow keeper to work with complex types

Signed-off-by: Pedro Parra Ortega <parraortega.pedro@gmail.com>

---------

Signed-off-by: External Secrets Operator <ExternalSecretsOperator@users.noreply.github.com>
Signed-off-by: Pedro Parra Ortega <parraortega.pedro@gmail.com>
Co-authored-by: eso-service-account-app[bot] <85832941+eso-service-account-app[bot]@users.noreply.github.com>
Co-authored-by: External Secrets Operator <ExternalSecretsOperator@users.noreply.github.com>
Co-authored-by: Moritz Johner <moolen@users.noreply.github.com>
Pedro Parra Ortega 2 years ago
parent
commit
ba8cf6bde5

+ 3 - 1
docs/provider/keeper-security.md

@@ -56,8 +56,10 @@ Be sure the `keepersecurity` provider is listed in the `Kind=SecretStore`
         * Files: Record's file's Name
         * Files: Record's file's Name
     * `find.tags` are not supported at this time.
     * `find.tags` are not supported at this time.
 
 
+**NOTE:** For complex [types](https://docs.keeper.io/secrets-manager/secrets-manager/about/field-record-types), like name, phone, bankAccount, which does not match with a single string value, external secrets will return the complete json string. Use the json template functions to decode.
+
 ### Creating external secret
 ### Creating external secret
-To create a kubernetes secret from the GCP Secret Manager secret a `Kind=ExternalSecret` is needed.
+To create a kubernetes secret from Keeper Secret Manager secret a `Kind=ExternalSecret` is needed.
 
 
 ```yaml
 ```yaml
 {% include 'keepersecurity-external-secret.yaml' %}
 {% include 'keepersecurity-external-secret.yaml' %}

+ 20 - 0
docs/snippets/keepersecurity-external-secret.yaml

@@ -70,4 +70,24 @@ spec:
       remoteRef:
       remoteRef:
         key: OqPt3Vd37My7G8rTb-8Q
         key: OqPt3Vd37My7G8rTb-8Q
         property: password
         property: password
+---
+apiVersion: external-secrets.io/v1beta1
+kind: ExternalSecret
+metadata:
+  name: example
+spec:
+  refreshInterval: 1h           # rate SecretManager pulls KeeperSrucity
+  secretStoreRef:
+    kind: SecretStore
+    name: example               # name of the SecretStore (or kind specified)
+  target:
+    name: secret-to-be-created  # name of the k8s Secret to be created
+    creationPolicy: Owner
+    template:
+      engineVersion: v2
+      data:
+        username: "{{  (fromJson .name).first }} {{  (fromJson .name).middle }} {{  (fromJson .name).last }}" # decode json string into vars
+  dataFrom:
+    - extract:
+        key: OqPt3Vd37My7G8rTb-8Q  # ID of the Keeper Record
 {% endraw %}
 {% endraw %}

+ 24 - 9
pkg/provider/keepersecurity/client.go

@@ -71,14 +71,14 @@ type SecurityClient interface {
 }
 }
 
 
 type Field struct {
 type Field struct {
-	Type  string   `json:"type"`
-	Value []string `json:"value"`
+	Type  string        `json:"type"`
+	Value []interface{} `json:"value"`
 }
 }
 
 
 type CustomField struct {
 type CustomField struct {
-	Type  string   `json:"type"`
-	Label string   `json:"label"`
-	Value []string `json:"value"`
+	Type  string        `json:"type"`
+	Label string        `json:"label"`
+	Value []interface{} `json:"value"`
 }
 }
 
 
 type File struct {
 type File struct {
@@ -401,10 +401,25 @@ func (s *Secret) getItems(ref esv1beta1.ExternalSecretDataRemoteRef) (map[string
 	return secretData, nil
 	return secretData, nil
 }
 }
 
 
+func getFieldValue(value []interface{}) []byte {
+	if len(value) < 1 {
+		return []byte{}
+	} else if len(value) == 1 {
+		res, _ := json.Marshal(value[0])
+		if str, ok := value[0].(string); ok {
+			res = []byte(str)
+		}
+		return res
+	} else {
+		res, _ := json.Marshal(value)
+		return res
+	}
+}
+
 func (s *Secret) getField(key string) ([]byte, error) {
 func (s *Secret) getField(key string) ([]byte, error) {
 	for _, field := range s.Fields {
 	for _, field := range s.Fields {
 		if field.Type == key && field.Type != keeperSecurityFileRef && field.Type != keeperSecurityMfa && len(field.Value) > 0 {
 		if field.Type == key && field.Type != keeperSecurityFileRef && field.Type != keeperSecurityMfa && len(field.Value) > 0 {
-			return []byte(field.Value[0]), nil
+			return getFieldValue(field.Value), nil
 		}
 		}
 	}
 	}
 
 
@@ -415,7 +430,7 @@ func (s *Secret) getFields() map[string][]byte {
 	secretData := make(map[string][]byte)
 	secretData := make(map[string][]byte)
 	for _, field := range s.Fields {
 	for _, field := range s.Fields {
 		if len(field.Value) > 0 {
 		if len(field.Value) > 0 {
-			secretData[field.Type] = []byte(field.Value[0])
+			secretData[field.Type] = getFieldValue(field.Value)
 		}
 		}
 	}
 	}
 
 
@@ -425,7 +440,7 @@ func (s *Secret) getFields() map[string][]byte {
 func (s *Secret) getCustomField(key string) ([]byte, error) {
 func (s *Secret) getCustomField(key string) ([]byte, error) {
 	for _, field := range s.Custom {
 	for _, field := range s.Custom {
 		if field.Label == key && len(field.Value) > 0 {
 		if field.Label == key && len(field.Value) > 0 {
-			return []byte(field.Value[0]), nil
+			return getFieldValue(field.Value), nil
 		}
 		}
 	}
 	}
 
 
@@ -436,7 +451,7 @@ func (s *Secret) getCustomFields() map[string][]byte {
 	secretData := make(map[string][]byte)
 	secretData := make(map[string][]byte)
 	for _, field := range s.Custom {
 	for _, field := range s.Custom {
 		if len(field.Value) > 0 {
 		if len(field.Value) > 0 {
-			secretData[field.Label] = []byte(field.Value[0])
+			secretData[field.Label] = getFieldValue(field.Value)
 		}
 		}
 	}
 	}
 
 

+ 8 - 6
pkg/provider/keepersecurity/client_test.go

@@ -33,14 +33,15 @@ const (
 	folderID            = "a8ekf031k"
 	folderID            = "a8ekf031k"
 	validExistingRecord = "record0/login"
 	validExistingRecord = "record0/login"
 	invalidRecord       = "record5/login"
 	invalidRecord       = "record5/login"
-	outputRecord0       = "{\"title\":\"record0\",\"type\":\"login\",\"fields\":[{\"type\":\"login\",\"value\":[\"foo\"]},{\"type\":\"password\",\"value\":[\"bar\"]}],\"custom\":null,\"files\":null}"
-	outputRecord1       = "{\"title\":\"record1\",\"type\":\"login\",\"fields\":[{\"type\":\"login\",\"value\":[\"foo\"]},{\"type\":\"password\",\"value\":[\"bar\"]}],\"custom\":null,\"files\":null}"
-	outputRecord2       = "{\"title\":\"record2\",\"type\":\"login\",\"fields\":[{\"type\":\"login\",\"value\":[\"foo\"]},{\"type\":\"password\",\"value\":[\"bar\"]}],\"custom\":null,\"files\":null}"
+	outputRecord0       = "{\"title\":\"record0\",\"type\":\"login\",\"fields\":[{\"type\":\"login\",\"value\":[\"foo\"]},{\"type\":\"password\",\"value\":[\"bar\"]}],\"custom\":[{\"type\":\"host\",\"label\":\"host0\",\"value\":[{\"hostName\":\"mysql\",\"port\":\"3306\"}]}],\"files\":null}"
+	outputRecord1       = "{\"title\":\"record1\",\"type\":\"login\",\"fields\":[{\"type\":\"login\",\"value\":[\"foo\"]},{\"type\":\"password\",\"value\":[\"bar\"]}],\"custom\":[{\"type\":\"host\",\"label\":\"host1\",\"value\":[{\"hostName\":\"mysql\",\"port\":\"3306\"}]}],\"files\":null}"
+	outputRecord2       = "{\"title\":\"record2\",\"type\":\"login\",\"fields\":[{\"type\":\"login\",\"value\":[\"foo\"]},{\"type\":\"password\",\"value\":[\"bar\"]}],\"custom\":[{\"type\":\"host\",\"label\":\"host2\",\"value\":[{\"hostName\":\"mysql\",\"port\":\"3306\"}]}],\"files\":null}"
 	record0             = "record0"
 	record0             = "record0"
 	record1             = "record1"
 	record1             = "record1"
 	record2             = "record2"
 	record2             = "record2"
 	LoginKey            = "login"
 	LoginKey            = "login"
 	PasswordKey         = "password"
 	PasswordKey         = "password"
+	HostKeyFormat       = "host%d"
 	RecordNameFormat    = "record%d"
 	RecordNameFormat    = "record%d"
 )
 )
 
 
@@ -412,8 +413,9 @@ func TestClientGetSecretMap(t *testing.T) {
 				},
 				},
 			},
 			},
 			want: map[string][]byte{
 			want: map[string][]byte{
-				LoginKey:    []byte("foo"),
-				PasswordKey: []byte("bar"),
+				LoginKey:                      []byte("foo"),
+				PasswordKey:                   []byte("bar"),
+				fmt.Sprintf(HostKeyFormat, 0): []byte("{\"hostName\":\"mysql\",\"port\":\"3306\"}"),
 			},
 			},
 			wantErr: false,
 			wantErr: false,
 		},
 		},
@@ -650,7 +652,7 @@ func generateRecords() []*ksm.Record {
 				},
 				},
 			}
 			}
 		}
 		}
-		sec := fmt.Sprintf("{\"title\":\"record%d\",\"type\":\"login\",\"fields\":[{\"type\":\"login\",\"value\":[\"foo\"]},{\"type\":\"password\",\"value\":[\"bar\"]}]}", i)
+		sec := fmt.Sprintf("{\"title\":\"record%d\",\"type\":\"login\",\"fields\":[{\"type\":\"login\",\"value\":[\"foo\"]},{\"type\":\"password\",\"value\":[\"bar\"]}],\"custom\":[{\"type\":\"host\",\"label\":\"host%d\",\"value\":[{\"hostName\":\"mysql\",\"port\":\"3306\"}]}]}", i, i)
 		record.SetTitle(fmt.Sprintf(RecordNameFormat, i))
 		record.SetTitle(fmt.Sprintf(RecordNameFormat, i))
 		record.SetStandardFieldValue(LoginKey, "foo")
 		record.SetStandardFieldValue(LoginKey, "foo")
 		record.SetStandardFieldValue(PasswordKey, "bar")
 		record.SetStandardFieldValue(PasswordKey, "bar")