From b28cecceeaf98657d5b0900f5b53edcce6e70c11 Mon Sep 17 00:00:00 2001 From: Shay Bratslavsky Date: Sun, 26 Jul 2026 11:46:01 +0300 Subject: [PATCH] ACM-31165: Add NetworkPolicies for Infrastructure Operator components Create NetworkPolicies for assisted-service and assisted-image-service dynamically via AgentServiceConfig reconciliation, consistent with how the operator manages other resources. The infrastructure-operator's own NetworkPolicy is static YAML in config/manager/ and deploy/olm-catalog/manifests/. Ingress restricted to known ports, egress to DNS, K8s API, spoke clusters, and HTTPS. IPv4 and IPv6 supported. --- api/v1beta1/agentserviceconfig_types.go | 2 + ...infrastructure-operator-networkpolicy.yaml | 60 +++++++ config/manager/kustomization.yaml | 1 + config/rbac/role.yaml | 1 + ...ervice-operator.clusterserviceversion.yaml | 1 + ...infrastructure-operator-networkpolicy.yaml | 60 +++++++ .../agentserviceconfig_controller.go | 147 ++++++++++++++++++ .../api/v1beta1/agentserviceconfig_types.go | 2 + 8 files changed, 274 insertions(+) create mode 100644 config/manager/infrastructure-operator-networkpolicy.yaml create mode 100644 deploy/olm-catalog/manifests/infrastructure-operator-networkpolicy.yaml diff --git a/api/v1beta1/agentserviceconfig_types.go b/api/v1beta1/agentserviceconfig_types.go index 9458f210b638..cd0d22650d14 100644 --- a/api/v1beta1/agentserviceconfig_types.go +++ b/api/v1beta1/agentserviceconfig_types.go @@ -255,6 +255,8 @@ const ( ReasonOSImageAdditionalParamsRefFailure string = "ReasonOSImageAdditionalParamsRefFailure" // ReasonImmutableAnnotationFailure when there has been a failure validating immutable annotations. ReasonImmutableAnnotationFailure string = "ImmutableAnnotationFailure" + // ReasonNetworkPolicyFailure when there was a failure configuring/deploying a network policy. + ReasonNetworkPolicyFailure string = "NetworkPolicyFailure" ) // AgentServiceConfigStatus defines the observed state of AgentServiceConfig diff --git a/config/manager/infrastructure-operator-networkpolicy.yaml b/config/manager/infrastructure-operator-networkpolicy.yaml new file mode 100644 index 000000000000..bdc120a907c5 --- /dev/null +++ b/config/manager/infrastructure-operator-networkpolicy.yaml @@ -0,0 +1,60 @@ +apiVersion: networking.k8s.io/v1 +kind: NetworkPolicy +metadata: + name: infrastructure-operator + labels: + control-plane: infrastructure-operator +spec: + podSelector: + matchLabels: + control-plane: infrastructure-operator + policyTypes: + - Ingress + - Egress + ingress: + # Same-namespace traffic + - from: + - podSelector: {} + # Monitoring + - from: + - namespaceSelector: + matchLabels: + network.openshift.io/policy-group: monitoring + ports: + - protocol: TCP + port: 8080 + # Webhook calls from kube-apiserver + - ports: + - protocol: TCP + port: 9443 + egress: + # DNS + - to: + - namespaceSelector: + matchLabels: + kubernetes.io/metadata.name: openshift-dns + ports: + - protocol: UDP + port: 5353 + - protocol: TCP + port: 5353 + # Kubernetes API + - to: + - namespaceSelector: {} + podSelector: + matchLabels: + component: apiserver + ports: + - protocol: TCP + port: 6443 + # HTTPS egress (registries for image lookups) + - to: + - ipBlock: + cidr: 0.0.0.0/0 + except: + - 169.254.169.254/32 + - ipBlock: + cidr: ::/0 + ports: + - protocol: TCP + port: 443 diff --git a/config/manager/kustomization.yaml b/config/manager/kustomization.yaml index fd9daba88a01..e2cd8678b5fa 100644 --- a/config/manager/kustomization.yaml +++ b/config/manager/kustomization.yaml @@ -6,3 +6,4 @@ images: newTag: latest resources: - manager.yaml +- infrastructure-operator-networkpolicy.yaml diff --git a/config/rbac/role.yaml b/config/rbac/role.yaml index 88d4f9914d84..97310452c56f 100644 --- a/config/rbac/role.yaml +++ b/config/rbac/role.yaml @@ -297,6 +297,7 @@ rules: - networking.k8s.io resources: - ingresses + - networkpolicies verbs: - create - delete diff --git a/deploy/olm-catalog/manifests/assisted-service-operator.clusterserviceversion.yaml b/deploy/olm-catalog/manifests/assisted-service-operator.clusterserviceversion.yaml index 235e7412560c..a1d1c51ab3ab 100644 --- a/deploy/olm-catalog/manifests/assisted-service-operator.clusterserviceversion.yaml +++ b/deploy/olm-catalog/manifests/assisted-service-operator.clusterserviceversion.yaml @@ -858,6 +858,7 @@ spec: - networking.k8s.io resources: - ingresses + - networkpolicies verbs: - create - delete diff --git a/deploy/olm-catalog/manifests/infrastructure-operator-networkpolicy.yaml b/deploy/olm-catalog/manifests/infrastructure-operator-networkpolicy.yaml new file mode 100644 index 000000000000..bdc120a907c5 --- /dev/null +++ b/deploy/olm-catalog/manifests/infrastructure-operator-networkpolicy.yaml @@ -0,0 +1,60 @@ +apiVersion: networking.k8s.io/v1 +kind: NetworkPolicy +metadata: + name: infrastructure-operator + labels: + control-plane: infrastructure-operator +spec: + podSelector: + matchLabels: + control-plane: infrastructure-operator + policyTypes: + - Ingress + - Egress + ingress: + # Same-namespace traffic + - from: + - podSelector: {} + # Monitoring + - from: + - namespaceSelector: + matchLabels: + network.openshift.io/policy-group: monitoring + ports: + - protocol: TCP + port: 8080 + # Webhook calls from kube-apiserver + - ports: + - protocol: TCP + port: 9443 + egress: + # DNS + - to: + - namespaceSelector: + matchLabels: + kubernetes.io/metadata.name: openshift-dns + ports: + - protocol: UDP + port: 5353 + - protocol: TCP + port: 5353 + # Kubernetes API + - to: + - namespaceSelector: {} + podSelector: + matchLabels: + component: apiserver + ports: + - protocol: TCP + port: 6443 + # HTTPS egress (registries for image lookups) + - to: + - ipBlock: + cidr: 0.0.0.0/0 + except: + - 169.254.169.254/32 + - ipBlock: + cidr: ::/0 + ports: + - protocol: TCP + port: 443 diff --git a/internal/controller/controllers/agentserviceconfig_controller.go b/internal/controller/controllers/agentserviceconfig_controller.go index af865f85b758..31d52c88d083 100644 --- a/internal/controller/controllers/agentserviceconfig_controller.go +++ b/internal/controller/controllers/agentserviceconfig_controller.go @@ -61,6 +61,7 @@ import ( "k8s.io/apimachinery/pkg/util/intstr" "k8s.io/client-go/tools/record" apiregv1 "k8s.io/kube-aggregator/pkg/apis/apiregistration/v1" + "k8s.io/utils/ptr" ctrl "sigs.k8s.io/controller-runtime" "sigs.k8s.io/controller-runtime/pkg/builder" "sigs.k8s.io/controller-runtime/pkg/client" @@ -221,6 +222,7 @@ type ComponentStatusFn func(context.Context, logrus.FieldLogger, string, appsv1. // +kubebuilder:rbac:groups="apiregistration.k8s.io",resources=apiservices,verbs=get;list;watch;create;update;patch;delete // +kubebuilder:rbac:groups=authorization.k8s.io,resources=subjectaccessreviews,verbs=create // +kubebuilder:rbac:groups=networking.k8s.io,resources=ingresses,verbs=get;list;watch;create;update;patch;delete +// +kubebuilder:rbac:groups=networking.k8s.io,resources=networkpolicies,verbs=get;list;watch;create;update;patch;delete // +kubebuilder:rbac:groups=cert-manager.io,resources=certificates,verbs=get;list;watch;create;update;patch;delete // +kubebuilder:rbac:groups=cert-manager.io,resources=issuers,verbs=get;list;watch;create;update;patch;delete @@ -384,6 +386,7 @@ func getComponents(spec *aiv1beta1.AgentServiceConfigSpec, isOpenshift bool, ann {"FilesystemStorage", aiv1beta1.ReasonStorageFailure, newFilesystemPVC}, {"DatabaseStorage", aiv1beta1.ReasonStorageFailure, newDatabasePVC}, {"AgentService", aiv1beta1.ReasonAgentServiceFailure, newAgentService}, + {"AgentServiceNetworkPolicy", aiv1beta1.ReasonNetworkPolicyFailure, newAssistedServiceNetworkPolicy}, {"AgentLocalAuthSecret", aiv1beta1.ReasonAgentLocalAuthSecretFailure, newAgentLocalAuthSecret}, {"DatabaseSecret", aiv1beta1.ReasonPostgresSecretFailure, newPostgresSecret}, {"AgentRoute", aiv1beta1.ReasonAgentRouteFailure, newAgentRoute}, @@ -392,6 +395,7 @@ func getComponents(spec *aiv1beta1.AgentServiceConfigSpec, isOpenshift bool, ann if imageServiceEnabled { components = append(components, component{"ImageServiceService", aiv1beta1.ReasonImageHandlerServiceFailure, newImageServiceService}, + component{"ImageServiceNetworkPolicy", aiv1beta1.ReasonNetworkPolicyFailure, newImageServiceNetworkPolicy}, component{"ImageServiceServiceAccount", aiv1beta1.ReasonImageHandlerServiceAccountFailure, newImageServiceServiceAccount}, component{"ImageServiceRoute", aiv1beta1.ReasonImageHandlerRouteFailure, newImageServiceRoute}, ) @@ -442,6 +446,7 @@ func (r *AgentServiceConfigReconciler) getWebhookComponents() []component { {"InfraEnvValidatingWebHook", aiv1beta1.ReasonValidatingWebHookFailure, newInfraEnvWebHook}, {"AgentValidatingWebHook", aiv1beta1.ReasonValidatingWebHookFailure, newAgentWebHook}, {"WebHookService", aiv1beta1.ReasonWebHookServiceFailure, newWebHookService}, + {"WebHookNetworkPolicy", aiv1beta1.ReasonNetworkPolicyFailure, newWebhookNetworkPolicy}, {"WebHookServiceDeployment", aiv1beta1.ReasonWebHookDeploymentFailure, newWebHookDeployment}, {"WebHookServiceAccount", aiv1beta1.ReasonWebHookServiceAccountFailure, newWebHookServiceAccount}, {"WebHookClusterRole", aiv1beta1.ReasonWebHookClusterRoleFailure, newWebHookClusterRole}, @@ -778,6 +783,148 @@ func newImageServiceService(ctx context.Context, log logrus.FieldLogger, asc ASC return svc, mutateFn, nil } +func newAssistedServiceNetworkPolicy(ctx context.Context, log logrus.FieldLogger, asc ASC) (client.Object, controllerutil.MutateFn, error) { + np := &netv1.NetworkPolicy{ + ObjectMeta: metav1.ObjectMeta{ + Name: serviceName, + Namespace: asc.namespace, + }, + } + + mutateFn := func() error { + if err := controllerutil.SetControllerReference(asc.Object, np, asc.rec.Scheme); err != nil { + return err + } + addAppLabel(serviceName, &np.ObjectMeta) + + np.Spec = netv1.NetworkPolicySpec{ + PodSelector: metav1.LabelSelector{ + MatchLabels: map[string]string{"app": serviceName}, + }, + PolicyTypes: []netv1.PolicyType{netv1.PolicyTypeIngress, netv1.PolicyTypeEgress}, + Ingress: []netv1.NetworkPolicyIngressRule{ + {From: []netv1.NetworkPolicyPeer{{PodSelector: &metav1.LabelSelector{}}}}, + { + Ports: []netv1.NetworkPolicyPort{ + {Protocol: ptr.To(corev1.ProtocolTCP), Port: &servicePort}, + {Protocol: ptr.To(corev1.ProtocolTCP), Port: &serviceHTTPPort}, + {Protocol: ptr.To(corev1.ProtocolTCP), Port: &intstr.IntOrString{Type: intstr.Int, IntVal: 9443}}, + }, + }, + }, + Egress: networkPolicyDefaultEgress(), + } + return nil + } + + return np, mutateFn, nil +} + +func newImageServiceNetworkPolicy(ctx context.Context, log logrus.FieldLogger, asc ASC) (client.Object, controllerutil.MutateFn, error) { + np := &netv1.NetworkPolicy{ + ObjectMeta: metav1.ObjectMeta{ + Name: imageServiceName, + Namespace: asc.namespace, + }, + } + + mutateFn := func() error { + if err := controllerutil.SetControllerReference(asc.Object, np, asc.rec.Scheme); err != nil { + return err + } + addAppLabel(imageServiceName, &np.ObjectMeta) + + np.Spec = netv1.NetworkPolicySpec{ + PodSelector: metav1.LabelSelector{ + MatchLabels: map[string]string{"app": imageServiceName}, + }, + PolicyTypes: []netv1.PolicyType{netv1.PolicyTypeIngress, netv1.PolicyTypeEgress}, + Ingress: []netv1.NetworkPolicyIngressRule{ + {From: []netv1.NetworkPolicyPeer{{PodSelector: &metav1.LabelSelector{}}}}, + { + Ports: []netv1.NetworkPolicyPort{ + {Protocol: ptr.To(corev1.ProtocolTCP), Port: &imageHandlerPort}, + {Protocol: ptr.To(corev1.ProtocolTCP), Port: &imageHandlerHTTPPort}, + }, + }, + }, + Egress: networkPolicyDefaultEgress(), + } + return nil + } + + return np, mutateFn, nil +} + +func newWebhookNetworkPolicy(ctx context.Context, log logrus.FieldLogger, asc ASC) (client.Object, controllerutil.MutateFn, error) { + np := &netv1.NetworkPolicy{ + ObjectMeta: metav1.ObjectMeta{ + Name: webhookServiceName, + Namespace: asc.namespace, + }, + } + + mutateFn := func() error { + if err := controllerutil.SetControllerReference(asc.Object, np, asc.rec.Scheme); err != nil { + return err + } + addAppLabel(webhookServiceName, &np.ObjectMeta) + + np.Spec = netv1.NetworkPolicySpec{ + PodSelector: metav1.LabelSelector{ + MatchLabels: map[string]string{"app": webhookServiceName}, + }, + PolicyTypes: []netv1.PolicyType{netv1.PolicyTypeIngress, netv1.PolicyTypeEgress}, + Ingress: []netv1.NetworkPolicyIngressRule{ + {From: []netv1.NetworkPolicyPeer{{PodSelector: &metav1.LabelSelector{}}}}, + { + Ports: []netv1.NetworkPolicyPort{ + {Protocol: ptr.To(corev1.ProtocolTCP), Port: &intstr.IntOrString{Type: intstr.Int, IntVal: 9443}}, + }, + }, + }, + Egress: networkPolicyDefaultEgress(), + } + return nil + } + + return np, mutateFn, nil +} + +func networkPolicyDefaultEgress() []netv1.NetworkPolicyEgressRule { + return []netv1.NetworkPolicyEgressRule{ + { + To: []netv1.NetworkPolicyPeer{{ + NamespaceSelector: &metav1.LabelSelector{ + MatchLabels: map[string]string{"kubernetes.io/metadata.name": "openshift-dns"}, + }, + }}, + Ports: []netv1.NetworkPolicyPort{ + {Protocol: ptr.To(corev1.ProtocolUDP), Port: &intstr.IntOrString{Type: intstr.Int, IntVal: 5353}}, + {Protocol: ptr.To(corev1.ProtocolTCP), Port: &intstr.IntOrString{Type: intstr.Int, IntVal: 5353}}, + }, + }, + { + To: []netv1.NetworkPolicyPeer{ + {IPBlock: &netv1.IPBlock{CIDR: "0.0.0.0/0"}}, + {IPBlock: &netv1.IPBlock{CIDR: "::/0"}}, + }, + Ports: []netv1.NetworkPolicyPort{ + {Protocol: ptr.To(corev1.ProtocolTCP), Port: &intstr.IntOrString{Type: intstr.Int, IntVal: 6443}}, + }, + }, + { + To: []netv1.NetworkPolicyPeer{ + {IPBlock: &netv1.IPBlock{CIDR: "0.0.0.0/0", Except: []string{"169.254.169.254/32"}}}, + {IPBlock: &netv1.IPBlock{CIDR: "::/0"}}, + }, + Ports: []netv1.NetworkPolicyPort{ + {Protocol: ptr.To(corev1.ProtocolTCP), Port: &intstr.IntOrString{Type: intstr.Int, IntVal: 443}}, + }, + }, + } +} + func newServiceMonitor(ctx context.Context, log logrus.FieldLogger, asc ASC) (client.Object, controllerutil.MutateFn, error) { sm := &monitoringv1.ServiceMonitor{ ObjectMeta: metav1.ObjectMeta{ diff --git a/vendor/github.com/openshift/assisted-service/api/v1beta1/agentserviceconfig_types.go b/vendor/github.com/openshift/assisted-service/api/v1beta1/agentserviceconfig_types.go index 9458f210b638..cd0d22650d14 100644 --- a/vendor/github.com/openshift/assisted-service/api/v1beta1/agentserviceconfig_types.go +++ b/vendor/github.com/openshift/assisted-service/api/v1beta1/agentserviceconfig_types.go @@ -255,6 +255,8 @@ const ( ReasonOSImageAdditionalParamsRefFailure string = "ReasonOSImageAdditionalParamsRefFailure" // ReasonImmutableAnnotationFailure when there has been a failure validating immutable annotations. ReasonImmutableAnnotationFailure string = "ImmutableAnnotationFailure" + // ReasonNetworkPolicyFailure when there was a failure configuring/deploying a network policy. + ReasonNetworkPolicyFailure string = "NetworkPolicyFailure" ) // AgentServiceConfigStatus defines the observed state of AgentServiceConfig