Skip to content

Commit 1a08851

Browse files
fix: cluster-admin issue
1 parent 64ebd13 commit 1a08851

2 files changed

Lines changed: 53 additions & 0 deletions

File tree

internal/authz/kube.go

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -129,6 +129,19 @@ func (k *Kube) ListAllowed(ctx context.Context, sub Subject, act Action) ([]stri
129129
return nil, fmt.Errorf("list namespaces: %w", err)
130130
}
131131

132+
// Fast path: if the subject can perform the action CLUSTER-WIDE (a
133+
// cluster-admin, or anyone with a cluster-scoped RoleBinding for it), grant
134+
// every namespace and skip the per-namespace sweep. A SAR with an empty
135+
// namespace checks cluster scope. This both fixes cluster-admins (who the
136+
// per-namespace sweep can miss) and is far cheaper (one SAR, not hundreds).
137+
if d, err := k.Authorize(ctx, sub, act, ""); err == nil && d.Allowed {
138+
all := make([]string, 0, len(nsList.Items))
139+
for _, ns := range nsList.Items {
140+
all = append(all, ns.Name)
141+
}
142+
return all, nil
143+
}
144+
132145
var (
133146
mu sync.Mutex
134147
allowed []string
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
package authz
2+
3+
import (
4+
"context"
5+
"testing"
6+
7+
authzv1 "k8s.io/api/authorization/v1"
8+
corev1 "k8s.io/api/core/v1"
9+
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
10+
"k8s.io/apimachinery/pkg/runtime"
11+
"k8s.io/client-go/kubernetes/fake"
12+
k8stesting "k8s.io/client-go/testing"
13+
)
14+
15+
// clusterAdmin: SAR always allowed → ListAllowed returns every namespace via the
16+
// cluster-wide fast path (one SAR).
17+
func TestListAllowedClusterWideFastPath(t *testing.T) {
18+
cs := fake.NewSimpleClientset(
19+
&corev1.Namespace{ObjectMeta: metav1.ObjectMeta{Name: "team-a"}},
20+
&corev1.Namespace{ObjectMeta: metav1.ObjectMeta{Name: "kube-system"}},
21+
&corev1.Namespace{ObjectMeta: metav1.ObjectMeta{Name: "openshift-operators"}},
22+
)
23+
var sarCount int
24+
cs.PrependReactor("create", "subjectaccessreviews", func(a k8stesting.Action) (bool, runtime.Object, error) {
25+
sarCount++
26+
return true, &authzv1.SubjectAccessReview{Status: authzv1.SubjectAccessReviewStatus{Allowed: true}}, nil
27+
})
28+
k := &Kube{client: cs, listConcurrency: 4}
29+
30+
got, err := k.ListAllowed(context.Background(), Subject{User: "admin@x"}, Action{Verb: "get", Resource: "pods"})
31+
if err != nil {
32+
t.Fatal(err)
33+
}
34+
if len(got) != 3 {
35+
t.Fatalf("admin should see all namespaces, got %v", got)
36+
}
37+
if sarCount != 1 {
38+
t.Fatalf("cluster-wide fast path should use exactly 1 SAR, used %d", sarCount)
39+
}
40+
}

0 commit comments

Comments
 (0)