mirror of
https://github.com/external-secrets/external-secrets.git
synced 2024-12-14 11:57:59 +00:00
Add support for multiple Items fields in DelineSecretServer secrets (#4051)
* Add support for multiple fields in DelineSecretServer secrets Signed-off-by: Ronaldo Saheki <rsaheki@gmail.com> * Add tested cases for errors and update documentation Signed-off-by: Ronaldo Saheki <rsaheki@gmail.com> * Update docs/provider/secretserver.md Signed-off-by: Gergely Brautigam <182850+Skarlso@users.noreply.github.com> --------- Signed-off-by: Ronaldo Saheki <rsaheki@gmail.com> Signed-off-by: Gergely Brautigam <182850+Skarlso@users.noreply.github.com> Co-authored-by: Ronaldo Saheki <ronaldo.saheki@veeam.com> Co-authored-by: Gergely Brautigam <182850+Skarlso@users.noreply.github.com>
This commit is contained in:
parent
4dfa4d2622
commit
4f3909e0c9
3 changed files with 166 additions and 17 deletions
|
@ -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.<br />
|
||||
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)<br />
|
||||
spec.data.remoteRef.property = "Username" (Items.0.FieldName)<br />
|
||||
returns: usernamevalue
|
||||
|
||||
- Lookup a nested property using secret name.
|
||||
|
||||
>spec.data.remoteRef.key = "Secretname" (name of the secret)<br />
|
||||
spec.data.remoteRef.property = "password" (Items.1.slug)<br />
|
||||
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)<br />
|
||||
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
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
|
|
@ -61,6 +61,9 @@ func (c *client) GetSecret(ctx context.Context, ref esv1beta1.ExternalSecretData
|
|||
if ref.Property == "" {
|
||||
return jsonStr, 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() {
|
||||
|
@ -71,8 +74,24 @@ func (c *client) GetSecret(ctx context.Context, ref esv1beta1.ExternalSecretData
|
|||
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.
|
||||
|
|
|
@ -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 {
|
||||
|
|
Loading…
Reference in a new issue