From 1f99c3da177b90c1ddbaa29d0b9b1d4e1b926d01 Mon Sep 17 00:00:00 2001 From: Eric Wittmann Date: Tue, 11 Feb 2025 15:44:17 -0500 Subject: [PATCH] feat(operator): Add support for managed NetworkPolicy in operator (#5922) * Add support for managed NetworkPolicy in operator * Delete NetworkPolicy resource in activation condition * fix(operator): missing RBAC for NetworkPolicy * Apply PR feedback * chore(operator): update install file --------- Co-authored-by: Jakub Senko --- .../deploy/rbac/cluster/cluster-role.yaml | 1 + .../operator/ApicurioRegistry3Reconciler.java | 27 ++++++++ .../resource/ActivationConditions.java | 50 ++++++++++++++ .../resource/LabelDiscriminators.java | 46 ++++++++++++- .../operator/resource/ResourceFactory.java | 26 ++++++++ .../operator/resource/ResourceKey.java | 19 ++++++ .../app/AppNetworkPolicyResource.java | 37 +++++++++++ .../StudioUINetworkPolicyResource.java | 37 +++++++++++ .../resource/ui/UINetworkPolicyResource.java | 37 +++++++++++ .../k8s/default/app.networkpolicy.yaml | 10 +++ .../k8s/default/studio-ui.networkpolicy.yaml | 10 +++ .../k8s/default/ui.networkpolicy.yaml | 10 +++ .../apicurio/registry/operator/it/ITBase.java | 14 ++++ .../operator/it/NetworkPolicyITTest.java | 65 +++++++++++++++++++ operator/install/install.yaml | 34 ++++++++++ .../operator/api/v1/spec/ComponentSpec.java | 40 +++++++++--- .../api/v1/spec/NetworkPolicySpec.java | 46 +++++++++++++ 17 files changed, 498 insertions(+), 11 deletions(-) create mode 100644 operator/controller/src/main/java/io/apicurio/registry/operator/resource/app/AppNetworkPolicyResource.java create mode 100644 operator/controller/src/main/java/io/apicurio/registry/operator/resource/studioui/StudioUINetworkPolicyResource.java create mode 100644 operator/controller/src/main/java/io/apicurio/registry/operator/resource/ui/UINetworkPolicyResource.java create mode 100644 operator/controller/src/main/resources/k8s/default/app.networkpolicy.yaml create mode 100644 operator/controller/src/main/resources/k8s/default/studio-ui.networkpolicy.yaml create mode 100644 operator/controller/src/main/resources/k8s/default/ui.networkpolicy.yaml create mode 100644 operator/controller/src/test/java/io/apicurio/registry/operator/it/NetworkPolicyITTest.java create mode 100644 operator/model/src/main/java/io/apicurio/registry/operator/api/v1/spec/NetworkPolicySpec.java diff --git a/operator/controller/src/main/deploy/rbac/cluster/cluster-role.yaml b/operator/controller/src/main/deploy/rbac/cluster/cluster-role.yaml index 68e8b52844..dc447f3257 100644 --- a/operator/controller/src/main/deploy/rbac/cluster/cluster-role.yaml +++ b/operator/controller/src/main/deploy/rbac/cluster/cluster-role.yaml @@ -47,6 +47,7 @@ rules: - networking.k8s.io resources: - ingresses + - networkpolicies verbs: - '*' diff --git a/operator/controller/src/main/java/io/apicurio/registry/operator/ApicurioRegistry3Reconciler.java b/operator/controller/src/main/java/io/apicurio/registry/operator/ApicurioRegistry3Reconciler.java index 0356ceadb2..e157a92fc5 100644 --- a/operator/controller/src/main/java/io/apicurio/registry/operator/ApicurioRegistry3Reconciler.java +++ b/operator/controller/src/main/java/io/apicurio/registry/operator/ApicurioRegistry3Reconciler.java @@ -4,14 +4,17 @@ import io.apicurio.registry.operator.resource.LabelDiscriminators.AppDeploymentDiscriminator; import io.apicurio.registry.operator.resource.app.AppDeploymentResource; import io.apicurio.registry.operator.resource.app.AppIngressResource; +import io.apicurio.registry.operator.resource.app.AppNetworkPolicyResource; import io.apicurio.registry.operator.resource.app.AppPodDisruptionBudgetResource; import io.apicurio.registry.operator.resource.app.AppServiceResource; import io.apicurio.registry.operator.resource.studioui.StudioUIDeploymentResource; import io.apicurio.registry.operator.resource.studioui.StudioUIIngressResource; +import io.apicurio.registry.operator.resource.studioui.StudioUINetworkPolicyResource; import io.apicurio.registry.operator.resource.studioui.StudioUIPodDisruptionBudgetResource; import io.apicurio.registry.operator.resource.studioui.StudioUIServiceResource; import io.apicurio.registry.operator.resource.ui.UIDeploymentResource; import io.apicurio.registry.operator.resource.ui.UIIngressResource; +import io.apicurio.registry.operator.resource.ui.UINetworkPolicyResource; import io.apicurio.registry.operator.resource.ui.UIPodDisruptionBudgetResource; import io.apicurio.registry.operator.resource.ui.UIServiceResource; import io.apicurio.registry.operator.updater.IngressCRUpdater; @@ -31,22 +34,28 @@ import org.slf4j.LoggerFactory; import static io.apicurio.registry.operator.resource.ActivationConditions.AppIngressActivationCondition; +import static io.apicurio.registry.operator.resource.ActivationConditions.AppNetworkPolicyActivationCondition; import static io.apicurio.registry.operator.resource.ActivationConditions.AppPodDisruptionBudgetActivationCondition; import static io.apicurio.registry.operator.resource.ActivationConditions.StudioUIDeploymentActivationCondition; import static io.apicurio.registry.operator.resource.ActivationConditions.StudioUIIngressActivationCondition; +import static io.apicurio.registry.operator.resource.ActivationConditions.StudioUINetworkPolicyActivationCondition; import static io.apicurio.registry.operator.resource.ActivationConditions.StudioUIPodDisruptionBudgetActivationCondition; import static io.apicurio.registry.operator.resource.ActivationConditions.UIIngressActivationCondition; +import static io.apicurio.registry.operator.resource.ActivationConditions.UINetworkPolicyActivationCondition; import static io.apicurio.registry.operator.resource.ActivationConditions.UIPodDisruptionBudgetActivationCondition; import static io.apicurio.registry.operator.resource.ResourceKey.APP_DEPLOYMENT_ID; import static io.apicurio.registry.operator.resource.ResourceKey.APP_INGRESS_ID; +import static io.apicurio.registry.operator.resource.ResourceKey.APP_NETWORK_POLICY_ID; import static io.apicurio.registry.operator.resource.ResourceKey.APP_POD_DISRUPTION_BUDGET_ID; import static io.apicurio.registry.operator.resource.ResourceKey.APP_SERVICE_ID; import static io.apicurio.registry.operator.resource.ResourceKey.STUDIO_UI_DEPLOYMENT_ID; import static io.apicurio.registry.operator.resource.ResourceKey.STUDIO_UI_INGRESS_ID; +import static io.apicurio.registry.operator.resource.ResourceKey.STUDIO_UI_NETWORK_POLICY_ID; import static io.apicurio.registry.operator.resource.ResourceKey.STUDIO_UI_POD_DISRUPTION_BUDGET_ID; import static io.apicurio.registry.operator.resource.ResourceKey.STUDIO_UI_SERVICE_ID; import static io.apicurio.registry.operator.resource.ResourceKey.UI_DEPLOYMENT_ID; import static io.apicurio.registry.operator.resource.ResourceKey.UI_INGRESS_ID; +import static io.apicurio.registry.operator.resource.ResourceKey.UI_NETWORK_POLICY_ID; import static io.apicurio.registry.operator.resource.ResourceKey.UI_POD_DISRUPTION_BUDGET_ID; import static io.apicurio.registry.operator.resource.ResourceKey.UI_SERVICE_ID; @@ -74,6 +83,12 @@ dependsOn = {APP_DEPLOYMENT_ID}, activationCondition = AppPodDisruptionBudgetActivationCondition.class ), + @Dependent( + type = AppNetworkPolicyResource.class, + name = APP_NETWORK_POLICY_ID, + dependsOn = {APP_DEPLOYMENT_ID}, + activationCondition = AppNetworkPolicyActivationCondition.class + ), // ===== Registry UI @Dependent( type = UIDeploymentResource.class, @@ -96,6 +111,12 @@ dependsOn = {UI_DEPLOYMENT_ID}, activationCondition = UIPodDisruptionBudgetActivationCondition.class ), + @Dependent( + type = UINetworkPolicyResource.class, + name = UI_NETWORK_POLICY_ID, + dependsOn = {UI_DEPLOYMENT_ID}, + activationCondition = UINetworkPolicyActivationCondition.class + ), // ===== Studio UI @Dependent( type = StudioUIDeploymentResource.class, @@ -119,6 +140,12 @@ dependsOn = {STUDIO_UI_DEPLOYMENT_ID}, activationCondition = StudioUIPodDisruptionBudgetActivationCondition.class ), + @Dependent( + type = StudioUINetworkPolicyResource.class, + name = STUDIO_UI_NETWORK_POLICY_ID, + dependsOn = {STUDIO_UI_DEPLOYMENT_ID}, + activationCondition = StudioUINetworkPolicyActivationCondition.class + ) } ) // TODO: When renaming, do not forget to update application.properties (until we have a test for this). diff --git a/operator/controller/src/main/java/io/apicurio/registry/operator/resource/ActivationConditions.java b/operator/controller/src/main/java/io/apicurio/registry/operator/resource/ActivationConditions.java index c3f242a6dd..00d54d91b4 100644 --- a/operator/controller/src/main/java/io/apicurio/registry/operator/resource/ActivationConditions.java +++ b/operator/controller/src/main/java/io/apicurio/registry/operator/resource/ActivationConditions.java @@ -5,18 +5,23 @@ import io.apicurio.registry.operator.api.v1.spec.AppSpec; import io.apicurio.registry.operator.api.v1.spec.ComponentSpec; import io.apicurio.registry.operator.api.v1.spec.IngressSpec; +import io.apicurio.registry.operator.api.v1.spec.NetworkPolicySpec; import io.apicurio.registry.operator.api.v1.spec.PodDisruptionSpec; import io.apicurio.registry.operator.api.v1.spec.StudioUiSpec; import io.apicurio.registry.operator.api.v1.spec.UiSpec; import io.apicurio.registry.operator.resource.app.AppIngressResource; +import io.apicurio.registry.operator.resource.app.AppNetworkPolicyResource; import io.apicurio.registry.operator.resource.app.AppPodDisruptionBudgetResource; import io.apicurio.registry.operator.resource.studioui.StudioUIDeploymentResource; import io.apicurio.registry.operator.resource.studioui.StudioUIIngressResource; +import io.apicurio.registry.operator.resource.studioui.StudioUINetworkPolicyResource; import io.apicurio.registry.operator.resource.studioui.StudioUIPodDisruptionBudgetResource; import io.apicurio.registry.operator.resource.ui.UIIngressResource; +import io.apicurio.registry.operator.resource.ui.UINetworkPolicyResource; import io.apicurio.registry.operator.resource.ui.UIPodDisruptionBudgetResource; import io.fabric8.kubernetes.api.model.apps.Deployment; import io.fabric8.kubernetes.api.model.networking.v1.Ingress; +import io.fabric8.kubernetes.api.model.networking.v1.NetworkPolicy; import io.fabric8.kubernetes.api.model.policy.v1.PodDisruptionBudget; import io.javaoperatorsdk.operator.api.reconciler.Context; import io.javaoperatorsdk.operator.api.reconciler.dependent.DependentResource; @@ -70,6 +75,21 @@ public boolean isMet(DependentResource r } } + public static class AppNetworkPolicyActivationCondition + implements Condition { + @Override + public boolean isMet(DependentResource resource, + ApicurioRegistry3 primary, Context context) { + Boolean isManaged = ofNullable(primary.getSpec()).map(ApicurioRegistry3Spec::getApp) + .map(ComponentSpec::getNetworkPolicy).map(NetworkPolicySpec::getEnabled) + .orElse(Boolean.TRUE); + if (!isManaged) { + ((AppNetworkPolicyResource) resource).delete(primary, context); + } + return isManaged; + } + } + // ===== Registry UI public static class UIIngressActivationCondition implements Condition { @@ -106,6 +126,21 @@ public boolean isMet(DependentResource r } } + public static class UINetworkPolicyActivationCondition + implements Condition { + @Override + public boolean isMet(DependentResource resource, + ApicurioRegistry3 primary, Context context) { + Boolean isManaged = ofNullable(primary.getSpec()).map(ApicurioRegistry3Spec::getUi) + .map(ComponentSpec::getNetworkPolicy).map(NetworkPolicySpec::getEnabled) + .orElse(Boolean.TRUE); + if (!isManaged) { + ((UINetworkPolicyResource) resource).delete(primary, context); + } + return isManaged; + } + } + // ===== Studio UI public static class StudioUIDeploymentActivationCondition @@ -158,4 +193,19 @@ public boolean isMet(DependentResource r } } + + public static class StudioUINetworkPolicyActivationCondition + implements Condition { + @Override + public boolean isMet(DependentResource resource, + ApicurioRegistry3 primary, Context context) { + Boolean isManaged = ofNullable(primary.getSpec()).map(ApicurioRegistry3Spec::getStudioUi) + .map(ComponentSpec::getNetworkPolicy).map(NetworkPolicySpec::getEnabled) + .orElse(Boolean.TRUE); + if (!isManaged) { + ((StudioUINetworkPolicyResource) resource).delete(primary, context); + } + return isManaged; + } + } } diff --git a/operator/controller/src/main/java/io/apicurio/registry/operator/resource/LabelDiscriminators.java b/operator/controller/src/main/java/io/apicurio/registry/operator/resource/LabelDiscriminators.java index 7cda1f3c6e..4c6193c7f5 100644 --- a/operator/controller/src/main/java/io/apicurio/registry/operator/resource/LabelDiscriminators.java +++ b/operator/controller/src/main/java/io/apicurio/registry/operator/resource/LabelDiscriminators.java @@ -4,12 +4,15 @@ import io.fabric8.kubernetes.api.model.Service; import io.fabric8.kubernetes.api.model.apps.Deployment; import io.fabric8.kubernetes.api.model.networking.v1.Ingress; +import io.fabric8.kubernetes.api.model.networking.v1.NetworkPolicy; import io.fabric8.kubernetes.api.model.policy.v1.PodDisruptionBudget; import io.javaoperatorsdk.operator.api.reconciler.ResourceDiscriminator; import java.util.Map; -import static io.apicurio.registry.operator.resource.ResourceFactory.*; +import static io.apicurio.registry.operator.resource.ResourceFactory.COMPONENT_APP; +import static io.apicurio.registry.operator.resource.ResourceFactory.COMPONENT_STUDIO_UI; +import static io.apicurio.registry.operator.resource.ResourceFactory.COMPONENT_UI; public class LabelDiscriminators { @@ -68,6 +71,20 @@ public AppPodDisruptionBudgetDiscriminator() { } } + public static class AppNetworkPolicyDiscriminator extends LabelDiscriminator { + + public static final ResourceDiscriminator INSTANCE = new AppNetworkPolicyDiscriminator(); + + public AppNetworkPolicyDiscriminator() { + // spotless:off + super(Map.of( + "app.kubernetes.io/name", "apicurio-registry", + "app.kubernetes.io/component", COMPONENT_APP + )); + // spotless:on + } + } + // ===== Registry UI public static class UIDeploymentDiscriminator extends LabelDiscriminator { @@ -120,6 +137,20 @@ public UiPodDisruptionBudgetDiscriminator() { } } + public static class UINetworkPolicyDiscriminator extends LabelDiscriminator { + + public static final ResourceDiscriminator INSTANCE = new AppNetworkPolicyDiscriminator(); + + public UINetworkPolicyDiscriminator() { + // spotless:off + super(Map.of( + "app.kubernetes.io/name", "apicurio-registry", + "app.kubernetes.io/component", COMPONENT_UI + )); + // spotless:on + } + } + // ===== Studio UI public static class StudioUIDeploymentDiscriminator extends LabelDiscriminator { @@ -173,4 +204,17 @@ public StudioUiPodDisruptionBudgetDiscriminator() { } } + public static class StudioUINetworkPolicyDiscriminator extends LabelDiscriminator { + + public static final ResourceDiscriminator INSTANCE = new AppNetworkPolicyDiscriminator(); + + public StudioUINetworkPolicyDiscriminator() { + // spotless:off + super(Map.of( + "app.kubernetes.io/name", "apicurio-registry", + "app.kubernetes.io/component", COMPONENT_STUDIO_UI + )); + // spotless:on + } + } } diff --git a/operator/controller/src/main/java/io/apicurio/registry/operator/resource/ResourceFactory.java b/operator/controller/src/main/java/io/apicurio/registry/operator/resource/ResourceFactory.java index 3a56806ef1..44b163170f 100644 --- a/operator/controller/src/main/java/io/apicurio/registry/operator/resource/ResourceFactory.java +++ b/operator/controller/src/main/java/io/apicurio/registry/operator/resource/ResourceFactory.java @@ -12,6 +12,7 @@ import io.fabric8.kubernetes.api.model.apps.Deployment; import io.fabric8.kubernetes.api.model.apps.DeploymentSpec; import io.fabric8.kubernetes.api.model.networking.v1.Ingress; +import io.fabric8.kubernetes.api.model.networking.v1.NetworkPolicy; import io.fabric8.kubernetes.api.model.policy.v1.PodDisruptionBudget; import java.nio.charset.Charset; @@ -39,6 +40,7 @@ public class ResourceFactory { public static final String RESOURCE_TYPE_SERVICE = "service"; public static final String RESOURCE_TYPE_INGRESS = "ingress"; public static final String RESOURCE_TYPE_POD_DISRUPTION_BUDGET = "poddisruptionbudget"; + public static final String RESOURCE_TYPE_NETWORK_POLICY = "networkpolicy"; public Deployment getDefaultAppDeployment(ApicurioRegistry3 primary) { var r = initDefaultDeployment(primary, COMPONENT_APP, @@ -258,6 +260,30 @@ private T getDefaultResource(ApicurioRegistry3 primary, return r; } + public NetworkPolicy getDefaultAppNetworkPolicy(ApicurioRegistry3 primary) { + var networkPolicy = getDefaultResource(primary, NetworkPolicy.class, RESOURCE_TYPE_NETWORK_POLICY, + COMPONENT_APP); + networkPolicy.getSpec().getPodSelector().getMatchLabels().put("app.kubernetes.io/instance", + primary.getMetadata().getName()); + return networkPolicy; + } + + public NetworkPolicy getDefaultUINetworkPolicy(ApicurioRegistry3 primary) { + var networkPolicy = getDefaultResource(primary, NetworkPolicy.class, RESOURCE_TYPE_NETWORK_POLICY, + COMPONENT_UI); + networkPolicy.getSpec().getPodSelector().getMatchLabels().put("app.kubernetes.io/instance", + primary.getMetadata().getName()); + return networkPolicy; + } + + public NetworkPolicy getDefaultStudioUINetworkPolicy(ApicurioRegistry3 primary) { + var networkPolicy = getDefaultResource(primary, NetworkPolicy.class, RESOURCE_TYPE_NETWORK_POLICY, + COMPONENT_STUDIO_UI); + networkPolicy.getSpec().getPodSelector().getMatchLabels().put("app.kubernetes.io/instance", + primary.getMetadata().getName()); + return networkPolicy; + } + private void addDefaultLabels(Map labels, ApicurioRegistry3 primary, String component) { labels.putAll(Map.of( "app", primary.getMetadata().getName(), diff --git a/operator/controller/src/main/java/io/apicurio/registry/operator/resource/ResourceKey.java b/operator/controller/src/main/java/io/apicurio/registry/operator/resource/ResourceKey.java index c3699a8d7c..ef1d70aff4 100644 --- a/operator/controller/src/main/java/io/apicurio/registry/operator/resource/ResourceKey.java +++ b/operator/controller/src/main/java/io/apicurio/registry/operator/resource/ResourceKey.java @@ -6,6 +6,7 @@ import io.fabric8.kubernetes.api.model.Service; import io.fabric8.kubernetes.api.model.apps.Deployment; import io.fabric8.kubernetes.api.model.networking.v1.Ingress; +import io.fabric8.kubernetes.api.model.networking.v1.NetworkPolicy; import io.fabric8.kubernetes.api.model.policy.v1.PodDisruptionBudget; import io.javaoperatorsdk.operator.api.reconciler.ResourceDiscriminator; import lombok.AllArgsConstructor; @@ -29,16 +30,19 @@ public class ResourceKey { public static final String APP_SERVICE_ID = "AppServiceResource"; public static final String APP_INGRESS_ID = "AppIngressResource"; public static final String APP_POD_DISRUPTION_BUDGET_ID = "AppPodDisruptionBudgetResource"; + public static final String APP_NETWORK_POLICY_ID = "AppNetworkPolicyResource"; public static final String UI_DEPLOYMENT_ID = "UIDeploymentResource"; public static final String UI_SERVICE_ID = "UIServiceResource"; public static final String UI_INGRESS_ID = "UIIngressResource"; public static final String UI_POD_DISRUPTION_BUDGET_ID = "UIPodDisruptionBudgetResource"; + public static final String UI_NETWORK_POLICY_ID = "UINetworkPolicyResource"; public static final String STUDIO_UI_DEPLOYMENT_ID = "StudioUIDeploymentResource"; public static final String STUDIO_UI_SERVICE_ID = "StudioUIServiceResource"; public static final String STUDIO_UI_INGRESS_ID = "StudioUIIngressResource"; public static final String STUDIO_UI_POD_DISRUPTION_BUDGET_ID = "StudioUIPodDisruptionBudgetResource"; + public static final String STUDIO_UI_NETWORK_POLICY_ID = "StudioUINetworkPolicyResource"; public static final ResourceKey REGISTRY_KEY = new ResourceKey<>( REGISTRY_ID, ApicurioRegistry3.class, @@ -62,6 +66,11 @@ public class ResourceKey { AppIngressDiscriminator.INSTANCE, ResourceFactory.INSTANCE::getDefaultAppIngress ); + public static final ResourceKey APP_NETWORK_POLICY_KEY = new ResourceKey<>( + APP_NETWORK_POLICY_ID, NetworkPolicy.class, + AppNetworkPolicyDiscriminator.INSTANCE, ResourceFactory.INSTANCE::getDefaultAppNetworkPolicy + ); + public static final ResourceKey APP_POD_DISRUPTION_BUDGET_KEY = new ResourceKey<>( APP_POD_DISRUPTION_BUDGET_ID, PodDisruptionBudget.class, AppPodDisruptionBudgetDiscriminator.INSTANCE, ResourceFactory.INSTANCE::getDefaultAppPodDisruptionBudget @@ -84,6 +93,11 @@ public class ResourceKey { UIIngressDiscriminator.INSTANCE, ResourceFactory.INSTANCE::getDefaultUIIngress ); + public static final ResourceKey UI_NETWORK_POLICY_KEY = new ResourceKey<>( + UI_NETWORK_POLICY_ID, NetworkPolicy.class, + UINetworkPolicyDiscriminator.INSTANCE, ResourceFactory.INSTANCE::getDefaultUINetworkPolicy + ); + public static final ResourceKey UI_POD_DISRUPTION_BUDGET_KEY = new ResourceKey<>( UI_POD_DISRUPTION_BUDGET_ID, PodDisruptionBudget.class, UiPodDisruptionBudgetDiscriminator.INSTANCE, ResourceFactory.INSTANCE::getDefaultUIPodDisruptionBudget @@ -106,6 +120,11 @@ public class ResourceKey { StudioUIIngressDiscriminator.INSTANCE, ResourceFactory.INSTANCE::getDefaultStudioUIIngress ); + public static final ResourceKey STUDIO_UI_NETWORK_POLICY_KEY = new ResourceKey<>( + STUDIO_UI_NETWORK_POLICY_ID, NetworkPolicy.class, + StudioUINetworkPolicyDiscriminator.INSTANCE, ResourceFactory.INSTANCE::getDefaultStudioUINetworkPolicy + ); + public static final ResourceKey STUDIO_UI_POD_DISRUPTION_BUDGET_KEY = new ResourceKey<>( STUDIO_UI_POD_DISRUPTION_BUDGET_ID, PodDisruptionBudget.class, StudioUiPodDisruptionBudgetDiscriminator.INSTANCE, ResourceFactory.INSTANCE::getDefaultStudioUIPodDisruptionBudget diff --git a/operator/controller/src/main/java/io/apicurio/registry/operator/resource/app/AppNetworkPolicyResource.java b/operator/controller/src/main/java/io/apicurio/registry/operator/resource/app/AppNetworkPolicyResource.java new file mode 100644 index 0000000000..8514070499 --- /dev/null +++ b/operator/controller/src/main/java/io/apicurio/registry/operator/resource/app/AppNetworkPolicyResource.java @@ -0,0 +1,37 @@ +package io.apicurio.registry.operator.resource.app; + +import io.apicurio.registry.operator.api.v1.ApicurioRegistry3; +import io.apicurio.registry.operator.resource.LabelDiscriminators.AppNetworkPolicyDiscriminator; +import io.fabric8.kubernetes.api.model.networking.v1.NetworkPolicy; +import io.javaoperatorsdk.operator.api.reconciler.Context; +import io.javaoperatorsdk.operator.processing.dependent.kubernetes.CRUDKubernetesDependentResource; +import io.javaoperatorsdk.operator.processing.dependent.kubernetes.KubernetesDependent; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import static io.apicurio.registry.operator.resource.ResourceFactory.COMPONENT_APP; +import static io.apicurio.registry.operator.resource.ResourceKey.APP_NETWORK_POLICY_KEY; +import static io.apicurio.registry.operator.utils.Mapper.toYAML; + +// spotless:off +@KubernetesDependent( + labelSelector = "app.kubernetes.io/name=apicurio-registry,app.kubernetes.io/component=" + COMPONENT_APP, + resourceDiscriminator = AppNetworkPolicyDiscriminator.class +) +// spotless:on +public class AppNetworkPolicyResource + extends CRUDKubernetesDependentResource { + + private static final Logger log = LoggerFactory.getLogger(AppNetworkPolicyResource.class); + + public AppNetworkPolicyResource() { + super(NetworkPolicy.class); + } + + @Override + protected NetworkPolicy desired(ApicurioRegistry3 primary, Context context) { + var networkPolicy = APP_NETWORK_POLICY_KEY.getFactory().apply(primary); + log.debug("Desired {} is {}", APP_NETWORK_POLICY_KEY.getId(), toYAML(networkPolicy)); + return networkPolicy; + } +} diff --git a/operator/controller/src/main/java/io/apicurio/registry/operator/resource/studioui/StudioUINetworkPolicyResource.java b/operator/controller/src/main/java/io/apicurio/registry/operator/resource/studioui/StudioUINetworkPolicyResource.java new file mode 100644 index 0000000000..efc19eb123 --- /dev/null +++ b/operator/controller/src/main/java/io/apicurio/registry/operator/resource/studioui/StudioUINetworkPolicyResource.java @@ -0,0 +1,37 @@ +package io.apicurio.registry.operator.resource.studioui; + +import io.apicurio.registry.operator.api.v1.ApicurioRegistry3; +import io.apicurio.registry.operator.resource.LabelDiscriminators.StudioUINetworkPolicyDiscriminator; +import io.fabric8.kubernetes.api.model.networking.v1.NetworkPolicy; +import io.javaoperatorsdk.operator.api.reconciler.Context; +import io.javaoperatorsdk.operator.processing.dependent.kubernetes.CRUDKubernetesDependentResource; +import io.javaoperatorsdk.operator.processing.dependent.kubernetes.KubernetesDependent; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import static io.apicurio.registry.operator.resource.ResourceFactory.COMPONENT_STUDIO_UI; +import static io.apicurio.registry.operator.resource.ResourceKey.STUDIO_UI_NETWORK_POLICY_KEY; +import static io.apicurio.registry.operator.utils.Mapper.toYAML; + +// spotless:off +@KubernetesDependent( + labelSelector = "app.kubernetes.io/name=apicurio-registry,app.kubernetes.io/component=" + COMPONENT_STUDIO_UI, + resourceDiscriminator = StudioUINetworkPolicyDiscriminator.class +) +// spotless:on +public class StudioUINetworkPolicyResource + extends CRUDKubernetesDependentResource { + + private static final Logger log = LoggerFactory.getLogger(StudioUINetworkPolicyResource.class); + + public StudioUINetworkPolicyResource() { + super(NetworkPolicy.class); + } + + @Override + protected NetworkPolicy desired(ApicurioRegistry3 primary, Context context) { + var networkPolicy = STUDIO_UI_NETWORK_POLICY_KEY.getFactory().apply(primary); + log.debug("Desired {} is {}", STUDIO_UI_NETWORK_POLICY_KEY.getId(), toYAML(networkPolicy)); + return networkPolicy; + } +} diff --git a/operator/controller/src/main/java/io/apicurio/registry/operator/resource/ui/UINetworkPolicyResource.java b/operator/controller/src/main/java/io/apicurio/registry/operator/resource/ui/UINetworkPolicyResource.java new file mode 100644 index 0000000000..20310dee65 --- /dev/null +++ b/operator/controller/src/main/java/io/apicurio/registry/operator/resource/ui/UINetworkPolicyResource.java @@ -0,0 +1,37 @@ +package io.apicurio.registry.operator.resource.ui; + +import io.apicurio.registry.operator.api.v1.ApicurioRegistry3; +import io.apicurio.registry.operator.resource.LabelDiscriminators.UINetworkPolicyDiscriminator; +import io.fabric8.kubernetes.api.model.networking.v1.NetworkPolicy; +import io.javaoperatorsdk.operator.api.reconciler.Context; +import io.javaoperatorsdk.operator.processing.dependent.kubernetes.CRUDKubernetesDependentResource; +import io.javaoperatorsdk.operator.processing.dependent.kubernetes.KubernetesDependent; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import static io.apicurio.registry.operator.resource.ResourceFactory.COMPONENT_UI; +import static io.apicurio.registry.operator.resource.ResourceKey.UI_NETWORK_POLICY_KEY; +import static io.apicurio.registry.operator.utils.Mapper.toYAML; + +// spotless:off +@KubernetesDependent( + labelSelector = "app.kubernetes.io/name=apicurio-registry,app.kubernetes.io/component=" + COMPONENT_UI, + resourceDiscriminator = UINetworkPolicyDiscriminator.class +) +// spotless:on +public class UINetworkPolicyResource + extends CRUDKubernetesDependentResource { + + private static final Logger log = LoggerFactory.getLogger(UINetworkPolicyResource.class); + + public UINetworkPolicyResource() { + super(NetworkPolicy.class); + } + + @Override + protected NetworkPolicy desired(ApicurioRegistry3 primary, Context context) { + var networkPolicy = UI_NETWORK_POLICY_KEY.getFactory().apply(primary); + log.debug("Desired {} is {}", UI_NETWORK_POLICY_KEY.getId(), toYAML(networkPolicy)); + return networkPolicy; + } +} diff --git a/operator/controller/src/main/resources/k8s/default/app.networkpolicy.yaml b/operator/controller/src/main/resources/k8s/default/app.networkpolicy.yaml new file mode 100644 index 0000000000..72c8244d79 --- /dev/null +++ b/operator/controller/src/main/resources/k8s/default/app.networkpolicy.yaml @@ -0,0 +1,10 @@ +apiVersion: networking.k8s.io/v1 +kind: NetworkPolicy +metadata: { } +spec: + podSelector: + matchLabels: + app.kubernetes.io/name: apicurio-registry + app.kubernetes.io/component: app + policyTypes: + - Ingress diff --git a/operator/controller/src/main/resources/k8s/default/studio-ui.networkpolicy.yaml b/operator/controller/src/main/resources/k8s/default/studio-ui.networkpolicy.yaml new file mode 100644 index 0000000000..00d8e51623 --- /dev/null +++ b/operator/controller/src/main/resources/k8s/default/studio-ui.networkpolicy.yaml @@ -0,0 +1,10 @@ +apiVersion: networking.k8s.io/v1 +kind: NetworkPolicy +metadata: { } +spec: + podSelector: + matchLabels: + app.kubernetes.io/name: apicurio-registry + app.kubernetes.io/component: studio-ui + policyTypes: + - Ingress diff --git a/operator/controller/src/main/resources/k8s/default/ui.networkpolicy.yaml b/operator/controller/src/main/resources/k8s/default/ui.networkpolicy.yaml new file mode 100644 index 0000000000..8653ad5b79 --- /dev/null +++ b/operator/controller/src/main/resources/k8s/default/ui.networkpolicy.yaml @@ -0,0 +1,10 @@ +apiVersion: networking.k8s.io/v1 +kind: NetworkPolicy +metadata: { } +spec: + podSelector: + matchLabels: + app.kubernetes.io/name: apicurio-registry + app.kubernetes.io/component: ui + policyTypes: + - Ingress diff --git a/operator/controller/src/test/java/io/apicurio/registry/operator/it/ITBase.java b/operator/controller/src/test/java/io/apicurio/registry/operator/it/ITBase.java index 314eee395d..d575c54409 100644 --- a/operator/controller/src/test/java/io/apicurio/registry/operator/it/ITBase.java +++ b/operator/controller/src/test/java/io/apicurio/registry/operator/it/ITBase.java @@ -6,6 +6,7 @@ import io.fabric8.kubernetes.api.model.NamespaceBuilder; import io.fabric8.kubernetes.api.model.Pod; import io.fabric8.kubernetes.api.model.apps.Deployment; +import io.fabric8.kubernetes.api.model.networking.v1.NetworkPolicy; import io.fabric8.kubernetes.api.model.policy.v1.PodDisruptionBudget; import io.fabric8.kubernetes.client.Config; import io.fabric8.kubernetes.client.ConfigBuilder; @@ -175,6 +176,19 @@ protected static PodDisruptionBudget checkPodDisruptionBudgetExists(ApicurioRegi return rval.getValue(); } + protected static NetworkPolicy checkNetworkPolicyExists(ApicurioRegistry3 primary, String component) { + final ValueOrNull rval = new ValueOrNull<>(); + + await().ignoreExceptions().untilAsserted(() -> { + NetworkPolicy networkPolicy = client.network().v1().networkPolicies() + .withName(primary.getMetadata().getName() + "-" + component + "-networkpolicy").get(); + assertThat(networkPolicy).isNotNull(); + rval.setValue(networkPolicy); + }); + + return rval.getValue(); + } + static KubernetesClient createK8sClient(String namespace) { return new KubernetesClientBuilder() .withConfig(new ConfigBuilder(Config.autoConfigure(null)).withNamespace(namespace).build()) diff --git a/operator/controller/src/test/java/io/apicurio/registry/operator/it/NetworkPolicyITTest.java b/operator/controller/src/test/java/io/apicurio/registry/operator/it/NetworkPolicyITTest.java new file mode 100644 index 0000000000..8253b1a79b --- /dev/null +++ b/operator/controller/src/test/java/io/apicurio/registry/operator/it/NetworkPolicyITTest.java @@ -0,0 +1,65 @@ +package io.apicurio.registry.operator.it; + +import io.apicurio.registry.operator.api.v1.ApicurioRegistry3; +import io.apicurio.registry.operator.resource.ResourceFactory; +import io.fabric8.kubernetes.api.model.networking.v1.NetworkPolicy; +import io.quarkus.test.junit.QuarkusTest; +import org.assertj.core.api.Assertions; +import org.junit.jupiter.api.Test; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.Map; +import java.util.stream.Collectors; + +@QuarkusTest +public class NetworkPolicyITTest extends ITBase { + + private static final Logger log = LoggerFactory.getLogger(NetworkPolicyITTest.class); + + @Test + void testNetworkPolicy() { + ApicurioRegistry3 registry = ResourceFactory.deserialize( + "/k8s/examples/simple-with-studio.apicurioregistry3.yaml", ApicurioRegistry3.class); + client.resource(registry).create(); + + // Wait for the deployment to exist + checkDeploymentExists(registry, ResourceFactory.COMPONENT_APP, 1); + + // Check that the two expected NetworkPolicy resources were created + NetworkPolicy appPolicy = checkNetworkPolicyExists(registry, ResourceFactory.COMPONENT_APP); + NetworkPolicy uiNetworkPolicy = checkNetworkPolicyExists(registry, ResourceFactory.COMPONENT_UI); + NetworkPolicy studioNetworkPolicy = checkNetworkPolicyExists(registry, + ResourceFactory.COMPONENT_STUDIO_UI); + + // Verify the content of the app component's network policy + assertLabelsContains(appPolicy.getMetadata().getLabels(), "app.kubernetes.io/component=app", + "app.kubernetes.io/managed-by=apicurio-registry-operator", + "app.kubernetes.io/name=apicurio-registry"); + assertLabelsContains(appPolicy.getSpec().getPodSelector().getMatchLabels(), + "app.kubernetes.io/component=app", "app.kubernetes.io/name=apicurio-registry", + "app.kubernetes.io/instance=" + registry.getMetadata().getName()); + + // Verify the content of the ui component's network policy + assertLabelsContains(uiNetworkPolicy.getMetadata().getLabels(), "app.kubernetes.io/component=ui", + "app.kubernetes.io/managed-by=apicurio-registry-operator", + "app.kubernetes.io/name=apicurio-registry"); + assertLabelsContains(uiNetworkPolicy.getSpec().getPodSelector().getMatchLabels(), + "app.kubernetes.io/component=ui", "app.kubernetes.io/name=apicurio-registry", + "app.kubernetes.io/instance=" + registry.getMetadata().getName()); + + // Verify the content of the studio component's network policy + assertLabelsContains(studioNetworkPolicy.getMetadata().getLabels(), + "app.kubernetes.io/component=studio-ui", + "app.kubernetes.io/managed-by=apicurio-registry-operator", + "app.kubernetes.io/name=apicurio-registry"); + assertLabelsContains(studioNetworkPolicy.getSpec().getPodSelector().getMatchLabels(), + "app.kubernetes.io/component=studio-ui", "app.kubernetes.io/name=apicurio-registry", + "app.kubernetes.io/instance=" + registry.getMetadata().getName()); + } + + private void assertLabelsContains(Map labels, String... values) { + Assertions.assertThat(labels.entrySet().stream().map(l -> l.getKey() + "=" + l.getValue()) + .collect(Collectors.toSet())).contains(values); + } +} \ No newline at end of file diff --git a/operator/install/install.yaml b/operator/install/install.yaml index a90136bb8b..c072129aff 100644 --- a/operator/install/install.yaml +++ b/operator/install/install.yaml @@ -123,6 +123,17 @@ spec: automatically.' type: string type: object + networkPolicy: + description: |2 + Configuration of a NetworkPolicy for the component. + properties: + enabled: + description: | + Whether a NetworkPolicy should be managed by the operator. Defaults to 'true'. + + Set this to 'false' if you want to create your own custom NetworkPolicy. + type: boolean + type: object podDisruptionBudget: description: | Configuration of a PodDisruptionBudget for the component. @@ -3277,6 +3288,17 @@ spec: IMPORTANT: If the Ingress already exists and the value becomes empty, the Ingress will be deleted. type: string type: object + networkPolicy: + description: |2 + Configuration of a NetworkPolicy for the component. + properties: + enabled: + description: | + Whether a NetworkPolicy should be managed by the operator. Defaults to 'true'. + + Set this to 'false' if you want to create your own custom NetworkPolicy. + type: boolean + type: object podDisruptionBudget: description: | Configuration of a PodDisruptionBudget for the component. @@ -6283,6 +6305,17 @@ spec: IMPORTANT: If the Ingress already exists and the value becomes empty, the Ingress will be deleted. type: string type: object + networkPolicy: + description: |2 + Configuration of a NetworkPolicy for the component. + properties: + enabled: + description: | + Whether a NetworkPolicy should be managed by the operator. Defaults to 'true'. + + Set this to 'false' if you want to create your own custom NetworkPolicy. + type: boolean + type: object podDisruptionBudget: description: | Configuration of a PodDisruptionBudget for the component. @@ -9348,6 +9381,7 @@ rules: - networking.k8s.io resources: - ingresses + - networkpolicies verbs: - '*' - apiGroups: diff --git a/operator/model/src/main/java/io/apicurio/registry/operator/api/v1/spec/ComponentSpec.java b/operator/model/src/main/java/io/apicurio/registry/operator/api/v1/spec/ComponentSpec.java index ee000e06d5..534f72b005 100644 --- a/operator/model/src/main/java/io/apicurio/registry/operator/api/v1/spec/ComponentSpec.java +++ b/operator/model/src/main/java/io/apicurio/registry/operator/api/v1/spec/ComponentSpec.java @@ -1,12 +1,22 @@ package io.apicurio.registry.operator.api.v1.spec; -import com.fasterxml.jackson.annotation.*; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.annotation.JsonPropertyDescription; +import com.fasterxml.jackson.annotation.JsonPropertyOrder; +import com.fasterxml.jackson.annotation.JsonSetter; +import com.fasterxml.jackson.annotation.Nulls; import com.fasterxml.jackson.databind.JsonDeserializer.None; import com.fasterxml.jackson.databind.annotation.JsonDeserialize; import io.apicurio.registry.operator.api.v1.ContainerNames; import io.fabric8.kubernetes.api.model.EnvVar; import io.fabric8.kubernetes.api.model.PodTemplateSpec; -import lombok.*; +import lombok.AllArgsConstructor; +import lombok.EqualsAndHashCode; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; +import lombok.ToString; import lombok.experimental.SuperBuilder; import java.util.ArrayList; @@ -19,7 +29,7 @@ @JsonDeserialize(using = None.class) @JsonInclude(NON_NULL) -@JsonPropertyOrder({ "env", "ingress", "host", "podTemplateSpec", "podDisruptionBudget" }) +@JsonPropertyOrder({ "env", "ingress", "host", "podDisruptionBudget", "networkPolicy", "podTemplateSpec" }) @NoArgsConstructor(access = PROTECTED) @AllArgsConstructor(access = PROTECTED) @SuperBuilder(toBuilder = true) @@ -92,13 +102,6 @@ public abstract class ComponentSpec { @JsonSetter(nulls = Nulls.SKIP) private Integer replicas; - public IngressSpec withIngress() { - if (ingress == null) { - ingress = new IngressSpec(); - } - return ingress; - } - /** * Pod disruption budget config */ @@ -109,4 +112,21 @@ public IngressSpec withIngress() { @JsonSetter(nulls = Nulls.SKIP) private PodDisruptionSpec podDisruptionBudget; + /** + * Network policy config + */ + @JsonProperty("networkPolicy") + @JsonPropertyDescription(""" + Configuration of a NetworkPolicy for the component. + """) + @JsonSetter(nulls = Nulls.SKIP) + private NetworkPolicySpec networkPolicy; + + public IngressSpec withIngress() { + if (ingress == null) { + ingress = new IngressSpec(); + } + return ingress; + } + } diff --git a/operator/model/src/main/java/io/apicurio/registry/operator/api/v1/spec/NetworkPolicySpec.java b/operator/model/src/main/java/io/apicurio/registry/operator/api/v1/spec/NetworkPolicySpec.java new file mode 100644 index 0000000000..e2ac24f00f --- /dev/null +++ b/operator/model/src/main/java/io/apicurio/registry/operator/api/v1/spec/NetworkPolicySpec.java @@ -0,0 +1,46 @@ +package io.apicurio.registry.operator.api.v1.spec; + +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.annotation.JsonPropertyDescription; +import com.fasterxml.jackson.annotation.JsonPropertyOrder; +import com.fasterxml.jackson.annotation.JsonSetter; +import com.fasterxml.jackson.annotation.Nulls; +import com.fasterxml.jackson.databind.JsonDeserializer.None; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import lombok.AllArgsConstructor; +import lombok.EqualsAndHashCode; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; +import lombok.ToString; +import lombok.experimental.SuperBuilder; + +import static com.fasterxml.jackson.annotation.JsonInclude.Include.NON_NULL; +import static lombok.AccessLevel.PRIVATE; + +@JsonDeserialize(using = None.class) +@JsonInclude(NON_NULL) +@JsonPropertyOrder({ "enabled" }) +@NoArgsConstructor +@AllArgsConstructor(access = PRIVATE) +@SuperBuilder(toBuilder = true) +@Getter +@Setter +@EqualsAndHashCode +@ToString +public class NetworkPolicySpec { + + /** + * Indicates whether to create and manage a network policy + */ + @JsonProperty("enabled") + @JsonPropertyDescription(""" + Whether a NetworkPolicy should be managed by the operator. Defaults to 'true'. + + Set this to 'false' if you want to create your own custom NetworkPolicy. + """) + @JsonSetter(nulls = Nulls.SKIP) + private Boolean enabled; + +} \ No newline at end of file