github.com/cilium/cilium@v1.16.2/operator/pkg/secretsync/secretsync_reconcile_test.go (about) 1 // SPDX-License-Identifier: Apache-2.0 2 // Copyright Authors of Cilium 3 4 package secretsync_test 5 6 import ( 7 "context" 8 "io" 9 "testing" 10 11 "github.com/sirupsen/logrus" 12 "github.com/stretchr/testify/require" 13 corev1 "k8s.io/api/core/v1" 14 networkingv1 "k8s.io/api/networking/v1" 15 k8sErrors "k8s.io/apimachinery/pkg/api/errors" 16 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 17 "k8s.io/apimachinery/pkg/runtime" 18 "k8s.io/apimachinery/pkg/types" 19 utilruntime "k8s.io/apimachinery/pkg/util/runtime" 20 clientgoscheme "k8s.io/client-go/kubernetes/scheme" 21 ctrl "sigs.k8s.io/controller-runtime" 22 "sigs.k8s.io/controller-runtime/pkg/client" 23 "sigs.k8s.io/controller-runtime/pkg/client/fake" 24 gatewayv1 "sigs.k8s.io/gateway-api/apis/v1" 25 26 gateway_api "github.com/cilium/cilium/operator/pkg/gateway-api" 27 "github.com/cilium/cilium/operator/pkg/ingress" 28 "github.com/cilium/cilium/operator/pkg/model" 29 "github.com/cilium/cilium/operator/pkg/secretsync" 30 ciliumv2 "github.com/cilium/cilium/pkg/k8s/apis/cilium.io/v2" 31 ) 32 33 var secretsNamespace = "cilium-secrets-test" 34 35 var secretFixture = []client.Object{ 36 &corev1.Secret{ 37 ObjectMeta: metav1.ObjectMeta{ 38 Namespace: secretsNamespace, 39 Name: "test-synced-secret-no-source", 40 Labels: map[string]string{ 41 secretsync.OwningSecretNamespace: "test", 42 secretsync.OwningSecretName: "synced-secret-no-source", 43 }, 44 }, 45 }, 46 &corev1.Secret{ 47 ObjectMeta: metav1.ObjectMeta{ 48 Namespace: "test", 49 Name: "synced-secret-no-reference", 50 }, 51 }, 52 &corev1.Secret{ 53 ObjectMeta: metav1.ObjectMeta{ 54 Namespace: secretsNamespace, 55 Name: "test-synced-secret-no-reference", 56 Labels: map[string]string{ 57 secretsync.OwningSecretNamespace: "test", 58 secretsync.OwningSecretName: "syced-secret-no-reference", 59 }, 60 }, 61 }, 62 &corev1.Secret{ 63 ObjectMeta: metav1.ObjectMeta{ 64 Namespace: "test", 65 Name: "synced-secret-with-source-and-ref", 66 }, 67 }, 68 &corev1.Secret{ 69 ObjectMeta: metav1.ObjectMeta{ 70 Namespace: secretsNamespace, 71 Name: "test-synced-secret-with-source-and-ref", 72 Labels: map[string]string{ 73 secretsync.OwningSecretNamespace: "test", 74 secretsync.OwningSecretName: "synced-secret-with-source-and-ref", 75 }, 76 }, 77 }, 78 &gatewayv1.GatewayClass{ 79 ObjectMeta: metav1.ObjectMeta{ 80 Name: "cilium", 81 }, 82 Spec: gatewayv1.GatewayClassSpec{ 83 ControllerName: "io.cilium/gateway-controller", 84 }, 85 }, 86 &gatewayv1.Gateway{ 87 ObjectMeta: metav1.ObjectMeta{ 88 Namespace: "test", 89 Name: "valid-gateway", 90 }, 91 Spec: gatewayv1.GatewaySpec{ 92 GatewayClassName: "cilium", 93 Listeners: []gatewayv1.Listener{ 94 { 95 Name: "https", 96 Port: 443, 97 Hostname: model.AddressOf[gatewayv1.Hostname]("*.cilium.io"), 98 Protocol: "HTTPS", 99 TLS: &gatewayv1.GatewayTLSConfig{ 100 CertificateRefs: []gatewayv1.SecretObjectReference{ 101 { 102 Name: "synced-secret-with-source-and-ref", 103 }, 104 { 105 Name: "secret-with-ref-not-synced", 106 }, 107 }, 108 }, 109 }, 110 }, 111 }, 112 }, 113 &corev1.Secret{ 114 ObjectMeta: metav1.ObjectMeta{ 115 Namespace: "test", 116 Name: "secret-with-ref-not-synced", 117 }, 118 }, 119 &gatewayv1.GatewayClass{ 120 ObjectMeta: metav1.ObjectMeta{ 121 Name: "third-party", 122 }, 123 Spec: gatewayv1.GatewayClassSpec{ 124 ControllerName: "third-party", 125 }, 126 }, 127 &gatewayv1.Gateway{ 128 ObjectMeta: metav1.ObjectMeta{ 129 Namespace: "test", 130 Name: "valid-gateway-non-cilium", 131 }, 132 Spec: gatewayv1.GatewaySpec{ 133 GatewayClassName: "third-party", 134 Listeners: []gatewayv1.Listener{ 135 { 136 Name: "https", 137 Port: 443, 138 Hostname: model.AddressOf[gatewayv1.Hostname]("*.acme.io"), 139 Protocol: "HTTPS", 140 TLS: &gatewayv1.GatewayTLSConfig{ 141 CertificateRefs: []gatewayv1.SecretObjectReference{ 142 { 143 Name: "secret-with-non-cilium-ref", 144 }, 145 }, 146 }, 147 }, 148 }, 149 }, 150 }, 151 &corev1.Secret{ 152 ObjectMeta: metav1.ObjectMeta{ 153 Namespace: "test", 154 Name: "secret-shared-not-synced", 155 }, 156 }, 157 &gatewayv1.Gateway{ 158 ObjectMeta: metav1.ObjectMeta{ 159 Namespace: "test", 160 Name: "valid-gateway-shared", 161 }, 162 Spec: gatewayv1.GatewaySpec{ 163 GatewayClassName: "cilium", 164 Listeners: []gatewayv1.Listener{ 165 { 166 Name: "https", 167 Port: 443, 168 Hostname: model.AddressOf[gatewayv1.Hostname]("*.cilium.io"), 169 Protocol: "HTTPS", 170 TLS: &gatewayv1.GatewayTLSConfig{ 171 CertificateRefs: []gatewayv1.SecretObjectReference{ 172 { 173 Name: "secret-shared-not-synced", 174 }, 175 }, 176 }, 177 }, 178 }, 179 }, 180 }, 181 &networkingv1.Ingress{ 182 ObjectMeta: metav1.ObjectMeta{ 183 Namespace: "test", 184 Name: "valid-ingress-cilium", 185 }, 186 Spec: networkingv1.IngressSpec{ 187 IngressClassName: model.AddressOf("cilium"), 188 TLS: []networkingv1.IngressTLS{ 189 { 190 Hosts: []string{"*.cilium.io"}, 191 SecretName: "secret-shared-not-synced", 192 }, 193 }, 194 }, 195 }, 196 } 197 198 func Test_SecretSync_Reconcile(t *testing.T) { 199 logger := logrus.New() 200 logger.SetOutput(io.Discard) 201 202 c := fake.NewClientBuilder(). 203 WithScheme(testScheme()). 204 WithObjects(secretFixture...). 205 Build() 206 207 r := secretsync.NewSecretSyncReconciler(c, logger, []*secretsync.SecretSyncRegistration{ 208 { 209 RefObject: &gatewayv1.Gateway{}, 210 RefObjectEnqueueFunc: gateway_api.EnqueueTLSSecrets(c, logger), 211 RefObjectCheckFunc: gateway_api.IsReferencedByCiliumGateway, 212 SecretsNamespace: secretsNamespace, 213 }, 214 { 215 RefObject: &networkingv1.Ingress{}, 216 RefObjectEnqueueFunc: ingress.EnqueueReferencedTLSSecrets(c, logger), 217 RefObjectCheckFunc: ingress.IsReferencedByCiliumIngress, 218 SecretsNamespace: secretsNamespace + "-2", 219 }, 220 }) 221 222 t.Run("delete synced secret if source secret doesn't exist", func(t *testing.T) { 223 result, err := r.Reconcile(context.Background(), ctrl.Request{ 224 NamespacedName: types.NamespacedName{ 225 Namespace: "test", 226 Name: "synced-secret-no-source", 227 }, 228 }) 229 require.NoError(t, err) 230 require.Equal(t, ctrl.Result{}, result) 231 232 secret := &corev1.Secret{} 233 err = c.Get(context.Background(), types.NamespacedName{Namespace: secretsNamespace, Name: "test-synced-secret-no-source"}, secret) 234 235 require.Error(t, err) 236 require.ErrorContains(t, err, "secrets \"test-synced-secret-no-source\" not found") 237 }) 238 239 t.Run("delete synced secret if source secret isn't referenced by a CIlium Gateway resource", func(t *testing.T) { 240 result, err := r.Reconcile(context.Background(), ctrl.Request{ 241 NamespacedName: types.NamespacedName{ 242 Namespace: "test", 243 Name: "synced-secret-no-reference", 244 }, 245 }) 246 require.NoError(t, err) 247 require.Equal(t, ctrl.Result{}, result) 248 249 secret := &corev1.Secret{} 250 err = c.Get(context.Background(), types.NamespacedName{Namespace: secretsNamespace, Name: "test-synced-secret-no-reference"}, secret) 251 252 require.Error(t, err) 253 require.ErrorContains(t, err, "secrets \"test-synced-secret-no-reference\" not found") 254 }) 255 256 t.Run("keep synced secret if source secret exists and is referenced by a Gateway resource", func(t *testing.T) { 257 result, err := r.Reconcile(context.Background(), ctrl.Request{ 258 NamespacedName: types.NamespacedName{ 259 Namespace: "test", 260 Name: "synced-secret-with-source-and-ref", 261 }, 262 }) 263 require.NoError(t, err) 264 require.Equal(t, ctrl.Result{}, result) 265 266 secret := &corev1.Secret{} 267 err = c.Get(context.Background(), types.NamespacedName{Namespace: secretsNamespace, Name: "test-synced-secret-with-source-and-ref"}, secret) 268 require.NoError(t, err) 269 }) 270 271 t.Run("don't create synced secret for source secret that is referenced by a non Cilium Gateway resource", func(t *testing.T) { 272 result, err := r.Reconcile(context.Background(), ctrl.Request{ 273 NamespacedName: types.NamespacedName{ 274 Namespace: "test", 275 Name: "secret-with-non-cilium-ref", 276 }, 277 }) 278 require.NoError(t, err) 279 require.Equal(t, ctrl.Result{}, result) 280 281 secret := &corev1.Secret{} 282 err = c.Get(context.Background(), types.NamespacedName{Namespace: secretsNamespace, Name: "test-synced-secret-non-cilium-ref"}, secret) 283 284 require.Error(t, err) 285 require.ErrorContains(t, err, "secrets \"test-synced-secret-non-cilium-ref\" not found") 286 }) 287 288 t.Run("create synced secret for source secret that is referenced by a Cilium Gateway resource", func(t *testing.T) { 289 result, err := r.Reconcile(context.Background(), ctrl.Request{ 290 NamespacedName: types.NamespacedName{ 291 Namespace: "test", 292 Name: "secret-with-ref-not-synced", 293 }, 294 }) 295 require.NoError(t, err) 296 require.Equal(t, ctrl.Result{}, result) 297 298 secret := &corev1.Secret{} 299 err = c.Get(context.Background(), types.NamespacedName{Namespace: secretsNamespace, Name: "test-secret-with-ref-not-synced"}, secret) 300 require.NoError(t, err) 301 }) 302 303 t.Run("create synced secret in multiple namespaces for source secret that is referenced by a Gateway and Ingress", func(t *testing.T) { 304 result, err := r.Reconcile(context.Background(), ctrl.Request{ 305 NamespacedName: types.NamespacedName{ 306 Namespace: "test", 307 Name: "secret-shared-not-synced", 308 }, 309 }) 310 require.NoError(t, err) 311 require.Equal(t, ctrl.Result{}, result) 312 313 err = c.Get(context.Background(), types.NamespacedName{Namespace: secretsNamespace, Name: "test-secret-shared-not-synced"}, &corev1.Secret{}) 314 require.NoError(t, err) 315 316 err = c.Get(context.Background(), types.NamespacedName{Namespace: secretsNamespace + "-2", Name: "test-secret-shared-not-synced"}, &corev1.Secret{}) 317 require.NoError(t, err) 318 }) 319 320 t.Run("delete synced secret in ingress namespace after deleting the Ingress resource", func(t *testing.T) { 321 ingress := networkingv1.Ingress{ 322 ObjectMeta: metav1.ObjectMeta{ 323 Namespace: "test", 324 Name: "valid-ingress-cilium", 325 }, 326 } 327 328 var err error 329 err = c.Delete(context.Background(), &ingress) 330 require.NoError(t, err) 331 332 result, err := r.Reconcile(context.Background(), ctrl.Request{ 333 NamespacedName: types.NamespacedName{ 334 Namespace: "test", 335 Name: "secret-shared-not-synced", 336 }, 337 }) 338 require.NoError(t, err) 339 require.Equal(t, ctrl.Result{}, result) 340 341 err = c.Get(context.Background(), types.NamespacedName{Namespace: secretsNamespace, Name: "test-secret-shared-not-synced"}, &corev1.Secret{}) 342 require.NoError(t, err) 343 344 err = c.Get(context.Background(), types.NamespacedName{Namespace: secretsNamespace + "-2", Name: "test-secret-shared-not-synced"}, &corev1.Secret{}) 345 require.True(t, k8sErrors.IsNotFound(err)) 346 }) 347 } 348 349 var secretFixtureDefaultSecret = []client.Object{ 350 &corev1.Secret{ 351 ObjectMeta: metav1.ObjectMeta{ 352 Namespace: "test", 353 Name: "unsynced-secret-no-reference", 354 }, 355 }, 356 } 357 358 func Test_SecretSync_Reconcile_WithDefaultSecret(t *testing.T) { 359 logger := logrus.New() 360 logger.SetOutput(io.Discard) 361 362 c := fake.NewClientBuilder(). 363 WithScheme(testScheme()). 364 WithObjects(secretFixtureDefaultSecret...). 365 Build() 366 r := secretsync.NewSecretSyncReconciler(c, logger, []*secretsync.SecretSyncRegistration{ 367 { 368 RefObject: &gatewayv1.Gateway{}, 369 RefObjectEnqueueFunc: gateway_api.EnqueueTLSSecrets(c, logger), 370 RefObjectCheckFunc: gateway_api.IsReferencedByCiliumGateway, 371 SecretsNamespace: secretsNamespace, 372 DefaultSecret: &secretsync.DefaultSecret{ 373 Namespace: "test", 374 Name: "unsynced-secret-no-reference", 375 }, 376 }, 377 }) 378 379 t.Run("create synced secret for source secret that is the default secret and therefore doesn't need to be referenced by any Cilium Gateway resource", func(t *testing.T) { 380 result, err := r.Reconcile(context.Background(), ctrl.Request{ 381 NamespacedName: types.NamespacedName{ 382 Namespace: "test", 383 Name: "unsynced-secret-no-reference", 384 }, 385 }) 386 require.NoError(t, err) 387 require.Equal(t, ctrl.Result{}, result) 388 389 secret := &corev1.Secret{} 390 err = c.Get(context.Background(), types.NamespacedName{Namespace: secretsNamespace, Name: "test-unsynced-secret-no-reference"}, secret) 391 require.NoError(t, err) 392 }) 393 } 394 395 func testScheme() *runtime.Scheme { 396 scheme := runtime.NewScheme() 397 398 utilruntime.Must(clientgoscheme.AddToScheme(scheme)) 399 utilruntime.Must(ciliumv2.AddToScheme(scheme)) 400 utilruntime.Must(gatewayv1.AddToScheme(scheme)) 401 402 return scheme 403 }