Skip to content

Commit

Permalink
Network Policy - Strict mode support (#209)
Browse files Browse the repository at this point in the history
* Network Policy - Strict mode support

* CR updates

* Bump up conntrack map size and UTs

* Minor cleanup
  • Loading branch information
achevuru authored Feb 28, 2024
1 parent f80d4d9 commit 5121349
Show file tree
Hide file tree
Showing 10 changed files with 585 additions and 15 deletions.
47 changes: 43 additions & 4 deletions controllers/policyendpoints_controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -94,7 +94,6 @@ func NewPolicyEndpointsReconciler(k8sClient client.Client, log logr.Logger,
// Start prometheus
prometheusRegister()
}

return r, err
}

Expand Down Expand Up @@ -544,16 +543,15 @@ func (r *PolicyEndpointsReconciler) deriveTargetPods(ctx context.Context,
// by the Host IP value.
nodeIP := net.ParseIP(r.nodeIP)
for _, pod := range policyEndpoint.Spec.PodSelectorEndpoints {
podIdentifier := utils.GetPodIdentifier(pod.Name, pod.Namespace)
if nodeIP.Equal(net.ParseIP(string(pod.HostIP))) {
r.log.Info("Found a matching Pod: ", "name: ", pod.Name, "namespace: ", pod.Namespace)
targetPods = append(targetPods, types.NamespacedName{Name: pod.Name, Namespace: pod.Namespace})
podIdentifier := utils.GetPodIdentifier(pod.Name, pod.Namespace)
podIdentifiers[podIdentifier] = true
r.log.Info("Derived ", "Pod identifier: ", podIdentifier)
r.updatePodIdentifierToPEMap(ctx, podIdentifier, parentPEList)
}
r.updatePodIdentifierToPEMap(ctx, podIdentifier, parentPEList)
}

return targetPods, podIdentifiers
}

Expand Down Expand Up @@ -665,3 +663,44 @@ func (r *PolicyEndpointsReconciler) derivePolicyEndpointsOfParentNP(ctx context.
}
return parentPolicyEndpointList
}

func (r *PolicyEndpointsReconciler) GeteBPFClient() ebpf.BpfClient {
return r.ebpfClient
}

func (r *PolicyEndpointsReconciler) DeriveFireWallRulesPerPodIdentifier(podIdentifier string, podNamespace string) ([]ebpf.EbpfFirewallRules,
[]ebpf.EbpfFirewallRules, error) {

ingressRules, egressRules, isIngressIsolated, isEgressIsolated, err := r.deriveIngressAndEgressFirewallRules(context.Background(), podIdentifier,
podNamespace, "", false)
if err != nil {
r.log.Error(err, "Error deriving firewall rules")
return ingressRules, egressRules, nil
}

if len(ingressRules) == 0 && !isIngressIsolated {
// No active ingress rules for this pod, but we only should land here
// if there are active egress rules. So, we need to add an allow-all entry to ingress rule set
r.log.Info("No Ingress rules and no ingress isolation - Appending catch all entry")
r.addCatchAllEntry(context.Background(), &ingressRules)
}

if len(egressRules) == 0 && !isEgressIsolated {
// No active egress rules for this pod but we only should land here
// if there are active ingress rules. So, we need to add an allow-all entry to egress rule set
r.log.Info("No Egress rules and no egress isolation - Appending catch all entry")
r.addCatchAllEntry(context.Background(), &egressRules)
}

return ingressRules, egressRules, nil
}

func (r *PolicyEndpointsReconciler) ArePoliciesAvailableInLocalCache(podIdentifier string) bool {
if policyEndpointList, ok := r.podIdentifierToPolicyEndpointMap.Load(podIdentifier); ok {
if len(policyEndpointList.([]string)) > 0 {
r.log.Info("Active policies available against", "podIdentifier", podIdentifier)
return true
}
}
return false
}
306 changes: 306 additions & 0 deletions controllers/policyendpoints_controller_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -712,3 +712,309 @@ func TestDeriveDefaultPodIsolation(t *testing.T) {
})
}
}

func TestArePoliciesAvailableInLocalCache(t *testing.T) {
type want struct {
activePoliciesAvailable bool
}

tests := []struct {
name string
podIdentifier string
policyEndpointName []string
want want
}{
{
name: "Active policies present against the PodIdentifier",
podIdentifier: "foo-bar",
policyEndpointName: []string{"foo", "bar"},
want: want{
activePoliciesAvailable: true,
},
},

{
name: "No Active policies present against the PodIdentifier",
podIdentifier: "foo-bar",
want: want{
activePoliciesAvailable: false,
},
},
}

for _, tt := range tests {
ctrl := gomock.NewController(t)
defer ctrl.Finish()

mockClient := mock_client.NewMockClient(ctrl)
policyEndpointReconciler, _ := NewPolicyEndpointsReconciler(mockClient, logr.New(&log.NullLogSink{}),
false, false, false, false, 300)
var policyEndpointsList []string
policyEndpointsList = append(policyEndpointsList, tt.policyEndpointName...)
policyEndpointReconciler.podIdentifierToPolicyEndpointMap.Store(tt.podIdentifier, policyEndpointsList)

t.Run(tt.name, func(t *testing.T) {
activePoliciesAvailable := policyEndpointReconciler.ArePoliciesAvailableInLocalCache(tt.podIdentifier)
assert.Equal(t, tt.want.activePoliciesAvailable, activePoliciesAvailable)
})
}
}

func TestDeriveFireWallRulesPerPodIdentifier(t *testing.T) {
protocolTCP := corev1.ProtocolTCP
protocolUDP := corev1.ProtocolUDP
var port80 int32 = 80

type policyendpointGetCall struct {
peRef types.NamespacedName
pe *policyendpoint.PolicyEndpoint
err error
}

type want struct {
ingressRules []ebpf.EbpfFirewallRules
egressRules []ebpf.EbpfFirewallRules
isIngressIsolated bool
isEgressIsolated bool
}

ingressAndEgressPolicy := policyendpoint.PolicyEndpoint{
ObjectMeta: metav1.ObjectMeta{
Name: "foo",
Namespace: "bar",
},
Spec: policyendpoint.PolicyEndpointSpec{
PodSelector: &metav1.LabelSelector{},
PolicyRef: policyendpoint.PolicyReference{
Name: "foo",
Namespace: "bar",
},
Ingress: []policyendpoint.EndpointInfo{
{
CIDR: "1.1.1.1/32",
Ports: []policyendpoint.Port{
{
Port: &port80,
Protocol: &protocolTCP,
},
},
},
},
Egress: []policyendpoint.EndpointInfo{
{
CIDR: "2.2.2.2/32",
Ports: []policyendpoint.Port{
{
Port: &port80,
Protocol: &protocolUDP,
},
},
},
},
},
}

ingressRulesOnlyPolicy := policyendpoint.PolicyEndpoint{
ObjectMeta: metav1.ObjectMeta{
Name: "foo",
Namespace: "bar",
},
Spec: policyendpoint.PolicyEndpointSpec{
PodSelector: &metav1.LabelSelector{},
PolicyRef: policyendpoint.PolicyReference{
Name: "foo",
Namespace: "bar",
},
PodIsolation: []networking.PolicyType{
networking.PolicyTypeIngress,
networking.PolicyTypeEgress,
},
Ingress: []policyendpoint.EndpointInfo{
{
CIDR: "1.1.1.1/32",
Ports: []policyendpoint.Port{
{
Port: &port80,
Protocol: &protocolTCP,
},
},
},
},
},
}

egressRulesOnlyPolicy := policyendpoint.PolicyEndpoint{
ObjectMeta: metav1.ObjectMeta{
Name: "foo",
Namespace: "bar",
},
Spec: policyendpoint.PolicyEndpointSpec{
PodSelector: &metav1.LabelSelector{},
PolicyRef: policyendpoint.PolicyReference{
Name: "foo",
Namespace: "bar",
},
PodIsolation: []networking.PolicyType{
networking.PolicyTypeIngress,
networking.PolicyTypeEgress,
},
Egress: []policyendpoint.EndpointInfo{
{
CIDR: "2.2.2.2/32",
Ports: []policyendpoint.Port{
{
Port: &port80,
Protocol: &protocolUDP,
},
},
},
},
},
}

tests := []struct {
name string
podIdentifier string
resourceNamespace string
policyEndpointName string
policyendpointGetCall []policyendpointGetCall
want want
wantErr error
}{
{
name: "Ingress and Egress Policy",
podIdentifier: "foo-bar",
resourceNamespace: "bar",
policyEndpointName: "foo",
policyendpointGetCall: []policyendpointGetCall{
{
peRef: types.NamespacedName{
Name: "foo",
Namespace: "bar",
},
pe: &ingressAndEgressPolicy,
},
},
want: want{
ingressRules: []ebpf.EbpfFirewallRules{
{
IPCidr: "1.1.1.1/32",
L4Info: []policyendpoint.Port{
{
Protocol: &protocolTCP,
Port: &port80,
},
},
},
},
egressRules: []ebpf.EbpfFirewallRules{
{
IPCidr: "2.2.2.2/32",
L4Info: []policyendpoint.Port{
{
Protocol: &protocolUDP,
Port: &port80,
},
},
},
},
isIngressIsolated: false,
isEgressIsolated: false,
},
wantErr: nil,
},
{
name: "Ingress Only Policy",
podIdentifier: "foo-bar",
resourceNamespace: "bar",
policyEndpointName: "foo",
policyendpointGetCall: []policyendpointGetCall{
{
peRef: types.NamespacedName{
Name: "foo",
Namespace: "bar",
},
pe: &ingressRulesOnlyPolicy,
},
},
want: want{
ingressRules: []ebpf.EbpfFirewallRules{
{
IPCidr: "1.1.1.1/32",
L4Info: []policyendpoint.Port{
{
Protocol: &protocolTCP,
Port: &port80,
},
},
},
},
isIngressIsolated: false,
isEgressIsolated: true,
},
wantErr: nil,
},

{
name: "Egress Only Policy",
podIdentifier: "foo-bar",
resourceNamespace: "bar",
policyEndpointName: "foo",
policyendpointGetCall: []policyendpointGetCall{
{
peRef: types.NamespacedName{
Name: "foo",
Namespace: "bar",
},
pe: &egressRulesOnlyPolicy,
},
},
want: want{
egressRules: []ebpf.EbpfFirewallRules{
{
IPCidr: "2.2.2.2/32",
L4Info: []policyendpoint.Port{
{
Protocol: &protocolUDP,
Port: &port80,
},
},
},
},
isIngressIsolated: true,
isEgressIsolated: false,
},
wantErr: nil,
},
}

for _, tt := range tests {
ctrl := gomock.NewController(t)
defer ctrl.Finish()

mockClient := mock_client.NewMockClient(ctrl)
policyEndpointReconciler, _ := NewPolicyEndpointsReconciler(mockClient, logr.New(&log.NullLogSink{}),
false, false, false, false, 300)
var policyEndpointsList []string
policyEndpointsList = append(policyEndpointsList, tt.policyEndpointName)
policyEndpointReconciler.podIdentifierToPolicyEndpointMap.Store(tt.podIdentifier, policyEndpointsList)
for _, item := range tt.policyendpointGetCall {
call := item
mockClient.EXPECT().Get(gomock.Any(), call.peRef, gomock.Any()).DoAndReturn(
func(ctx context.Context, key types.NamespacedName, currentPE *policyendpoint.PolicyEndpoint, opts ...client.GetOption) error {
if call.pe != nil {
*currentPE = *call.pe
}
return call.err
},
).AnyTimes()
}

t.Run(tt.name, func(t *testing.T) {
gotIngressRules, gotEgressRules, gotError := policyEndpointReconciler.DeriveFireWallRulesPerPodIdentifier(tt.podIdentifier, tt.resourceNamespace)
assert.Equal(t, tt.want.ingressRules, gotIngressRules)
assert.Equal(t, tt.want.egressRules, gotEgressRules)
assert.Equal(t, tt.wantErr, gotError)
})
}
}
Loading

0 comments on commit 5121349

Please sign in to comment.