Skip to content

Commit 777addf

Browse files
committed
NE-1476: Added network policies for the router
Added the framework for network policies for the router. The operator has a deny all network policy that for the openshift-ingress-operator namespace and an allow policy for egress to the apiserver and dns ports at any IP. The operator installs a deny all network policy for the openshift-ingress namespace. Then for each ingresscontroller that it manages it installs an allow policy for ingress for http and https traffic and metrics. It has to allow ingress from the router pods to any IP because the route endpoints can be at any ip or pod. It also needs access to the api server, but that is covered by the wildcard allow policy. https://issues.redhat.com/browse/NE-1476
1 parent 0c57689 commit 777addf

File tree

11 files changed

+363
-7
lines changed

11 files changed

+363
-7
lines changed

manifests/00-cluster-role.yaml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -155,6 +155,7 @@ rules:
155155
- networking.k8s.io
156156
resources:
157157
- ingressclasses
158+
- networkpolicies
158159
verbs:
159160
- '*'
160161

manifests/01-network-policy.yaml

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
# We control the namespace, deny anything we do not explicitly allow
2+
apiVersion: networking.k8s.io/v1
3+
kind: NetworkPolicy
4+
metadata:
5+
name: ingress-operator-deny-all
6+
namespace: openshift-ingress-operator
7+
annotations:
8+
capability.openshift.io/name: Ingress
9+
include.release.openshift.io/self-managed-high-availability: "true"
10+
include.release.openshift.io/single-node-developer: "true"
11+
spec:
12+
podSelector: {}
13+
policyTypes:
14+
- Ingress
15+
- Egress
16+
---
17+
### Allow the operator needs to talk to the apiserver and dns,
18+
### but it also needs to be able to configure external DNS and the endpoints
19+
### are only known at run time. So it needs wildcard egress.
20+
### Allow access to the metrics ports on the operators
21+
apiVersion: networking.k8s.io/v1
22+
kind: NetworkPolicy
23+
metadata:
24+
name: ingress-operator-allow
25+
namespace: openshift-ingress-operator
26+
annotations:
27+
include.release.openshift.io/self-managed-high-availability: "true"
28+
include.release.openshift.io/single-node-developer: "true"
29+
spec:
30+
podSelector:
31+
matchLabels:
32+
name: ingress-operator
33+
policyTypes:
34+
- Egress
35+
- Ingress
36+
egress:
37+
- to:
38+
- ipBlock:
39+
cidr: 0.0.0.0/0
40+
ingress:
41+
- ports:
42+
- protocol: TCP
43+
port: 9393

manifests/01-role.yaml

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,13 @@ rules:
3737
- services
3838
verbs:
3939
- "*"
40+
41+
- apiGroups:
42+
- networking.k8s.io
43+
resources:
44+
- networkpolicies
45+
verbs:
46+
- "*"
4047
---
4148
# Role for the operator to delete Role and RoleBindings
4249
# in the openshift-config namespace.
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
# Router Pods
2+
# Egress to api server and everything (routes set endpoints, so can be literally anything)
3+
# Ingress to http on port 80 and https on port 443 (TCP) and to metrics (1936)
4+
apiVersion: networking.k8s.io/v1
5+
kind: NetworkPolicy
6+
# name, namespace,labels and annotations are set at runtime
7+
spec:
8+
podSelector:
9+
# matchLabels are set at runtime
10+
matchLabels: {}
11+
ingress:
12+
- ports:
13+
- protocol: TCP
14+
port: 80
15+
- protocol: TCP
16+
port: 443
17+
- protocol: TCP
18+
port: 1936
19+
egress:
20+
- to:
21+
- ipBlock:
22+
cidr: 0.0.0.0/0
23+
policyTypes:
24+
- Ingress
25+
- Egress
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
# Default deny all policy for all pods in the namespace
2+
apiVersion: networking.k8s.io/v1
3+
kind: NetworkPolicy
4+
metadata:
5+
name: openshift-ingress-deny-all
6+
namespace: openshift-ingress
7+
spec:
8+
podSelector: {}
9+
policyTypes:
10+
- Ingress
11+
- Egress

pkg/manifests/manifests.go

Lines changed: 35 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import (
1313

1414
appsv1 "k8s.io/api/apps/v1"
1515
corev1 "k8s.io/api/core/v1"
16+
networkingv1 "k8s.io/api/networking/v1"
1617
rbacv1 "k8s.io/api/rbac/v1"
1718
apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
1819

@@ -24,13 +25,15 @@ import (
2425
)
2526

2627
const (
27-
RouterNamespaceAsset = "assets/router/namespace.yaml"
28-
RouterServiceAccountAsset = "assets/router/service-account.yaml"
29-
RouterClusterRoleAsset = "assets/router/cluster-role.yaml"
30-
RouterClusterRoleBindingAsset = "assets/router/cluster-role-binding.yaml"
31-
RouterDeploymentAsset = "assets/router/deployment.yaml"
32-
RouterServiceInternalAsset = "assets/router/service-internal.yaml"
33-
RouterServiceCloudAsset = "assets/router/service-cloud.yaml"
28+
RouterNamespaceAsset = "assets/router/namespace.yaml"
29+
RouterServiceAccountAsset = "assets/router/service-account.yaml"
30+
RouterClusterRoleAsset = "assets/router/cluster-role.yaml"
31+
RouterClusterRoleBindingAsset = "assets/router/cluster-role-binding.yaml"
32+
RouterDeploymentAsset = "assets/router/deployment.yaml"
33+
RouterServiceInternalAsset = "assets/router/service-internal.yaml"
34+
RouterServiceCloudAsset = "assets/router/service-cloud.yaml"
35+
RouterNetworkPolicyDenyAllAsset = "assets/router/networkpolicy-deny-all.yaml"
36+
RouterNetworkPolicyAllowAsset = "assets/router/networkpolicy-allow.yaml"
3437

3538
MetricsClusterRoleAsset = "assets/router/metrics/cluster-role.yaml"
3639
MetricsClusterRoleBindingAsset = "assets/router/metrics/cluster-role-binding.yaml"
@@ -314,6 +317,22 @@ func GatewayAPIViewClusterRole() *rbacv1.ClusterRole {
314317
return clusterRole
315318
}
316319

320+
func RouterNetworkPolicyDenyAll() *networkingv1.NetworkPolicy {
321+
networkPolicy, err := NewNetworkPolicy(MustAssetReader(RouterNetworkPolicyDenyAllAsset))
322+
if err != nil {
323+
panic(err)
324+
}
325+
return networkPolicy
326+
}
327+
328+
func RouterNetworkPolicyAllow() *networkingv1.NetworkPolicy {
329+
networkPolicy, err := NewNetworkPolicy(MustAssetReader(RouterNetworkPolicyAllowAsset))
330+
if err != nil {
331+
panic(err)
332+
}
333+
return networkPolicy
334+
}
335+
317336
func NewServiceAccount(manifest io.Reader) (*corev1.ServiceAccount, error) {
318337
sa := corev1.ServiceAccount{}
319338
if err := yaml.NewYAMLOrJSONDecoder(manifest, 100).Decode(&sa); err != nil {
@@ -404,6 +423,15 @@ func NewRoute(manifest io.Reader) (*routev1.Route, error) {
404423
return &o, nil
405424
}
406425

426+
func NewNetworkPolicy(manifest io.Reader) (*networkingv1.NetworkPolicy, error) {
427+
o := networkingv1.NetworkPolicy{}
428+
if err := yaml.NewYAMLOrJSONDecoder(manifest, 100).Decode(&o); err != nil {
429+
return nil, err
430+
}
431+
432+
return &o, nil
433+
}
434+
407435
func NewCustomResourceDefinition(manifest io.Reader) (*apiextensionsv1.CustomResourceDefinition, error) {
408436
o := apiextensionsv1.CustomResourceDefinition{}
409437
if err := yaml.NewYAMLOrJSONDecoder(manifest, 100).Decode(&o); err != nil {

pkg/operator/controller/ingress/controller.go

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ import (
2424

2525
appsv1 "k8s.io/api/apps/v1"
2626
corev1 "k8s.io/api/core/v1"
27+
networkingv1 "k8s.io/api/networking/v1"
2728

2829
"k8s.io/client-go/tools/record"
2930

@@ -114,6 +115,9 @@ func New(mgr manager.Manager, config Config) (controller.Controller, error) {
114115
if err := c.Watch(source.Kind[client.Object](operatorCache, &corev1.Service{}, enqueueRequestForOwningIngressController(config.Namespace))); err != nil {
115116
return nil, err
116117
}
118+
if err := c.Watch(source.Kind[client.Object](operatorCache, &networkingv1.NetworkPolicy{}, enqueueRequestForOwningIngressController(config.Namespace))); err != nil {
119+
return nil, err
120+
}
117121
// Add watch for deleted pods specifically for ensuring ingress deletion.
118122
if err := c.Watch(source.Kind[client.Object](operatorCache, &corev1.Pod{}, enqueueRequestForOwningIngressController(config.Namespace), predicate.Funcs{
119123
CreateFunc: func(e event.CreateEvent) bool { return false },
@@ -1078,6 +1082,10 @@ func (r *reconciler) ensureIngressController(ci *operatorv1.IngressController, d
10781082
return fmt.Errorf("failed to ensure namespace: %v", err)
10791083
}
10801084

1085+
if _, _, err := r.ensureRouterNamespaceNetworkPolicy(ci); err != nil {
1086+
return fmt.Errorf("failed to ensure default namespacenetwork policy: %v", err)
1087+
}
1088+
10811089
if err := r.ensureRouterServiceAccount(); err != nil {
10821090
return fmt.Errorf("failed to ensure service account: %v", err)
10831091
}
@@ -1086,6 +1094,10 @@ func (r *reconciler) ensureIngressController(ci *operatorv1.IngressController, d
10861094
return fmt.Errorf("failed to ensure cluster role binding: %v", err)
10871095
}
10881096

1097+
if err := r.ensureRouterNetworkPolicy(ci); err != nil {
1098+
return fmt.Errorf("failed to ensure network policy: %v", err)
1099+
}
1100+
10891101
var errs []error
10901102
if _, _, err := r.ensureServiceCAConfigMap(); err != nil {
10911103
// Even if we were unable to create the configmap at this time,

pkg/operator/controller/ingress/namespace.go

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,10 +8,12 @@ import (
88
"github.com/google/go-cmp/cmp/cmpopts"
99

1010
corev1 "k8s.io/api/core/v1"
11+
networkingv1 "k8s.io/api/networking/v1"
1112

1213
"k8s.io/apimachinery/pkg/api/errors"
1314
"k8s.io/apimachinery/pkg/types"
1415

16+
operatorv1 "github.com/openshift/api/operator/v1"
1517
"github.com/openshift/cluster-ingress-operator/pkg/manifests"
1618
operatorcontroller "github.com/openshift/cluster-ingress-operator/pkg/operator/controller"
1719
)
@@ -155,3 +157,17 @@ func (r *reconciler) ensureRouterClusterRoleBinding() error {
155157
}
156158
return nil
157159
}
160+
161+
func (r *reconciler) ensureRouterNamespaceNetworkPolicy(ci *operatorv1.IngressController) (bool, *networkingv1.NetworkPolicy, error) {
162+
np := manifests.RouterNetworkPolicyDenyAll()
163+
if err := r.client.Get(context.TODO(), types.NamespacedName{Namespace: np.Namespace, Name: np.Name}, np); err != nil {
164+
if !errors.IsNotFound(err) {
165+
return false, nil, fmt.Errorf("failed to get router network policy %s/%s: %v", np.Namespace, np.Name, err)
166+
}
167+
if err := r.client.Create(context.TODO(), np); err != nil {
168+
return false, nil, fmt.Errorf("failed to create router network policy %s/%s: %v", np.Namespace, np.Name, err)
169+
}
170+
log.Info("created router network policy", "namespace", np.Namespace, "name", np.Name)
171+
}
172+
return true, np, nil
173+
}
Lines changed: 121 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,121 @@
1+
package ingress
2+
3+
import (
4+
"context"
5+
"fmt"
6+
7+
"github.com/google/go-cmp/cmp"
8+
"github.com/google/go-cmp/cmp/cmpopts"
9+
10+
operatorv1 "github.com/openshift/api/operator/v1"
11+
"github.com/openshift/cluster-ingress-operator/pkg/manifests"
12+
controller "github.com/openshift/cluster-ingress-operator/pkg/operator/controller"
13+
14+
networkingv1 "k8s.io/api/networking/v1"
15+
16+
"k8s.io/apimachinery/pkg/api/errors"
17+
)
18+
19+
// ensureRouterNetworkPolicy ensures the per-ingresscontroller NetworkPolicy that
20+
// allows ingress to router pods and egress to the network exists and is up to date.
21+
func (r *reconciler) ensureRouterNetworkPolicy(ic *operatorv1.IngressController) error {
22+
desired := desiredRouterNetworkPolicy(ic)
23+
24+
have, current, err := r.currentRouterNetworkPolicy(ic)
25+
if err != nil {
26+
return err
27+
}
28+
29+
switch {
30+
case !have:
31+
if err := r.client.Create(context.TODO(), desired); err != nil {
32+
return fmt.Errorf("failed to create router network policy: %v", err)
33+
}
34+
log.Info("created router network policy", "networkpolicy", desired)
35+
return nil
36+
default:
37+
if updated, err := r.updateRouterNetworkPolicy(current, desired); err != nil {
38+
return fmt.Errorf("failed to update router network policy: %v", err)
39+
} else if updated {
40+
return nil
41+
}
42+
}
43+
44+
return nil
45+
}
46+
47+
// desiredRouterNetworkPolicy returns the desired per-ingresscontroller NetworkPolicy.
48+
func desiredRouterNetworkPolicy(ic *operatorv1.IngressController) *networkingv1.NetworkPolicy {
49+
np := manifests.RouterNetworkPolicyAllow()
50+
51+
name := controller.RouterNetworkPolicyName(ic)
52+
np.Namespace = name.Namespace
53+
np.Name = name.Name
54+
55+
if np.Labels == nil {
56+
np.Labels = map[string]string{}
57+
}
58+
np.Labels[manifests.OwningIngressControllerLabel] = ic.Name
59+
60+
// Select router pods for this ingresscontroller.
61+
np.Spec.PodSelector = *controller.IngressControllerDeploymentPodSelector(ic)
62+
63+
return np
64+
}
65+
66+
// currentRouterNetworkPolicy returns the current per-ingresscontroller NetworkPolicy, if it exists.
67+
func (r *reconciler) currentRouterNetworkPolicy(ic *operatorv1.IngressController) (bool, *networkingv1.NetworkPolicy, error) {
68+
current := &networkingv1.NetworkPolicy{}
69+
if err := r.client.Get(context.TODO(), controller.RouterNetworkPolicyName(ic), current); err != nil {
70+
if errors.IsNotFound(err) {
71+
return false, nil, nil
72+
}
73+
return false, nil, err
74+
}
75+
return true, current, nil
76+
}
77+
78+
// updateRouterNetworkPolicy updates the NetworkPolicy if it differs from the desired state.
79+
func (r *reconciler) updateRouterNetworkPolicy(current, desired *networkingv1.NetworkPolicy) (bool, error) {
80+
changed, updated := routerNetworkPolicyChanged(current, desired)
81+
if !changed {
82+
return false, nil
83+
}
84+
85+
// Diff before updating because the client may mutate the object.
86+
diff := cmp.Diff(current, updated, cmpopts.EquateEmpty())
87+
if err := r.client.Update(context.TODO(), updated); err != nil {
88+
return false, err
89+
}
90+
log.Info("updated router network policy", "namespace", updated.Namespace, "name", updated.Name, "diff", diff)
91+
return true, nil
92+
}
93+
94+
// routerNetworkPolicyChanged checks whether the current NetworkPolicy matches the expected
95+
// state and, if not, returns an updated NetworkPolicy.
96+
func routerNetworkPolicyChanged(current, expected *networkingv1.NetworkPolicy) (bool, *networkingv1.NetworkPolicy) {
97+
changed := false
98+
99+
if !cmp.Equal(current.Spec, expected.Spec, cmpopts.EquateEmpty()) {
100+
changed = true
101+
}
102+
103+
// Ensure the owning-ingresscontroller label value is as expected while preserving other labels.
104+
if current.Labels == nil || current.Labels[manifests.OwningIngressControllerLabel] != expected.Labels[manifests.OwningIngressControllerLabel] {
105+
changed = true
106+
}
107+
108+
if !changed {
109+
return false, nil
110+
}
111+
112+
updated := current.DeepCopy()
113+
updated.Spec = expected.Spec
114+
115+
if updated.Labels == nil {
116+
updated.Labels = map[string]string{}
117+
}
118+
updated.Labels[manifests.OwningIngressControllerLabel] = expected.Labels[manifests.OwningIngressControllerLabel]
119+
120+
return true, updated
121+
}

0 commit comments

Comments
 (0)