diff --git a/api/v1/runtimecomponent_types.go b/api/v1/runtimecomponent_types.go index 2e694dfcd..ce883e0aa 100644 --- a/api/v1/runtimecomponent_types.go +++ b/api/v1/runtimecomponent_types.go @@ -328,13 +328,37 @@ type RuntimeComponentNetworkPolicy struct { // +operator-sdk:csv:customresourcedefinitions:order=46,type=spec,displayName="Disable",xDescriptors="urn:alm:descriptor:com.tectonic.ui:booleanSwitch" Disable *bool `json:"disable,omitempty"` - // Specify the labels of namespaces that incoming traffic is allowed from. - // +operator-sdk:csv:customresourcedefinitions:order=47,type=spec,displayName="Namespace Labels",xDescriptors="urn:alm:descriptor:com.tectonic.ui:text" + // Disable the creation of the network policy ingress. Defaults to false. + // +operator-sdk:csv:customresourcedefinitions:order=47,type=spec,displayName="Disable Ingress",xDescriptors="urn:alm:descriptor:com.tectonic.ui:booleanSwitch" + DisableIngress *bool `json:"disableIngress,omitempty"` + + // Disable the creation of the network policy egress. Defaults to false. + // +operator-sdk:csv:customresourcedefinitions:order=48,type=spec,displayName="Disable Egress",xDescriptors="urn:alm:descriptor:com.tectonic.ui:booleanSwitch" + DisableEgress *bool `json:"disableEgress,omitempty"` + + // Bypasses deny all egress rules to allow API server and DNS access. Defaults to false. + // +operator-sdk:csv:customresourcedefinitions:order=49,type=spec,displayName="Bypass Deny All Egress",xDescriptors="urn:alm:descriptor:com.tectonic.ui:booleanSwitch" + BypassDenyAllEgress *bool `json:"bypassDenyAllEgress,omitempty"` + + // Deprecated. .spec.networkPolicy.fromNamespaceLabels should be used instead. If both are specified, .spec.networkPolicy.fromNamespaceLabels will override this. + // +operator-sdk:csv:customresourcedefinitions:order=50,type=spec,displayName="Namespace Labels",xDescriptors="urn:alm:descriptor:com.tectonic.ui:text" NamespaceLabels *map[string]string `json:"namespaceLabels,omitempty"` + // Specify the labels of namespaces that incoming traffic is allowed from. + // +operator-sdk:csv:customresourcedefinitions:order=51,type=spec,displayName="From Namespace Labels",xDescriptors="urn:alm:descriptor:com.tectonic.ui:text" + FromNamespaceLabels *map[string]string `json:"fromNamespaceLabels,omitempty"` + // Specify the labels of pod(s) that incoming traffic is allowed from. - // +operator-sdk:csv:customresourcedefinitions:order=48,type=spec,displayName="From Labels",xDescriptors="urn:alm:descriptor:com.tectonic.ui:text" + // +operator-sdk:csv:customresourcedefinitions:order=52,type=spec,displayName="From Labels",xDescriptors="urn:alm:descriptor:com.tectonic.ui:text" FromLabels *map[string]string `json:"fromLabels,omitempty"` + + // Specify the labels of namespaces that outgoing traffic is allowed to. + // +operator-sdk:csv:customresourcedefinitions:order=53,type=spec,displayName="To Namespace Labels",xDescriptors="urn:alm:descriptor:com.tectonic.ui:text" + ToNamespaceLabels *map[string]string `json:"toNamespaceLabels,omitempty"` + + // Specify the labels of pod(s) that outgoing traffic is allowed to. + // +operator-sdk:csv:customresourcedefinitions:order=54,type=spec,displayName="To Labels",xDescriptors="urn:alm:descriptor:com.tectonic.ui:text" + ToLabels *map[string]string `json:"toLabels,omitempty"` } // Defines the desired state and cycle of applications. @@ -910,7 +934,25 @@ func (ssa *RuntimeComponentServiceSessionAffinity) GetConfig() *corev1.SessionAf return ssa.Config } -func (np *RuntimeComponentNetworkPolicy) GetNamespaceLabels() map[string]string { +func (np *RuntimeComponentNetworkPolicy) GetToNamespaceLabels() map[string]string { + if np.ToNamespaceLabels != nil { + return *np.ToNamespaceLabels + } + return nil +} + +func (np *RuntimeComponentNetworkPolicy) GetToLabels() map[string]string { + if np.ToLabels != nil { + return *np.ToLabels + } + return nil +} + +func (np *RuntimeComponentNetworkPolicy) GetFromNamespaceLabels() map[string]string { + if np.FromNamespaceLabels != nil { + return *np.FromNamespaceLabels + } + // fallback to deprecated flag np.NamespaceLabels for when we only supported one type of network policy (ingress) if np.NamespaceLabels != nil { return *np.NamespaceLabels } @@ -928,6 +970,18 @@ func (np *RuntimeComponentNetworkPolicy) IsDisabled() bool { return np.Disable != nil && *np.Disable } +func (np *RuntimeComponentNetworkPolicy) IsIngressDisabled() bool { + return np.DisableIngress != nil && *np.DisableIngress +} + +func (np *RuntimeComponentNetworkPolicy) IsEgressDisabled() bool { + return np.DisableEgress != nil && *np.DisableEgress +} + +func (np *RuntimeComponentNetworkPolicy) IsBypassingDenyAllEgress() bool { + return np.BypassDenyAllEgress != nil && *np.BypassDenyAllEgress +} + // GetLabels returns labels to be added on ServiceMonitor func (m *RuntimeComponentMonitoring) GetLabels() map[string]string { return m.Labels diff --git a/api/v1/zz_generated.deepcopy.go b/api/v1/zz_generated.deepcopy.go index 921a97e33..2eaf5456a 100644 --- a/api/v1/zz_generated.deepcopy.go +++ b/api/v1/zz_generated.deepcopy.go @@ -286,6 +286,21 @@ func (in *RuntimeComponentNetworkPolicy) DeepCopyInto(out *RuntimeComponentNetwo *out = new(bool) **out = **in } + if in.DisableIngress != nil { + in, out := &in.DisableIngress, &out.DisableIngress + *out = new(bool) + **out = **in + } + if in.DisableEgress != nil { + in, out := &in.DisableEgress, &out.DisableEgress + *out = new(bool) + **out = **in + } + if in.BypassDenyAllEgress != nil { + in, out := &in.BypassDenyAllEgress, &out.BypassDenyAllEgress + *out = new(bool) + **out = **in + } if in.NamespaceLabels != nil { in, out := &in.NamespaceLabels, &out.NamespaceLabels *out = new(map[string]string) @@ -297,6 +312,17 @@ func (in *RuntimeComponentNetworkPolicy) DeepCopyInto(out *RuntimeComponentNetwo } } } + if in.FromNamespaceLabels != nil { + in, out := &in.FromNamespaceLabels, &out.FromNamespaceLabels + *out = new(map[string]string) + if **in != nil { + in, out := *in, *out + *out = make(map[string]string, len(*in)) + for key, val := range *in { + (*out)[key] = val + } + } + } if in.FromLabels != nil { in, out := &in.FromLabels, &out.FromLabels *out = new(map[string]string) @@ -308,6 +334,28 @@ func (in *RuntimeComponentNetworkPolicy) DeepCopyInto(out *RuntimeComponentNetwo } } } + if in.ToNamespaceLabels != nil { + in, out := &in.ToNamespaceLabels, &out.ToNamespaceLabels + *out = new(map[string]string) + if **in != nil { + in, out := *in, *out + *out = make(map[string]string, len(*in)) + for key, val := range *in { + (*out)[key] = val + } + } + } + if in.ToLabels != nil { + in, out := &in.ToLabels, &out.ToLabels + *out = new(map[string]string) + if **in != nil { + in, out := *in, *out + *out = make(map[string]string, len(*in)) + for key, val := range *in { + (*out)[key] = val + } + } + } } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new RuntimeComponentNetworkPolicy. diff --git a/bundle/manifests/rc.app.stacks_runtimecomponents.yaml b/bundle/manifests/rc.app.stacks_runtimecomponents.yaml index 96b7f2186..2c42d852b 100644 --- a/bundle/manifests/rc.app.stacks_runtimecomponents.yaml +++ b/bundle/manifests/rc.app.stacks_runtimecomponents.yaml @@ -3842,22 +3842,53 @@ spec: networkPolicy: description: Defines the network policy properties: + bypassDenyAllEgress: + description: Bypasses deny all egress rules to allow API server + and DNS access. Defaults to false. + type: boolean disable: description: Disable the creation of the network policy. Defaults to false. type: boolean + disableEgress: + description: Disable the creation of the network policy egress. + Defaults to false. + type: boolean + disableIngress: + description: Disable the creation of the network policy ingress. + Defaults to false. + type: boolean fromLabels: additionalProperties: type: string description: Specify the labels of pod(s) that incoming traffic is allowed from. type: object - namespaceLabels: + fromNamespaceLabels: additionalProperties: type: string description: Specify the labels of namespaces that incoming traffic is allowed from. type: object + namespaceLabels: + additionalProperties: + type: string + description: Deprecated. .spec.networkPolicy.fromNamespaceLabels + should be used instead. If both are specified, .spec.networkPolicy.fromNamespaceLabels + will override this. + type: object + toLabels: + additionalProperties: + type: string + description: Specify the labels of pod(s) that outgoing traffic + is allowed to. + type: object + toNamespaceLabels: + additionalProperties: + type: string + description: Specify the labels of namespaces that outgoing traffic + is allowed to. + type: object type: object probes: description: Define health checks on application container to determine diff --git a/bundle/manifests/runtime-component.clusterserviceversion.yaml b/bundle/manifests/runtime-component.clusterserviceversion.yaml index 019e03f9a..86fe7fe82 100644 --- a/bundle/manifests/runtime-component.clusterserviceversion.yaml +++ b/bundle/manifests/runtime-component.clusterserviceversion.yaml @@ -492,18 +492,55 @@ spec: path: networkPolicy.disable x-descriptors: - urn:alm:descriptor:com.tectonic.ui:booleanSwitch - - description: Specify the labels of namespaces that incoming traffic is allowed - from. + - description: Disable the creation of the network policy ingress. Defaults + to false. + displayName: Disable Ingress + path: networkPolicy.disableIngress + x-descriptors: + - urn:alm:descriptor:com.tectonic.ui:booleanSwitch + - description: Disable the creation of the network policy egress. Defaults to + false. + displayName: Disable Egress + path: networkPolicy.disableEgress + x-descriptors: + - urn:alm:descriptor:com.tectonic.ui:booleanSwitch + - description: Bypasses deny all egress rules to allow API server and DNS access. + Defaults to false. + displayName: Bypass Deny All Egress + path: networkPolicy.bypassDenyAllEgress + x-descriptors: + - urn:alm:descriptor:com.tectonic.ui:booleanSwitch + - description: Deprecated. .spec.networkPolicy.fromNamespaceLabels should be + used instead. If both are specified, .spec.networkPolicy.fromNamespaceLabels + will override this. displayName: Namespace Labels path: networkPolicy.namespaceLabels x-descriptors: - urn:alm:descriptor:com.tectonic.ui:text + - description: Specify the labels of namespaces that incoming traffic is allowed + from. + displayName: From Namespace Labels + path: networkPolicy.fromNamespaceLabels + x-descriptors: + - urn:alm:descriptor:com.tectonic.ui:text - description: Specify the labels of pod(s) that incoming traffic is allowed from. displayName: From Labels path: networkPolicy.fromLabels x-descriptors: - urn:alm:descriptor:com.tectonic.ui:text + - description: Specify the labels of namespaces that outgoing traffic is allowed + to. + displayName: To Namespace Labels + path: networkPolicy.toNamespaceLabels + x-descriptors: + - urn:alm:descriptor:com.tectonic.ui:text + - description: Specify the labels of pod(s) that outgoing traffic is allowed + to. + displayName: To Labels + path: networkPolicy.toLabels + x-descriptors: + - urn:alm:descriptor:com.tectonic.ui:text - description: Hide liveness probe's Exec field displayName: Livness Probe's Exec path: probes.liveness.exec @@ -1191,6 +1228,14 @@ spec: - list - update - watch + - apiGroups: + - "" + resources: + - endpoints + verbs: + - get + - list + - watch - apiGroups: - "" resources: diff --git a/common/types.go b/common/types.go index 42c54ff27..af270e8a4 100644 --- a/common/types.go +++ b/common/types.go @@ -148,7 +148,13 @@ type BaseComponentCertificate interface { // BaseComponentNetworkPolicy represents a basic network policy configuration type BaseComponentNetworkPolicy interface { - GetNamespaceLabels() map[string]string + IsDisabled() bool + IsIngressDisabled() bool + IsEgressDisabled() bool + IsBypassingDenyAllEgress() bool + GetToNamespaceLabels() map[string]string + GetToLabels() map[string]string + GetFromNamespaceLabels() map[string]string GetFromLabels() map[string]string } diff --git a/config/crd/bases/rc.app.stacks_runtimecomponents.yaml b/config/crd/bases/rc.app.stacks_runtimecomponents.yaml index 3eb6cf951..682c99a27 100644 --- a/config/crd/bases/rc.app.stacks_runtimecomponents.yaml +++ b/config/crd/bases/rc.app.stacks_runtimecomponents.yaml @@ -3838,22 +3838,53 @@ spec: networkPolicy: description: Defines the network policy properties: + bypassDenyAllEgress: + description: Bypasses deny all egress rules to allow API server + and DNS access. Defaults to false. + type: boolean disable: description: Disable the creation of the network policy. Defaults to false. type: boolean + disableEgress: + description: Disable the creation of the network policy egress. + Defaults to false. + type: boolean + disableIngress: + description: Disable the creation of the network policy ingress. + Defaults to false. + type: boolean fromLabels: additionalProperties: type: string description: Specify the labels of pod(s) that incoming traffic is allowed from. type: object - namespaceLabels: + fromNamespaceLabels: additionalProperties: type: string description: Specify the labels of namespaces that incoming traffic is allowed from. type: object + namespaceLabels: + additionalProperties: + type: string + description: Deprecated. .spec.networkPolicy.fromNamespaceLabels + should be used instead. If both are specified, .spec.networkPolicy.fromNamespaceLabels + will override this. + type: object + toLabels: + additionalProperties: + type: string + description: Specify the labels of pod(s) that outgoing traffic + is allowed to. + type: object + toNamespaceLabels: + additionalProperties: + type: string + description: Specify the labels of namespaces that outgoing traffic + is allowed to. + type: object type: object probes: description: Define health checks on application container to determine diff --git a/config/manifests/bases/runtime-component.clusterserviceversion.yaml b/config/manifests/bases/runtime-component.clusterserviceversion.yaml index 9b5ff94b1..64fa2eee2 100644 --- a/config/manifests/bases/runtime-component.clusterserviceversion.yaml +++ b/config/manifests/bases/runtime-component.clusterserviceversion.yaml @@ -426,18 +426,55 @@ spec: path: networkPolicy.disable x-descriptors: - urn:alm:descriptor:com.tectonic.ui:booleanSwitch - - description: Specify the labels of namespaces that incoming traffic is allowed - from. + - description: Disable the creation of the network policy ingress. Defaults + to false. + displayName: Disable Ingress + path: networkPolicy.disableIngress + x-descriptors: + - urn:alm:descriptor:com.tectonic.ui:booleanSwitch + - description: Disable the creation of the network policy egress. Defaults to + false. + displayName: Disable Egress + path: networkPolicy.disableEgress + x-descriptors: + - urn:alm:descriptor:com.tectonic.ui:booleanSwitch + - description: Bypasses deny all egress rules to allow API server and DNS access. + Defaults to false. + displayName: Bypass Deny All Egress + path: networkPolicy.bypassDenyAllEgress + x-descriptors: + - urn:alm:descriptor:com.tectonic.ui:booleanSwitch + - description: Deprecated. .spec.networkPolicy.fromNamespaceLabels should be + used instead. If both are specified, .spec.networkPolicy.fromNamespaceLabels + will override this. displayName: Namespace Labels path: networkPolicy.namespaceLabels x-descriptors: - urn:alm:descriptor:com.tectonic.ui:text + - description: Specify the labels of namespaces that incoming traffic is allowed + from. + displayName: From Namespace Labels + path: networkPolicy.fromNamespaceLabels + x-descriptors: + - urn:alm:descriptor:com.tectonic.ui:text - description: Specify the labels of pod(s) that incoming traffic is allowed from. displayName: From Labels path: networkPolicy.fromLabels x-descriptors: - urn:alm:descriptor:com.tectonic.ui:text + - description: Specify the labels of namespaces that outgoing traffic is allowed + to. + displayName: To Namespace Labels + path: networkPolicy.toNamespaceLabels + x-descriptors: + - urn:alm:descriptor:com.tectonic.ui:text + - description: Specify the labels of pod(s) that outgoing traffic is allowed + to. + displayName: To Labels + path: networkPolicy.toLabels + x-descriptors: + - urn:alm:descriptor:com.tectonic.ui:text statusDescriptors: - description: Exposed URI of the application endpoint displayName: Application diff --git a/config/rbac/role.yaml b/config/rbac/role.yaml index 414ff68f4..113bd2788 100644 --- a/config/rbac/role.yaml +++ b/config/rbac/role.yaml @@ -61,6 +61,14 @@ rules: - list - update - watch +- apiGroups: + - "" + resources: + - endpoints + verbs: + - get + - list + - watch - apiGroups: - "" resources: diff --git a/internal/controller/runtimecomponent_controller.go b/internal/controller/runtimecomponent_controller.go index 59c0546a3..5f5bb668a 100644 --- a/internal/controller/runtimecomponent_controller.go +++ b/internal/controller/runtimecomponent_controller.go @@ -23,6 +23,7 @@ import ( "strings" "github.com/application-stacks/runtime-component-operator/common" + "github.com/application-stacks/runtime-component-operator/utils" "github.com/pkg/errors" kcontroller "sigs.k8s.io/controller-runtime/pkg/controller" @@ -70,6 +71,7 @@ type RuntimeComponentReconciler struct { // +kubebuilder:rbac:groups=apps,resources=deployments;statefulsets,verbs=get;list;watch;create;update;delete,namespace=runtime-component-operator // +kubebuilder:rbac:groups=apps,resources=deployments/finalizers;statefulsets,verbs=update,namespace=runtime-component-operator // +kubebuilder:rbac:groups=core,resources=services;secrets;serviceaccounts;configmaps,verbs=get;list;watch;create;update;delete,namespace=runtime-component-operator +// +kubebuilder:rbac:groups=core,resources=endpoints,verbs=get;list;watch,namespace=runtime-component-operator // +kubebuilder:rbac:groups=autoscaling,resources=horizontalpodautoscalers,verbs=get;list;watch;create;update;delete,namespace=runtime-component-operator // +kubebuilder:rbac:groups=networking.k8s.io,resources=ingresses;networkpolicies,verbs=get;list;watch;create;update;delete,namespace=runtime-component-operator // +kubebuilder:rbac:groups=route.openshift.io,resources=routes;routes/custom-host,verbs=get;list;watch;create;update;delete,namespace=runtime-component-operator @@ -351,7 +353,7 @@ func (r *RuntimeComponentReconciler) Reconcile(ctx context.Context, req ctrl.Req networkPolicy := &networkingv1.NetworkPolicy{ObjectMeta: defaultMeta} if np := instance.Spec.NetworkPolicy; np == nil || np != nil && !np.IsDisabled() { err = r.CreateOrUpdate(networkPolicy, instance, func() error { - appstacksutils.CustomizeNetworkPolicy(networkPolicy, r.IsOpenShift(), instance) + appstacksutils.CustomizeNetworkPolicy(networkPolicy, r.IsOpenShift(), r.getDNSEgressRule, r.getEndpoints, instance) return nil }) if err != nil { @@ -560,6 +562,43 @@ func (r *RuntimeComponentReconciler) Reconcile(ctx context.Context, req ctrl.Req return r.ManageSuccess(common.StatusConditionTypeReconciled, instance) } +func (r *RuntimeComponentReconciler) getEndpoints(serviceName string, namespace string) (*corev1.Endpoints, error) { + endpoints := &corev1.Endpoints{} + if err := r.GetClient().Get(context.TODO(), types.NamespacedName{Name: serviceName, Namespace: namespace}, endpoints); err != nil { + return nil, err + } else { + return endpoints, nil + } +} + +func (r *RuntimeComponentReconciler) getDNSEgressRule(endpointsName string, endpointsNamespace string) (bool, networkingv1.NetworkPolicyEgressRule) { + dnsRule := networkingv1.NetworkPolicyEgressRule{} + if dnsEndpoints, err := r.getEndpoints(endpointsName, endpointsNamespace); err == nil { + if len(dnsEndpoints.Subsets) > 0 { + if endpointPort := utils.GetEndpointPortByName(&dnsEndpoints.Subsets[0].Ports, "dns"); endpointPort != nil { + dnsRule.Ports = append(dnsRule.Ports, utils.CreateNetworkPolicyPortFromEndpointPort(endpointPort)) + } + if endpointPort := utils.GetEndpointPortByName(&dnsEndpoints.Subsets[0].Ports, "dns-tcp"); endpointPort != nil { + dnsRule.Ports = append(dnsRule.Ports, utils.CreateNetworkPolicyPortFromEndpointPort(endpointPort)) + } + } + peer := networkingv1.NetworkPolicyPeer{} + peer.NamespaceSelector = &metav1.LabelSelector{ + MatchLabels: map[string]string{ + "kubernetes.io/metadata.name": endpointsNamespace, + }, + } + dnsRule.To = append(dnsRule.To, peer) + // reqLogger.Info("Found endpoints for " + endpointsName + " service in the " + endpointsNamespace + " namespace") + return false, dnsRule + } + // use permissive rule + // egress: + // - {} + // reqLogger.Info("Failed to retrieve endpoints for " + endpointsName + " service in the " + endpointsNamespace + " namespace. Using more permissive rule.") + return true, dnsRule +} + // SetupWithManager initializes reconciler func (r *RuntimeComponentReconciler) SetupWithManager(mgr ctrl.Manager) error { diff --git a/internal/deploy/kubectl/runtime-component-crd.yaml b/internal/deploy/kubectl/runtime-component-crd.yaml index 95784363c..ef01e30c7 100644 --- a/internal/deploy/kubectl/runtime-component-crd.yaml +++ b/internal/deploy/kubectl/runtime-component-crd.yaml @@ -3841,22 +3841,53 @@ spec: networkPolicy: description: Defines the network policy properties: + bypassDenyAllEgress: + description: Bypasses deny all egress rules to allow API server + and DNS access. Defaults to false. + type: boolean disable: description: Disable the creation of the network policy. Defaults to false. type: boolean + disableEgress: + description: Disable the creation of the network policy egress. + Defaults to false. + type: boolean + disableIngress: + description: Disable the creation of the network policy ingress. + Defaults to false. + type: boolean fromLabels: additionalProperties: type: string description: Specify the labels of pod(s) that incoming traffic is allowed from. type: object - namespaceLabels: + fromNamespaceLabels: additionalProperties: type: string description: Specify the labels of namespaces that incoming traffic is allowed from. type: object + namespaceLabels: + additionalProperties: + type: string + description: Deprecated. .spec.networkPolicy.fromNamespaceLabels + should be used instead. If both are specified, .spec.networkPolicy.fromNamespaceLabels + will override this. + type: object + toLabels: + additionalProperties: + type: string + description: Specify the labels of pod(s) that outgoing traffic + is allowed to. + type: object + toNamespaceLabels: + additionalProperties: + type: string + description: Specify the labels of namespaces that outgoing traffic + is allowed to. + type: object type: object probes: description: Define health checks on application container to determine diff --git a/internal/deploy/kubectl/runtime-component-operator.yaml b/internal/deploy/kubectl/runtime-component-operator.yaml index 4fe537cf6..c4764b65d 100644 --- a/internal/deploy/kubectl/runtime-component-operator.yaml +++ b/internal/deploy/kubectl/runtime-component-operator.yaml @@ -118,6 +118,14 @@ rules: - list - update - watch +- apiGroups: + - "" + resources: + - endpoints + verbs: + - get + - list + - watch - apiGroups: - "" resources: diff --git a/internal/deploy/kubectl/runtime-component-rbac-watch-all.yaml b/internal/deploy/kubectl/runtime-component-rbac-watch-all.yaml index 16077a594..d065c6f9f 100644 --- a/internal/deploy/kubectl/runtime-component-rbac-watch-all.yaml +++ b/internal/deploy/kubectl/runtime-component-rbac-watch-all.yaml @@ -110,6 +110,14 @@ rules: - list - update - watch +- apiGroups: + - "" + resources: + - endpoints + verbs: + - get + - list + - watch - apiGroups: - "" resources: diff --git a/internal/deploy/kubectl/runtime-component-rbac-watch-another.yaml b/internal/deploy/kubectl/runtime-component-rbac-watch-another.yaml index cdbedd482..859f98792 100644 --- a/internal/deploy/kubectl/runtime-component-rbac-watch-another.yaml +++ b/internal/deploy/kubectl/runtime-component-rbac-watch-another.yaml @@ -112,6 +112,14 @@ rules: - list - update - watch +- apiGroups: + - "" + resources: + - endpoints + verbs: + - get + - list + - watch - apiGroups: - "" resources: diff --git a/internal/deploy/kustomize/daily/base/runtime-component-crd.yaml b/internal/deploy/kustomize/daily/base/runtime-component-crd.yaml index 95784363c..ef01e30c7 100644 --- a/internal/deploy/kustomize/daily/base/runtime-component-crd.yaml +++ b/internal/deploy/kustomize/daily/base/runtime-component-crd.yaml @@ -3841,22 +3841,53 @@ spec: networkPolicy: description: Defines the network policy properties: + bypassDenyAllEgress: + description: Bypasses deny all egress rules to allow API server + and DNS access. Defaults to false. + type: boolean disable: description: Disable the creation of the network policy. Defaults to false. type: boolean + disableEgress: + description: Disable the creation of the network policy egress. + Defaults to false. + type: boolean + disableIngress: + description: Disable the creation of the network policy ingress. + Defaults to false. + type: boolean fromLabels: additionalProperties: type: string description: Specify the labels of pod(s) that incoming traffic is allowed from. type: object - namespaceLabels: + fromNamespaceLabels: additionalProperties: type: string description: Specify the labels of namespaces that incoming traffic is allowed from. type: object + namespaceLabels: + additionalProperties: + type: string + description: Deprecated. .spec.networkPolicy.fromNamespaceLabels + should be used instead. If both are specified, .spec.networkPolicy.fromNamespaceLabels + will override this. + type: object + toLabels: + additionalProperties: + type: string + description: Specify the labels of pod(s) that outgoing traffic + is allowed to. + type: object + toNamespaceLabels: + additionalProperties: + type: string + description: Specify the labels of namespaces that outgoing traffic + is allowed to. + type: object type: object probes: description: Define health checks on application container to determine diff --git a/internal/deploy/kustomize/daily/base/runtime-component-roles.yaml b/internal/deploy/kustomize/daily/base/runtime-component-roles.yaml index 1e0b23d82..cabf1d6d8 100644 --- a/internal/deploy/kustomize/daily/base/runtime-component-roles.yaml +++ b/internal/deploy/kustomize/daily/base/runtime-component-roles.yaml @@ -121,6 +121,14 @@ rules: - list - update - watch +- apiGroups: + - "" + resources: + - endpoints + verbs: + - get + - list + - watch - apiGroups: - "" resources: diff --git a/internal/deploy/kustomize/daily/overlays/watch-all-namespaces/cluster-roles.yaml b/internal/deploy/kustomize/daily/overlays/watch-all-namespaces/cluster-roles.yaml index e55f677ef..36bad8ab3 100644 --- a/internal/deploy/kustomize/daily/overlays/watch-all-namespaces/cluster-roles.yaml +++ b/internal/deploy/kustomize/daily/overlays/watch-all-namespaces/cluster-roles.yaml @@ -110,6 +110,14 @@ rules: - list - update - watch +- apiGroups: + - "" + resources: + - endpoints + verbs: + - get + - list + - watch - apiGroups: - "" resources: diff --git a/internal/deploy/kustomize/daily/overlays/watch-another-namespace/rco-watched-ns/watched-roles.yaml b/internal/deploy/kustomize/daily/overlays/watch-another-namespace/rco-watched-ns/watched-roles.yaml index 4605591de..f55a58eb0 100644 --- a/internal/deploy/kustomize/daily/overlays/watch-another-namespace/rco-watched-ns/watched-roles.yaml +++ b/internal/deploy/kustomize/daily/overlays/watch-another-namespace/rco-watched-ns/watched-roles.yaml @@ -112,6 +112,14 @@ rules: - list - update - watch +- apiGroups: + - "" + resources: + - endpoints + verbs: + - get + - list + - watch - apiGroups: - "" resources: diff --git a/utils/utils.go b/utils/utils.go index 5005717ee..4769ec4b8 100644 --- a/utils/utils.go +++ b/utils/utils.go @@ -340,25 +340,171 @@ func customizeProbeDefaults(config *corev1.Probe, defaultProbe *corev1.Probe) *c return probe } -// CustomizeNetworkPolicy configures the network policy. -func CustomizeNetworkPolicy(networkPolicy *networkingv1.NetworkPolicy, isOpenShift bool, ba common.BaseComponent) { +func createEgressBypass(ba common.BaseComponent, isOpenShift bool, getDNSEgressRule func(string, string) (bool, networkingv1.NetworkPolicyEgressRule), getEndpoints func(string, string) (*corev1.Endpoints, error)) (bool, []networkingv1.NetworkPolicyEgressRule) { + egressRules := []networkingv1.NetworkPolicyEgressRule{} + + var dnsRule networkingv1.NetworkPolicyEgressRule + var usingPermissiveRule bool + // If allowed, add an Egress rule to access the OpenShift DNS or K8s CoreDNS. Otherwise, use a permissive cluster-wide Egress rule. + if isOpenShift { + usingPermissiveRule, dnsRule = getDNSEgressRule("dns-default", "openshift-dns") + } else { + usingPermissiveRule, dnsRule = getDNSEgressRule("kube-dns", "kube-system") + } + egressRules = append(egressRules, dnsRule) + + // If the DNS rule is a specific Egress rule also check if another Egress rule can be created for the API server. + // Otherwise, fallback to a permissive cluster-wide Egress rule. + if !usingPermissiveRule { + if apiServerEndpoints, err := getEndpoints("kubernetes", "default"); err == nil { + rule := networkingv1.NetworkPolicyEgressRule{} + // Define the port + port := networkingv1.NetworkPolicyPort{} + port.Protocol = &apiServerEndpoints.Subsets[0].Ports[0].Protocol + var portNumber intstr.IntOrString = intstr.FromInt((int)(apiServerEndpoints.Subsets[0].Ports[0].Port)) + port.Port = &portNumber + rule.Ports = append(rule.Ports, port) + + // Add the endpoint address as ipBlock entries + for _, endpoint := range apiServerEndpoints.Subsets { + for _, address := range endpoint.Addresses { + peer := networkingv1.NetworkPolicyPeer{} + ipBlock := networkingv1.IPBlock{} + ipBlock.CIDR = address.IP + "/32" + + peer.IPBlock = &ipBlock + rule.To = append(rule.To, peer) + } + } + egressRules = append(egressRules, rule) + } else { + // The operator couldn't create a rule for the K8s API server so add a permissive Egress rule + rule := networkingv1.NetworkPolicyEgressRule{} + egressRules = append(egressRules, rule) + } + } + return usingPermissiveRule, egressRules +} + +// createNetworkPolicyEgressRules returns the network policy rules for outgoing traffic to other Pod(s) +func createNetworkPolicyEgressRules(networkPolicy *networkingv1.NetworkPolicy, isOpenShift bool, isBypassingDenyAllEgress bool, getDNSEgressRule func(string, string) (bool, networkingv1.NetworkPolicyEgressRule), getEndpoints func(string, string) (*corev1.Endpoints, error), ba common.BaseComponent) []networkingv1.NetworkPolicyEgressRule { + config := ba.GetNetworkPolicy() + egressRules := []networkingv1.NetworkPolicyEgressRule{} + if isBypassingDenyAllEgress { + usingPermissiveRule, bypassRules := createEgressBypass(ba, isOpenShift, getDNSEgressRule, getEndpoints) + egressRules = append(egressRules, bypassRules...) + if usingPermissiveRule { + return egressRules // exit early because permissive egress is set + } + } + // configure the main application egress rule + var rule networkingv1.NetworkPolicyEgressRule + if config == nil || ((config.GetToNamespaceLabels() != nil && len(config.GetToNamespaceLabels()) == 0) && + (config.GetToLabels() != nil && len(config.GetToLabels()) == 0)) { + rule = createAllowAllNetworkPolicyEgressRule() + } else { + rule = createNetworkPolicyEgressRule(ba.GetApplicationName(), networkPolicy.Namespace, config) + } + egressRules = append(egressRules, rule) + return egressRules +} + +func createNetworkPolicyEgressRule(appName string, namespace string, config common.BaseComponentNetworkPolicy) networkingv1.NetworkPolicyEgressRule { + rule := networkingv1.NetworkPolicyEgressRule{} + if config != nil { + rule.To = append(rule.To, + // Add peer to allow traffic to other pods belonging to the app + createNetworkPolicyPeer(appName, namespace, config, getNetworkPolicyEgressLabelGetters), + ) + } + return rule +} + +func createAllowAllNetworkPolicyEgressRule() networkingv1.NetworkPolicyEgressRule { + return networkingv1.NetworkPolicyEgressRule{ + To: []networkingv1.NetworkPolicyPeer{{ + NamespaceSelector: &metav1.LabelSelector{}, + }}, + } +} + +func GetEndpointPortByName(endpointPorts *[]corev1.EndpointPort, name string) *corev1.EndpointPort { + if endpointPorts != nil { + for _, endpointPort := range *endpointPorts { + if endpointPort.Name == name { + return &endpointPort + } + } + } + return nil +} + +func CreateNetworkPolicyPortFromEndpointPort(endpointPort *corev1.EndpointPort) networkingv1.NetworkPolicyPort { + port := networkingv1.NetworkPolicyPort{} + port.Protocol = &endpointPort.Protocol + var portNumber intstr.IntOrString = intstr.FromInt((int)(endpointPort.Port)) + port.Port = &portNumber + return port +} + +func CustomizeNetworkPolicy(networkPolicy *networkingv1.NetworkPolicy, isOpenShift bool, getDNSEgressRule func(string, string) (bool, networkingv1.NetworkPolicyEgressRule), getEndpoints func(string, string) (*corev1.Endpoints, error), ba common.BaseComponent) { obj := ba.(metav1.Object) networkPolicy.Labels = ba.GetLabels() networkPolicy.Annotations = MergeMaps(networkPolicy.Annotations, ba.GetAnnotations()) - networkPolicy.Spec.PolicyTypes = []networkingv1.PolicyType{networkingv1.PolicyTypeIngress} - networkPolicy.Spec.PodSelector = metav1.LabelSelector{ MatchLabels: map[string]string{ common.GetComponentNameLabel(ba): obj.GetName(), }, } + ingressDisabled := ba.GetNetworkPolicy() != nil && ba.GetNetworkPolicy().IsIngressDisabled() + hasIngressPolicy := policyTypesContains(networkPolicy.Spec.PolicyTypes, networkingv1.PolicyTypeIngress) + if ingressDisabled { + if hasIngressPolicy { + networkPolicy.Spec.PolicyTypes = networkPolicy.Spec.PolicyTypes[1:] // remove the first element + } + networkPolicy.Spec.Ingress = []networkingv1.NetworkPolicyIngressRule{} + } else { + if !hasIngressPolicy { + networkPolicy.Spec.PolicyTypes = append(networkPolicy.Spec.PolicyTypes, networkingv1.PolicyTypeIngress) + } + networkPolicy.Spec.Ingress = createNetworkPolicyIngressRules(networkPolicy, isOpenShift, ba) + } + + egressDisabled := ba.GetNetworkPolicy() != nil && ba.GetNetworkPolicy().IsEgressDisabled() + hasEgressPolicy := policyTypesContains(networkPolicy.Spec.PolicyTypes, networkingv1.PolicyTypeEgress) + if egressDisabled { + if hasEgressPolicy { + networkPolicy.Spec.PolicyTypes = networkPolicy.Spec.PolicyTypes[:len(networkPolicy.Spec.PolicyTypes)-1] // remove the last element + } + networkPolicy.Spec.Egress = []networkingv1.NetworkPolicyEgressRule{} + } else { + egressConfigured := ba.GetNetworkPolicy() != nil && (ba.GetNetworkPolicy().GetToLabels() != nil || ba.GetNetworkPolicy().GetToNamespaceLabels() != nil) + egressBypass := ba.GetNetworkPolicy() != nil && ba.GetNetworkPolicy().IsBypassingDenyAllEgress() // check if egress should bypass deny all policy to access the API server and DNS + if egressConfigured || egressBypass { + if !hasEgressPolicy { + networkPolicy.Spec.PolicyTypes = append(networkPolicy.Spec.PolicyTypes, networkingv1.PolicyTypeEgress) + } + + networkPolicy.Spec.Egress = createNetworkPolicyEgressRules(networkPolicy, isOpenShift, egressBypass, getDNSEgressRule, getEndpoints, ba) + } else { + // if egress is not configured, consider the network policy disabled + if hasEgressPolicy { + networkPolicy.Spec.PolicyTypes = networkPolicy.Spec.PolicyTypes[:len(networkPolicy.Spec.PolicyTypes)-1] // remove the last element + } + networkPolicy.Spec.Egress = []networkingv1.NetworkPolicyEgressRule{} + } + } +} + +// createNetworkPolicyIngressRules returns the network policy rules for incoming traffic from other Pod(s) +func createNetworkPolicyIngressRules(networkPolicy *networkingv1.NetworkPolicy, isOpenShift bool, ba common.BaseComponent) []networkingv1.NetworkPolicyIngressRule { config := ba.GetNetworkPolicy() isExposed := ba.GetExpose() != nil && *ba.GetExpose() var rule networkingv1.NetworkPolicyIngressRule - if config != nil && config.GetNamespaceLabels() != nil && len(config.GetNamespaceLabels()) == 0 && + if config != nil && config.GetFromNamespaceLabels() != nil && len(config.GetFromNamespaceLabels()) == 0 && config.GetFromLabels() != nil && len(config.GetFromLabels()) == 0 { rule = createAllowAllNetworkPolicyIngressRule() } else if isOpenShift { @@ -368,7 +514,7 @@ func CustomizeNetworkPolicy(networkPolicy *networkingv1.NetworkPolicy, isOpenShi } customizeNetworkPolicyPorts(&rule, ba) - networkPolicy.Spec.Ingress = []networkingv1.NetworkPolicyIngressRule{rule} + return []networkingv1.NetworkPolicyIngressRule{rule} } func createOpenShiftNetworkPolicyIngressRule(appName string, namespace string, isExposed bool, config common.BaseComponentNetworkPolicy) networkingv1.NetworkPolicyIngressRule { @@ -397,8 +543,7 @@ func createOpenShiftNetworkPolicyIngressRule(appName string, namespace string, i rule.From = append(rule.From, // Add peer to allow traffic from other pods belonging to the app - createNetworkPolicyPeer(appName, namespace, config), - + createNetworkPolicyPeer(appName, namespace, config, getNetworkPolicyIngressLabelGetters), // Add peer to allow traffic from OpenShift monitoring networkingv1.NetworkPolicyPeer{ NamespaceSelector: &metav1.LabelSelector{ @@ -419,7 +564,7 @@ func createKubernetesNetworkPolicyIngressRule(appName string, namespace string, rule := networkingv1.NetworkPolicyIngressRule{} rule.From = []networkingv1.NetworkPolicyPeer{ - createNetworkPolicyPeer(appName, namespace, config), + createNetworkPolicyPeer(appName, namespace, config, getNetworkPolicyIngressLabelGetters), } return rule } @@ -432,26 +577,26 @@ func createAllowAllNetworkPolicyIngressRule() networkingv1.NetworkPolicyIngressR } } -func createNetworkPolicyPeer(appName string, namespace string, networkPolicy common.BaseComponentNetworkPolicy) networkingv1.NetworkPolicyPeer { +func createNetworkPolicyPeer(appName string, namespace string, networkPolicy common.BaseComponentNetworkPolicy, getNetworkPolicyLabelGetters func(common.BaseComponentNetworkPolicy) (func() map[string]string, func() map[string]string)) networkingv1.NetworkPolicyPeer { peer := networkingv1.NetworkPolicyPeer{ NamespaceSelector: &metav1.LabelSelector{}, PodSelector: &metav1.LabelSelector{}, } - - if networkPolicy == nil || networkPolicy.GetNamespaceLabels() == nil { + getNamespaceLabels, getLabels := getNetworkPolicyLabelGetters(networkPolicy) + if networkPolicy == nil || getNamespaceLabels() == nil { peer.NamespaceSelector.MatchLabels = map[string]string{ "kubernetes.io/metadata.name": namespace, } } else { - peer.NamespaceSelector.MatchLabels = networkPolicy.GetNamespaceLabels() + peer.NamespaceSelector.MatchLabels = getNamespaceLabels() } - if networkPolicy == nil || networkPolicy.GetFromLabels() == nil { + if networkPolicy == nil || getLabels() == nil { peer.PodSelector.MatchLabels = map[string]string{ "app.kubernetes.io/part-of": appName, } } else { - peer.PodSelector.MatchLabels = networkPolicy.GetFromLabels() + peer.PodSelector.MatchLabels = getLabels() } return peer @@ -494,6 +639,40 @@ func customizeNetworkPolicyPorts(ingress *networkingv1.NetworkPolicyIngressRule, } } +func getNetworkPolicyIngressLabelGetters(config common.BaseComponentNetworkPolicy) (func() map[string]string, func() map[string]string) { + var getNamespaceLabels, getLabels func() map[string]string + if config != nil { + getNamespaceLabels = config.GetFromNamespaceLabels + getLabels = config.GetFromLabels + } else { + getNamespaceLabels = nil + getLabels = nil + } + return getNamespaceLabels, getLabels +} + +func getNetworkPolicyEgressLabelGetters(config common.BaseComponentNetworkPolicy) (func() map[string]string, func() map[string]string) { + var getNamespaceLabels, getLabels func() map[string]string + if config != nil { + getNamespaceLabels = config.GetToNamespaceLabels + getLabels = config.GetToLabels + } else { + getNamespaceLabels = nil + getLabels = nil + } + return getNamespaceLabels, getLabels +} + +// returns true if policy is contained within the policyTypes array, false otherwise +func policyTypesContains(policyTypes []networkingv1.PolicyType, policy networkingv1.PolicyType) bool { + for _, currentPolicy := range policyTypes { + if currentPolicy == policy { + return true + } + } + return false +} + // CustomizeAffinity ... func CustomizeAffinity(affinity *corev1.Affinity, ba common.BaseComponent) { affinityConfig := ba.GetAffinity()