@@ -26,6 +26,7 @@ import (
26
26
27
27
"github.com/stretchr/testify/require"
28
28
29
+ corev1 "k8s.io/api/core/v1"
29
30
rbacv1 "k8s.io/api/rbac/v1"
30
31
"k8s.io/apiextensions-apiserver/pkg/apihelpers"
31
32
apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
@@ -472,13 +473,274 @@ metadata:
472
473
}, wait .ForeverTestTimeout , 100 * time .Millisecond , "expected service-provider-2-admin to get a forbidden for shadowed cowboy resources" )
473
474
}
474
475
476
+ func TestAPIExportBindingAuthorizer (t * testing.T ) {
477
+ t .Parallel ()
478
+ framework .Suite (t , "control-plane" )
479
+
480
+ server := kcptesting .SharedKcpServer (t )
481
+
482
+ ctx , cancel := context .WithCancel (context .Background ())
483
+ t .Cleanup (cancel )
484
+
485
+ orgPath , _ := framework .NewOrganizationFixture (t , server ) //nolint:staticcheck
486
+
487
+ serviceProviderPath , _ := kcptesting .NewWorkspaceFixture (t , server , orgPath , kcptesting .WithName ("service-provider" ))
488
+ tenantPath , tenantWorkspace := kcptesting .NewWorkspaceFixture (t , server , orgPath , kcptesting .WithName ("tenant" ))
489
+
490
+ cfg := server .BaseConfig (t )
491
+
492
+ serviceProviderAdmin := server .ClientCAUserConfig (t , rest .CopyConfig (cfg ), "service-provider-admin" )
493
+ tenantUser := server .ClientCAUserConfig (t , rest .CopyConfig (cfg ), "tenant-user" )
494
+
495
+ kubeClient , err := kcpkubernetesclientset .NewForConfig (rest .CopyConfig (cfg ))
496
+ require .NoError (t , err )
497
+ kcpClient , err := kcpclientset .NewForConfig (rest .CopyConfig (cfg ))
498
+ require .NoError (t , err )
499
+
500
+ framework .AdmitWorkspaceAccess (ctx , t , kubeClient , orgPath , []string {"service-provider-admin" , "tenant-user" }, nil , false )
501
+ framework .AdmitWorkspaceAccess (ctx , t , kubeClient , serviceProviderPath , []string {"service-provider-admin" }, nil , true )
502
+ framework .AdmitWorkspaceAccess (ctx , t , kubeClient , tenantPath , []string {"tenant-user" }, nil , true )
503
+
504
+ t .Logf ("install sherriffs API resource schema, API export, permissions for tenant-user to be able to bind to the export in service provider workspace %q" , serviceProviderPath )
505
+ require .NoError (t , apply (t , ctx , serviceProviderPath , serviceProviderAdmin ,
506
+ & apisv1alpha1.APIResourceSchema {
507
+ ObjectMeta : metav1.ObjectMeta {Name : "today.sheriffs.wild.wild.west" },
508
+ Spec : apisv1alpha1.APIResourceSchemaSpec {
509
+ Group : "wild.wild.west" ,
510
+ Names : apiextensionsv1.CustomResourceDefinitionNames {Plural : "sheriffs" , Singular : "sheriff" , Kind : "Sheriff" , ListKind : "SheriffList" },
511
+ Scope : "Namespaced" ,
512
+ Versions : []apisv1alpha1.APIResourceVersion {
513
+ {Name : "v1alpha1" , Served : true , Storage : true , Schema : runtime.RawExtension {Raw : []byte (`{"type":"object"}` )}},
514
+ },
515
+ },
516
+ },
517
+ & apisv1alpha1.APIExport {
518
+ ObjectMeta : metav1.ObjectMeta {Name : "wild.wild.west" },
519
+ Spec : apisv1alpha1.APIExportSpec {
520
+ LatestResourceSchemas : []string {"today.sheriffs.wild.wild.west" },
521
+ PermissionClaims : []apisv1alpha1.PermissionClaim {
522
+ {
523
+ GroupResource : apisv1alpha1.GroupResource {
524
+ Group : "" ,
525
+ Resource : "configmaps" ,
526
+ },
527
+ All : true ,
528
+ },
529
+ },
530
+ },
531
+ },
532
+
533
+ & rbacv1.ClusterRole {
534
+ ObjectMeta : metav1.ObjectMeta {Name : "tenant-user-bind-apiexport" },
535
+ Rules : []rbacv1.PolicyRule {
536
+ {APIGroups : []string {"apis.kcp.io" }, ResourceNames : []string {"wild.wild.west" }, Resources : []string {"apiexports" }, Verbs : []string {"bind" }},
537
+ },
538
+ },
539
+ & rbacv1.ClusterRoleBinding {
540
+ ObjectMeta : metav1.ObjectMeta {Name : "tenant-user-bind-apiexport" },
541
+ Subjects : []rbacv1.Subject {{Kind : "User" , Name : "tenant-user" }},
542
+ RoleRef : rbacv1.RoleRef {APIGroup : rbacv1 .SchemeGroupVersion .Group , Kind : "ClusterRole" , Name : "tenant-user-bind-apiexport" },
543
+ },
544
+ ))
545
+
546
+ t .Logf ("Create virtual workspace client for \" today-sherriffs\" APIExport in workspace %q" , serviceProviderPath )
547
+ serviceProviderAdminApiExportVWCfg := rest .CopyConfig (serviceProviderAdmin )
548
+ serviceProviderAdminClient , err := kcpclientset .NewForConfig (serviceProviderAdmin )
549
+ require .NoError (t , err )
550
+ kcptestinghelpers .Eventually (t , func () (bool , string ) {
551
+ apiExport , err := serviceProviderAdminClient .Cluster (serviceProviderPath ).ApisV1alpha1 ().APIExports ().Get (ctx , "wild.wild.west" , metav1.GetOptions {})
552
+ require .NoError (t , err )
553
+ var found bool
554
+ serviceProviderAdminApiExportVWCfg .Host , found , err = framework .VirtualWorkspaceURL (ctx , kcpClient , tenantWorkspace , framework .ExportVirtualWorkspaceURLs (apiExport ))
555
+ require .NoError (t , err )
556
+ //nolint:staticcheck // SA1019 VirtualWorkspaces is deprecated but not removed yet
557
+ return found , fmt .Sprintf ("waiting for virtual workspace URLs to be available: %v" , apiExport .Status .VirtualWorkspaces )
558
+ }, wait .ForeverTestTimeout , time .Millisecond * 100 )
559
+
560
+ serviceProviderDynamicVWClientForTenantWorkspace , err := kcpdynamic .NewForConfig (serviceProviderAdminApiExportVWCfg )
561
+ require .NoError (t , err )
562
+
563
+ configMap := & corev1.ConfigMap {
564
+ TypeMeta : metav1.TypeMeta {
565
+ Kind : "ConfigMap" ,
566
+ APIVersion : "v1" ,
567
+ },
568
+ ObjectMeta : metav1.ObjectMeta {
569
+ Name : "default" ,
570
+ },
571
+ Data : map [string ]string {
572
+ "a" : "b" ,
573
+ },
574
+ }
575
+ u , err := runtime .DefaultUnstructuredConverter .ToUnstructured (configMap )
576
+ require .NoError (t , err )
577
+ cm := & unstructured.Unstructured {Object : u }
578
+
579
+ t .Logf ("trying to create a ConfigMap in the tenant workspace before creating the APIBinding" )
580
+ _ , err = serviceProviderDynamicVWClientForTenantWorkspace .Cluster (logicalcluster .NewPath (tenantWorkspace .Spec .Cluster )).
581
+ Resource (schema.GroupVersionResource {Version : "v1" , Resource : "configmaps" , Group : "" }).Namespace ("default" ).Create (ctx , cm , metav1.CreateOptions {})
582
+ require .True (t , apierrors .IsForbidden (err ), "expected to be forbidden from creating ConfigMap" )
583
+
584
+ t .Logf ("trying to create a Sheriff in the tenant workspace before creating the APIBinding" )
585
+ _ , err = serviceProviderDynamicVWClientForTenantWorkspace .Cluster (logicalcluster .NewPath (tenantWorkspace .Spec .Cluster )).
586
+ Resource (schema.GroupVersionResource {Version : "v1" , Resource : "sheriffs" , Group : "" }).Namespace ("default" ).Create (ctx , & unstructured.Unstructured {
587
+ Object : map [string ]any {
588
+ "apiVersion" : "wild.wild.west/v1alpha1" ,
589
+ "kind" : "Sheriff" ,
590
+ "metadata" : map [string ]any {
591
+ "name" : "default" ,
592
+ },
593
+ },
594
+ }, metav1.CreateOptions {})
595
+ require .True (t , apierrors .IsForbidden (err ), "expected to be forbidden from creating Sheriff" )
596
+
597
+ t .Logf ("bind sherriffs with ConfigMaps PermissionClaim rejected in the tenant workspace %q" , tenantPath )
598
+ kcptestinghelpers .Eventually (t , func () (success bool , reason string ) {
599
+ err := apply (t , ctx , tenantPath , tenantUser ,
600
+ & apisv1alpha1.APIBinding {
601
+ ObjectMeta : metav1.ObjectMeta {
602
+ Name : "wild.wild.west" ,
603
+ },
604
+ Spec : apisv1alpha1.APIBindingSpec {
605
+ PermissionClaims : []apisv1alpha1.AcceptablePermissionClaim {
606
+ {
607
+ PermissionClaim : apisv1alpha1.PermissionClaim {
608
+ GroupResource : apisv1alpha1.GroupResource {Resource : "configmaps" },
609
+ All : true ,
610
+ },
611
+ State : apisv1alpha1 .ClaimRejected ,
612
+ },
613
+ },
614
+ Reference : apisv1alpha1.BindingReference {
615
+ Export : & apisv1alpha1.ExportBindingReference {
616
+ Path : serviceProviderPath .String (),
617
+ Name : "wild.wild.west" ,
618
+ },
619
+ },
620
+ },
621
+ },
622
+ )
623
+ if err != nil {
624
+ return false , err .Error ()
625
+ }
626
+ return true , ""
627
+ }, wait .ForeverTestTimeout , time .Millisecond * 100 )
628
+
629
+ t .Logf ("trying to create a ConfigMap in the tenant workspace after creating the APIBinding with rejected PermissionClaim" )
630
+ _ , err = serviceProviderDynamicVWClientForTenantWorkspace .Cluster (logicalcluster .NewPath (tenantWorkspace .Spec .Cluster )).
631
+ Resource (schema.GroupVersionResource {Version : "v1" , Resource : "configmaps" , Group : "" }).Namespace ("default" ).Create (ctx , cm , metav1.CreateOptions {})
632
+ require .True (t , apierrors .IsForbidden (err ), "expected to be forbidden from creating ConfigMap" )
633
+
634
+ t .Logf ("update sherriffs APIBinding to accept the ConfigMaps PermissionClaim" )
635
+ kcptestinghelpers .Eventually (t , func () (success bool , reason string ) {
636
+ err := apply (t , ctx , tenantPath , tenantUser ,
637
+ & apisv1alpha1.APIBinding {
638
+ ObjectMeta : metav1.ObjectMeta {
639
+ Name : "wild.wild.west" ,
640
+ },
641
+ Spec : apisv1alpha1.APIBindingSpec {
642
+ PermissionClaims : []apisv1alpha1.AcceptablePermissionClaim {
643
+ {
644
+ PermissionClaim : apisv1alpha1.PermissionClaim {
645
+ GroupResource : apisv1alpha1.GroupResource {Resource : "configmaps" },
646
+ All : true ,
647
+ },
648
+ State : apisv1alpha1 .ClaimAccepted ,
649
+ },
650
+ },
651
+ Reference : apisv1alpha1.BindingReference {
652
+ Export : & apisv1alpha1.ExportBindingReference {
653
+ Path : serviceProviderPath .String (),
654
+ Name : "wild.wild.west" ,
655
+ },
656
+ },
657
+ },
658
+ },
659
+ )
660
+ if err != nil {
661
+ return false , err .Error ()
662
+ }
663
+ return true , ""
664
+ }, wait .ForeverTestTimeout , time .Millisecond * 100 )
665
+
666
+ t .Logf ("trying to create a ConfigMap in the tenant workspace after updating the APIBinding with accepted PermissionClaim" )
667
+ kcptestinghelpers .Eventually (t , func () (bool , string ) {
668
+ _ , err = serviceProviderDynamicVWClientForTenantWorkspace .Cluster (logicalcluster .NewPath (tenantWorkspace .Spec .Cluster )).
669
+ Resource (schema.GroupVersionResource {Version : "v1" , Resource : "configmaps" , Group : "" }).Namespace ("default" ).Create (ctx , cm , metav1.CreateOptions {})
670
+ if err != nil {
671
+ return false , err .Error ()
672
+ }
673
+
674
+ return true , ""
675
+ }, wait .ForeverTestTimeout , 100 * time .Millisecond , "error creating ConfigMap" )
676
+
677
+ t .Logf ("trying to create a Sheriff in the tenant workspace" )
678
+ kcptestinghelpers .Eventually (t , func () (bool , string ) {
679
+ _ , err = serviceProviderDynamicVWClientForTenantWorkspace .Cluster (logicalcluster .NewPath (tenantWorkspace .Spec .Cluster )).
680
+ Resource (schema.GroupVersionResource {Version : "v1alpha1" , Resource : "sheriffs" , Group : "wild.wild.west" }).Namespace ("default" ).Create (ctx , & unstructured.Unstructured {
681
+ Object : map [string ]any {
682
+ "apiVersion" : "wild.wild.west/v1alpha1" ,
683
+ "kind" : "Sheriff" ,
684
+ "metadata" : map [string ]any {
685
+ "name" : "default" ,
686
+ },
687
+ },
688
+ }, metav1.CreateOptions {})
689
+ if err != nil {
690
+ return false , err .Error ()
691
+ }
692
+ return true , ""
693
+ }, wait .ForeverTestTimeout , 100 * time .Millisecond , "error creating Sheriff" )
694
+
695
+ t .Logf ("update sherriffs APIBinding to reject the ConfigMaps PermissionClaim again" )
696
+ kcptestinghelpers .Eventually (t , func () (success bool , reason string ) {
697
+ err := apply (t , ctx , tenantPath , tenantUser ,
698
+ & apisv1alpha1.APIBinding {
699
+ ObjectMeta : metav1.ObjectMeta {
700
+ Name : "wild.wild.west" ,
701
+ },
702
+ Spec : apisv1alpha1.APIBindingSpec {
703
+ PermissionClaims : []apisv1alpha1.AcceptablePermissionClaim {
704
+ {
705
+ PermissionClaim : apisv1alpha1.PermissionClaim {
706
+ GroupResource : apisv1alpha1.GroupResource {Resource : "configmaps" },
707
+ All : true ,
708
+ },
709
+ State : apisv1alpha1 .ClaimRejected ,
710
+ },
711
+ },
712
+ Reference : apisv1alpha1.BindingReference {
713
+ Export : & apisv1alpha1.ExportBindingReference {
714
+ Path : serviceProviderPath .String (),
715
+ Name : "wild.wild.west" ,
716
+ },
717
+ },
718
+ },
719
+ },
720
+ )
721
+ if err != nil {
722
+ return false , err .Error ()
723
+ }
724
+ return true , ""
725
+ }, wait .ForeverTestTimeout , time .Millisecond * 100 )
726
+
727
+ // I know, I know, but we need to wait for the APIBinding update to reach the informer in the authorizer ...
728
+ time .Sleep (5 * time .Second )
729
+
730
+ t .Logf ("trying to delete the created ConfigMap in the tenant workspace after updating the APIBinding with rejected PermissionClaim" )
731
+ err = serviceProviderDynamicVWClientForTenantWorkspace .Cluster (logicalcluster .NewPath (tenantWorkspace .Spec .Cluster )).
732
+ Resource (schema.GroupVersionResource {Version : "v1" , Resource : "configmaps" , Group : "" }).Namespace ("default" ).Delete (ctx , "default" , metav1.DeleteOptions {})
733
+ require .True (t , apierrors .IsForbidden (err ), "expected to be forbidden from deleting ConfigMap" )
734
+ }
735
+
475
736
var scheme * runtime.Scheme
476
737
477
738
func init () {
478
739
scheme = runtime .NewScheme ()
479
740
_ = apisv1alpha1 .AddToScheme (scheme )
480
741
_ = rbacv1 .AddToScheme (scheme )
481
742
_ = apiextensionsv1 .AddToScheme (scheme )
743
+ _ = corev1 .AddToScheme (scheme )
482
744
}
483
745
484
746
func apply (t * testing.T , ctx context.Context , workspace logicalcluster.Path , cfg * rest.Config , manifests ... any ) error {
0 commit comments