1
0
Fork 0
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:
Ronaldo 2024-11-27 06:35:52 +00:00 committed by GitHub
parent 4dfa4d2622
commit 4f3909e0c9
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 166 additions and 17 deletions

View file

@ -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
}
]
}
```

View file

@ -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.

View file

@ -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 {