diff --git a/docs/provider/secretserver.md b/docs/provider/secretserver.md
index e1ed50c7c..2f389971f 100644
--- a/docs/provider/secretserver.md
+++ b/docs/provider/secretserver.md
@@ -69,7 +69,7 @@ You can either retrieve your entire secret or you can use a JSON formatted strin
stored in your secret located at Items[0].ItemValue to retrieve a specific value.
See example JSON secret below.
-### Examples
+#### Examples
Using the json formatted secret below:
- Lookup a single top level property using secret ID.
@@ -131,3 +131,81 @@ returns: The entire secret in JSON format as displayed below
]
}
```
+
+### Referencing Secrets in multiple Items secrets
+
+If there is more then one Item in the secret, it supports to retrieve them (all Item.\*.ItemValue) looking up by Item.\*.FieldName or Item.\*.Slug, instead of the above behaviour to use gjson only on the first item Items.0.ItemValue only.
+
+#### Examples
+
+Using the json formatted secret below:
+
+- Lookup a single top level property using secret ID.
+
+>spec.data.remoteRef.key = 4000 (id of the secret)
+spec.data.remoteRef.property = "Username" (Items.0.FieldName)
+returns: usernamevalue
+
+- Lookup a nested property using secret name.
+
+>spec.data.remoteRef.key = "Secretname" (name of the secret)
+spec.data.remoteRef.property = "password" (Items.1.slug)
+returns: passwordvalue
+
+- Lookup by secret ID (*secret name will work as well*) and return the entire secret.
+
+>spec.data.remoteRef.key = "4000" (id of the secret)
+returns: The entire secret in JSON format as displayed below
+
+
+```JSON
+{
+ "Name": "Secretname",
+ "FolderID": 0,
+ "ID": 4000,
+ "SiteID": 0,
+ "SecretTemplateID": 0,
+ "LauncherConnectAsSecretID": 0,
+ "CheckOutIntervalMinutes": 0,
+ "Active": false,
+ "CheckedOut": false,
+ "CheckOutEnabled": false,
+ "AutoChangeEnabled": false,
+ "CheckOutChangePasswordEnabled": false,
+ "DelayIndexing": false,
+ "EnableInheritPermissions": false,
+ "EnableInheritSecretPolicy": false,
+ "ProxyEnabled": false,
+ "RequiresComment": false,
+ "SessionRecordingEnabled": false,
+ "WebLauncherRequiresIncognitoMode": false,
+ "Items": [
+ {
+ "ItemID": 0,
+ "FieldID": 0,
+ "FileAttachmentID": 0,
+ "FieldName": "Username",
+ "Slug": "username",
+ "FieldDescription": "",
+ "Filename": "",
+ "ItemValue": "usernamevalue",
+ "IsFile": false,
+ "IsNotes": false,
+ "IsPassword": false
+ },
+ {
+ "ItemID": 0,
+ "FieldID": 0,
+ "FileAttachmentID": 0,
+ "FieldName": "Password",
+ "Slug": "password",
+ "FieldDescription": "",
+ "Filename": "",
+ "ItemValue": "passwordvalue",
+ "IsFile": false,
+ "IsNotes": false,
+ "IsPassword": false
+ }
+ ]
+}
+```
diff --git a/pkg/provider/secretserver/client.go b/pkg/provider/secretserver/client.go
index c3f19db3b..49398675e 100644
--- a/pkg/provider/secretserver/client.go
+++ b/pkg/provider/secretserver/client.go
@@ -61,18 +61,37 @@ func (c *client) GetSecret(ctx context.Context, ref esv1beta1.ExternalSecretData
if ref.Property == "" {
return jsonStr, nil
}
- // extract first "field" i.e. Items.0.ItemValue, data from secret using gjson
- val := gjson.Get(string(jsonStr), "Items.0.ItemValue")
- if !val.Exists() {
- return nil, esv1beta1.NoSecretError{}
- }
- // extract specific value from data directly above using gjson
- out := gjson.Get(val.String(), ref.Property)
- if !out.Exists() {
- return nil, esv1beta1.NoSecretError{}
- }
- return []byte(out.String()), nil
+ // Keep original behavior of decoding first Item into gjson
+ if len(secret.Fields) == 1 {
+ // extract first "field" i.e. Items.0.ItemValue, data from secret using gjson
+ val := gjson.Get(string(jsonStr), "Items.0.ItemValue")
+ if !val.Exists() {
+ return nil, esv1beta1.NoSecretError{}
+ }
+ // extract specific value from data directly above using gjson
+ out := gjson.Get(val.String(), ref.Property)
+ if !out.Exists() {
+ return nil, esv1beta1.NoSecretError{}
+ }
+ return []byte(out.String()), nil
+ } else {
+ // More general case Fields is an array in DelineaXPM/tss-sdk-go/v2/server
+ // https://github.com/DelineaXPM/tss-sdk-go/blob/571e5674a8103031ad6f873453db27959ec1ca67/server/secret.go#L23
+ secretMap := make(map[string]string)
+
+ for index := range secret.Fields {
+ secretMap[secret.Fields[index].FieldName] = secret.Fields[index].ItemValue
+ secretMap[secret.Fields[index].Slug] = secret.Fields[index].ItemValue
+ }
+
+ out, ok := secretMap[ref.Property]
+ if !ok {
+ return nil, esv1beta1.NoSecretError{}
+ }
+
+ return []byte(out), nil
+ }
}
// Not supported at this time.
diff --git a/pkg/provider/secretserver/client_test.go b/pkg/provider/secretserver/client_test.go
index c338de70e..026136c36 100644
--- a/pkg/provider/secretserver/client_test.go
+++ b/pkg/provider/secretserver/client_test.go
@@ -79,6 +79,20 @@ func getJSONData() (*server.Secret, error) {
return s, nil
}
+func createTestSecretFromCode(id int) *server.Secret {
+ s := new(server.Secret)
+ s.ID = id
+ s.Name = "Secretname"
+ s.Fields = make([]server.SecretField, 2)
+ s.Fields[0].ItemValue = "usernamevalue"
+ s.Fields[0].FieldName = "Username"
+ s.Fields[0].Slug = "username"
+ s.Fields[1].FieldName = "Password"
+ s.Fields[1].Slug = "password"
+ s.Fields[1].ItemValue = "passwordvalue"
+ return s
+}
+
func newTestClient() esv1beta1.SecretsClient {
return &client{
api: &fakeAPI{
@@ -86,16 +100,18 @@ func newTestClient() esv1beta1.SecretsClient {
createSecret(1000, "{ \"user\": \"robertOppenheimer\", \"password\": \"badPassword\",\"server\":\"192.168.1.50\"}"),
createSecret(2000, "{ \"user\": \"helloWorld\", \"password\": \"badPassword\",\"server\":[ \"192.168.1.50\",\"192.168.1.51\"] }"),
createSecret(3000, "{ \"user\": \"chuckTesta\", \"password\": \"badPassword\",\"server\":\"192.168.1.50\"}"),
+ createTestSecretFromCode(4000),
},
},
}
}
-func TestGetSecret(t *testing.T) {
+func TestGetSecretSecretServer(t *testing.T) {
ctx := context.Background()
c := newTestClient()
s, _ := getJSONData()
jsonStr, _ := json.Marshal(s)
+ jsonStr2, _ := json.Marshal(createTestSecretFromCode(4000))
testCases := map[string]struct {
ref esv1beta1.ExternalSecretDataRemoteRef
@@ -116,33 +132,69 @@ func TestGetSecret(t *testing.T) {
},
want: []byte(`robertOppenheimer`),
},
- "key and password property returns a single value": {
+ "Secret from JSON: key and password property returns a single value": {
ref: esv1beta1.ExternalSecretDataRemoteRef{
Key: "1000",
Property: "password",
},
want: []byte(`badPassword`),
},
- "key and nested property returns a single value": {
+ "Secret from JSON: key and nested property returns a single value": {
ref: esv1beta1.ExternalSecretDataRemoteRef{
Key: "2000",
Property: "server.1",
},
want: []byte(`192.168.1.51`),
},
- "existent key with non-existing propery": {
+ "Secret from JSON: existent key with non-existing propery": {
ref: esv1beta1.ExternalSecretDataRemoteRef{
Key: "3000",
Property: "foo.bar",
},
err: esv1beta1.NoSecretError{},
},
- "existent 'name' key with no propery": {
+ "Secret from JSON: existent 'name' key with no propery": {
ref: esv1beta1.ExternalSecretDataRemoteRef{
Key: "1000",
},
want: jsonStr,
},
+ "Secret from code: existent key with no property": {
+ ref: esv1beta1.ExternalSecretDataRemoteRef{
+ Key: "4000",
+ },
+ want: jsonStr2,
+ },
+ "Secret from code: key and username fieldnamereturns a single value": {
+ ref: esv1beta1.ExternalSecretDataRemoteRef{
+ Key: "4000",
+ Property: "Username",
+ },
+ want: []byte(`usernamevalue`),
+ },
+ "Secret from code: 'name' and password slug returns a single value": {
+ ref: esv1beta1.ExternalSecretDataRemoteRef{
+ Key: "Secretname",
+ Property: "password",
+ },
+ want: []byte(`passwordvalue`),
+ },
+ "Secret from code: 'name' not found and password slug returns error": {
+ ref: esv1beta1.ExternalSecretDataRemoteRef{
+ Key: "Secretnameerror",
+ Property: "password",
+ },
+ want: []byte(nil),
+ err: errNotFound,
+ },
+ "Secret from code: 'name' found and non-existent attribute slug returns noSecretError": {
+ ref: esv1beta1.ExternalSecretDataRemoteRef{
+ Key: "Secretname",
+ Property: "passwordkey",
+ },
+ want: []byte(nil),
+ err: esv1beta1.NoSecretError{},
+ },
}
for name, tc := range testCases {