Skip to content

Commit ab674ff

Browse files
feat: add improved refelection (#1074)
* feat: add improved refelection Signed-off-by: Oliver Baehler <oliver@sudo-i.net> * feat: add improved refelection Signed-off-by: Oliver Baehler <oliver@sudo-i.net> * feat: add improved refelection Signed-off-by: Oliver Baehler <oliver@sudo-i.net> --------- Signed-off-by: Oliver Baehler <oliver@sudo-i.net>
1 parent f9ad9a6 commit ab674ff

14 files changed

Lines changed: 667 additions & 100 deletions

File tree

Makefile

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -184,6 +184,7 @@ ifeq ($(CAPSULE_PROXY_MODE),http)
184184
--set "options.logLevel=10" \
185185
--set "options.pprof=true" \
186186
--set "options.leaderElection=true" \
187+
--set "options.roleBindingReflector=true" \
187188
--set "service.type=NodePort" \
188189
--set "kind=DaemonSet" \
189190
--set "daemonset.hostNetwork=true" \
@@ -200,6 +201,7 @@ else
200201
--set "options.logLevel=10" \
201202
--set "options.pprof=true" \
202203
--set "options.leaderElection=true" \
204+
--set "options.roleBindingReflector=true" \
203205
--set "service.type=NodePort" \
204206
--set "kind=DaemonSet" \
205207
--set "daemonset.hostNetwork=true" \

charts/capsule-proxy/README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -200,7 +200,7 @@ If you only need to make minor customizations, you can specify them on the comma
200200
| options.logLevel | int | `4` | Set the log verbosity of the capsule-proxy with a value from 1 to 10 |
201201
| options.oidcUsernameClaim | string | `"preferred_username"` | Specify if capsule-proxy will use SSL |
202202
| options.pprof | bool | `false` | Enable Pprof for profiling |
203-
| options.roleBindingReflector | bool | `false` | Enable the rolebinding reflector, which allows to list the namespaces, where a rolebinding mentions a user. |
203+
| options.roleBindingReflector | bool | `false` | Enable reflection for RoleBindings labelled reflection.proxy.projectcapsule.dev/enabled=true. |
204204
| options.rolebindingsResyncPeriod | string | `"10h"` | Set the role bindings reflector resync period, a local cache to store mappings between users and their namespaces. [Use a lower value in case of flaky etcd server connections.](https://github.com/projectcapsule/capsule-proxy/issues/174) |
205205
| options.trustedProxyCidrs | list | `[]` | CIDR ranges of trusted proxies allowed to make requests to the proxy |
206206

charts/capsule-proxy/ci/config-values.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ options:
3838
rolebindingsResyncPeriod: 10h
3939
# -- Disable the go-client caching to hit directly the Kubernetes API Server, it disables any local caching as the rolebinding reflector.
4040
disableCaching: false
41-
# -- Enable the rolebinding reflector, which allows to list the namespaces, where a rolebinding mentions a user.
41+
# -- Enable reflection for RoleBindings labelled reflection.proxy.projectcapsule.dev/enabled=true.
4242
roleBindingReflector: false
4343
# -- Authentication types to be used for requests. Possible Auth Types: [BearerToken, TLSCertificate,XForwardedClientCert]
4444
authPreferredTypes: "BearerToken,TLSCertificate"

charts/capsule-proxy/values.schema.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1015,7 +1015,7 @@
10151015
"type": "boolean"
10161016
},
10171017
"roleBindingReflector": {
1018-
"description": "Enable the rolebinding reflector, which allows to list the namespaces, where a rolebinding mentions a user.",
1018+
"description": "Enable reflection for RoleBindings labelled reflection.proxy.projectcapsule.dev/enabled=true.",
10191019
"type": "boolean"
10201020
},
10211021
"rolebindingsResyncPeriod": {

charts/capsule-proxy/values.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -330,7 +330,7 @@ options:
330330
rolebindingsResyncPeriod: 10h
331331
# -- Disable the go-client caching to hit directly the Kubernetes API Server, it disables any local caching as the rolebinding reflector.
332332
disableCaching: false
333-
# -- Enable the rolebinding reflector, which allows to list the namespaces, where a rolebinding mentions a user.
333+
# -- Enable reflection for RoleBindings labelled reflection.proxy.projectcapsule.dev/enabled=true.
334334
roleBindingReflector: false
335335
# -- Authentication types to be used for requests. Possible Auth Types: [BearerToken, TLSCertificate, XForwardedClientCert]
336336
authPreferredTypes: "BearerToken,TLSCertificate"

e2e/distro/objects/capsule.flux.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ spec:
1919
chart:
2020
spec:
2121
chart: capsule
22-
version: "0.13.7"
22+
version: "0.13.9"
2323
sourceRef:
2424
kind: HelmRepository
2525
name: projectcapsule
Lines changed: 158 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,158 @@
1+
package e2e_test
2+
3+
import (
4+
"context"
5+
6+
. "github.com/onsi/ginkgo/v2"
7+
. "github.com/onsi/gomega"
8+
capsulev1beta2 "github.com/projectcapsule/capsule/api/v1beta2"
9+
capsulemeta "github.com/projectcapsule/capsule/pkg/api/meta"
10+
capsulerbac "github.com/projectcapsule/capsule/pkg/api/rbac"
11+
corev1 "k8s.io/api/core/v1"
12+
rbacv1 "k8s.io/api/rbac/v1"
13+
apierrors "k8s.io/apimachinery/pkg/api/errors"
14+
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
15+
"k8s.io/apimachinery/pkg/types"
16+
"sigs.k8s.io/controller-runtime/pkg/client"
17+
18+
"github.com/projectcapsule/capsule-proxy/internal/controllers"
19+
)
20+
21+
var _ = Describe("RoleBinding reflection", func() {
22+
const (
23+
reflectedTenantName = "rbac-reflected-tenant"
24+
privateTenantName = "rbac-private-tenant"
25+
reflectedNamespace = "rbac-reflected-namespace"
26+
privateNamespace = "rbac-private-namespace"
27+
)
28+
29+
owner := capsulerbac.OwnerListSpec{{
30+
CoreOwnerSpec: capsulerbac.CoreOwnerSpec{
31+
UserSpec: capsulerbac.UserSpec{Name: "alice", Kind: "User"},
32+
},
33+
}}
34+
35+
reflectedTenant := &capsulev1beta2.Tenant{
36+
ObjectMeta: metav1.ObjectMeta{Name: reflectedTenantName, Labels: e2eLabels()},
37+
Spec: capsulev1beta2.TenantSpec{
38+
Owners: owner,
39+
AdditionalRoleBindings: []capsulerbac.AdditionalRoleBindingsSpec{{
40+
ClusterRoleName: "view",
41+
Subjects: []rbacv1.Subject{{
42+
Kind: rbacv1.UserKind, APIGroup: rbacv1.GroupName, Name: "bob",
43+
}},
44+
Labels: map[string]string{controllers.RoleBindingReflectionLabel: "true"},
45+
}},
46+
},
47+
}
48+
privateTenant := &capsulev1beta2.Tenant{
49+
ObjectMeta: metav1.ObjectMeta{Name: privateTenantName, Labels: e2eLabels()},
50+
Spec: capsulev1beta2.TenantSpec{Owners: owner},
51+
}
52+
53+
BeforeEach(func() {
54+
for _, tenant := range []*capsulev1beta2.Tenant{reflectedTenant, privateTenant} {
55+
tenant := tenant
56+
Eventually(func() error {
57+
tenant.ResourceVersion = ""
58+
59+
return k8sClient.Create(context.Background(), tenant)
60+
}, defaultTimeoutInterval, defaultPollInterval).Should(Succeed())
61+
}
62+
63+
for _, namespace := range []*corev1.Namespace{
64+
NewNamespace(reflectedNamespace, map[string]string{capsulemeta.TenantLabel: reflectedTenantName}),
65+
NewNamespace(privateNamespace, map[string]string{capsulemeta.TenantLabel: privateTenantName}),
66+
} {
67+
NamespaceCreation(namespace, owner[0], defaultTimeoutInterval).Should(Succeed())
68+
}
69+
70+
Eventually(func(g Gomega) {
71+
bindings := &rbacv1.RoleBindingList{}
72+
g.Expect(k8sClient.List(context.Background(), bindings,
73+
client.InNamespace(reflectedNamespace),
74+
client.MatchingLabels{controllers.RoleBindingReflectionLabel: "true"},
75+
)).To(Succeed())
76+
g.Expect(bindings.Items).To(HaveLen(1))
77+
g.Expect(bindings.Items[0].RoleRef.Kind).To(Equal("ClusterRole"))
78+
g.Expect(bindings.Items[0].RoleRef.Name).To(Equal("view"))
79+
}, defaultTimeoutInterval, defaultPollInterval).Should(Succeed())
80+
})
81+
82+
JustAfterEach(func() {
83+
for _, name := range []string{reflectedNamespace, privateNamespace} {
84+
name := name
85+
Eventually(func() error {
86+
return client.IgnoreNotFound(k8sClient.Delete(context.Background(), &corev1.Namespace{
87+
ObjectMeta: metav1.ObjectMeta{Name: name},
88+
}))
89+
}, defaultTimeoutInterval, defaultPollInterval).Should(Succeed())
90+
Eventually(func() bool {
91+
err := k8sClient.Get(context.Background(), types.NamespacedName{Name: name}, &corev1.Namespace{})
92+
93+
return apierrors.IsNotFound(err)
94+
}, defaultTimeoutInterval, defaultPollInterval).Should(BeTrue())
95+
}
96+
97+
Eventually(func() error {
98+
return cleanResources([]client.Object{&capsulev1beta2.Tenant{}}, e2eSelector())
99+
}, defaultTimeoutInterval, defaultPollInterval).Should(Succeed())
100+
})
101+
102+
It("lists only the namespace with the labelled additional RoleBinding", func() {
103+
bobClient, err := loadKubeConfig("bob")
104+
Expect(err).NotTo(HaveOccurred())
105+
106+
Eventually(func() ([]string, error) {
107+
list, err := bobClient.CoreV1().Namespaces().List(context.Background(), metav1.ListOptions{})
108+
if err != nil {
109+
return nil, err
110+
}
111+
112+
names := make([]string, 0, len(list.Items))
113+
for i := range list.Items {
114+
names = append(names, list.Items[i].Name)
115+
}
116+
117+
return names, nil
118+
}, defaultTimeoutInterval, defaultPollInterval).
119+
Should(ConsistOf(reflectedNamespace))
120+
})
121+
122+
It("lists Pods only from the tenant with the labelled additional RoleBinding", func() {
123+
pods := []*corev1.Pod{
124+
{
125+
ObjectMeta: metav1.ObjectMeta{Name: "reflected-pod", Namespace: reflectedNamespace, Labels: map[string]string{capsulemeta.ManagedByCapsuleLabel: reflectedTenantName}},
126+
Spec: corev1.PodSpec{Containers: []corev1.Container{{Name: "pause", Image: "registry.k8s.io/pause:3.10"}}},
127+
},
128+
{
129+
ObjectMeta: metav1.ObjectMeta{Name: "private-pod", Namespace: privateNamespace, Labels: map[string]string{capsulemeta.ManagedByCapsuleLabel: privateTenantName}},
130+
Spec: corev1.PodSpec{Containers: []corev1.Container{{Name: "pause", Image: "registry.k8s.io/pause:3.10"}}},
131+
},
132+
}
133+
for _, pod := range pods {
134+
pod := pod
135+
Eventually(func() error {
136+
return k8sClient.Create(context.Background(), pod)
137+
}, defaultTimeoutInterval, defaultPollInterval).Should(Succeed())
138+
}
139+
140+
bobClient, err := loadKubeConfig("bob")
141+
Expect(err).NotTo(HaveOccurred())
142+
143+
Eventually(func() ([]string, error) {
144+
list, err := bobClient.CoreV1().Pods("").List(context.Background(), metav1.ListOptions{})
145+
if err != nil {
146+
return nil, err
147+
}
148+
149+
names := make([]string, 0, len(list.Items))
150+
for i := range list.Items {
151+
names = append(names, list.Items[i].Name)
152+
}
153+
154+
return names, nil
155+
}, defaultTimeoutInterval, defaultPollInterval).
156+
Should(ConsistOf("reflected-pod"))
157+
})
158+
})

0 commit comments

Comments
 (0)