Skip to content

Commit 8efef67

Browse files
committed
e2e: add warrant and scope tests
Signed-off-by: Dr. Stefan Schimanski <[email protected]>
1 parent 1be9d64 commit 8efef67

File tree

1 file changed

+203
-0
lines changed

1 file changed

+203
-0
lines changed

test/e2e/authorizer/scopes_test.go

+203
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,203 @@
1+
/*
2+
Copyright 2024 The KCP Authors.
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
http://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
*/
16+
17+
package authorizer
18+
19+
import (
20+
"context"
21+
"fmt"
22+
"sort"
23+
"testing"
24+
25+
kcpkubernetesclientset "github.com/kcp-dev/client-go/kubernetes"
26+
"github.com/stretchr/testify/require"
27+
28+
authorizationv1 "k8s.io/api/authorization/v1"
29+
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
30+
"k8s.io/apiserver/pkg/authentication/serviceaccount"
31+
"k8s.io/client-go/rest"
32+
"k8s.io/kubernetes/pkg/registry/rbac/validation"
33+
34+
"github.com/kcp-dev/kcp/test/e2e/framework"
35+
)
36+
37+
func TestSubjectAccessReview(t *testing.T) {
38+
t.Parallel()
39+
framework.Suite(t, "control-plane")
40+
41+
server := framework.SharedKcpServer(t)
42+
cfg := server.BaseConfig(t)
43+
wsPath, ws := framework.NewOrganizationFixture(t, server)
44+
45+
clusterClient, err := kcpkubernetesclientset.NewForConfig(cfg)
46+
require.NoError(t, err)
47+
48+
type tests struct {
49+
name string
50+
user string
51+
groups []string
52+
extra map[string]authorizationv1.ExtraValue
53+
wantAllowed bool
54+
}
55+
for _, tt := range []tests{
56+
{name: "normal user", user: "user", groups: []string{"system:kcp:admin"}, wantAllowed: true},
57+
{name: "in-scope users", user: "user", groups: []string{"system:kcp:admin"}, extra: map[string]authorizationv1.ExtraValue{
58+
validation.ScopeExtraKey: {"cluster:" + ws.Spec.Cluster},
59+
}, wantAllowed: true},
60+
{name: "out-of-scoped users", user: "user", groups: []string{"system:kcp:admin"}, extra: map[string]authorizationv1.ExtraValue{
61+
validation.ScopeExtraKey: {"cluster:root"},
62+
}, wantAllowed: false},
63+
{name: "service account", user: "system:serviceaccount:default:default", groups: []string{"system:kcp:admin"}, wantAllowed: true},
64+
{name: "service account with cluster", user: "system:serviceaccount:default:default", groups: []string{"system:kcp:admin"}, extra: map[string]authorizationv1.ExtraValue{
65+
serviceaccount.ClusterNameKey: {ws.Spec.Cluster},
66+
}, wantAllowed: true},
67+
{name: "service account with other cluster", user: "system:serviceaccount:default:default", groups: []string{"system:kcp:admin"}, extra: map[string]authorizationv1.ExtraValue{
68+
serviceaccount.ClusterNameKey: {"root"},
69+
}, wantAllowed: false},
70+
{name: "service account with other cluster and warrant", user: "system:serviceaccount:default:default", groups: []string{"system:kcp:admin"}, extra: map[string]authorizationv1.ExtraValue{
71+
serviceaccount.ClusterNameKey: {"root"},
72+
validation.WarrantExtraKey: {`{"user":"user","groups":["system:kcp:admin"]}`},
73+
}, wantAllowed: true},
74+
} {
75+
t.Run(tt.name, func(t *testing.T) {
76+
ctx, cancel := context.WithCancel(context.Background())
77+
defer cancel()
78+
79+
req := &authorizationv1.SubjectAccessReview{
80+
Spec: authorizationv1.SubjectAccessReviewSpec{
81+
ResourceAttributes: &authorizationv1.ResourceAttributes{
82+
Verb: "get",
83+
Resource: "namespaces",
84+
Version: "v1",
85+
},
86+
User: tt.user,
87+
Groups: tt.groups,
88+
Extra: tt.extra,
89+
},
90+
}
91+
resp, err := clusterClient.Cluster(wsPath).AuthorizationV1().SubjectAccessReviews().Create(ctx, req, metav1.CreateOptions{})
92+
require.NoError(t, err)
93+
if tt.wantAllowed {
94+
require.True(t, resp.Status.Allowed, "expected allowed, got: %s", resp.Status.Reason)
95+
} else {
96+
require.False(t, resp.Status.Allowed, "expected denied, got: %s", resp.Status.Reason)
97+
}
98+
})
99+
}
100+
}
101+
102+
func TestSelfSubjectRulesReview(t *testing.T) {
103+
t.Parallel()
104+
framework.Suite(t, "control-plane")
105+
106+
server := framework.SharedKcpServer(t)
107+
cfg := server.BaseConfig(t)
108+
wsPath, ws := framework.NewOrganizationFixture(t, server)
109+
110+
authenticatedBaseRules := []authorizationv1.ResourceRule{
111+
{Verbs: []string{"create"}, APIGroups: []string{"authentication.k8s.io"}, Resources: []string{"selfsubjectreviews"}},
112+
{Verbs: []string{"create"}, APIGroups: []string{"authorization.k8s.io"}, Resources: []string{"selfsubjectaccessreviews", "selfsubjectrulesreviews"}},
113+
}
114+
115+
type tests struct {
116+
name string
117+
user string
118+
groups []string
119+
extra map[string][]string
120+
wantRules []authorizationv1.ResourceRule
121+
}
122+
for _, tt := range []tests{
123+
{
124+
name: "normal user",
125+
user: "user", groups: []string{"system:kcp:admin"},
126+
wantRules: append([]authorizationv1.ResourceRule{
127+
{Verbs: []string{"*"}, APIGroups: []string{"*"}, Resources: []string{"*"}},
128+
}, authenticatedBaseRules...)},
129+
{
130+
name: "in-scope users",
131+
user: "user", groups: []string{"system:kcp:admin"}, extra: map[string][]string{validation.ScopeExtraKey: {"cluster:" + ws.Spec.Cluster}},
132+
wantRules: append([]authorizationv1.ResourceRule{
133+
{Verbs: []string{"*"}, APIGroups: []string{"*"}, Resources: []string{"*"}},
134+
}, authenticatedBaseRules...)},
135+
{
136+
name: "out-of-scoped users",
137+
user: "user", groups: []string{"system:kcp:admin"}, extra: map[string][]string{validation.ScopeExtraKey: {"cluster:root"}},
138+
wantRules: authenticatedBaseRules},
139+
{
140+
name: "service account",
141+
user: "system:serviceaccount:default:default", groups: []string{"system:kcp:admin"},
142+
wantRules: append([]authorizationv1.ResourceRule{
143+
{Verbs: []string{"*"}, APIGroups: []string{"*"}, Resources: []string{"*"}},
144+
}, authenticatedBaseRules...)},
145+
{
146+
name: "service account with cluster",
147+
user: "system:serviceaccount:default:default", groups: []string{"system:kcp:admin"}, extra: map[string][]string{serviceaccount.ClusterNameKey: {ws.Spec.Cluster}},
148+
wantRules: append([]authorizationv1.ResourceRule{
149+
{Verbs: []string{"*"}, APIGroups: []string{"*"}, Resources: []string{"*"}},
150+
}, authenticatedBaseRules...)},
151+
{
152+
name: "service account with other cluster",
153+
user: "system:serviceaccount:default:default", groups: []string{"system:kcp:admin"}, extra: map[string][]string{serviceaccount.ClusterNameKey: {"root"}},
154+
wantRules: authenticatedBaseRules},
155+
{
156+
name: "service account with other cluster and warrant",
157+
user: "system:serviceaccount:default:default", groups: []string{"system:kcp:admin"}, extra: map[string][]string{
158+
serviceaccount.ClusterNameKey: {"root"},
159+
validation.WarrantExtraKey: {`{"user":"user","groups":["system:kcp:admin"]}`},
160+
},
161+
wantRules: append([]authorizationv1.ResourceRule{
162+
{Verbs: []string{"*"}, APIGroups: []string{"*"}, Resources: []string{"*"}},
163+
}, authenticatedBaseRules...)},
164+
} {
165+
t.Run(tt.name, func(t *testing.T) {
166+
ctx, cancel := context.WithCancel(context.Background())
167+
defer cancel()
168+
169+
// impersonate as user, using the cfg as the base, adding a warrant for basic workspace access
170+
impersonationConfig := rest.CopyConfig(cfg)
171+
impersonationConfig.Impersonate = rest.ImpersonationConfig{
172+
UserName: tt.user,
173+
Groups: tt.groups,
174+
Extra: tt.extra,
175+
}
176+
if impersonationConfig.Impersonate.Extra == nil {
177+
impersonationConfig.Impersonate.Extra = map[string][]string{}
178+
}
179+
impersonationConfig.Impersonate.Extra[validation.WarrantExtraKey] = append(impersonationConfig.Impersonate.Extra[validation.WarrantExtraKey],
180+
fmt.Sprintf(`{"user":"system:serviceaccount:default:default","groups":["system:authenticated"],"extra":{"authentication.kubernetes.io/cluster-name":["%s"]}}`, ws.Spec.Cluster))
181+
impersonatedClient, err := kcpkubernetesclientset.NewForConfig(impersonationConfig)
182+
require.NoError(t, err)
183+
184+
req := &authorizationv1.SelfSubjectRulesReview{
185+
Spec: authorizationv1.SelfSubjectRulesReviewSpec{
186+
Namespace: "default",
187+
},
188+
}
189+
resp, err := impersonatedClient.Cluster(wsPath).AuthorizationV1().SelfSubjectRulesReviews().Create(ctx, req, metav1.CreateOptions{})
190+
require.NoError(t, err)
191+
192+
sort.Sort(sortedResourceRules(resp.Status.ResourceRules))
193+
sort.Sort(sortedResourceRules(tt.wantRules))
194+
require.Equal(t, tt.wantRules, resp.Status.ResourceRules)
195+
})
196+
}
197+
}
198+
199+
type sortedResourceRules []authorizationv1.ResourceRule
200+
201+
func (r sortedResourceRules) Len() int { return len(r) }
202+
func (r sortedResourceRules) Swap(i, j int) { r[i], r[j] = r[j], r[i] }
203+
func (r sortedResourceRules) Less(i, j int) bool { return r[i].String() < r[j].String() }

0 commit comments

Comments
 (0)