Skip to content

Commit 1df430e

Browse files
committed
test: preventing serviceaccount privilege escalation
1 parent 75525ac commit 1df430e

File tree

1 file changed

+146
-0
lines changed

1 file changed

+146
-0
lines changed
+146
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,146 @@
1+
//go:build e2e
2+
3+
// Copyright 2020-2021 Clastix Labs
4+
// SPDX-License-Identifier: Apache-2.0
5+
6+
package e2e
7+
8+
import (
9+
"context"
10+
"fmt"
11+
"time"
12+
13+
. "github.com/onsi/ginkgo"
14+
. "github.com/onsi/gomega"
15+
rbacv1 "k8s.io/api/rbac/v1"
16+
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
17+
"k8s.io/apimachinery/pkg/types"
18+
"k8s.io/apimachinery/pkg/util/uuid"
19+
"k8s.io/client-go/kubernetes"
20+
"sigs.k8s.io/controller-runtime/pkg/client/config"
21+
22+
capsulev1beta1 "github.com/clastix/capsule/api/v1beta1"
23+
)
24+
25+
var _ = Describe("trying to escalate from a Tenant Namespace ServiceAccount", func() {
26+
tnt := &capsulev1beta1.Tenant{
27+
ObjectMeta: metav1.ObjectMeta{
28+
Name: "sa-privilege-escalation",
29+
},
30+
Spec: capsulev1beta1.TenantSpec{
31+
Owners: capsulev1beta1.OwnerListSpec{
32+
{
33+
Name: "mario",
34+
Kind: "User",
35+
},
36+
},
37+
NodeSelector: map[string]string{
38+
"kubernetes.io/os": "linux",
39+
},
40+
},
41+
}
42+
43+
ns := NewNamespace("attack")
44+
45+
JustBeforeEach(func() {
46+
EventuallyCreation(func() error {
47+
return k8sClient.Create(context.TODO(), tnt)
48+
}).Should(Succeed())
49+
50+
NamespaceCreation(ns, tnt.Spec.Owners[0], defaultTimeoutInterval).Should(Succeed())
51+
TenantNamespaceList(tnt, defaultTimeoutInterval).Should(ContainElement(ns.GetName()))
52+
})
53+
54+
JustAfterEach(func() {
55+
Expect(k8sClient.Delete(context.TODO(), tnt)).Should(Succeed())
56+
})
57+
58+
It("should block Namespace changes", func() {
59+
role := rbacv1.Role{
60+
ObjectMeta: metav1.ObjectMeta{
61+
Name: "ns-update-role",
62+
Namespace: ns.GetName(),
63+
},
64+
Rules: []rbacv1.PolicyRule{
65+
{
66+
Verbs: []string{"update"},
67+
APIGroups: []string{""},
68+
Resources: []string{"namespaces"},
69+
ResourceNames: []string{ns.GetName()},
70+
},
71+
},
72+
}
73+
74+
EventuallyCreation(func() error {
75+
return k8sClient.Create(context.Background(), &role)
76+
}).Should(Succeed())
77+
78+
rolebinding := rbacv1.RoleBinding{
79+
ObjectMeta: metav1.ObjectMeta{
80+
Name: "attacker-rolebinding",
81+
Namespace: ns.GetName(),
82+
},
83+
Subjects: []rbacv1.Subject{
84+
{
85+
Kind: "ServiceAccount",
86+
Name: "attacker",
87+
Namespace: ns.GetName(),
88+
},
89+
},
90+
RoleRef: rbacv1.RoleRef{
91+
APIGroup: "rbac.authorization.k8s.io",
92+
Kind: "Role",
93+
Name: role.GetName(),
94+
},
95+
}
96+
97+
EventuallyCreation(func() error {
98+
return k8sClient.Create(context.Background(), &rolebinding)
99+
}).Should(Succeed())
100+
101+
c, err := config.GetConfig()
102+
Expect(err).ToNot(HaveOccurred())
103+
c.Impersonate.Groups = []string{"system:serviceaccounts"}
104+
c.Impersonate.UserName = fmt.Sprintf("system:serviceaccount:%s:%s", rolebinding.Subjects[0].Namespace, rolebinding.Subjects[0].Name)
105+
saClient, err := kubernetes.NewForConfig(c)
106+
Expect(err).ToNot(HaveOccurred())
107+
// Changing Owner Reference is forbidden
108+
Consistently(func() error {
109+
if err := k8sClient.Get(context.Background(), types.NamespacedName{Name: ns.GetName()}, ns); err != nil {
110+
return err
111+
}
112+
113+
ns.OwnerReferences[0].UID = uuid.NewUUID()
114+
115+
_, err = saClient.CoreV1().Namespaces().Update(context.Background(), ns, metav1.UpdateOptions{})
116+
117+
return err
118+
}, 10*time.Second, time.Second).ShouldNot(Succeed())
119+
// Removing Owner Reference is forbidden
120+
Consistently(func() error {
121+
if err := k8sClient.Get(context.Background(), types.NamespacedName{Name: ns.GetName()}, ns); err != nil {
122+
return err
123+
}
124+
125+
ns.SetOwnerReferences(nil)
126+
127+
_, err = saClient.CoreV1().Namespaces().Update(context.Background(), ns, metav1.UpdateOptions{})
128+
129+
return err
130+
}, 10*time.Second, time.Second).ShouldNot(Succeed())
131+
// Breaking nodeSelector is forbidden
132+
Consistently(func() error {
133+
if err := k8sClient.Get(context.Background(), types.NamespacedName{Name: ns.GetName()}, ns); err != nil {
134+
return err
135+
}
136+
137+
ns.SetAnnotations(map[string]string{
138+
"scheduler.alpha.kubernetes.io/node-selector": "kubernetes.io/os=forbidden",
139+
})
140+
141+
_, err = saClient.CoreV1().Namespaces().Update(context.Background(), ns, metav1.UpdateOptions{})
142+
143+
return err
144+
}, 10*time.Second, time.Second).ShouldNot(Succeed())
145+
})
146+
})

0 commit comments

Comments
 (0)