Browse Source

feat: add support for fetching Secret by Path on Delinea Secret Server provider (#5270)

* update tss-sdk-go module

Signed-off-by: DelineaSahilWankhede <sahil.wankhede@c.delinea.com>

* Revert "update tss-sdk-go module"

This reverts commit 71b284c5afd2f05f15819e69e2a2e72c15a83f2e.

Signed-off-by: DelineaSahilWankhede <sahil.wankhede@c.delinea.com>

* update tss-sdk-go module

Signed-off-by: DelineaSahilWankhede <sahil.wankhede@c.delinea.com>

* update Delinea SecretServer provider

Signed-off-by: DelineaSahilWankhede <sahil.wankhede@c.delinea.com>

* update Delinea SecretServer provider doc

Signed-off-by: DelineaSahilWankhede <sahil.wankhede@c.delinea.com>

* fix linting issues

Signed-off-by: DelineaSahilWankhede <sahil.wankhede@c.delinea.com>

* Update docs/provider/secretserver.md

Co-authored-by: Moritz Johner <moolen@users.noreply.github.com>
Signed-off-by: DelineaSahilWankhede <161290557+DelineaSahilWankhede@users.noreply.github.com>

* fix e2e go.mod/go.sum

Signed-off-by: DelineaSahilWankhede <sahil.wankhede@c.delinea.com>

* update e2e go modules and provider

Signed-off-by: DelineaSahilWankhede <sahil.wankhede@c.delinea.com>

* revert deletion of other provider mods

Signed-off-by: DelineaSahilWankhede <sahil.wankhede@c.delinea.com>

* Update e2e/go.mod as needed

Signed-off-by: DelineaSahilWankhede <sahil.wankhede@c.delinea.com>

* added running make check-diff

Signed-off-by: Gergely Brautigam <182850+Skarlso@users.noreply.github.com>

---------

Signed-off-by: DelineaSahilWankhede <sahil.wankhede@c.delinea.com>
Signed-off-by: DelineaSahilWankhede <161290557+DelineaSahilWankhede@users.noreply.github.com>
Signed-off-by: Gergely Brautigam <182850+Skarlso@users.noreply.github.com>
Co-authored-by: Moritz Johner <moolen@users.noreply.github.com>
Co-authored-by: Gergely Brautigam <182850+Skarlso@users.noreply.github.com>
DelineaSahilWankhede 7 months ago
parent
commit
081ebca997

+ 43 - 0
docs/provider/secretserver.md

@@ -90,6 +90,49 @@ In this example, the secret with ID 52622 is retrieved in its entirety and store
 
 This feature simplifies the integration process for applications that require secrets in specific formats, eliminating the need for custom parsing logic within your applications.
 
+### Support for Fetching Secrets by Path
+
+In addition to retrieving secrets by ID or Name, the Secret Server provider now supports fetching secrets by **path**.  
+This allows you to specify a secret’s folder hierarchy and name in the format:
+>/FolderName/SecretName
+
+#### Example
+
+```yaml
+apiVersion: external-secrets.io/v1beta1
+kind: ExternalSecret
+metadata:
+  name: secret-server-external-secret
+spec:
+  refreshInterval: 15s
+  secretStoreRef:
+    kind: SecretStore
+    name: secret-server-store
+  data:
+    - secretKey: SecretServerValue  # Key in the Kubernetes Secret
+      remoteRef:
+        key: "/secretFolder/secretname"  # Path format: /<Folder>/<SecretName>
+        property: ""                    # Optional: use gjson syntax to extract a specific field
+```
+
+#### Notes:
+
+The path must exactly match the folder and secret name in Secret Server.
+If multiple secrets with the same name exist in different folders, the path helps to uniquely identify the correct one.
+You can still use property to extract values from JSON-formatted secrets or omit it to retrieve the entire secret (JSON or non-JSON).
+Updated Referencing Secrets Section
+
+Secrets may be referenced by:
+>Secret ID<br />
+Secret Name<br />
+Secret Path (/FolderName/SecretName)<br />
+
+Please note if using the secret name or path,
+the field must not contain spaces or control characters.<br />
+If multiple secrets are found, only the first found secret will be returned.
+
+Please note: Retrieving a specific version of a secret is not yet supported.
+
 ### Preparing your secret
 You can either retrieve your entire secret or you can use a JSON formatted string
 stored in your secret located at Items[0].ItemValue to retrieve a specific value.<br />

+ 1 - 1
e2e/go.mod

@@ -45,7 +45,7 @@ require (
 	github.com/Azure/go-autorest/autorest v0.11.30
 	github.com/Azure/go-autorest/autorest/azure/auth v0.5.13
 	github.com/DelineaXPM/dsv-sdk-go/v2 v2.2.0
-	github.com/DelineaXPM/tss-sdk-go/v2 v2.0.3
+	github.com/DelineaXPM/tss-sdk-go/v3 v3.0.0
 	github.com/akeylesslabs/akeyless-go-cloud-id v0.3.5
 	github.com/akeylesslabs/akeyless-go/v4 v4.0.0
 	github.com/aliyun/alibaba-cloud-sdk-go v1.62.271

+ 3 - 3
e2e/go.sum

@@ -116,8 +116,8 @@ github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03
 github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
 github.com/DelineaXPM/dsv-sdk-go/v2 v2.2.0 h1:62E66sDf+Hs1TChuu3R7d+0U5s7yV84QIOvvnfxtUJM=
 github.com/DelineaXPM/dsv-sdk-go/v2 v2.2.0/go.mod h1:58Pflli0BtqeF0VgluDSSVE5QlIfLOJvat0JSvo/d70=
-github.com/DelineaXPM/tss-sdk-go/v2 v2.0.3 h1:Yk8VZUIer8deRzi1Zx2Di2wEpw138IP09O5eKUYmDRs=
-github.com/DelineaXPM/tss-sdk-go/v2 v2.0.3/go.mod h1:xz6FXP2Do88Vc5Hx7OamZgZC1W45yfmLy4+iDKxlGXo=
+github.com/DelineaXPM/tss-sdk-go/v3 v3.0.0 h1:RXL9/Kd1XsuzBLuIr6am0jDOM1NtXbz7UtL4okBihUY=
+github.com/DelineaXPM/tss-sdk-go/v3 v3.0.0/go.mod h1:VmyoHQ25FhSVHTI3/ptQNOviNEMfCy2ALAf/3E4Eqxg=
 github.com/Masterminds/goutils v1.1.1 h1:5nUrii3FMTL5diU80unEVvNevw1nH4+ZV4DSLVJLSYI=
 github.com/Masterminds/goutils v1.1.1/go.mod h1:8cTjp+g8YejhMuvIA5y2vz3BpJxksy863GQaJW2MFNU=
 github.com/Masterminds/semver/v3 v3.2.0/go.mod h1:qvl/7zhW3nngYb5+80sSMF+FG2BjYrf8m9wsX0PNOMQ=
@@ -1099,4 +1099,4 @@ sigs.k8s.io/structured-merge-diff/v6 v6.3.0/go.mod h1:M3W8sfWvn2HhQDIbGWj3S099Yo
 sigs.k8s.io/yaml v1.6.0 h1:G8fkbMSAFqgEFgh4b1wmtzDnioxFCUgTZhlbj5P9QYs=
 sigs.k8s.io/yaml v1.6.0/go.mod h1:796bPqUfzR/0jLAl6XjHl3Ck7MiyVv8dbTdyT3/pMf4=
 software.sslmate.com/src/go-pkcs12 v0.6.0 h1:f3sQittAeF+pao32Vb+mkli+ZyT+VwKaD014qFGq6oU=
-software.sslmate.com/src/go-pkcs12 v0.6.0/go.mod h1:Qiz0EyvDRJjjxGyUQa2cCNZn/wMyzrRJ/qcDXOQazLI=
+software.sslmate.com/src/go-pkcs12 v0.6.0/go.mod h1:Qiz0EyvDRJjjxGyUQa2cCNZn/wMyzrRJ/qcDXOQazLI=

+ 5 - 6
e2e/suites/provider/cases/secretserver/provider.go

@@ -3,17 +3,16 @@ package secretserver
 import (
 	"encoding/json"
 
-	"github.com/DelineaXPM/tss-sdk-go/v2/server"
+	"github.com/DelineaXPM/tss-sdk-go/v3/server"
 	"github.com/external-secrets/external-secrets-e2e/framework"
 	"github.com/onsi/gomega"
 )
 
-
 type secretStoreProvider struct {
-	api *server.Server
-	cfg *config
+	api       *server.Server
+	cfg       *config
 	framework *framework.Framework
-	secretID map[string]int
+	secretID  map[string]int
 }
 
 func (p *secretStoreProvider) init(cfg *config, f *framework.Framework) {
@@ -25,7 +24,7 @@ func (p *secretStoreProvider) init(cfg *config, f *framework.Framework) {
 			Username: cfg.username,
 			Password: cfg.password,
 		},
-		ServerURL:      cfg.serverURL,
+		ServerURL: cfg.serverURL,
 	})
 	gomega.Expect(err).ToNot(gomega.HaveOccurred())
 

+ 1 - 1
go.mod

@@ -72,7 +72,7 @@ require (
 	github.com/Azure/go-ntlmssp v0.0.0-20221128193559-754e69321358
 	github.com/BeyondTrust/go-client-library-passwordsafe v0.22.1
 	github.com/DelineaXPM/dsv-sdk-go/v2 v2.2.0
-	github.com/DelineaXPM/tss-sdk-go/v2 v2.0.3
+	github.com/DelineaXPM/tss-sdk-go/v3 v3.0.0
 	github.com/Onboardbase/go-cryptojs-aes-decrypt v0.0.0-20230430095000-27c0d3a9016d
 	github.com/akeylesslabs/akeyless-go/v4 v4.0.0
 	github.com/alibabacloud-go/darabonba-openapi/v2 v2.1.11

+ 2 - 2
go.sum

@@ -128,8 +128,8 @@ github.com/BurntSushi/toml v1.5.0/go.mod h1:ukJfTF/6rtPPRCnwkur4qwRxa8vTRFBF0uk2
 github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
 github.com/DelineaXPM/dsv-sdk-go/v2 v2.2.0 h1:62E66sDf+Hs1TChuu3R7d+0U5s7yV84QIOvvnfxtUJM=
 github.com/DelineaXPM/dsv-sdk-go/v2 v2.2.0/go.mod h1:58Pflli0BtqeF0VgluDSSVE5QlIfLOJvat0JSvo/d70=
-github.com/DelineaXPM/tss-sdk-go/v2 v2.0.3 h1:Yk8VZUIer8deRzi1Zx2Di2wEpw138IP09O5eKUYmDRs=
-github.com/DelineaXPM/tss-sdk-go/v2 v2.0.3/go.mod h1:xz6FXP2Do88Vc5Hx7OamZgZC1W45yfmLy4+iDKxlGXo=
+github.com/DelineaXPM/tss-sdk-go/v3 v3.0.0 h1:RXL9/Kd1XsuzBLuIr6am0jDOM1NtXbz7UtL4okBihUY=
+github.com/DelineaXPM/tss-sdk-go/v3 v3.0.0/go.mod h1:VmyoHQ25FhSVHTI3/ptQNOviNEMfCy2ALAf/3E4Eqxg=
 github.com/HdrHistogram/hdrhistogram-go v1.1.2 h1:5IcZpTvzydCQeHzK4Ef/D5rrSqwxob0t8PQPMybUNFM=
 github.com/HdrHistogram/hdrhistogram-go v1.1.2/go.mod h1:yDgFjdqOqDEKOvasDdhWNXYg9BVp4O+o5f6V/ehm6Oo=
 github.com/IBM/go-sdk-core/v5 v5.21.0 h1:DUnYhvC4SoC8T84rx5omnhY3+xcQg/Whyoa3mDPIMkk=

+ 15 - 2
pkg/provider/secretserver/client.go

@@ -19,8 +19,9 @@ import (
 	"encoding/json"
 	"errors"
 	"strconv"
+	"strings"
 
-	"github.com/DelineaXPM/tss-sdk-go/v2/server"
+	"github.com/DelineaXPM/tss-sdk-go/v3/server"
 	"github.com/tidwall/gjson"
 	corev1 "k8s.io/api/core/v1"
 
@@ -73,7 +74,7 @@ func (c *client) GetSecret(ctx context.Context, ref esv1.ExternalSecretDataRemot
 		}
 	}
 
-	// More general case Fields is an array in DelineaXPM/tss-sdk-go/v2/server
+	// More general case Fields is an array in DelineaXPM/tss-sdk-go/v3/server
 	// https://github.com/DelineaXPM/tss-sdk-go/blob/571e5674a8103031ad6f873453db27959ec1ca67/server/secret.go#L23
 	secretMap := make(map[string]string)
 	for index := range secret.Fields {
@@ -145,6 +146,18 @@ func (c *client) getSecret(_ context.Context, ref esv1.ExternalSecretDataRemoteR
 	if ref.Version != "" {
 		return nil, errors.New("specifying a version is not supported")
 	}
+
+	// If the ref.Key looks like a full path (starts with "/"), fetch by path.
+	// Example: "/Folder/Subfolder/SecretName"
+	if strings.HasPrefix(ref.Key, "/") {
+		s, err := c.api.SecretByPath(ref.Key)
+		if err != nil {
+			return nil, err
+		}
+		return s, nil
+	}
+
+	// Otherwise try converting it to an ID
 	id, err := strconv.Atoi(ref.Key)
 	if err != nil {
 		s, err := c.api.Secrets(ref.Key, "Name")

+ 44 - 9
pkg/provider/secretserver/client_test.go

@@ -21,7 +21,7 @@ import (
 	"os"
 	"testing"
 
-	"github.com/DelineaXPM/tss-sdk-go/v2/server"
+	"github.com/DelineaXPM/tss-sdk-go/v3/server"
 	"github.com/stretchr/testify/assert"
 
 	esv1 "github.com/external-secrets/external-secrets/apis/externalsecrets/v1"
@@ -35,6 +35,11 @@ type fakeAPI struct {
 	secrets []*server.Secret
 }
 
+const (
+    usernameSlug = "username"
+    passwordSlug = "password"
+)
+
 func (f *fakeAPI) Secret(id int) (*server.Secret, error) {
 	for _, s := range f.secrets {
 		if s.ID == id {
@@ -55,7 +60,15 @@ func (f *fakeAPI) Secrets(searchText, _ string) ([]server.Secret, error) {
 	return nil, errNotFound
 }
 
-// createSecret assembles a server.Secret from file test_data.json.
+func (f *fakeAPI) SecretByPath(path string) (*server.Secret, error) {
+	for _, s := range f.secrets {
+		if "/"+s.Name == path {
+			return s, nil
+		}
+	}
+	return nil, errNotFound
+}
+
 func createSecret(id int, itemValue string) *server.Secret {
 	s, _ := getJSONData()
 	s.ID = id
@@ -86,9 +99,24 @@ func createTestSecretFromCode(id int) *server.Secret {
 	s.Fields = make([]server.SecretField, 2)
 	s.Fields[0].ItemValue = "usernamevalue"
 	s.Fields[0].FieldName = "Username"
-	s.Fields[0].Slug = "username"
+	s.Fields[0].Slug = usernameSlug
+	s.Fields[1].FieldName = "Password"
+	s.Fields[1].Slug = passwordSlug
+	s.Fields[1].ItemValue = "passwordvalue"
+	return s
+}
+
+func createTestFolderSecret(id, folderId int) *server.Secret {
+	s := new(server.Secret)
+	s.FolderID = folderId
+	s.ID = id
+	s.Name = "FolderSecretname"
+	s.Fields = make([]server.SecretField, 2)
+	s.Fields[0].ItemValue = "usernamevalue"
+	s.Fields[0].FieldName = "Username"
+	s.Fields[0].Slug = usernameSlug
 	s.Fields[1].FieldName = "Password"
-	s.Fields[1].Slug = "password"
+	s.Fields[1].Slug = passwordSlug
 	s.Fields[1].ItemValue = "passwordvalue"
 	return s
 }
@@ -132,7 +160,7 @@ func newTestClient() esv1.SecretsClient {
 				createSecret(6000, "{ \"user\": \"betaTest\", \"password\": \"badPassword\" }"),
 				createNilFieldsSecret(7000),
 				createEmptyFieldsSecret(8000),
-				createSecret(9000, "{ \"user\": \"robertOppenheimer\", \"password\": \"badPassword\", \"domain\":\"domain1\", \"server\":\"192.168.1.50\"}"),
+				createTestFolderSecret(9000, 4),
 			},
 		},
 	}
@@ -145,6 +173,7 @@ func TestGetSecretSecretServer(t *testing.T) {
 	jsonStr, _ := json.Marshal(s)
 	jsonStr2, _ := json.Marshal(createTestSecretFromCode(4000))
 	jsonStr3, _ := json.Marshal(createPlainTextSecret(5000))
+	jsonStr4, _ := json.Marshal(createTestFolderSecret(9000, 4))
 
 	testCases := map[string]struct {
 		ref  esv1.ExternalSecretDataRemoteRef
@@ -263,12 +292,18 @@ func TestGetSecretSecretServer(t *testing.T) {
 			want: []byte(nil),
 			err:  esv1.NoSecretError{},
 		},
-		"Secret from code: with domain": {
+		"Secret by path: valid path returns secret": {
+			ref: esv1.ExternalSecretDataRemoteRef{
+				Key: "/FolderSecretname",
+			},
+			want: jsonStr4,
+		},
+		"Secret by path: invalid path returns error": {
 			ref: esv1.ExternalSecretDataRemoteRef{
-				Key:      "9000",
-				Property: "domain",
+				Key: "/invalid/secret/path",
 			},
-			want: []byte(`domain1`),
+			want: []byte(nil),
+			err:  errNotFound,
 		},
 	}
 

+ 1 - 1
pkg/provider/secretserver/provider.go

@@ -18,7 +18,7 @@ import (
 	"context"
 	"errors"
 
-	"github.com/DelineaXPM/tss-sdk-go/v2/server"
+	"github.com/DelineaXPM/tss-sdk-go/v3/server"
 	kubeClient "sigs.k8s.io/controller-runtime/pkg/client"
 	"sigs.k8s.io/controller-runtime/pkg/webhook/admission"
 

+ 2 - 2
pkg/provider/secretserver/provider_test.go

@@ -19,7 +19,7 @@ import (
 	"math/rand"
 	"testing"
 
-	"github.com/DelineaXPM/tss-sdk-go/v2/server"
+	"github.com/DelineaXPM/tss-sdk-go/v3/server"
 	"github.com/stretchr/testify/assert"
 	corev1 "k8s.io/api/core/v1"
 	kubeErrors "k8s.io/apimachinery/pkg/api/errors"
@@ -385,4 +385,4 @@ func generateRandomString() string {
 	}
 
 	return string(b)
-}
+}

+ 3 - 2
pkg/provider/secretserver/secret_api.go

@@ -15,12 +15,13 @@ limitations under the License.
 package secretserver
 
 import (
-	"github.com/DelineaXPM/tss-sdk-go/v2/server"
+	"github.com/DelineaXPM/tss-sdk-go/v3/server"
 )
 
 // secretAPI represents the subset of the Secret Server API
-// which is supported by tss-sdk-go/v2.
+// which is supported by tss-sdk-go/v3.
 type secretAPI interface {
 	Secret(id int) (*server.Secret, error)
 	Secrets(searchText, field string) ([]server.Secret, error)
+	SecretByPath(secretPath string) (*server.Secret, error)
 }