|
|
@@ -63,6 +63,44 @@ func TestDoesConfigDependOnNamespace(t *testing.T) {
|
|
|
},
|
|
|
want: false,
|
|
|
},
|
|
|
+ "true when Token references a secret without explicit namespace": {
|
|
|
+ cfg: esv1.SecretServerProvider{
|
|
|
+ Token: &esv1.SecretServerProviderRef{
|
|
|
+ SecretRef: &v1.SecretKeySelector{Name: "foo"},
|
|
|
+ },
|
|
|
+ },
|
|
|
+ want: true,
|
|
|
+ },
|
|
|
+ "false when Token uses a direct value": {
|
|
|
+ cfg: esv1.SecretServerProvider{
|
|
|
+ Token: &esv1.SecretServerProviderRef{Value: "foo"},
|
|
|
+ },
|
|
|
+ want: false,
|
|
|
+ },
|
|
|
+ "false when Token has explicit namespace even if Username ref lacks one": {
|
|
|
+ // Token takes precedence, so the ignored Username ref must not
|
|
|
+ // introduce a namespace dependency.
|
|
|
+ cfg: esv1.SecretServerProvider{
|
|
|
+ Token: &esv1.SecretServerProviderRef{
|
|
|
+ SecretRef: &v1.SecretKeySelector{Name: "foo", Namespace: new("ns")},
|
|
|
+ },
|
|
|
+ Username: &esv1.SecretServerProviderRef{
|
|
|
+ SecretRef: &v1.SecretKeySelector{Name: "bar"},
|
|
|
+ },
|
|
|
+ },
|
|
|
+ want: false,
|
|
|
+ },
|
|
|
+ "true when Token ref lacks a namespace even if Username has one": {
|
|
|
+ cfg: esv1.SecretServerProvider{
|
|
|
+ Token: &esv1.SecretServerProviderRef{
|
|
|
+ SecretRef: &v1.SecretKeySelector{Name: "foo"},
|
|
|
+ },
|
|
|
+ Username: &esv1.SecretServerProviderRef{
|
|
|
+ SecretRef: &v1.SecretKeySelector{Name: "bar", Namespace: new("ns")},
|
|
|
+ },
|
|
|
+ },
|
|
|
+ want: true,
|
|
|
+ },
|
|
|
}
|
|
|
for name, tc := range tests {
|
|
|
t.Run(name, func(t *testing.T) {
|
|
|
@@ -147,6 +185,34 @@ func TestValidateStore(t *testing.T) {
|
|
|
},
|
|
|
want: nil,
|
|
|
},
|
|
|
+ "valid with token and no username/password": {
|
|
|
+ cfg: esv1.SecretServerProvider{
|
|
|
+ Token: validSecretRefUsingValue,
|
|
|
+ ServerURL: testURL,
|
|
|
+ },
|
|
|
+ want: nil,
|
|
|
+ },
|
|
|
+ "invalid without serverURL when using token": {
|
|
|
+ cfg: esv1.SecretServerProvider{
|
|
|
+ Token: validSecretRefUsingValue,
|
|
|
+ /*ServerURL: testURL,*/
|
|
|
+ },
|
|
|
+ want: errEmptyServerURL,
|
|
|
+ },
|
|
|
+ "invalid with ambiguous token": {
|
|
|
+ cfg: esv1.SecretServerProvider{
|
|
|
+ Token: ambiguousSecretRef,
|
|
|
+ ServerURL: testURL,
|
|
|
+ },
|
|
|
+ want: errSecretRefAndValueConflict,
|
|
|
+ },
|
|
|
+ "invalid with invalid token": {
|
|
|
+ cfg: esv1.SecretServerProvider{
|
|
|
+ Token: makeSecretRefUsingValue(""),
|
|
|
+ ServerURL: testURL,
|
|
|
+ },
|
|
|
+ want: errSecretRefAndValueMissing,
|
|
|
+ },
|
|
|
}
|
|
|
for name, tc := range tests {
|
|
|
t.Run(name, func(t *testing.T) {
|
|
|
@@ -170,6 +236,8 @@ func TestNewClient(t *testing.T) {
|
|
|
passwordKey := passwordSlug
|
|
|
passwordValue := generateRandomString()
|
|
|
domain := "domain1"
|
|
|
+ tokenKey := "token"
|
|
|
+ tokenValue := generateRandomString()
|
|
|
|
|
|
clientSecret := &corev1.Secret{
|
|
|
ObjectMeta: metav1.ObjectMeta{Name: "foo", Namespace: "default"},
|
|
|
@@ -179,6 +247,13 @@ func TestNewClient(t *testing.T) {
|
|
|
},
|
|
|
}
|
|
|
|
|
|
+ tokenSecret := &corev1.Secret{
|
|
|
+ ObjectMeta: metav1.ObjectMeta{Name: "token-secret", Namespace: "default"},
|
|
|
+ Data: map[string][]byte{
|
|
|
+ tokenKey: []byte(tokenValue),
|
|
|
+ },
|
|
|
+ }
|
|
|
+
|
|
|
validProvider := &esv1.SecretServerProvider{
|
|
|
Username: makeSecretRefUsingRef(clientSecret.Name, userNameKey),
|
|
|
Password: makeSecretRefUsingRef(clientSecret.Name, passwordKey),
|
|
|
@@ -332,6 +407,30 @@ QJ85ioEpy00NioqcF0WyMZH80uMsPycfpnl5uF7RkW8u
|
|
|
},
|
|
|
kube: clientfake.NewClientBuilder().WithObjects(clientSecret).Build(),
|
|
|
},
|
|
|
+ "valid token via value": {
|
|
|
+ provider: &esv1.SecretServerProvider{
|
|
|
+ Token: makeSecretRefUsingValue(tokenValue),
|
|
|
+ ServerURL: validProvider.ServerURL,
|
|
|
+ },
|
|
|
+ kube: clientfake.NewClientBuilder().Build(),
|
|
|
+ },
|
|
|
+ "valid token via secret ref": {
|
|
|
+ provider: &esv1.SecretServerProvider{
|
|
|
+ Token: makeSecretRefUsingRef(tokenSecret.Name, tokenKey),
|
|
|
+ ServerURL: validProvider.ServerURL,
|
|
|
+ },
|
|
|
+ kube: clientfake.NewClientBuilder().WithObjects(tokenSecret).Build(),
|
|
|
+ },
|
|
|
+ "dangling token ref": {
|
|
|
+ provider: &esv1.SecretServerProvider{
|
|
|
+ Token: makeSecretRefUsingRef("typo", tokenKey),
|
|
|
+ ServerURL: validProvider.ServerURL,
|
|
|
+ },
|
|
|
+ kube: clientfake.NewClientBuilder().WithObjects(tokenSecret).Build(),
|
|
|
+ errCheck: func(t *testing.T, err error) {
|
|
|
+ assert.True(t, kubeErrors.IsNotFound(err))
|
|
|
+ },
|
|
|
+ },
|
|
|
"cluster secret store": {
|
|
|
store: &esv1.ClusterSecretStore{
|
|
|
TypeMeta: metav1.TypeMeta{Kind: esv1.ClusterSecretStoreKind},
|
|
|
@@ -508,8 +607,11 @@ QJ85ioEpy00NioqcF0WyMZH80uMsPycfpnl5uF7RkW8u
|
|
|
Username: userNameValue,
|
|
|
Password: passwordValue,
|
|
|
}
|
|
|
- if name == "cluster secret store with domain" {
|
|
|
+ switch name {
|
|
|
+ case "cluster secret store with domain":
|
|
|
expectedCredentials.Domain = domain
|
|
|
+ case "valid token via value", "valid token via secret ref":
|
|
|
+ expectedCredentials = server.UserCredential{Token: tokenValue}
|
|
|
}
|
|
|
assert.Equal(t, expectedCredentials, secretServerClient.Configuration.Credentials)
|
|
|
} else {
|
|
|
@@ -628,6 +730,37 @@ func TestValidateStoreSecretRef(t *testing.T) {
|
|
|
}
|
|
|
}
|
|
|
|
|
|
+// TestLoadConfigSecretReferentValidation ensures the credential-loading path
|
|
|
+// enforces esutils referent validation. A namespaced SecretStore must not be
|
|
|
+// able to resolve a secret that lives in a different namespace, even if that
|
|
|
+// secret exists — this is the exfiltration guard.
|
|
|
+func TestLoadConfigSecretReferentValidation(t *testing.T) {
|
|
|
+ const storeNamespace = "default"
|
|
|
+ const otherNamespace = "other-ns"
|
|
|
+
|
|
|
+ // A secret in a namespace the store must NOT be allowed to read.
|
|
|
+ stolenSecret := &corev1.Secret{
|
|
|
+ ObjectMeta: metav1.ObjectMeta{Name: "stolen", Namespace: otherNamespace},
|
|
|
+ Data: map[string][]byte{"token": []byte("super-secret")},
|
|
|
+ }
|
|
|
+ kube := clientfake.NewClientBuilder().WithObjects(stolenSecret).Build()
|
|
|
+
|
|
|
+ // Namespaced SecretStore living in storeNamespace.
|
|
|
+ store := &esv1.SecretStore{
|
|
|
+ TypeMeta: metav1.TypeMeta{Kind: esv1.SecretStoreKind},
|
|
|
+ ObjectMeta: metav1.ObjectMeta{Namespace: storeNamespace},
|
|
|
+ }
|
|
|
+
|
|
|
+ // Token ref that tries to reach into the other namespace.
|
|
|
+ ref := makeSecretRefUsingNamespacedRef(otherNamespace, stolenSecret.Name, "token")
|
|
|
+
|
|
|
+ val, err := loadConfigSecret(context.Background(), store, ref, kube, storeNamespace)
|
|
|
+
|
|
|
+ assert.Error(t, err)
|
|
|
+ assert.Empty(t, val, "guard must block resolution before the value is read")
|
|
|
+ assert.ErrorContains(t, err, "namespace should either be empty or match")
|
|
|
+}
|
|
|
+
|
|
|
// TestCapabilities tests the Capabilities function.
|
|
|
func TestCapabilities(t *testing.T) {
|
|
|
tests := map[string]struct {
|