1 package providerctl_test
2
3 import (
4 "context"
5 "os"
6 "path/filepath"
7 "reflect"
8 "testing"
9 "time"
10
11 goext "github.com/external-secrets/external-secrets/apis/externalsecrets/v1beta1"
12 "github.com/stretchr/testify/assert"
13 "github.com/stretchr/testify/suite"
14 corev1 "k8s.io/api/core/v1"
15 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
16 "k8s.io/apimachinery/pkg/runtime"
17 "k8s.io/apimachinery/pkg/types"
18 utilruntime "k8s.io/apimachinery/pkg/util/runtime"
19 ctrl "sigs.k8s.io/controller-runtime"
20
21 edgeConditions "edge-infra.dev/pkg/k8s/runtime/conditions"
22 "edge-infra.dev/pkg/lib/fog"
23
24 "k8s.io/apimachinery/pkg/api/errors"
25 "sigs.k8s.io/controller-runtime/pkg/client"
26
27 "edge-infra.dev/test"
28 "edge-infra.dev/test/framework"
29 "edge-infra.dev/test/framework/k8s"
30
31 "edge-infra.dev/pkg/edge/constants"
32 api "edge-infra.dev/pkg/edge/iam/api/v1alpha1"
33 "edge-infra.dev/pkg/edge/iam/ctl/providerctl"
34 "edge-infra.dev/pkg/k8s/runtime/controller"
35 unstructuredutil "edge-infra.dev/pkg/k8s/unstructured"
36 "edge-infra.dev/test/framework/k8s/envtest"
37 )
38
39
40 type Suite struct {
41 *framework.Framework
42 *k8s.K8s
43 ctx context.Context
44 timeout time.Duration
45 tick time.Duration
46 }
47
48 func TestProviderEncryptionReconciler(t *testing.T) {
49
50 testEnv := envtest.Setup()
51 ctrl.SetLogger(fog.New())
52 cfg, opts := controller.ProcessOptions(controller.WithCfg(testEnv.Config), controller.WithMetricsAddress("0"))
53 opts.Scheme = createScheme()
54 mgr, err := ctrl.NewManager(cfg, opts)
55 if err != nil {
56 t.Errorf("unable to create manager %v", err)
57 }
58
59
60 cwd, err := os.Getwd()
61 if err != nil {
62 t.Errorf("error getting working directory: %v", err)
63 }
64
65
66 parentDir := filepath.Dir(cwd)
67 for i := 0; i < 4; i++ {
68 parentDir = filepath.Dir(parentDir)
69 }
70
71
72 os.Setenv("IAM_CLUSTER_ID", "123")
73 os.Setenv("IAM_ENCRYPTION_ENABLED", "true")
74 os.Setenv("IAM_ENV_TEST", "true")
75 os.Setenv("IAM_ENCRYPTION_KEY", "my-32bit-super-extra-secret-key!")
76 os.Setenv("IAM_ORGANIZATION_ID", "4efa46628b914711bce36939abcfd084")
77 os.Setenv("IAM_ORGANIZATION_NAME", "dev-ex")
78 os.Setenv("IAM_SITE_ID", "dev-site")
79
80 f := framework.New("providerctl").Component("providerctl")
81 s := &Suite{
82 Framework: f,
83 ctx: context.Background(),
84 timeout: 60 * time.Second,
85 tick: 50 * time.Millisecond,
86 }
87
88 resmaps, err := providerctl.CreateResmaps([]string{"test/test_manifests.yaml"})
89 assert.NoError(t, err)
90 assert.NotEmpty(t, resmaps)
91
92
93 resmaps["kind"] = resmaps["test"]
94
95
96 r := &providerctl.ProviderReconciler{
97 Name: "provider-controller",
98 Client: mgr.GetClient(),
99 Scheme: mgr.GetScheme(),
100 Resmaps: resmaps,
101 }
102 err = r.SetupWithManager(mgr)
103 test.NoError(err)
104
105 k := k8s.New(testEnv.Config, k8s.WithCtrlManager(mgr), k8s.WithKonfigKonnector())
106 s.K8s = k
107
108 f.Register(k)
109
110 suite.Run(t, s)
111
112 t.Cleanup(func() {
113 f.NoError(testEnv.Stop())
114 })
115 }
116
117
118 func (s *Suite) TestReconcileExternalSecretCreation() {
119 namespace := &corev1.Namespace{
120 TypeMeta: metav1.TypeMeta{
121 Kind: "Namespace",
122 APIVersion: "v1",
123 },
124 ObjectMeta: metav1.ObjectMeta{
125 Name: "edge-iam",
126 },
127 }
128 s.Require().NoError(s.Client.Create(s.ctx, namespace))
129
130
131 secret := &corev1.Secret{}
132 secret.ObjectMeta = metav1.ObjectMeta{
133 Name: "private-key-secret",
134 Namespace: "edge-iam",
135 }
136 secret.Data = map[string][]byte{
137 "private_key": []byte("key"),
138 "private_key_id": []byte("key-id"),
139 }
140 s.Require().NoError(s.Client.Create(s.ctx, secret))
141
142
143 challengeSecret := &corev1.Secret{}
144 challengeSecret.ObjectMeta = metav1.ObjectMeta{
145 Name: "challenge-secret",
146 Namespace: "edge-iam",
147 }
148 challengeSecret.Data = map[string][]byte{
149 "secret": []byte("secret"),
150 }
151 s.Require().NoError(s.Client.Create(s.ctx, challengeSecret))
152
153
154 provider := createProviderObj("1", "1")
155 s.Require().NoError(s.Client.Create(s.ctx, provider))
156
157
158 emptyStatus := provider.Status
159
160
161 var externalSecret = &goext.ExternalSecret{}
162 s.Require().Eventually(func() bool {
163 err := s.Client.Get(s.ctx, types.NamespacedName{
164 Name: providerctl.EncryptionKeySecretPrefix + "1",
165 Namespace: "edge-iam",
166 }, externalSecret)
167 return err == nil
168 }, s.timeout, s.tick, "expected external secret was never found")
169
170
171 providerObj := &api.Provider{}
172 s.Require().Eventually(func() bool {
173 err := s.Client.Get(s.ctx, types.NamespacedName{
174 Name: "provider",
175 Namespace: "edge-iam",
176 }, providerObj)
177
178 return !reflect.DeepEqual(providerObj.Status, emptyStatus) && err == nil
179 }, s.timeout, s.tick, "expected provider object with non-empty status was never found")
180
181
182 providerObj.Spec.Encryption.Version = "2"
183 s.Require().NoError(s.Client.Update(s.ctx, providerObj))
184
185
186 var externalSecret2 = &goext.ExternalSecret{}
187 s.Require().Eventually(func() bool {
188 err := s.Client.Get(s.ctx, types.NamespacedName{
189 Name: providerctl.EncryptionKeySecretPrefix + "2",
190 Namespace: "edge-iam",
191 }, externalSecret2)
192 return err == nil
193 }, s.timeout, s.tick, "expected second version of external secret was never found")
194
195
196 providerObj2 := &api.Provider{}
197 s.Require().Eventually(func() bool {
198 err := s.Client.Get(s.ctx, types.NamespacedName{
199 Name: "provider",
200 Namespace: "edge-iam",
201 }, providerObj2)
202 return !reflect.DeepEqual(providerObj2.Status, emptyStatus) && err == nil
203 }, s.timeout, s.tick, "expected second provider object with non-empty status was never found")
204
205 s.Require().True(providerObj2.Spec.Encryption.Version == "2")
206
207
208 gen := providerObj2.GetGeneration()
209 newCondition := metav1.Condition{
210 Type: "DatabaseUpdated",
211 Status: metav1.ConditionTrue,
212 Reason: "EncryptionRotationSucceeded",
213 Message: "successfully updated databases to version: 2",
214 LastTransitionTime: metav1.Now(),
215 ObservedGeneration: gen,
216 }
217 patch := client.MergeFrom(providerObj2.DeepCopy())
218 edgeConditions.Set(providerObj2, &newCondition)
219
220 s.Require().NoError(s.Client.Status().Patch(s.ctx, providerObj2, patch))
221
222
223 providerObj3 := &api.Provider{}
224 s.Require().Eventually(func() bool {
225 err := s.Client.Get(s.ctx, types.NamespacedName{
226 Name: "provider",
227 Namespace: "edge-iam",
228 }, providerObj3)
229 return !reflect.DeepEqual(providerObj3.Status, emptyStatus) && err == nil
230 }, s.timeout, s.tick, "expected second provider object with non-empty status was never found")
231
232
233
234 s.Require().Equal(len(providerObj3.Status.Conditions), 2)
235
236
237 var externalSecret3 = &goext.ExternalSecret{}
238 s.Require().Eventually(func() bool {
239 err := s.Client.Get(s.ctx, types.NamespacedName{
240 Name: providerctl.EncryptionKeySecretPrefix + "1",
241 Namespace: "edge-iam",
242 }, externalSecret3)
243
244
245 if err != nil {
246 if errors.IsNotFound(err) {
247 return true
248 }
249 }
250 return false
251 }, s.timeout, s.tick, "external secret was found or errored, when it should have been deleted")
252 }
253
254
255 func TestCreateEncryptionExternalSecret(t *testing.T) {
256 externalSecret := &goext.ExternalSecret{
257 TypeMeta: metav1.TypeMeta{
258 APIVersion: goext.ExtSecretGroupVersionKind.GroupVersion().String(),
259 Kind: goext.ExtSecretGroupVersionKind.Kind,
260 },
261 ObjectMeta: metav1.ObjectMeta{
262 Name: providerctl.EncryptionKeySecretPrefix + "1",
263 Namespace: "edge-iam",
264 Labels: map[string]string{
265 constants.PlatformComponent: "edge-iam",
266 },
267 },
268 Spec: goext.ExternalSecretSpec{
269 DataFrom: []goext.ExternalSecretDataFromRemoteRef{
270 {
271 Extract: &goext.ExternalSecretDataRemoteRef{
272 Key: providerctl.EncryptionKeySecretPrefix + "123",
273 Version: "1",
274 },
275 },
276 },
277 RefreshInterval: &metav1.Duration{
278 Duration: time.Minute,
279 },
280 SecretStoreRef: goext.SecretStoreRef{
281 Name: "gcp-provider",
282 Kind: "ClusterSecretStore",
283 },
284 Target: goext.ExternalSecretTarget{
285 Name: providerctl.EncryptionKeySecretPrefix + "1",
286 CreationPolicy: goext.CreatePolicyOwner,
287 },
288 },
289 }
290
291 uobj, err := unstructuredutil.ToUnstructured(externalSecret)
292 if err != nil {
293 t.Error("Unable to create unstructured external secret")
294 }
295
296 extSec, err := providerctl.CreateEncryptionExternalSecret("1", providerctl.EncryptionKeySecretPrefix+"123", providerctl.EncryptionKeySecretPrefix+"1")
297 if err != nil {
298 t.Error("Unable to create encryption external secret")
299 }
300
301 assert.Equal(t, uobj, extSec)
302 }
303
304 func TestDoesStatusMatchSpecVersion(t *testing.T) {
305
306 provider := createProviderObj("1", "1")
307 _, versionMatch := providerctl.DoesStatusMatchSpecVersion(*provider)
308 if !versionMatch {
309 t.Errorf("Error checking the status where the provider & db's versions match")
310 }
311
312
313 provider = createProviderObj("1", "2")
314 _, versionMatch = providerctl.DoesStatusMatchSpecVersion(*provider)
315 if versionMatch {
316 t.Errorf("Error checking the status where the provider & db's versions do not match")
317 }
318 }
319
320 func createProviderObj(specVersion string, statusVersion string) *api.Provider {
321 statusMessage := "successfully updated databases to version: " + statusVersion
322 providerObj := &api.Provider{
323 TypeMeta: metav1.TypeMeta{
324 APIVersion: "iam.edge-infra.dev/v1alpha1",
325 Kind: "Provider",
326 },
327 ObjectMeta: metav1.ObjectMeta{
328 Name: "provider",
329 Namespace: "edge-iam",
330 },
331 Spec: api.ProviderSpec{
332 Encryption: api.EncryptionFields{
333 Version: specVersion,
334 },
335 Issuer: "http://localhost:8080",
336 Target: "kind",
337 Barcode: api.BarcodeSpec{
338 Expire: "4320h",
339 Role: false,
340 },
341 },
342 Status: api.ProviderStatus{
343 Conditions: []metav1.Condition{
344 {
345 Type: "DatabaseUpdated",
346 Status: metav1.ConditionTrue,
347 Reason: "EncryptionRotationSucceeded",
348 Message: statusMessage,
349 },
350 },
351 },
352 }
353 return providerObj
354 }
355
356 func createScheme() *runtime.Scheme {
357 scheme := runtime.NewScheme()
358 utilruntime.Must(api.AddToScheme(scheme))
359 utilruntime.Must(goext.AddToScheme(scheme))
360 utilruntime.Must(corev1.AddToScheme(scheme))
361 return scheme
362 }
363
View as plain text