Skip to content

Commit ced9131

Browse files
committed
Add tests for the previous patch
Signed-off-by: Anastasios Papagiannis <anastasios.papagiannis@isovalent.com>
1 parent 2177bfa commit ced9131

File tree

8 files changed

+360
-1
lines changed

8 files changed

+360
-1
lines changed

.codex

Whitespace-only changes.

b.yaml

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
apiVersion: cilium.io/v1alpha1
2+
kind: TracingPolicy
3+
metadata:
4+
name: "protection-policy-cve-2022-0847"
5+
spec:
6+
kprobes:
7+
- call: "sys_splice"
8+
syscall: true
9+
selectors:
10+
# host segment: binaries that are allowed to call splice on the host
11+
- matchBinaries:
12+
- operator: "In"
13+
values:
14+
- "systemd"
15+
- "qemu"
16+
- "smbd"
17+
- "samba"
18+
matchWorkloads:
19+
- hostSelector: {}
20+
posSelector: null # or ~
21+
matchActions:
22+
- action: NoPost
23+
# haproxy pod on the network namespace: binaries that are allowed to call splice on that pod
24+
- matchBinaries:
25+
- operator: "In"
26+
values:
27+
- "haproxy"
28+
matchWorkloads:
29+
- podSelector:
30+
matchExpressions:
31+
- key: "k8s:io.kubernetes.pod.namespace"
32+
operator: In
33+
values:
34+
- network
35+
- key: "app"
36+
operator: In
37+
values:
38+
- haproxy
39+
hostSelector: null # or ~
40+
matchActions:
41+
- action: NoPost
42+
# block splice from all other binaries
43+
- matchWorkloads:
44+
- podSelector: {}
45+
hostSelector: {}
46+
matchActions:
47+
- action: Override
48+
argError: -1
1.19 MB
Binary file not shown.
1.74 MB
Binary file not shown.

new_approach.diff

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
diff --git a/pkg/labels/labels.go b/pkg/labels/labels.go
2+
index 6a64a54a6..6f47cd317 100644
3+
--- a/pkg/labels/labels.go
4+
+++ b/pkg/labels/labels.go
5+
@@ -51,11 +51,13 @@ func (s *selectorOp) match(labels Labels) bool {
6+
}
7+
}
8+
9+
-type Selector []selectorOp
10+
+type SelectorWithValues struct {
11+
+ ops []selectorOp
12+
+}
13+
14+
-func (s Selector) Match(labels Labels) bool {
15+
- for i := range s {
16+
- if !s[i].match(labels) {
17+
+func (s SelectorWithValues) Match(labels Labels) bool {
18+
+ for i := range s.ops {
19+
+ if !s.ops[i].match(labels) {
20+
return false
21+
}
22+
}
23+
@@ -63,10 +65,26 @@ func (s Selector) Match(labels Labels) bool {
24+
return true
25+
}
26+
27+
+type Selector interface {
28+
+ Match(labels Labels) bool
29+
+}
30+
+
31+
+type SelectorForAll struct {
32+
+ matchAll bool
33+
+}
34+
+
35+
+func (s SelectorForAll) Match(labels Labels) bool {
36+
+ return s.matchAll
37+
+}
38+
+
39+
func SelectorFromLabelSelector(ls *slimv1.LabelSelector) (Selector, error) {
40+
if ls == nil {
41+
- return []selectorOp{}, nil
42+
+ return SelectorForAll{matchAll: false}, nil
43+
+ }
44+
+ if ls != nil && (len(ls.MatchLabels)+len(ls.MatchExpressions) == 0) {
45+
+ return SelectorForAll{matchAll: true}, nil
46+
}
47+
+ // now we have ls != nil and len(ls.MatchLabels)+len(ls.MatchExpressions) > 0
48+
ret := make([]selectorOp, 0, len(ls.MatchLabels)+len(ls.MatchExpressions))
49+
for key, val := range ls.MatchLabels {
50+
ret = append(ret, selectorOp{
51+
@@ -97,7 +115,7 @@ func SelectorFromLabelSelector(ls *slimv1.LabelSelector) (Selector, error) {
52+
})
53+
}
54+
55+
- return ret, nil
56+
+ return SelectorWithValues{ops: ret}, nil
57+
}
58+
59+
// Cmp checks if the labels are different. Returns true if they are.
60+

pkg/policyfilter/map_test.go

Lines changed: 28 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ import (
1111
"github.com/stretchr/testify/require"
1212
)
1313

14-
func requirePfmEqualTo(t *testing.T, m PfMap, val map[uint64][]uint64) {
14+
func buildExpectedPfMap(val map[uint64][]uint64) (map[PolicyID]map[CgroupID]struct{}, map[CgroupID]map[PolicyID]struct{}) {
1515

1616
checkVals := map[PolicyID]map[CgroupID]struct{}{}
1717
for k, ids := range val {
@@ -31,6 +31,33 @@ func requirePfmEqualTo(t *testing.T, m PfMap, val map[uint64][]uint64) {
3131
}
3232
}
3333

34+
return checkVals, checkCgroupVals
35+
}
36+
37+
func stripAllPodsPolicy(val PfMapDump) PfMapDump {
38+
delete(val.Policy, AllPodsPolicyID)
39+
for cgID, policyIDs := range val.Cgroup {
40+
delete(policyIDs, AllPodsPolicyID)
41+
if len(policyIDs) == 0 {
42+
delete(val.Cgroup, cgID)
43+
}
44+
}
45+
return val
46+
}
47+
48+
func requirePfmEqualTo(t *testing.T, m PfMap, val map[uint64][]uint64) {
49+
checkVals, checkCgroupVals := buildExpectedPfMap(val)
50+
51+
mapVals, err := m.readAll()
52+
require.NoError(t, err)
53+
mapVals = stripAllPodsPolicy(mapVals)
54+
require.Equal(t, checkVals, mapVals.Policy)
55+
require.Equal(t, checkCgroupVals, mapVals.Cgroup)
56+
}
57+
58+
func requirePfmEqualToIncludingAllPods(t *testing.T, m PfMap, val map[uint64][]uint64) {
59+
checkVals, checkCgroupVals := buildExpectedPfMap(val)
60+
3461
mapVals, err := m.readAll()
3562
require.NoError(t, err)
3663
require.Equal(t, checkVals, mapVals.Policy)

pkg/policyfilter/state_test.go

Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -104,3 +104,85 @@ func TestState(t *testing.T) {
104104
require.Empty(t, s.policies)
105105
require.Empty(t, s.pods)
106106
}
107+
108+
// New
109+
func TestStateAllPodsPolicyEntry(t *testing.T) {
110+
s, err := New(true)
111+
if err != nil {
112+
t.Skipf("failed to inialize policy filter state: %s", err)
113+
}
114+
defer s.Close()
115+
116+
requirePfmEqualToIncludingAllPods(t, s.pfMap, map[uint64][]uint64{
117+
uint64(AllPodsPolicyID): {},
118+
})
119+
120+
pod1 := PodID(uuid.New())
121+
cgid1 := CgroupID(4001)
122+
err = s.AddPodContainer(pod1, "ns1", "wl1", "kind1", nil, "cont1", cgid1, podhelpers.ContainerInfo{Name: "main1", Repo: "repo1"})
123+
require.NoError(t, err)
124+
125+
pod2 := PodID(uuid.New())
126+
cgid2 := CgroupID(4002)
127+
err = s.AddPodContainer(pod2, "ns2", "wl2", "kind2", nil, "cont2", cgid2, podhelpers.ContainerInfo{Name: "main2", Repo: "repo2"})
128+
require.NoError(t, err)
129+
130+
requirePfmEqualToIncludingAllPods(t, s.pfMap, map[uint64][]uint64{
131+
uint64(AllPodsPolicyID): {4001, 4002},
132+
})
133+
134+
err = s.AddPolicy(PolicyID(1), "ns2", nil, nil, nil)
135+
require.NoError(t, err)
136+
requirePfmEqualToIncludingAllPods(t, s.pfMap, map[uint64][]uint64{
137+
1: {4002},
138+
uint64(AllPodsPolicyID): {4001, 4002},
139+
})
140+
141+
err = s.DelPolicy(PolicyID(1))
142+
require.NoError(t, err)
143+
requirePfmEqualToIncludingAllPods(t, s.pfMap, map[uint64][]uint64{
144+
uint64(AllPodsPolicyID): {4001, 4002},
145+
})
146+
147+
err = s.DelPodContainer(pod1, "cont1")
148+
require.NoError(t, err)
149+
requirePfmEqualToIncludingAllPods(t, s.pfMap, map[uint64][]uint64{
150+
uint64(AllPodsPolicyID): {4002},
151+
})
152+
153+
err = s.DelPod(pod2)
154+
require.NoError(t, err)
155+
requirePfmEqualToIncludingAllPods(t, s.pfMap, map[uint64][]uint64{
156+
uint64(AllPodsPolicyID): {},
157+
})
158+
}
159+
160+
func TestStateAllPodsPolicyEntryWithoutCgroupMap(t *testing.T) {
161+
s, err := New(false)
162+
if err != nil {
163+
t.Skipf("failed to inialize policy filter state: %s", err)
164+
}
165+
defer s.Close()
166+
167+
pod := PodID(uuid.New())
168+
cgid := CgroupID(5001)
169+
err = s.AddPodContainer(pod, "ns1", "wl1", "kind1", nil, "cont1", cgid, podhelpers.ContainerInfo{Name: "main1", Repo: "repo1"})
170+
require.NoError(t, err)
171+
172+
dump, err := s.pfMap.readAll()
173+
require.NoError(t, err)
174+
require.Equal(t, map[PolicyID]map[CgroupID]struct{}{
175+
AllPodsPolicyID: {cgid: {}},
176+
}, dump.Policy)
177+
require.Nil(t, dump.Cgroup)
178+
179+
err = s.DelPodContainer(pod, "cont1")
180+
require.NoError(t, err)
181+
182+
dump, err = s.pfMap.readAll()
183+
require.NoError(t, err)
184+
require.Equal(t, map[PolicyID]map[CgroupID]struct{}{
185+
AllPodsPolicyID: {},
186+
}, dump.Policy)
187+
require.Nil(t, dump.Cgroup)
188+
}

pol.txt

Lines changed: 142 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,142 @@
1+
apiVersion: cilium.io/v1alpha1
2+
kind: TracingPolicy
3+
metadata:
4+
name: "protection-policy-cve-2022-0847"
5+
spec:
6+
kprobes:
7+
- call: "sys_splice"
8+
syscall: true
9+
10+
11+
12+
13+
14+
15+
16+
17+
Notes:
18+
1. containerSelector -> matches only on Pod containers -- not arbitrary containers
19+
2. podSelector and containerSelector are ANDed
20+
21+
Now:
22+
`containerSelector: null` and `containerSelector: {}` are the same and match everything
23+
`podSelector: null` and `podSelector: {}` are the same and match everything
24+
everything starts with null as default
25+
26+
27+
28+
# Kornilios
29+
# nil -> matches nothing
30+
# {} -> matches everything
31+
# absence -> same as {}
32+
33+
34+
35+
# Anastasios
36+
# nil -> matches nothing
37+
# {} -> matches everything
38+
# absence -> same as nil
39+
# Special case -> all nil no filtering
40+
41+
workload is either host or pod
42+
43+
hostSelector (host workloads) vs nodeSelector (apply everything or nothing in the node)
44+
45+
46+
spec:
47+
podSelector: {}
48+
* AND *
49+
containerSelector: {}
50+
* OR *
51+
hostSelector: {} # empty + full for now
52+
nodeSelector: {} # k8s (i.e. staging or dev) + info (https://github.com/cilium/tetragon/tree/main/pkg/tetragoninfo) --> TODO create issue
53+
54+
spec:
55+
kprobes:
56+
57+
58+
59+
# not valid -- cannot match all containers without any pods
60+
spec:
61+
podSelector: ~
62+
containerSelector: {}
63+
hostSelector: ~
64+
65+
# not valid -- cannot match all pods without any containers
66+
spec:
67+
podSelector: {}
68+
containerSelector: ~
69+
hostSelector: ~
70+
71+
# valid -- match all containers inside all pods
72+
spec:
73+
podSelector: {}
74+
containerSelector: {}
75+
hostSelector: ~
76+
77+
# valid -- match all containers inside any pod with `name: ubuntu`
78+
spec:
79+
podSelector: {}
80+
containerSelector:
81+
matchExpressions:
82+
- key: "name"
83+
operator: In
84+
values:
85+
- ubuntu
86+
hostSelector: ~
87+
88+
# valid -- match containers with `name: ubuntu` inside pod with `namespace: network`
89+
spec:
90+
podSelector:
91+
matchExpressions:
92+
- key: "k8s:io.kubernetes.pod.namespace"
93+
operator: In
94+
values:
95+
- network
96+
containerSelector:
97+
matchExpressions:
98+
- key: "name"
99+
operator: In
100+
values:
101+
- ubuntu
102+
hostSelector: ~
103+
104+
# valid -- match all containers inside pod with `namespace: network`
105+
spec:
106+
podSelector:
107+
matchExpressions:
108+
- key: "k8s:io.kubernetes.pod.namespace"
109+
operator: In
110+
values:
111+
- network
112+
containerSelector: {}
113+
hostSelector: ~
114+
115+
# valid -- only host
116+
spec:
117+
podSelector: ~
118+
containerSelector: ~
119+
hostSelector: {}
120+
121+
# valid -- special case to match everything (i.e. this is the same as if all of these are absense)
122+
spec:
123+
podSelector: {}
124+
containerSelector: {}
125+
hostSelector: {}
126+
127+
# not valid -- no process can be both in the network namespace AND in host
128+
spec:
129+
podSelector:
130+
matchExpressions:
131+
- key: "k8s:io.kubernetes.pod.namespace"
132+
operator: In
133+
values:
134+
- network
135+
containerSelector: {}
136+
hostSelector: {}
137+
138+
## From Slack
139+
140+
# valid -- will match only host
141+
spec:
142+
podSelector: nil

0 commit comments

Comments
 (0)