Skip to content

Commit 4c7eb5e

Browse files
committed
test: add e2e test for credential rotation
Add an e2e test that verifies the ObjectStore controller updates the RBAC Role when an ObjectStore's secret reference changes. The test creates a Cluster with a MinIO ObjectStore, verifies the Role has the cluster label and references the original secret, then updates the ObjectStore to point to a new secret and waits for the Role to be patched accordingly. Signed-off-by: Marco Nenciarini <marco.nenciarini@enterprisedb.com>
1 parent 0475daa commit 4c7eb5e

2 files changed

Lines changed: 172 additions & 0 deletions

File tree

test/e2e/e2e_suite_test.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ import (
3737
"github.com/cloudnative-pg/plugin-barman-cloud/test/e2e/internal/kustomize"
3838

3939
_ "github.com/cloudnative-pg/plugin-barman-cloud/test/e2e/internal/tests/backup"
40+
_ "github.com/cloudnative-pg/plugin-barman-cloud/test/e2e/internal/tests/credentialrotation"
4041
_ "github.com/cloudnative-pg/plugin-barman-cloud/test/e2e/internal/tests/replicacluster"
4142

4243
. "github.com/onsi/ginkgo/v2"
Lines changed: 171 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,171 @@
1+
/*
2+
Copyright © contributors to CloudNativePG, established as
3+
CloudNativePG a Series of LF Projects, LLC.
4+
5+
Licensed under the Apache License, Version 2.0 (the "License");
6+
you may not use this file except in compliance with the License.
7+
You may obtain a copy of the License at
8+
9+
http://www.apache.org/licenses/LICENSE-2.0
10+
11+
Unless required by applicable law or agreed to in writing, software
12+
distributed under the License is distributed on an "AS IS" BASIS,
13+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
See the License for the specific language governing permissions and
15+
limitations under the License.
16+
17+
SPDX-License-Identifier: Apache-2.0
18+
*/
19+
20+
package credentialrotation
21+
22+
import (
23+
"time"
24+
25+
cloudnativepgv1 "github.com/cloudnative-pg/api/pkg/api/v1"
26+
corev1 "k8s.io/api/core/v1"
27+
rbacv1 "k8s.io/api/rbac/v1"
28+
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
29+
"k8s.io/apimachinery/pkg/types"
30+
"k8s.io/utils/ptr"
31+
"sigs.k8s.io/controller-runtime/pkg/client"
32+
33+
"github.com/cloudnative-pg/plugin-barman-cloud/internal/cnpgi/metadata"
34+
"github.com/cloudnative-pg/plugin-barman-cloud/internal/cnpgi/operator/specs"
35+
internalClient "github.com/cloudnative-pg/plugin-barman-cloud/test/e2e/internal/client"
36+
internalCluster "github.com/cloudnative-pg/plugin-barman-cloud/test/e2e/internal/cluster"
37+
nmsp "github.com/cloudnative-pg/plugin-barman-cloud/test/e2e/internal/namespace"
38+
"github.com/cloudnative-pg/plugin-barman-cloud/test/e2e/internal/objectstore"
39+
40+
. "github.com/onsi/ginkgo/v2"
41+
. "github.com/onsi/gomega"
42+
)
43+
44+
const (
45+
clusterName = "source"
46+
objectStoreName = "source"
47+
oldSecretName = "minio"
48+
newSecretName = "minio-rotated"
49+
)
50+
51+
var _ = Describe("Credential rotation", func() {
52+
var namespace *corev1.Namespace
53+
var cl client.Client
54+
55+
BeforeEach(func(ctx SpecContext) {
56+
var err error
57+
cl, _, err = internalClient.NewClient()
58+
Expect(err).NotTo(HaveOccurred())
59+
namespace, err = nmsp.CreateUniqueNamespace(ctx, cl, "cred-rotation")
60+
Expect(err).NotTo(HaveOccurred())
61+
})
62+
63+
AfterEach(func(ctx SpecContext) {
64+
Expect(cl.Delete(ctx, namespace)).To(Succeed())
65+
})
66+
67+
It("should update the Role when the ObjectStore secret reference changes", func(ctx SpecContext) {
68+
By("starting the ObjectStore deployment")
69+
resources := objectstore.NewMinioObjectStoreResources(namespace.Name, oldSecretName)
70+
Expect(resources.Create(ctx, cl)).To(Succeed())
71+
72+
By("creating the ObjectStore")
73+
store := objectstore.NewMinioObjectStore(namespace.Name, objectStoreName, oldSecretName)
74+
Expect(cl.Create(ctx, store)).To(Succeed())
75+
76+
By("creating the Cluster")
77+
cluster := newCluster(namespace.Name)
78+
Expect(cl.Create(ctx, cluster)).To(Succeed())
79+
80+
By("waiting for the Cluster to be ready")
81+
Eventually(func(g Gomega) {
82+
g.Expect(cl.Get(ctx, types.NamespacedName{
83+
Name: cluster.Name,
84+
Namespace: cluster.Namespace,
85+
}, cluster)).To(Succeed())
86+
g.Expect(internalCluster.IsReady(*cluster)).To(BeTrue())
87+
}).WithTimeout(10 * time.Minute).WithPolling(10 * time.Second).Should(Succeed())
88+
89+
roleKey := types.NamespacedName{
90+
Name: specs.GetRBACName(clusterName),
91+
Namespace: namespace.Name,
92+
}
93+
94+
By("verifying the Role has the cluster label and references the original secret")
95+
var role rbacv1.Role
96+
Expect(cl.Get(ctx, roleKey, &role)).To(Succeed())
97+
Expect(role.Labels).To(HaveKeyWithValue(metadata.ClusterLabelName, clusterName))
98+
Expect(secretNamesFromRole(&role)).To(ContainElement(oldSecretName))
99+
100+
By("creating a new secret with the same credentials")
101+
newSecret := &corev1.Secret{
102+
ObjectMeta: metav1.ObjectMeta{
103+
Name: newSecretName,
104+
Namespace: namespace.Name,
105+
},
106+
Data: map[string][]byte{
107+
"ACCESS_KEY_ID": []byte("minio"),
108+
"ACCESS_SECRET_KEY": []byte("minio123"),
109+
},
110+
}
111+
Expect(cl.Create(ctx, newSecret)).To(Succeed())
112+
113+
By("updating the ObjectStore to reference the new secret")
114+
Expect(cl.Get(ctx, types.NamespacedName{
115+
Name: objectStoreName,
116+
Namespace: namespace.Name,
117+
}, store)).To(Succeed())
118+
store.Spec.Configuration.BarmanCredentials.AWS.AccessKeyIDReference.Name = newSecretName
119+
store.Spec.Configuration.BarmanCredentials.AWS.SecretAccessKeyReference.Name = newSecretName
120+
Expect(cl.Update(ctx, store)).To(Succeed())
121+
122+
By("waiting for the Role to reference the new secret")
123+
Eventually(func(g Gomega) {
124+
g.Expect(cl.Get(ctx, roleKey, &role)).To(Succeed())
125+
g.Expect(secretNamesFromRole(&role)).To(ContainElement(newSecretName))
126+
g.Expect(secretNamesFromRole(&role)).NotTo(ContainElement(oldSecretName))
127+
}).WithTimeout(3 * time.Minute).WithPolling(5 * time.Second).Should(Succeed())
128+
})
129+
})
130+
131+
func newCluster(namespace string) *cloudnativepgv1.Cluster {
132+
return &cloudnativepgv1.Cluster{
133+
TypeMeta: metav1.TypeMeta{
134+
Kind: "Cluster",
135+
APIVersion: "postgresql.cnpg.io/v1",
136+
},
137+
ObjectMeta: metav1.ObjectMeta{
138+
Name: clusterName,
139+
Namespace: namespace,
140+
},
141+
Spec: cloudnativepgv1.ClusterSpec{
142+
Instances: 1,
143+
ImagePullPolicy: corev1.PullAlways,
144+
Plugins: []cloudnativepgv1.PluginConfiguration{
145+
{
146+
Name: "barman-cloud.cloudnative-pg.io",
147+
Parameters: map[string]string{
148+
"barmanObjectName": objectStoreName,
149+
},
150+
IsWALArchiver: ptr.To(true),
151+
},
152+
},
153+
StorageConfiguration: cloudnativepgv1.StorageConfiguration{
154+
Size: "1Gi",
155+
},
156+
},
157+
}
158+
}
159+
160+
func secretNamesFromRole(role *rbacv1.Role) []string {
161+
for _, rule := range role.Rules {
162+
if len(rule.APIGroups) == 1 &&
163+
rule.APIGroups[0] == "" &&
164+
len(rule.Resources) == 1 &&
165+
rule.Resources[0] == "secrets" {
166+
return rule.ResourceNames
167+
}
168+
}
169+
170+
return nil
171+
}

0 commit comments

Comments
 (0)