VYPR
Medium severity6.8NVD Advisory· Published Apr 9, 2026· Updated May 13, 2026

CVE-2026-39961

CVE-2026-39961

Description

Aiven Operator allows you to provision and manage Aiven Services from your Kubernetes cluster. From 0.31.0 to before 0.37.0, a developer with create permission on ClickhouseUser CRDs in their own namespace can exfiltrate secrets from any other namespace — production database credentials, API keys, service tokens — with a single kubectl apply. The operator reads the victim's secret using its ClusterRole and writes the password into a new secret in the attacker's namespace. The operator acts as a confused deputy: its ServiceAccount has cluster-wide secret read/write (aiven-operator-role ClusterRole), and it trusts user-supplied namespace values in spec.connInfoSecretSource.namespace without validation. No admission webhook enforces this boundary — the ServiceUser webhook returns nil, and no ClickhouseUser webhook exists. This vulnerability is fixed in 0.37.0.

Affected packages

Versions sourced from the GitHub Security Advisory.

PackageAffected versionsPatched versions
github.com/aiven/aiven-operatorGo
>= 0.31.0, < 0.37.00.37.0

Affected products

2
  • Aiven/Aiven Operatorreferences2 versions
    (expand)+ 1 more
    • (no CPE)
    • cpe:2.3:a:aiven:aiven_operator:*:*:*:*:*:*:*:*range: >=0.31.0,<0.37.0

Patches

1
032c9ba63257

Merge commit from fork

https://github.com/aiven/aiven-operatorGuillaume WinterApr 9, 2026via ghsa
16 files changed · +47 393
  • api/v1alpha1/common.go+2 3 modified
    @@ -46,10 +46,9 @@ type ConnInfoSecretTarget struct {
     type ConnInfoSecretSource struct {
     	// +kubebuilder:validation:Required
     	// +kubebuilder:validation:MinLength=1
    -	// Name of the secret resource to read connection parameters from
    +	// Name of the secret resource to read connection parameters from.
    +	// The secret must be in the same namespace as the resource.
     	Name string `json:"name"`
    -	// Namespace of the source secret. If not specified, defaults to the same namespace as the resource
    -	Namespace string `json:"namespace,omitempty"`
     	// +kubebuilder:validation:Required
     	// +kubebuilder:validation:MinLength=1
     	// Key in the secret containing the password to use for authentication
    
  • CHANGELOG.md+3 0 modified
    @@ -5,6 +5,9 @@
     - Add `ServiceUser` field `accessControl`, type `object`: AccessControl configures service-specific access control rules for the user.
     When this block is present, the operator manages the full access-control scope it contains
     - Add `OpenSearchACLConfig` to manage OpenSearch ACL
    +- **BREAKING**: Removed `ClickhouseUser`/`ServiceUser` field `connInfoSecretSource.namespace`, type `string`: cross-namespace
    +  secret references are no longer supported. The source secret must be in the same namespace as the resource.
    +  This fixes a potential confused deputy vulnerability where the operator could be exploited to exfiltrate secrets from other namespaces.
     
     ## v0.36.0 - 2026-03-05
     
    
  • charts/aiven-operator-crds/templates/aiven.io_clickhouseusers.yaml+3 8 modified
    @@ -77,16 +77,11 @@ spec:
                         when the secret data is updated.
                       properties:
                         name:
    -                      description:
    -                        Name of the secret resource to read connection parameters
    -                        from
    +                      description: |-
    +                        Name of the secret resource to read connection parameters from.
    +                        The secret must be in the same namespace as the resource.
                           minLength: 1
                           type: string
    -                    namespace:
    -                      description:
    -                        Namespace of the source secret. If not specified,
    -                        defaults to the same namespace as the resource
    -                      type: string
                         passwordKey:
                           description:
                             Key in the secret containing the password to use
    
  • charts/aiven-operator-crds/templates/aiven.io_serviceusers.yaml+3 8 modified
    @@ -110,16 +110,11 @@ spec:
                         when the secret data is updated.
                       properties:
                         name:
    -                      description:
    -                        Name of the secret resource to read connection parameters
    -                        from
    +                      description: |-
    +                        Name of the secret resource to read connection parameters from.
    +                        The secret must be in the same namespace as the resource.
                           minLength: 1
                           type: string
    -                    namespace:
    -                      description:
    -                        Namespace of the source secret. If not specified,
    -                        defaults to the same namespace as the resource
    -                      type: string
                         passwordKey:
                           description:
                             Key in the secret containing the password to use
    
  • config/crd/bases/aiven.io_clickhouseusers.yaml+3 8 modified
    @@ -77,16 +77,11 @@ spec:
                         when the secret data is updated.
                       properties:
                         name:
    -                      description:
    -                        Name of the secret resource to read connection parameters
    -                        from
    +                      description: |-
    +                        Name of the secret resource to read connection parameters from.
    +                        The secret must be in the same namespace as the resource.
                           minLength: 1
                           type: string
    -                    namespace:
    -                      description:
    -                        Namespace of the source secret. If not specified,
    -                        defaults to the same namespace as the resource
    -                      type: string
                         passwordKey:
                           description:
                             Key in the secret containing the password to use
    
  • config/crd/bases/aiven.io_serviceusers.yaml+3 8 modified
    @@ -110,16 +110,11 @@ spec:
                         when the secret data is updated.
                       properties:
                         name:
    -                      description:
    -                        Name of the secret resource to read connection parameters
    -                        from
    +                      description: |-
    +                        Name of the secret resource to read connection parameters from.
    +                        The secret must be in the same namespace as the resource.
                           minLength: 1
                           type: string
    -                    namespace:
    -                      description:
    -                        Namespace of the source secret. If not specified,
    -                        defaults to the same namespace as the resource
    -                      type: string
                         passwordKey:
                           description:
                             Key in the secret containing the password to use
    
  • controllers/secret_password_manager.go+5 8 modified
    @@ -25,31 +25,28 @@ func GetPasswordFromSecret(ctx context.Context, k8sClient client.Client, resourc
     		return "", nil
     	}
     
    -	sourceNamespace := secretSource.Namespace
    -	if sourceNamespace == "" {
    -		sourceNamespace = resource.GetNamespace()
    -	}
    +	ns := resource.GetNamespace()
     
     	sourceSecret := &corev1.Secret{}
     	err := k8sClient.Get(ctx, types.NamespacedName{
     		Name:      secretSource.Name,
    -		Namespace: sourceNamespace,
    +		Namespace: ns,
     	}, sourceSecret)
     	if err != nil {
    -		return "", fmt.Errorf("failed to read connInfoSecretSource %s/%s: %w", sourceNamespace, secretSource.Name, err)
    +		return "", fmt.Errorf("failed to read connInfoSecretSource %s/%s: %w", ns, secretSource.Name, err)
     	}
     
     	passwordBytes, exists := sourceSecret.Data[secretSource.PasswordKey]
     	if !exists {
    -		return "", fmt.Errorf("password not found in source secret %s/%s (expected %s key)", sourceNamespace, secretSource.Name, secretSource.PasswordKey)
    +		return "", fmt.Errorf("password not found in source secret %s/%s (expected %s key)", ns, secretSource.Name, secretSource.PasswordKey)
     	}
     
     	newPassword := string(passwordBytes)
     
     	// validate password length according to API requirements
     	if len(newPassword) < 8 || len(newPassword) > 256 {
     		return "", fmt.Errorf("password length must be between 8 and 256 characters, got %d characters from source secret %s/%s (key: %s)",
    -			len(newPassword), sourceNamespace, secretSource.Name, secretSource.PasswordKey)
    +			len(newPassword), ns, secretSource.Name, secretSource.PasswordKey)
     	}
     
     	return newPassword, nil
    
  • controllers/secret_password_manager_test.go+21 80 modified
    @@ -53,26 +53,6 @@ func TestPasswordManager_GetPasswordFromSecret(t *testing.T) {
     			expectedResult: "ValidPassword123!",
     			expectError:    false,
     		},
    -		{
    -			name: "Valid password from secret in different namespace",
    -			secretSource: &v1alpha1.ConnInfoSecretSource{
    -				Name:        "test-secret",
    -				Namespace:   "other-ns",
    -				PasswordKey: "PASSWORD",
    -			},
    -			resourceNS: "default",
    -			secret: &corev1.Secret{
    -				ObjectMeta: metav1.ObjectMeta{
    -					Name:      "test-secret",
    -					Namespace: "other-ns",
    -				},
    -				Data: map[string][]byte{
    -					"PASSWORD": []byte("CrossNSPassword123!"),
    -				},
    -			},
    -			expectedResult: "CrossNSPassword123!",
    -			expectError:    false,
    -		},
     		{
     			name: "Secret not found",
     			secretSource: &v1alpha1.ConnInfoSecretSource{
    @@ -256,72 +236,33 @@ func TestPasswordManager_NamespaceResolution(t *testing.T) {
     	require.NoError(t, corev1.AddToScheme(scheme))
     	require.NoError(t, v1alpha1.AddToScheme(scheme))
     
    -	tests := []struct {
    -		name         string
    -		resourceNS   string
    -		sourceNS     string
    -		expectedNS   string
    -		secretExists bool
    -	}{
    -		{
    -			name:         "Uses resource namespace when source namespace is empty",
    -			resourceNS:   "resource-ns",
    -			sourceNS:     "",
    -			expectedNS:   "resource-ns",
    -			secretExists: true,
    +	// Secret must always be in the same namespace as the resource
    +	secret := &corev1.Secret{
    +		ObjectMeta: metav1.ObjectMeta{
    +			Name:      "test-secret",
    +			Namespace: "resource-ns",
     		},
    -		{
    -			name:         "Uses source namespace when specified",
    -			resourceNS:   "resource-ns",
    -			sourceNS:     "source-ns",
    -			expectedNS:   "source-ns",
    -			secretExists: true,
    +		Data: map[string][]byte{
    +			"PASSWORD": []byte("ValidPassword123!"),
     		},
     	}
     
    -	for _, tt := range tests {
    -		t.Run(tt.name, func(t *testing.T) {
    -			var objects []client.Object
    -			if tt.secretExists {
    -				secret := &corev1.Secret{
    -					ObjectMeta: metav1.ObjectMeta{
    -						Name:      "test-secret",
    -						Namespace: tt.expectedNS,
    -					},
    -					Data: map[string][]byte{
    -						"PASSWORD": []byte("ValidPassword123!"),
    -					},
    -				}
    -				objects = append(objects, secret)
    -			}
    -
    -			secretSource := &v1alpha1.ConnInfoSecretSource{
    +	user := &v1alpha1.ClickhouseUser{
    +		ObjectMeta: metav1.ObjectMeta{
    +			Name:      "test-user",
    +			Namespace: "resource-ns",
    +		},
    +		Spec: v1alpha1.ClickhouseUserSpec{
    +			ConnInfoSecretSource: &v1alpha1.ConnInfoSecretSource{
     				Name:        "test-secret",
     				PasswordKey: "PASSWORD",
    -			}
    -			if tt.sourceNS != "" {
    -				secretSource.Namespace = tt.sourceNS
    -			}
    -
    -			user := &v1alpha1.ClickhouseUser{
    -				ObjectMeta: metav1.ObjectMeta{
    -					Name:      "test-user",
    -					Namespace: tt.resourceNS,
    -				},
    -				Spec: v1alpha1.ClickhouseUserSpec{
    -					ConnInfoSecretSource: secretSource,
    -				},
    -			}
    +			},
    +		},
    +	}
     
    -			k8sClient := fake.NewClientBuilder().WithScheme(scheme).WithObjects(objects...).Build()
    -			result, err := GetPasswordFromSecret(context.Background(), k8sClient, user)
    +	k8sClient := fake.NewClientBuilder().WithScheme(scheme).WithObjects(secret).Build()
    +	result, err := GetPasswordFromSecret(context.Background(), k8sClient, user)
     
    -			if tt.secretExists {
    -				require.NoError(t, err)
    -				assert.Equal(t, "ValidPassword123!", result)
    -			} else {
    -				require.Error(t, err)
    -			}
    -		})
    -	}
    +	require.NoError(t, err)
    +	assert.Equal(t, "ValidPassword123!", result)
     }
    
  • controllers/secret_watch_controller.go+1 6 modified
    @@ -112,12 +112,7 @@ func (c *SecretWatchController) getResourcesWithSecretSource() []SecretSourceRes
     func connInfoSecretRefIndexFunc(o client.Object) []string {
     	if resource, ok := o.(SecretSourceResource); ok {
     		if secretSource := resource.GetConnInfoSecretSource(); secretSource != nil {
    -			sourceNamespace := secretSource.Namespace
    -			if sourceNamespace == "" {
    -				sourceNamespace = resource.GetNamespace()
    -			}
    -
    -			return []string{fmt.Sprintf("%s/%s", sourceNamespace, secretSource.Name)}
    +			return []string{fmt.Sprintf("%s/%s", resource.GetNamespace(), secretSource.Name)}
     		}
     	}
     
    
  • controllers/secret_watch_controller_test.go+1 63 modified
    @@ -132,23 +132,6 @@ func TestConnInfoSecretRefIndexFunc(t *testing.T) {
     			},
     			expected: []string{"default/my-secret"},
     		},
    -		{
    -			name: "ServiceUser with secretSource in different namespace",
    -			resource: &v1alpha1.ServiceUser{
    -				ObjectMeta: metav1.ObjectMeta{
    -					Name:      "test-user",
    -					Namespace: "default",
    -				},
    -				Spec: v1alpha1.ServiceUserSpec{
    -					ConnInfoSecretSource: &v1alpha1.ConnInfoSecretSource{
    -						Name:        "my-secret",
    -						Namespace:   "other-namespace",
    -						PasswordKey: "password",
    -					},
    -				},
    -			},
    -			expected: []string{"other-namespace/my-secret"},
    -		},
     		{
     			name: "ServiceUser without secretSource",
     			resource: &v1alpha1.ServiceUser{
    @@ -178,23 +161,6 @@ func TestConnInfoSecretRefIndexFunc(t *testing.T) {
     			},
     			expected: []string{"default/ch-secret"},
     		},
    -		{
    -			name: "ClickhouseUser with cross-namespace secretSource",
    -			resource: &v1alpha1.ClickhouseUser{
    -				ObjectMeta: metav1.ObjectMeta{
    -					Name:      "test-ch-user",
    -					Namespace: "app-namespace",
    -				},
    -				Spec: v1alpha1.ClickhouseUserSpec{
    -					ConnInfoSecretSource: &v1alpha1.ConnInfoSecretSource{
    -						Name:        "shared-secret",
    -						Namespace:   "secrets-namespace",
    -						PasswordKey: "password",
    -					},
    -				},
    -			},
    -			expected: []string{"secrets-namespace/shared-secret"},
    -		},
     		{
     			name: "Non-SecretSourceResource",
     			resource: &corev1.Secret{
    @@ -246,29 +212,6 @@ func TestSecretWatchController_resourceMatchesSecret(t *testing.T) {
     			},
     			expected: true,
     		},
    -		{
    -			name: "ServiceUser matches secret in different namespace",
    -			resource: &v1alpha1.ServiceUser{
    -				ObjectMeta: metav1.ObjectMeta{
    -					Name:      "test-user",
    -					Namespace: "app-ns",
    -				},
    -				Spec: v1alpha1.ServiceUserSpec{
    -					ConnInfoSecretSource: &v1alpha1.ConnInfoSecretSource{
    -						Name:        "my-secret",
    -						Namespace:   "secret-ns",
    -						PasswordKey: "password",
    -					},
    -				},
    -			},
    -			secret: &corev1.Secret{
    -				ObjectMeta: metav1.ObjectMeta{
    -					Name:      "my-secret",
    -					Namespace: "secret-ns",
    -				},
    -			},
    -			expected: true,
    -		},
     		{
     			name: "ServiceUser does not match different secret name",
     			resource: &v1alpha1.ServiceUser{
    @@ -342,12 +285,7 @@ func TestSecretWatchController_resourceMatchesSecret(t *testing.T) {
     				return
     			}
     
    -			sourceNamespace := secretSource.Namespace
    -			if sourceNamespace == "" {
    -				sourceNamespace = tt.resource.GetNamespace()
    -			}
    -
    -			matches := secretSource.Name == tt.secret.Name && sourceNamespace == tt.secret.Namespace
    +			matches := secretSource.Name == tt.secret.Name && tt.resource.GetNamespace() == tt.secret.Namespace
     			assert.Equal(t, tt.expected, matches)
     		})
     	}
    
  • docs/docs/resources/clickhouseuser.md+1 5 modified
    @@ -62,7 +62,6 @@ This resource uses the following API operations, and for each operation, _any_ o
           # Use existing secret for credential management
           connInfoSecretSource:
             name: predefined-credentials
    -        # namespace: my-namespace  # Optional: defaults to same namespace as ClickhouseUser
             passwordKey: PASSWORD
         
           project: aiven-project-name
    @@ -205,12 +204,9 @@ when the secret data is updated.
     **Required**
     
     - [`name`](#spec.connInfoSecretSource.name-property){: name='spec.connInfoSecretSource.name-property'} (string, MinLength: 1). Name of the secret resource to read connection parameters from.
    +    The secret must be in the same namespace as the resource.
     - [`passwordKey`](#spec.connInfoSecretSource.passwordKey-property){: name='spec.connInfoSecretSource.passwordKey-property'} (string, MinLength: 1). Key in the secret containing the password to use for authentication.
     
    -**Optional**
    -
    -- [`namespace`](#spec.connInfoSecretSource.namespace-property){: name='spec.connInfoSecretSource.namespace-property'} (string). Namespace of the source secret. If not specified, defaults to the same namespace as the resource.
    -
     ## connInfoSecretTarget {: #spec.connInfoSecretTarget }
     
     _Appears on [`spec`](#spec)._
    
  • docs/docs/resources/examples/clickhouseuser.custom_credentials.yaml+0 1 modified
    @@ -32,7 +32,6 @@ spec:
       # Use existing secret for credential management
       connInfoSecretSource:
         name: predefined-credentials
    -    # namespace: my-namespace  # Optional: defaults to same namespace as ClickhouseUser
         passwordKey: PASSWORD
     
       project: aiven-project-name
    
  • docs/docs/resources/examples/serviceuser.custom_credentials.yaml+0 1 modified
    @@ -55,7 +55,6 @@ spec:
       # Use existing secret for credential management
       connInfoSecretSource:
         name: predefined-credentials
    -    # namespace: my-namespace  # Optional: defaults to same namespace as ServiceUser
         passwordKey: PASSWORD
     
       project: aiven-project-name
    
  • docs/docs/resources/serviceuser.md+1 5 modified
    @@ -86,7 +86,6 @@ This resource uses the following API operations, and for each operation, _any_ o
           # Use existing secret for credential management
           connInfoSecretSource:
             name: predefined-credentials
    -        # namespace: my-namespace  # Optional: defaults to same namespace as ServiceUser
             passwordKey: PASSWORD
         
           project: aiven-project-name
    @@ -251,12 +250,9 @@ when the secret data is updated.
     **Required**
     
     - [`name`](#spec.connInfoSecretSource.name-property){: name='spec.connInfoSecretSource.name-property'} (string, MinLength: 1). Name of the secret resource to read connection parameters from.
    +    The secret must be in the same namespace as the resource.
     - [`passwordKey`](#spec.connInfoSecretSource.passwordKey-property){: name='spec.connInfoSecretSource.passwordKey-property'} (string, MinLength: 1). Key in the secret containing the password to use for authentication.
     
    -**Optional**
    -
    -- [`namespace`](#spec.connInfoSecretSource.namespace-property){: name='spec.connInfoSecretSource.namespace-property'} (string). Namespace of the source secret. If not specified, defaults to the same namespace as the resource.
    -
     ## connInfoSecretTarget {: #spec.connInfoSecretTarget }
     
     _Appears on [`spec`](#spec)._
    
  • tests/clickhouseuser_test.go+0 83 modified
    @@ -443,52 +443,6 @@ func TestClickhouseUserCustomCredentials(t *testing.T) {
     		}))
     	})
     
    -	t.Run("CrossNamespacePasswordSource", func(t *testing.T) {
    -		// tests ClickhouseUser creation with a password from a different namespace
    -		userName := randName("chu-cross-ns")
    -		yml := getClickhouseUserWithCrossNamespaceSecretYaml(cfg.Project, chName, userName, cfg.PrimaryCloudName)
    -
    -		require.NoError(t, s.Apply(yml))
    -
    -		user := new(v1alpha1.ClickhouseUser)
    -		require.NoError(t, s.GetRunning(user, userName))
    -
    -		userAvn, err := getClickHouseUserByID(ctx, avnGen, cfg.Project, chName, user.Status.UUID)
    -		require.NoError(t, err)
    -		assert.Equal(t, userName, user.GetName())
    -		assert.Equal(t, userName, userAvn.Name)
    -		assert.Equal(t, chName, user.Spec.ServiceName)
    -
    -		secretName := fmt.Sprintf("my-clickhouse-user-secret-%s", userName)
    -		secret, err := s.GetSecret(secretName)
    -		require.NoError(t, err)
    -		assert.NotEmpty(t, secret.Data["HOST"])
    -		assert.NotEmpty(t, secret.Data["PORT"])
    -		assert.NotEmpty(t, secret.Data["USERNAME"])
    -		assert.NotEmpty(t, secret.Data["PASSWORD"])
    -
    -		// verify the password matches cross-namespace secret value
    -		actualPassword := string(secret.Data["PASSWORD"])
    -		assert.Equal(t, "CrossNamespacePassword456!", actualPassword, "Password should match cross-namespace secret value")
    -
    -		assert.Equal(t, map[string]string{"test": "cross-namespace-password"}, secret.Annotations)
    -		assert.Equal(t, map[string]string{"type": "cross-namespace"}, secret.Labels)
    -
    -		// test ClickHouse connection with cross-namespace password
    -		assert.NoError(t, pingClickhouse(
    -			ctx,
    -			secret.Data["CLICKHOUSEUSER_HOST"],
    -			secret.Data["CLICKHOUSEUSER_PORT"],
    -			secret.Data["CLICKHOUSEUSER_USERNAME"],
    -			secret.Data["CLICKHOUSEUSER_PASSWORD"],
    -		))
    -
    -		assert.NoError(t, s.Delete(user, func() error {
    -			_, err = getClickHouseUserByID(ctx, avnGen, cfg.Project, chName, user.Status.UUID)
    -			return err
    -		}))
    -	})
    -
     	t.Run("InvalidPasswordValidation", func(t *testing.T) {
     		// tests validation of invalid passwords in connInfoSecretSource
     		userName := randName("chu-invalid-pass")
    @@ -746,43 +700,6 @@ spec:
     `, project, chName, userName, cloudName)
     }
     
    -func getClickhouseUserWithCrossNamespaceSecretYaml(project, chName, userName, cloudName string) string {
    -	return fmt.Sprintf(`
    -apiVersion: v1
    -kind: Secret
    -metadata:
    -  name: cross-namespace-password-secret-%[3]s
    -  namespace: kube-system
    -data:
    -  PASSWORD: Q3Jvc3NOYW1lc3BhY2VQYXNzd29yZDQ1NiE= # CrossNamespacePassword456! base64 encoded # gitleaks:allow
    ----
    -
    -apiVersion: aiven.io/v1alpha1
    -kind: ClickhouseUser
    -metadata:
    -  name: %[3]s
    -spec:
    -  authSecretRef:
    -    name: aiven-token
    -    key: token
    -
    -  connInfoSecretTarget:
    -    name: my-clickhouse-user-secret-%[3]s
    -    annotations:
    -      test: cross-namespace-password
    -    labels:
    -      type: cross-namespace
    -
    -  connInfoSecretSource:
    -    name: cross-namespace-password-secret-%[3]s
    -    namespace: kube-system
    -    passwordKey: PASSWORD
    -
    -  project: %[1]s
    -  serviceName: %[2]s
    -`, project, chName, userName, cloudName)
    -}
    -
     func getClickhouseUserWithInvalidPasswordYaml(project, chName, userName, cloudName string) string {
     	return fmt.Sprintf(`
     apiVersion: v1
    
  • tests/serviceuser_watcher_test.go+0 106 modified
    @@ -3,15 +3,13 @@
     package tests
     
     import (
    -	"context"
     	"fmt"
     	"testing"
     	"time"
     
     	"github.com/stretchr/testify/assert"
     	"github.com/stretchr/testify/require"
     	corev1 "k8s.io/api/core/v1"
    -	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
     	"k8s.io/apimachinery/pkg/types"
     
     	"github.com/aiven/aiven-operator/api/v1alpha1"
    @@ -211,110 +209,6 @@ spec:
     `, project, serviceName, userName, secretName, targetSecretName)
     }
     
    -// TestCrossNamespaceSecretWatch tests secret watching across namespaces
    -func TestCrossNamespaceSecretWatch(t *testing.T) {
    -	defer recoverPanic(t)
    -
    -	// GIVEN
    -	ctx, cancel := testCtx()
    -	defer cancel()
    -
    -	pg, release, err := sharedResources.AcquirePostgreSQL(ctx)
    -	require.NoError(t, err)
    -	defer release()
    -
    -	serviceName := pg.GetName()
    -	userName := randName("cross-ns-user")
    -	secretName := randName("cross-ns-secret")
    -	secretNamespace := "test-secrets"
    -
    -	testNs := &corev1.Namespace{
    -		ObjectMeta: metav1.ObjectMeta{Name: secretNamespace},
    -	}
    -	err = k8sClient.Create(ctx, testNs)
    -	require.NoError(t, err)
    -
    -	defer func() {
    -		deleteCtx, deleteCancel := context.WithTimeout(context.Background(), 30*time.Second)
    -		defer deleteCancel()
    -		_ = k8sClient.Delete(deleteCtx, testNs)
    -	}()
    -
    -	yml := fmt.Sprintf(`
    -apiVersion: v1
    -kind: Secret
    -metadata:
    -  name: %[4]s
    -  namespace: %[5]s
    -data:
    -  password: Y3Jvc3MtbnMtcGFzc3dvcmQtMTIzNDU= # cross-ns-password-12345 # gitleaks:allow
    ----
    -apiVersion: aiven.io/v1alpha1
    -kind: ServiceUser
    -metadata:
    -  name: %[3]s
    -spec:
    -  authSecretRef:
    -    name: aiven-token
    -    key: token
    -
    -  connInfoSecretTarget:
    -    name: cross-ns-service-user-secret
    -
    -  connInfoSecretSource:
    -    name: %[4]s
    -    namespace: %[5]s
    -    passwordKey: password
    -
    -  project: %[1]s
    -  serviceName: %[2]s
    -`, cfg.Project, serviceName, userName, secretName, secretNamespace)
    -
    -	s := NewSession(ctx, k8sClient)
    -	defer s.Destroy(t)
    -
    -	// WHEN
    -	require.NoError(t, s.Apply(yml))
    -
    -	// wait for ServiceUser to be running
    -	user := new(v1alpha1.ServiceUser)
    -	require.NoError(t, s.GetRunning(user, userName))
    -
    -	// THEN
    -	userAvn, err := avnGen.ServiceUserGet(ctx, cfg.Project, serviceName, userName)
    -	require.NoError(t, err)
    -	assert.Equal(t, userName, userAvn.Username)
    -
    -	initialPassword := userAvn.Password
    -
    -	secret := &corev1.Secret{}
    -	err = k8sClient.Get(ctx, types.NamespacedName{
    -		Name:      secretName,
    -		Namespace: secretNamespace,
    -	}, secret)
    -	require.NoError(t, err)
    -
    -	secret.Data["password"] = []byte("updated-cross-ns-password-67890")
    -	err = k8sClient.Update(ctx, secret)
    -	require.NoError(t, err)
    -
    -	// wait for the password to be updated in Aiven
    -	require.Eventually(t, func() bool {
    -		finalUserAvn, err := avnGen.ServiceUserGet(ctx, cfg.Project, serviceName, userName)
    -		if err != nil {
    -			return false
    -		}
    -		return finalUserAvn.Password == "updated-cross-ns-password-67890"
    -	}, 1*time.Minute, 5*time.Second, "Cross-namespace secret watcher should trigger password update")
    -
    -	finalUserAvn, err := avnGen.ServiceUserGet(ctx, cfg.Project, serviceName, userName)
    -	require.NoError(t, err)
    -	assert.Equal(t, userName, finalUserAvn.Username)
    -
    -	assert.Equal(t, "updated-cross-ns-password-67890", finalUserAvn.Password, "Cross-namespace password should be updated to the new value from the secret")
    -	assert.NotEqual(t, initialPassword, finalUserAvn.Password, "Cross-namespace password should have changed from initial value")
    -}
    -
     // getUpdatedServiceUserAndSecretYaml returns YAML with both secret and ServiceUser changes
     // This simulates a user doing "kubectl apply -f config.yaml" with multiple resource changes
     func getUpdatedServiceUserAndSecretYaml(project, serviceName, userName, secretName, targetSecretName string) string {
    

Vulnerability mechanics

Generated by null/stub on May 9, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.

References

5

News mentions

0

No linked articles in our index yet.