@@ -34,6 +34,7 @@ import (
3434 kcpkubernetesclientset "github.com/kcp-dev/client-go/kubernetes"
3535 "github.com/kcp-dev/logicalcluster/v3"
3636
37+ "github.com/kcp-dev/kcp/pkg/authorization/bootstrap"
3738 tenancyv1alpha1 "github.com/kcp-dev/kcp/sdk/apis/tenancy/v1alpha1"
3839 kcpclientset "github.com/kcp-dev/kcp/sdk/client/clientset/versioned/cluster"
3940 kcptesting "github.com/kcp-dev/kcp/sdk/testing"
@@ -205,6 +206,18 @@ func TestWorkspaceOIDC(t *testing.T) {
205206 teamCPath : true ,
206207 },
207208 },
209+ {
210+ name : "impersonating system groups is not allowed and those groups will be stripped" ,
211+ username : "hacker" ,
212+ 213+ groups : []string {bootstrap .SystemKcpAdminGroup , "system:masters" },
214+ mock : mockB ,
215+ workspaceAccess : map [logicalcluster.Path ]bool {
216+ teamAPath : false ,
217+ teamBPath : false ,
218+ teamCPath : false ,
219+ },
220+ },
208221 }
209222
210223 for _ , testcase := range testcases {
@@ -306,6 +319,106 @@ func TestUserScope(t *testing.T) {
306319 require .Equal (t , user .Extra ["authentication.kcp.io/scopes" ], authenticationv1.ExtraValue {"cluster:" + teamWs .Spec .Cluster })
307320}
308321
322+ func TestForbiddenSystemAccess (t * testing.T ) {
323+ framework .Suite (t , "control-plane" )
324+
325+ ctx := context .Background ()
326+
327+ // start kcp and setup clients
328+ server := kcptesting .SharedKcpServer (t )
329+
330+ baseWsPath , _ := kcptesting .NewWorkspaceFixture (t , server , logicalcluster .NewPath ("root" ), kcptesting .WithNamePrefix ("oidc-scope" ))
331+
332+ kcpConfig := server .BaseConfig (t )
333+ kubeClusterClient , err := kcpkubernetesclientset .NewForConfig (kcpConfig )
334+ require .NoError (t , err )
335+ kcpClusterClient , err := kcpclientset .NewForConfig (kcpConfig )
336+ require .NoError (t , err )
337+
338+ mock , ca := startMockOIDC (t , server )
339+
340+ // create an evil AuthConfig that would not prefix OIDC-provided groups, theoretically allowing
341+ // users to become part of system groups.
342+ // setup a new workspace auth config that uses mockoidc's server
343+ authConfig := & tenancyv1alpha1.WorkspaceAuthenticationConfiguration {
344+ ObjectMeta : metav1.ObjectMeta {
345+ Name : "evil-oidc" ,
346+ },
347+ Spec : tenancyv1alpha1.WorkspaceAuthenticationConfigurationSpec {
348+ JWT : []tenancyv1alpha1.JWTAuthenticator {
349+ mockJWTAuthenticator (t , mock , ca , "" , "" ),
350+ },
351+ },
352+ }
353+
354+ t .Logf ("Creating WorkspaceAuthenticationConfguration %s..." , authConfig .Name )
355+ _ , err = kcpClusterClient .Cluster (baseWsPath ).TenancyV1alpha1 ().WorkspaceAuthenticationConfigurations ().Create (ctx , authConfig , metav1.CreateOptions {})
356+ require .NoError (t , err )
357+
358+ wsType := createWorkspaceType (t , ctx , kcpClusterClient , baseWsPath , authConfig .Name )
359+
360+ // create a new workspace with our new type
361+ t .Log ("Creating Workspaces..." )
362+ teamPath , _ := kcptesting .NewWorkspaceFixture (t , server , baseWsPath , kcptesting .WithName ("team-a" ), kcptesting .WithType (baseWsPath , tenancyv1alpha1 .WorkspaceTypeName (wsType )))
363+
364+ // give a dummy user access
365+ grantWorkspaceAccess (t , ctx , kubeClusterClient , teamPath , []rbacv1.Subject {{
366+ Kind : "User" ,
367+ 368+ }})
369+
370+ // wait until the authenticator is ready
371+ token := createOIDCToken (
t ,
mock ,
"dummy" ,
"[email protected] " ,
nil )
372+
373+ client , err := kcpkubernetesclientset .NewForConfig (framework .ConfigWithToken (token , kcpConfig ))
374+ require .NoError (t , err )
375+
376+ t .Log ("Waiting for authenticator to be ready..." )
377+ require .Eventually (t , func () bool {
378+ _ , err := client .Cluster (teamPath ).CoreV1 ().ConfigMaps ("default" ).List (ctx , metav1.ListOptions {})
379+
380+ return err == nil
381+ }, wait .ForeverTestTimeout , 500 * time .Millisecond )
382+
383+ // Now that we know that the authenticator is ready, run the actual tests that ensure we do NOT
384+ // gain access based on our system names / groups.
385+
386+ testcases := []struct {
387+ name string
388+ username string
389+ email string
390+ groups []string
391+ }{
392+ {
393+ name : fmt .Sprintf ("%s should not give workspace access" , bootstrap .SystemKcpAdminGroup ),
394+ username : "al" ,
395+ 396+ groups : []string {bootstrap .SystemKcpAdminGroup },
397+ },
398+ {
399+ name : "shard-admin should not be admitted" ,
400+ username : "al" ,
401+ email : "shard-admin" ,
402+ groups : nil ,
403+ },
404+ }
405+
406+ t .Log ("Testing tokens..." )
407+ for _ , testcase := range testcases {
408+ t .Run (testcase .name , func (t * testing.T ) {
409+ t .Parallel ()
410+
411+ token := createOIDCToken (t , mock , testcase .username , testcase .email , testcase .groups )
412+
413+ client , err := kcpkubernetesclientset .NewForConfig (framework .ConfigWithToken (token , kcpConfig ))
414+ require .NoError (t , err )
415+
416+ _ , err = client .Cluster (teamPath ).CoreV1 ().ConfigMaps ("default" ).List (ctx , metav1.ListOptions {})
417+ require .Error (t , err , "user should have no access" )
418+ })
419+ }
420+ }
421+
309422func createWorkspaceAuthentication (t * testing.T , ctx context.Context , client kcpclientset.ClusterInterface , workspace logicalcluster.Path , mock * mockoidc.MockOIDC , ca * crypto.CA ) string {
310423 name := fmt .Sprintf ("mockoidc-%d" , rand .Int ())
311424
@@ -316,7 +429,7 @@ func createWorkspaceAuthentication(t *testing.T, ctx context.Context, client kcp
316429 },
317430 Spec : tenancyv1alpha1.WorkspaceAuthenticationConfigurationSpec {
318431 JWT : []tenancyv1alpha1.JWTAuthenticator {
319- mockJWTAuthenticator (t , mock , ca ),
432+ mockJWTAuthenticator (t , mock , ca , "oidc:" , "oidc:" ),
320433 },
321434 },
322435 }
0 commit comments