Skip to content

Commit 78950e3

Browse files
committed
Add bypass deny all egress flag
1 parent 9ee8575 commit 78950e3

File tree

10 files changed

+160
-12
lines changed

10 files changed

+160
-12
lines changed

api/v1/runtimecomponent_types.go

Lines changed: 13 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -320,24 +320,28 @@ type RuntimeComponentNetworkPolicy struct {
320320
// +operator-sdk:csv:customresourcedefinitions:order=48,type=spec,displayName="Disable Egress",xDescriptors="urn:alm:descriptor:com.tectonic.ui:booleanSwitch"
321321
DisableEgress *bool `json:"disableEgress,omitempty"`
322322

323+
// Bypasses deny all egress rules to allow API server and DNS access. Defaults to false.
324+
// +operator-sdk:csv:customresourcedefinitions:order=49,type=spec,displayName="Bypass Deny All Egress",xDescriptors="urn:alm:descriptor:com.tectonic.ui:booleanSwitch"
325+
BypassDenyAllEgress *bool `json:"bypassDenyAllEgress,omitempty"`
326+
323327
// Deprecated. .spec.networkPolicy.fromNamespaceLabels should be used instead. If both are specified, .spec.networkPolicy.fromNamespaceLabels will override this.
324-
// +operator-sdk:csv:customresourcedefinitions:order=49,type=spec,displayName="Namespace Labels",xDescriptors="urn:alm:descriptor:com.tectonic.ui:text"
328+
// +operator-sdk:csv:customresourcedefinitions:order=50,type=spec,displayName="Namespace Labels",xDescriptors="urn:alm:descriptor:com.tectonic.ui:text"
325329
NamespaceLabels *map[string]string `json:"namespaceLabels,omitempty"`
326330

327331
// Specify the labels of namespaces that incoming traffic is allowed from.
328-
// +operator-sdk:csv:customresourcedefinitions:order=50,type=spec,displayName="From Namespace Labels",xDescriptors="urn:alm:descriptor:com.tectonic.ui:text"
332+
// +operator-sdk:csv:customresourcedefinitions:order=51,type=spec,displayName="From Namespace Labels",xDescriptors="urn:alm:descriptor:com.tectonic.ui:text"
329333
FromNamespaceLabels *map[string]string `json:"fromNamespaceLabels,omitempty"`
330334

331335
// Specify the labels of pod(s) that incoming traffic is allowed from.
332-
// +operator-sdk:csv:customresourcedefinitions:order=51,type=spec,displayName="From Labels",xDescriptors="urn:alm:descriptor:com.tectonic.ui:text"
336+
// +operator-sdk:csv:customresourcedefinitions:order=52,type=spec,displayName="From Labels",xDescriptors="urn:alm:descriptor:com.tectonic.ui:text"
333337
FromLabels *map[string]string `json:"fromLabels,omitempty"`
334338

335339
// Specify the labels of namespaces that outgoing traffic is allowed to.
336-
// +operator-sdk:csv:customresourcedefinitions:order=52,type=spec,displayName="To Namespace Labels",xDescriptors="urn:alm:descriptor:com.tectonic.ui:text"
340+
// +operator-sdk:csv:customresourcedefinitions:order=53,type=spec,displayName="To Namespace Labels",xDescriptors="urn:alm:descriptor:com.tectonic.ui:text"
337341
ToNamespaceLabels *map[string]string `json:"toNamespaceLabels,omitempty"`
338342

339343
// Specify the labels of pod(s) that outgoing traffic is allowed to.
340-
// +operator-sdk:csv:customresourcedefinitions:order=53,type=spec,displayName="To Labels",xDescriptors="urn:alm:descriptor:com.tectonic.ui:text"
344+
// +operator-sdk:csv:customresourcedefinitions:order=54,type=spec,displayName="To Labels",xDescriptors="urn:alm:descriptor:com.tectonic.ui:text"
341345
ToLabels *map[string]string `json:"toLabels,omitempty"`
342346
}
343347

@@ -940,6 +944,10 @@ func (np *RuntimeComponentNetworkPolicy) IsEgressDisabled() bool {
940944
return np.DisableEgress != nil && *np.DisableEgress
941945
}
942946

947+
func (np *RuntimeComponentNetworkPolicy) IsBypassingDenyAllEgress() bool {
948+
return np.BypassDenyAllEgress != nil && *np.BypassDenyAllEgress
949+
}
950+
943951
// GetLabels returns labels to be added on ServiceMonitor
944952
func (m *RuntimeComponentMonitoring) GetLabels() map[string]string {
945953
return m.Labels

api/v1/zz_generated.deepcopy.go

Lines changed: 5 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

bundle/manifests/rc.app.stacks_runtimecomponents.yaml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3365,6 +3365,10 @@ spec:
33653365
networkPolicy:
33663366
description: Defines the network policy
33673367
properties:
3368+
bypassDenyAllEgress:
3369+
description: Bypasses deny all egress rules to allow API server
3370+
and DNS access. Defaults to false.
3371+
type: boolean
33683372
disable:
33693373
description: Disable the creation of the network policy. Defaults
33703374
to false.

common/types.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -143,6 +143,7 @@ type BaseComponentNetworkPolicy interface {
143143
IsDisabled() bool
144144
IsIngressDisabled() bool
145145
IsEgressDisabled() bool
146+
IsBypassingDenyAllEgress() bool
146147
GetToNamespaceLabels() map[string]string
147148
GetToLabels() map[string]string
148149
GetFromNamespaceLabels() map[string]string

config/crd/bases/rc.app.stacks_runtimecomponents.yaml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3361,6 +3361,10 @@ spec:
33613361
networkPolicy:
33623362
description: Defines the network policy
33633363
properties:
3364+
bypassDenyAllEgress:
3365+
description: Bypasses deny all egress rules to allow API server
3366+
and DNS access. Defaults to false.
3367+
type: boolean
33643368
disable:
33653369
description: Disable the creation of the network policy. Defaults
33663370
to false.

config/manifests/bases/runtime-component.clusterserviceversion.yaml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -426,6 +426,12 @@ spec:
426426
path: networkPolicy.disableEgress
427427
x-descriptors:
428428
- urn:alm:descriptor:com.tectonic.ui:booleanSwitch
429+
- description: Bypasses deny all egress rules to allow API server and DNS access.
430+
Defaults to false.
431+
displayName: Bypass Deny All Egress
432+
path: networkPolicy.bypassDenyAllEgress
433+
x-descriptors:
434+
- urn:alm:descriptor:com.tectonic.ui:booleanSwitch
429435
- description: Deprecated. .spec.networkPolicy.fromNamespaceLabels should be
430436
used instead. If both are specified, .spec.networkPolicy.fromNamespaceLabels
431437
will override this.

internal/controller/runtimecomponent_controller.go

Lines changed: 39 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ import (
2323
"strings"
2424

2525
"github.com/application-stacks/runtime-component-operator/common"
26+
"github.com/application-stacks/runtime-component-operator/utils"
2627
"github.com/pkg/errors"
2728

2829
kcontroller "sigs.k8s.io/controller-runtime/pkg/controller"
@@ -351,7 +352,7 @@ func (r *RuntimeComponentReconciler) Reconcile(ctx context.Context, req ctrl.Req
351352
networkPolicy := &networkingv1.NetworkPolicy{ObjectMeta: defaultMeta}
352353
if np := instance.Spec.NetworkPolicy; np == nil || np != nil && !np.IsDisabled() {
353354
err = r.CreateOrUpdate(networkPolicy, instance, func() error {
354-
appstacksutils.CustomizeNetworkPolicy(networkPolicy, r.IsOpenShift(), instance)
355+
appstacksutils.CustomizeNetworkPolicy(networkPolicy, r.IsOpenShift(), r.getDNSEgressRule, r.getEndpoints, instance)
355356
return nil
356357
})
357358
if err != nil {
@@ -560,6 +561,43 @@ func (r *RuntimeComponentReconciler) Reconcile(ctx context.Context, req ctrl.Req
560561
return r.ManageSuccess(common.StatusConditionTypeReconciled, instance)
561562
}
562563

564+
func (r *RuntimeComponentReconciler) getEndpoints(serviceName string, namespace string) (*corev1.Endpoints, error) {
565+
endpoints := &corev1.Endpoints{}
566+
if err := r.GetClient().Get(context.TODO(), types.NamespacedName{Name: serviceName, Namespace: namespace}, endpoints); err != nil {
567+
return nil, err
568+
} else {
569+
return endpoints, nil
570+
}
571+
}
572+
573+
func (r *RuntimeComponentReconciler) getDNSEgressRule(endpointsName string, endpointsNamespace string) (bool, networkingv1.NetworkPolicyEgressRule) {
574+
dnsRule := networkingv1.NetworkPolicyEgressRule{}
575+
if dnsEndpoints, err := r.getEndpoints(endpointsName, endpointsNamespace); err == nil {
576+
if len(dnsEndpoints.Subsets) > 0 {
577+
if endpointPort := utils.GetEndpointPortByName(&dnsEndpoints.Subsets[0].Ports, "dns"); endpointPort != nil {
578+
dnsRule.Ports = append(dnsRule.Ports, utils.CreateNetworkPolicyPortFromEndpointPort(endpointPort))
579+
}
580+
if endpointPort := utils.GetEndpointPortByName(&dnsEndpoints.Subsets[0].Ports, "dns-tcp"); endpointPort != nil {
581+
dnsRule.Ports = append(dnsRule.Ports, utils.CreateNetworkPolicyPortFromEndpointPort(endpointPort))
582+
}
583+
}
584+
peer := networkingv1.NetworkPolicyPeer{}
585+
peer.NamespaceSelector = &metav1.LabelSelector{
586+
MatchLabels: map[string]string{
587+
"kubernetes.io/metadata.name": endpointsNamespace,
588+
},
589+
}
590+
dnsRule.To = append(dnsRule.To, peer)
591+
// reqLogger.Info("Found endpoints for " + endpointsName + " service in the " + endpointsNamespace + " namespace")
592+
return false, dnsRule
593+
}
594+
// use permissive rule
595+
// egress:
596+
// - {}
597+
// reqLogger.Info("Failed to retrieve endpoints for " + endpointsName + " service in the " + endpointsNamespace + " namespace. Using more permissive rule.")
598+
return true, dnsRule
599+
}
600+
563601
// SetupWithManager initializes reconciler
564602
func (r *RuntimeComponentReconciler) SetupWithManager(mgr ctrl.Manager) error {
565603

internal/deploy/kubectl/runtime-component-crd.yaml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3364,6 +3364,10 @@ spec:
33643364
networkPolicy:
33653365
description: Defines the network policy
33663366
properties:
3367+
bypassDenyAllEgress:
3368+
description: Bypasses deny all egress rules to allow API server
3369+
and DNS access. Defaults to false.
3370+
type: boolean
33673371
disable:
33683372
description: Disable the creation of the network policy. Defaults
33693373
to false.

internal/deploy/kustomize/daily/base/runtime-component-crd.yaml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3364,6 +3364,10 @@ spec:
33643364
networkPolicy:
33653365
description: Defines the network policy
33663366
properties:
3367+
bypassDenyAllEgress:
3368+
description: Bypasses deny all egress rules to allow API server
3369+
and DNS access. Defaults to false.
3370+
type: boolean
33673371
disable:
33683372
description: Disable the creation of the network policy. Defaults
33693373
to false.

utils/utils.go

Lines changed: 80 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -330,19 +330,73 @@ func customizeProbeDefaults(config *corev1.Probe, defaultProbe *corev1.Probe) *c
330330
return probe
331331
}
332332

333+
func createEgressBypass(ba common.BaseComponent, isOpenShift bool, getDNSEgressRule func(string, string) (bool, networkingv1.NetworkPolicyEgressRule), getEndpoints func(string, string) (*corev1.Endpoints, error)) (bool, []networkingv1.NetworkPolicyEgressRule) {
334+
egressRules := []networkingv1.NetworkPolicyEgressRule{}
335+
336+
var dnsRule networkingv1.NetworkPolicyEgressRule
337+
var usingPermissiveRule bool
338+
// If allowed, add an Egress rule to access the OpenShift DNS or K8s CoreDNS. Otherwise, use a permissive cluster-wide Egress rule.
339+
if isOpenShift {
340+
usingPermissiveRule, dnsRule = getDNSEgressRule("dns-default", "openshift-dns")
341+
} else {
342+
usingPermissiveRule, dnsRule = getDNSEgressRule("kube-dns", "kube-system")
343+
}
344+
egressRules = append(egressRules, dnsRule)
345+
346+
// If the DNS rule is a specific Egress rule also check if another Egress rule can be created for the API server.
347+
// Otherwise, fallback to a permissive cluster-wide Egress rule.
348+
if !usingPermissiveRule {
349+
if apiServerEndpoints, err := getEndpoints("kubernetes", "default"); err == nil {
350+
rule := networkingv1.NetworkPolicyEgressRule{}
351+
// Define the port
352+
port := networkingv1.NetworkPolicyPort{}
353+
port.Protocol = &apiServerEndpoints.Subsets[0].Ports[0].Protocol
354+
var portNumber intstr.IntOrString = intstr.FromInt((int)(apiServerEndpoints.Subsets[0].Ports[0].Port))
355+
port.Port = &portNumber
356+
rule.Ports = append(rule.Ports, port)
357+
358+
// Add the endpoint address as ipBlock entries
359+
for _, endpoint := range apiServerEndpoints.Subsets {
360+
for _, address := range endpoint.Addresses {
361+
peer := networkingv1.NetworkPolicyPeer{}
362+
ipBlock := networkingv1.IPBlock{}
363+
ipBlock.CIDR = address.IP + "/32"
364+
365+
peer.IPBlock = &ipBlock
366+
rule.To = append(rule.To, peer)
367+
}
368+
}
369+
egressRules = append(egressRules, rule)
370+
} else {
371+
// The operator couldn't create a rule for the K8s API server so add a permissive Egress rule
372+
rule := networkingv1.NetworkPolicyEgressRule{}
373+
egressRules = append(egressRules, rule)
374+
}
375+
}
376+
return usingPermissiveRule, egressRules
377+
}
378+
333379
// createNetworkPolicyEgressRules returns the network policy rules for outgoing traffic to other Pod(s)
334-
func createNetworkPolicyEgressRules(networkPolicy *networkingv1.NetworkPolicy, isOpenShift bool, ba common.BaseComponent) []networkingv1.NetworkPolicyEgressRule {
380+
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 {
335381
config := ba.GetNetworkPolicy()
382+
egressRules := []networkingv1.NetworkPolicyEgressRule{}
383+
if isBypassingDenyAllEgress {
384+
usingPermissiveRule, bypassRules := createEgressBypass(ba, isOpenShift, getDNSEgressRule, getEndpoints)
385+
egressRules = append(egressRules, bypassRules...)
386+
if usingPermissiveRule {
387+
return egressRules // exit early because permissive egress is set
388+
}
389+
}
390+
// configure the main application egress rule
336391
var rule networkingv1.NetworkPolicyEgressRule
337-
338392
if config == nil || ((config.GetToNamespaceLabels() != nil && len(config.GetToNamespaceLabels()) == 0) &&
339393
(config.GetToLabels() != nil && len(config.GetToLabels()) == 0)) {
340394
rule = createAllowAllNetworkPolicyEgressRule()
341395
} else {
342396
rule = createNetworkPolicyEgressRule(ba.GetApplicationName(), networkPolicy.Namespace, config)
343397
}
344-
345-
return []networkingv1.NetworkPolicyEgressRule{rule}
398+
egressRules = append(egressRules, rule)
399+
return egressRules
346400
}
347401

348402
func createNetworkPolicyEgressRule(appName string, namespace string, config common.BaseComponentNetworkPolicy) networkingv1.NetworkPolicyEgressRule {
@@ -364,7 +418,26 @@ func createAllowAllNetworkPolicyEgressRule() networkingv1.NetworkPolicyEgressRul
364418
}
365419
}
366420

367-
func CustomizeNetworkPolicy(networkPolicy *networkingv1.NetworkPolicy, isOpenShift bool, ba common.BaseComponent) {
421+
func GetEndpointPortByName(endpointPorts *[]corev1.EndpointPort, name string) *corev1.EndpointPort {
422+
if endpointPorts != nil {
423+
for _, endpointPort := range *endpointPorts {
424+
if endpointPort.Name == name {
425+
return &endpointPort
426+
}
427+
}
428+
}
429+
return nil
430+
}
431+
432+
func CreateNetworkPolicyPortFromEndpointPort(endpointPort *corev1.EndpointPort) networkingv1.NetworkPolicyPort {
433+
port := networkingv1.NetworkPolicyPort{}
434+
port.Protocol = &endpointPort.Protocol
435+
var portNumber intstr.IntOrString = intstr.FromInt((int)(endpointPort.Port))
436+
port.Port = &portNumber
437+
return port
438+
}
439+
440+
func CustomizeNetworkPolicy(networkPolicy *networkingv1.NetworkPolicy, isOpenShift bool, getDNSEgressRule func(string, string) (bool, networkingv1.NetworkPolicyEgressRule), getEndpoints func(string, string) (*corev1.Endpoints, error), ba common.BaseComponent) {
368441
obj := ba.(metav1.Object)
369442
networkPolicy.Labels = ba.GetLabels()
370443
networkPolicy.Annotations = MergeMaps(networkPolicy.Annotations, ba.GetAnnotations())
@@ -402,7 +475,8 @@ func CustomizeNetworkPolicy(networkPolicy *networkingv1.NetworkPolicy, isOpenShi
402475
if !hasEgressPolicy {
403476
networkPolicy.Spec.PolicyTypes = append(networkPolicy.Spec.PolicyTypes, networkingv1.PolicyTypeEgress)
404477
}
405-
networkPolicy.Spec.Egress = createNetworkPolicyEgressRules(networkPolicy, isOpenShift, ba)
478+
egressBypass := ba.GetNetworkPolicy() != nil && ba.GetNetworkPolicy().IsBypassingDenyAllEgress() // check if egress should bypass deny all policy to access the API server and DNS
479+
networkPolicy.Spec.Egress = createNetworkPolicyEgressRules(networkPolicy, isOpenShift, egressBypass, getDNSEgressRule, getEndpoints, ba)
406480
} else {
407481
// if egress is not configured, consider the network policy disabled
408482
if hasEgressPolicy {

0 commit comments

Comments
 (0)