Skip to content

Commit

Permalink
authorization: align with documentation
Browse files Browse the repository at this point in the history
  • Loading branch information
sttts authored and s-urbaniak committed Jun 15, 2022
1 parent 50434a5 commit 9d88afb
Show file tree
Hide file tree
Showing 3 changed files with 68 additions and 53 deletions.
2 changes: 1 addition & 1 deletion pkg/authorization/bootstrap/policy.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ const (
// ClusterRoleBindings return default rolebindings to the default roles
func clusterRoleBindings() []rbacv1.ClusterRoleBinding {
return []rbacv1.ClusterRoleBinding{
clusterRoleBindingCustomName(rbacv1helpers.NewClusterBinding("cluster-admin").Groups("system:kcp:clusterworkspace:admin").BindingOrDie(), "system:kcp:clusterworkspace:admin"),
clusterRoleBindingCustomName(rbacv1helpers.NewClusterBinding("cluster-admin").Groups(SystemKcpClusterWorkspaceAdminGroup).BindingOrDie(), "system:kcp:clusterworkspace:admin"),
}
}

Expand Down
40 changes: 30 additions & 10 deletions pkg/authorization/toplevel_org_authorizer.go
Original file line number Diff line number Diff line change
Expand Up @@ -75,9 +75,20 @@ func (a *topLevelOrgAccessAuthorizer) Authorize(ctx context.Context, attr author
return authorizer.DecisionNoOpinion, workspaceAccessNotPermittedReason, nil
}

// everybody authenticated has access to the root workspace
subjectClusters := map[logicalcluster.Name]bool{}
for _, sc := range attr.GetUser().GetExtra()[authserviceaccount.ClusterNameKey] {
subjectClusters[logicalcluster.New(sc)] = true
}

isAuthenticated := sets.NewString(attr.GetUser().GetGroups()...).Has("system:authenticated")
isUser := len(subjectClusters) == 0
isServiceAccount := len(subjectClusters) > 0
isServiceAccountFromRootCluster := subjectClusters[tenancyv1alpha1.RootCluster]

// Every authenticated user has access to the root workspace but not every service account.
// For root, only service accounts declared in root have access.
if cluster.Name == tenancyv1alpha1.RootCluster {
if sets.NewString(attr.GetUser().GetGroups()...).Has("system:authenticated") {
if isAuthenticated && (isUser || isServiceAccountFromRootCluster) {
return a.delegate.Authorize(ctx, attr)
}
return authorizer.DecisionNoOpinion, workspaceAccessNotPermittedReason, nil
Expand All @@ -89,26 +100,35 @@ func (a *topLevelOrgAccessAuthorizer) Authorize(ctx context.Context, attr author
return authorizer.DecisionNoOpinion, workspaceAccessNotPermittedReason, nil
}

// check the org in the root exists
// check the org workspace exists in the root workspace
if _, err := a.clusterWorkspaceLister.Get(clusters.ToClusterAwareKey(tenancyv1alpha1.RootCluster, requestTopLevelOrgName)); err != nil {
if errors.IsNotFound(err) {
return authorizer.DecisionDeny, workspaceAccessNotPermittedReason, nil
}
return authorizer.DecisionNoOpinion, workspaceAccessNotPermittedReason, err
}

if subjectCluster := attr.GetUser().GetExtra()[authserviceaccount.ClusterNameKey]; len(subjectCluster) > 0 {
// service account will automatically get access to its top-level org
subjectTopLevelOrgName, ok := topLevelOrg(logicalcluster.New(subjectCluster[0]))
if !ok || subjectTopLevelOrgName != requestTopLevelOrgName {
return authorizer.DecisionNoOpinion, workspaceAccessNotPermittedReason, nil
switch {
case isServiceAccount:
// All other service accounts will automatically get access to their top-level org
for sc := range subjectClusters {
subjectTopLevelOrg, ok := topLevelOrg(sc)
if !ok {
continue
}
if subjectTopLevelOrg == requestTopLevelOrgName {
return a.delegate.Authorize(ctx, attr)
}
}
return a.delegate.Authorize(ctx, attr)
} else {

return authorizer.DecisionNoOpinion, workspaceAccessNotPermittedReason, nil

case isUser:
var (
errList []error
reasonList []string
)

for _, verb := range []string{"access", "member"} {
workspaceAttr := authorizer.AttributesRecord{
User: attr.GetUser(),
Expand Down
79 changes: 37 additions & 42 deletions pkg/authorization/workspace_content_authorizer.go
Original file line number Diff line number Diff line change
Expand Up @@ -74,13 +74,25 @@ func (a *workspaceContentAuthorizer) Authorize(ctx context.Context, attr authori
if err != nil {
return authorizer.DecisionNoOpinion, fmt.Sprintf("%q workspace access not permitted", cluster.Name), err
}
if cluster == nil || cluster.Name.Empty() {
// empty or non-root based workspaces have no meaning in the context of authorizing workspace content.
if cluster == nil || cluster.Name.Empty() || !cluster.Name.HasPrefix(tenancyv1alpha1.RootCluster) {
return authorizer.DecisionNoOpinion, fmt.Sprintf("%q workspace access not permitted", cluster.Name), nil
}

// everybody authenticated has access to the root workspace
subjectClusters := map[logicalcluster.Name]bool{}
for _, sc := range attr.GetUser().GetExtra()[authserviceaccount.ClusterNameKey] {
subjectClusters[logicalcluster.New(sc)] = true
}

isAuthenticated := sets.NewString(attr.GetUser().GetGroups()...).Has("system:authenticated")
isUser := len(subjectClusters) == 0
isServiceAccountFromRootCluster := subjectClusters[tenancyv1alpha1.RootCluster]
isServiceAccountFromCluster := subjectClusters[cluster.Name]

// Every authenticated user has access to the root workspace but not every service account.
// For root, only service accounts declared in root have access.
if cluster.Name == tenancyv1alpha1.RootCluster {
if sets.NewString(attr.GetUser().GetGroups()...).Has("system:authenticated") {
if isAuthenticated && (isUser || isServiceAccountFromRootCluster) {
return a.delegate.Authorize(ctx, attributesWithReplacedGroups(attr, append(attr.GetUser().GetGroups(), bootstrap.SystemKcpClusterWorkspaceAccessGroup)))
}
return authorizer.DecisionNoOpinion, fmt.Sprintf("%q workspace access not permitted", cluster.Name), err
Expand All @@ -90,7 +102,6 @@ func (a *workspaceContentAuthorizer) Authorize(ctx context.Context, attr authori
if !hasParent {
return authorizer.DecisionNoOpinion, fmt.Sprintf("%q workspace access not permitted", cluster.Name), nil
}
clusterWorkspace := cluster.Name.Base()

parentWorkspaceKubeInformer := rbacwrapper.FilterInformers(parentClusterName, a.versionedInformers.Rbac().V1())
parentAuthorizer := rbac.New(
Expand All @@ -100,47 +111,25 @@ func (a *workspaceContentAuthorizer) Authorize(ctx context.Context, attr authori
&rbac.ClusterRoleBindingLister{Lister: parentWorkspaceKubeInformer.ClusterRoleBindings().Lister()},
)

extraGroups := sets.NewString()

// check the workspace even exists
// TODO: using scoping when available
if ws, err := a.clusterWorkspaceLister.Get(clusters.ToClusterAwareKey(parentClusterName, clusterWorkspace)); err != nil {
ws, err := a.clusterWorkspaceLister.Get(clusters.ToClusterAwareKey(parentClusterName, cluster.Name.Base()))
if err != nil {
if errors.IsNotFound(err) {
return authorizer.DecisionDeny, fmt.Sprintf("%q workspace access not permitted", cluster.Name), nil
return authorizer.DecisionDeny, fmt.Sprintf("%q workspace access not permitted", cluster.Name.Base()), nil
}
return authorizer.DecisionNoOpinion, "", err
} else if len(ws.Status.Initializers) > 0 {
workspaceAttr := authorizer.AttributesRecord{
User: attr.GetUser(),
Verb: attr.GetVerb(),
APIGroup: tenancyv1alpha1.SchemeGroupVersion.Group,
APIVersion: tenancyv1alpha1.SchemeGroupVersion.Version,
Resource: "clusterworkspaces",
Subresource: "initialize",
Name: clusterWorkspace,
ResourceRequest: true,
}

dec, reason, err := parentAuthorizer.Authorize(ctx, workspaceAttr)
if err != nil {
return dec, reason, err
}
if dec != authorizer.DecisionAllow {
return dec, fmt.Sprintf("%q workspace access not permitted", cluster.Name), nil
}
}

extraGroups := []string{}
if subjectCluster := attr.GetUser().GetExtra()[authserviceaccount.ClusterNameKey]; len(subjectCluster) > 0 {
// a subject from a workspace, like a ServiceAccount, is automatically authenticated
// against that workspace.
// On the other hand, referencing that in the parent cluster for further permissions
// is not possible. Hence, we skip the authorization steps for the verb below.
for _, sc := range subjectCluster {
if logicalcluster.New(sc) == cluster.Name {
extraGroups = append(extraGroups, bootstrap.SystemKcpClusterWorkspaceAccessGroup)
break
}
}
} else {
switch {
case isServiceAccountFromCluster:
// A service account declared in the requested workspace is authorized inside that workspace.
// Referencing such a service account in the parent workspace is not possible,
// hence authorization against "admin" or "access" verbs in the parent is not possible either.
extraGroups.Insert(bootstrap.SystemKcpClusterWorkspaceAccessGroup)

case isUser:
verbToGroupMembership := map[string][]string{
"admin": {bootstrap.SystemKcpClusterWorkspaceAccessGroup, bootstrap.SystemKcpClusterWorkspaceAdminGroup},
"access": {bootstrap.SystemKcpClusterWorkspaceAccessGroup},
Expand All @@ -158,7 +147,7 @@ func (a *workspaceContentAuthorizer) Authorize(ctx context.Context, attr authori
APIVersion: tenancyv1alpha1.SchemeGroupVersion.Version,
Resource: "clusterworkspaces",
Subresource: "content",
Name: clusterWorkspace,
Name: cluster.Name.Base(),
ResourceRequest: true,
}

Expand All @@ -169,18 +158,24 @@ func (a *workspaceContentAuthorizer) Authorize(ctx context.Context, attr authori
continue
}
if dec == authorizer.DecisionAllow {
extraGroups = append(extraGroups, groups...)
extraGroups.Insert(groups...)
}
}
if len(errList) > 0 {
return authorizer.DecisionNoOpinion, strings.Join(reasonList, "\n"), utilerrors.NewAggregate(errList)
}
}

// non-admin subjects don't have access to initializing workspaces.
if ws.Status.Phase == tenancyv1alpha1.ClusterWorkspacePhaseInitializing && !extraGroups.Has(bootstrap.SystemKcpClusterWorkspaceAdminGroup) {
return authorizer.DecisionNoOpinion, fmt.Sprintf("%q workspace access not permitted", cluster.Name), nil
}

if len(extraGroups) == 0 {
return authorizer.DecisionNoOpinion, fmt.Sprintf("%q workspace access not permitted", cluster.Name), nil
}

return a.delegate.Authorize(ctx, attributesWithReplacedGroups(attr, append(attr.GetUser().GetGroups(), extraGroups...)))
return a.delegate.Authorize(ctx, attributesWithReplacedGroups(attr, append(attr.GetUser().GetGroups(), extraGroups.List()...)))
}

func attributesWithReplacedGroups(attr authorizer.Attributes, groups []string) authorizer.Attributes {
Expand Down

0 comments on commit 9d88afb

Please sign in to comment.