Skip to content

Commit

Permalink
authorization: wire global informers into requiredgroups and workspac…
Browse files Browse the repository at this point in the history
…e content
  • Loading branch information
sttts committed Jan 27, 2023
1 parent cbea43c commit 20fa070
Show file tree
Hide file tree
Showing 5 changed files with 71 additions and 53 deletions.
5 changes: 0 additions & 5 deletions pkg/authorization/global_authorizer.go
Original file line number Diff line number Diff line change
Expand Up @@ -63,11 +63,6 @@ func (a *GlobalAuthorizer) RulesFor(ctx context.Context, user user.Info, namespa
}

func (a *GlobalAuthorizer) Authorize(ctx context.Context, attr authorizer.Attributes) (authorized authorizer.Decision, reason string, err error) {
if !IsDeepSubjectAccessReviewFrom(ctx, attr) {
// requests not being a deep SAR request are not supposed to be cached.
return authorizer.DecisionNoOpinion, "not a deep SAR request", nil
}

cluster := genericapirequest.ClusterFrom(ctx)
if cluster == nil || cluster.Name.Empty() {
return authorizer.DecisionNoOpinion, "empty cluster name", nil
Expand Down
16 changes: 11 additions & 5 deletions pkg/authorization/requiredgroups_authorizer.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ import (
"k8s.io/apiserver/pkg/authorization/authorizer"
genericapirequest "k8s.io/apiserver/pkg/endpoints/request"

"github.com/kcp-dev/kcp/pkg/apis/core/v1alpha1"
corev1alpha1 "github.com/kcp-dev/kcp/pkg/apis/core/v1alpha1"
"github.com/kcp-dev/kcp/pkg/authorization/bootstrap"
corev1alpha1listers "github.com/kcp-dev/kcp/pkg/client/listers/core/v1alpha1"
)
Expand All @@ -42,17 +42,23 @@ const (

// NewRequiredGroupsAuthorizer returns an authorizer that a set of groups stored
// on the LogicalCluster object. Service account by-pass this.
func NewRequiredGroupsAuthorizer(logicalClusterLister corev1alpha1listers.LogicalClusterClusterLister, delegate authorizer.Authorizer) authorizer.Authorizer {
func NewRequiredGroupsAuthorizer(local, global corev1alpha1listers.LogicalClusterClusterLister, delegate authorizer.Authorizer) authorizer.Authorizer {
return &requiredGroupsAuthorizer{
getLogicalCluster: func(logicalCluster logicalcluster.Name) (*v1alpha1.LogicalCluster, error) {
return logicalClusterLister.Cluster(logicalCluster).Get(v1alpha1.LogicalClusterName)
getLogicalCluster: func(logicalCluster logicalcluster.Name) (*corev1alpha1.LogicalCluster, error) {
obj, err := local.Cluster(logicalCluster).Get(corev1alpha1.LogicalClusterName)
if err != nil && !errors.IsNotFound(err) {
return nil, err
} else if errors.IsNotFound(err) {
return global.Cluster(logicalCluster).Get(corev1alpha1.LogicalClusterName)
}
return obj, nil
},
delegate: delegate,
}
}

type requiredGroupsAuthorizer struct {
getLogicalCluster func(logicalCluster logicalcluster.Name) (*v1alpha1.LogicalCluster, error)
getLogicalCluster func(logicalCluster logicalcluster.Name) (*corev1alpha1.LogicalCluster, error)
delegate authorizer.Authorizer
}

Expand Down
53 changes: 32 additions & 21 deletions pkg/authorization/workspace_content_authorizer.go
Original file line number Diff line number Diff line change
Expand Up @@ -44,24 +44,36 @@ const (
WorkspaceAccessNotPermittedReason = "workspace access not permitted"
)

func NewWorkspaceContentAuthorizer(versionedInformers kcpkubernetesinformers.SharedInformerFactory, logicalClusterLister corev1alpha1listers.LogicalClusterClusterLister, delegate authorizer.Authorizer) authorizer.Authorizer {
func NewWorkspaceContentAuthorizer(localInformers, globalInformers kcpkubernetesinformers.SharedInformerFactory, localLogicalClusterLister, globalLogicalClusterLister corev1alpha1listers.LogicalClusterClusterLister, delegate authorizer.Authorizer) authorizer.Authorizer {
return &workspaceContentAuthorizer{
roleLister: versionedInformers.Rbac().V1().Roles().Lister(),
roleBindingLister: versionedInformers.Rbac().V1().RoleBindings().Lister(),
clusterRoleLister: versionedInformers.Rbac().V1().ClusterRoles().Lister(),
clusterRoleBindingLister: versionedInformers.Rbac().V1().ClusterRoleBindings().Lister(),
logicalClusterLister: logicalClusterLister,
localClusterRoleLister: localInformers.Rbac().V1().ClusterRoles().Lister(),
localClusterRoleBindingLister: localInformers.Rbac().V1().ClusterRoleBindings().Lister(),

globalClusterRoleLister: globalInformers.Rbac().V1().ClusterRoles().Lister(),
globalClusterRoleBindingLister: globalInformers.Rbac().V1().ClusterRoleBindings().Lister(),

getLogicalCluster: func(logicalCluster logicalcluster.Name) (*corev1alpha1.LogicalCluster, error) {
obj, err := localLogicalClusterLister.Cluster(logicalCluster).Get(corev1alpha1.LogicalClusterName)
if err != nil && !errors.IsNotFound(err) {
return nil, err
} else if errors.IsNotFound(err) {
return globalLogicalClusterLister.Cluster(logicalCluster).Get(corev1alpha1.LogicalClusterName)
}
return obj, nil
},

delegate: delegate,
}
}

type workspaceContentAuthorizer struct {
roleLister rbacv1listers.RoleClusterLister
roleBindingLister rbacv1listers.RoleBindingClusterLister
clusterRoleBindingLister rbacv1listers.ClusterRoleBindingClusterLister
clusterRoleLister rbacv1listers.ClusterRoleClusterLister
logicalClusterLister corev1alpha1listers.LogicalClusterClusterLister
localClusterRoleBindingLister rbacv1listers.ClusterRoleBindingClusterLister
localClusterRoleLister rbacv1listers.ClusterRoleClusterLister

globalClusterRoleBindingLister rbacv1listers.ClusterRoleBindingClusterLister
globalClusterRoleLister rbacv1listers.ClusterRoleClusterLister

getLogicalCluster func(logicalCluster logicalcluster.Name) (*corev1alpha1.LogicalCluster, error)

delegate authorizer.Authorizer
}
Expand Down Expand Up @@ -105,7 +117,7 @@ func (a *workspaceContentAuthorizer) Authorize(ctx context.Context, attr authori
}

// check the workspace even exists
logicalCluster, err := a.logicalClusterLister.Cluster(cluster.Name).Get(corev1alpha1.LogicalClusterName)
logicalCluster, err := a.getLogicalCluster(cluster.Name)
if err != nil {
if errors.IsNotFound(err) {
return authorizer.DecisionDeny, "LogicalCluster not found", nil
Expand All @@ -128,18 +140,17 @@ func (a *workspaceContentAuthorizer) Authorize(ctx context.Context, attr authori

case isUser:
authz := rbac.New(
&rbac.RoleGetter{Lister: rbacwrapper.NewMergedRoleLister(
a.roleLister.Cluster(cluster.Name),
a.roleLister.Cluster(genericcontrolplane.LocalAdminCluster),
)},
&rbac.RoleBindingLister{Lister: a.roleBindingLister.Cluster(cluster.Name)},
&rbac.RoleGetter{Lister: rbacwrapper.NewMergedRoleLister()},
&rbac.RoleBindingLister{Lister: rbacwrapper.NewMergedRoleBindingLister()},
&rbac.ClusterRoleGetter{Lister: rbacwrapper.NewMergedClusterRoleLister(
a.clusterRoleLister.Cluster(cluster.Name),
a.clusterRoleLister.Cluster(genericcontrolplane.LocalAdminCluster),
a.localClusterRoleLister.Cluster(cluster.Name),
a.globalClusterRoleLister.Cluster(cluster.Name),
a.localClusterRoleLister.Cluster(genericcontrolplane.LocalAdminCluster),
)},
&rbac.ClusterRoleBindingLister{Lister: rbacwrapper.NewMergedClusterRoleBindingLister(
a.clusterRoleBindingLister.Cluster(cluster.Name),
a.clusterRoleBindingLister.Cluster(genericcontrolplane.LocalAdminCluster),
a.localClusterRoleBindingLister.Cluster(cluster.Name),
a.globalClusterRoleBindingLister.Cluster(cluster.Name),
a.localClusterRoleBindingLister.Cluster(genericcontrolplane.LocalAdminCluster),
)},
)

Expand Down
43 changes: 24 additions & 19 deletions pkg/authorization/workspace_content_authorizer_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -207,7 +207,7 @@ func TestWorkspaceContentAuthorizer(t *testing.T) {
t.Run(tt.testName, func(t *testing.T) {
ctx := context.Background()

kubeClient := kcpfakeclient.NewSimpleClientset(
localKubeClient := kcpfakeclient.NewSimpleClientset(
&v1.ClusterRole{
ObjectMeta: metav1.ObjectMeta{
Annotations: map[string]string{
Expand Down Expand Up @@ -303,45 +303,50 @@ func TestWorkspaceContentAuthorizer(t *testing.T) {
},
},
)
kubeShareInformerFactory := kcpkubernetesinformers.NewSharedInformerFactory(kubeClient, controller.NoResyncPeriodFunc())
informers := []cache.SharedIndexInformer{
kubeShareInformerFactory.Rbac().V1().Roles().Informer(),
kubeShareInformerFactory.Rbac().V1().RoleBindings().Informer(),
kubeShareInformerFactory.Rbac().V1().ClusterRoles().Informer(),
kubeShareInformerFactory.Rbac().V1().ClusterRoleBindings().Informer(),
}
globalKubeClient := kcpfakeclient.NewSimpleClientset() // TODO(sttts): add some global fixtures
local := kcpkubernetesinformers.NewSharedInformerFactory(localKubeClient, controller.NoResyncPeriodFunc())
global := kcpkubernetesinformers.NewSharedInformerFactory(globalKubeClient, controller.NoResyncPeriodFunc())
var syncs []cache.InformerSynced
for i := range informers {
go informers[i].Run(ctx.Done())
syncs = append(syncs, informers[i].HasSynced)
for _, inf := range []cache.SharedIndexInformer{
local.Rbac().V1().ClusterRoles().Informer(),
local.Rbac().V1().ClusterRoleBindings().Informer(),
global.Rbac().V1().ClusterRoles().Informer(),
global.Rbac().V1().ClusterRoleBindings().Informer(),
} {
go inf.Run(ctx.Done())
syncs = append(syncs, inf.HasSynced)
}
cache.WaitForCacheSync(ctx.Done(), syncs...)

indexer := cache.NewIndexer(kcpcache.MetaClusterNamespaceKeyFunc, cache.Indexers{})
require.NoError(t, indexer.Add(&corev1alpha1.LogicalCluster{
localIndexer := cache.NewIndexer(kcpcache.MetaClusterNamespaceKeyFunc, cache.Indexers{})
require.NoError(t, localIndexer.Add(&corev1alpha1.LogicalCluster{
ObjectMeta: metav1.ObjectMeta{Name: corev1alpha1.LogicalClusterName, Annotations: map[string]string{logicalcluster.AnnotationKey: "root"}},
Status: corev1alpha1.LogicalClusterStatus{Phase: corev1alpha1.LogicalClusterPhaseReady},
}))
require.NoError(t, indexer.Add(&corev1alpha1.LogicalCluster{
require.NoError(t, localIndexer.Add(&corev1alpha1.LogicalCluster{
ObjectMeta: metav1.ObjectMeta{Name: corev1alpha1.LogicalClusterName, Annotations: map[string]string{logicalcluster.AnnotationKey: "root:ready"}},
Status: corev1alpha1.LogicalClusterStatus{Phase: corev1alpha1.LogicalClusterPhaseReady},
}))
require.NoError(t, indexer.Add(&corev1alpha1.LogicalCluster{
require.NoError(t, localIndexer.Add(&corev1alpha1.LogicalCluster{
ObjectMeta: metav1.ObjectMeta{Name: corev1alpha1.LogicalClusterName, Annotations: map[string]string{logicalcluster.AnnotationKey: "root:scheduling"}},
Status: corev1alpha1.LogicalClusterStatus{Phase: corev1alpha1.LogicalClusterPhaseScheduling},
}))
require.NoError(t, indexer.Add(&corev1alpha1.LogicalCluster{
require.NoError(t, localIndexer.Add(&corev1alpha1.LogicalCluster{
ObjectMeta: metav1.ObjectMeta{Name: corev1alpha1.LogicalClusterName, Annotations: map[string]string{logicalcluster.AnnotationKey: "root:initializing"}},
Status: corev1alpha1.LogicalClusterStatus{Phase: corev1alpha1.LogicalClusterPhaseInitializing},
}))
require.NoError(t, indexer.Add(&corev1alpha1.LogicalCluster{
require.NoError(t, localIndexer.Add(&corev1alpha1.LogicalCluster{
ObjectMeta: metav1.ObjectMeta{Name: corev1alpha1.LogicalClusterName, Annotations: map[string]string{logicalcluster.AnnotationKey: "rootwithoutparent"}},
Status: corev1alpha1.LogicalClusterStatus{Phase: corev1alpha1.LogicalClusterPhaseReady},
}))
lister := corev1alpha1listers.NewLogicalClusterClusterLister(indexer)
localLogicalClusters := corev1alpha1listers.NewLogicalClusterClusterLister(localIndexer)

globalIndexer := cache.NewIndexer(kcpcache.MetaClusterNamespaceKeyFunc, cache.Indexers{})
// TODO(sttts): add global fixtures
globalLogicalClusters := corev1alpha1listers.NewLogicalClusterClusterLister(globalIndexer)

recordingAuthorizer := &recordingAuthorizer{decision: authorizer.DecisionAllow, reason: "allowed"}
w := NewWorkspaceContentAuthorizer(kubeShareInformerFactory, lister, recordingAuthorizer)
w := NewWorkspaceContentAuthorizer(local, global, localLogicalClusters, globalLogicalClusters, recordingAuthorizer)

requestedCluster := request.Cluster{
Name: logicalcluster.Name(tt.requestedWorkspace),
Expand Down
7 changes: 4 additions & 3 deletions pkg/server/options/authorization.go
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,8 @@ func (s *Authorization) AddFlags(fs *pflag.FlagSet) {
func (s *Authorization) ApplyTo(config *genericapiserver.Config, kubeInformers, globalKubeInformers kcpkubernetesinformers.SharedInformerFactory, kcpInformers, globalKcpInformers kcpinformers.SharedInformerFactory) error {
var authorizers []authorizer.Authorizer

workspaceLister := kcpInformers.Core().V1alpha1().LogicalClusters().Lister()
localLogicalClusterLister := kcpInformers.Core().V1alpha1().LogicalClusters().Lister()
globalLogicalClusterLister := globalKcpInformers.Core().V1alpha1().LogicalClusters().Lister()

// group authorizer
if len(s.AlwaysAllowGroups) > 0 {
Expand Down Expand Up @@ -128,14 +129,14 @@ func (s *Authorization) ApplyTo(config *genericapiserver.Config, kubeInformers,
// of default permissions given even to system:authenticated (like access to discovery) - this authorizer allows
// kcp to make workspaces entirely invisible to users that have not been given access, by making system:authenticated
// mean nothing unless they also have `verb=access` on `/`
contentAuth := authz.NewWorkspaceContentAuthorizer(kubeInformers, workspaceLister, systemCRDAuth)
contentAuth := authz.NewWorkspaceContentAuthorizer(kubeInformers, globalKubeInformers, localLogicalClusterLister, globalLogicalClusterLister, systemCRDAuth)
contentAuth = authz.NewDecorator("02-content", contentAuth).AddAuditLogging().AddAnonymization().AddReasonAnnotation()

// workspaces are annotated to list the groups required on users wishing to access the workspace -
// this is mostly useful when adding a core set of groups to an org workspace and having them inherited
// by child workspaces; this gives administrators of an org control over which users can be given access
// to content in sub-workspaces
requiredGroupsAuth := authz.NewRequiredGroupsAuthorizer(workspaceLister, contentAuth)
requiredGroupsAuth := authz.NewRequiredGroupsAuthorizer(localLogicalClusterLister, globalLogicalClusterLister, contentAuth)
requiredGroupsAuth = authz.NewDecorator("01-requiredgroups", requiredGroupsAuth).AddAuditLogging().AddAnonymization()

authorizers = append(authorizers, requiredGroupsAuth)
Expand Down

0 comments on commit 20fa070

Please sign in to comment.