103 lines
2.9 KiB
Go
103 lines
2.9 KiB
Go
package utils
|
|
|
|
import (
|
|
"context"
|
|
"slices"
|
|
|
|
"k8s.io/apiserver/pkg/authorization/authorizer"
|
|
|
|
"github.com/grafana/authlib/authz"
|
|
"github.com/grafana/grafana-app-sdk/logging"
|
|
"github.com/grafana/grafana/pkg/apimachinery/identity"
|
|
)
|
|
|
|
type AuthorizeFromName struct {
|
|
Teams TeamService
|
|
OKNames []string
|
|
Resource map[string][]ResourceOwner // may include unknown
|
|
}
|
|
|
|
func (a *AuthorizeFromName) Authorize(ctx context.Context, attr authorizer.Attributes) (authorizer.Decision, string, error) {
|
|
user, err := identity.GetRequester(ctx)
|
|
if err != nil || user == nil {
|
|
return authorizer.DecisionDeny, "valid user is required", err
|
|
}
|
|
|
|
if !attr.IsResourceRequest() {
|
|
return authorizer.DecisionNoOpinion, "", nil
|
|
}
|
|
|
|
owners, ok := a.Resource[attr.GetResource()]
|
|
if !ok {
|
|
return authorizer.DecisionDeny, "missing resource name", nil
|
|
}
|
|
|
|
// Check if the request includes explicit permissions
|
|
res := authz.CheckServicePermissions(user, attr.GetAPIGroup(), attr.GetResource(), attr.GetVerb())
|
|
if !res.Allowed {
|
|
log := logging.FromContext(ctx)
|
|
log.Info("calling service lacks required permissions",
|
|
"isServiceCall", res.ServiceCall,
|
|
"apiGroup", attr.GetAPIGroup(),
|
|
"resource", attr.GetResource(),
|
|
"verb", attr.GetVerb(),
|
|
"permissions", len(res.Permissions),
|
|
)
|
|
return authorizer.DecisionDeny, "calling service lacks required permissions", nil
|
|
}
|
|
|
|
if attr.GetName() == "" {
|
|
if attr.IsReadOnly() {
|
|
return authorizer.DecisionAllow, "", nil
|
|
}
|
|
return authorizer.DecisionDeny, "mutating request without a name", nil
|
|
}
|
|
|
|
// the pseudo sub-resource
|
|
if a.OKNames != nil && slices.Contains(a.OKNames, attr.GetName()) {
|
|
return authorizer.DecisionAllow, "", nil
|
|
}
|
|
|
|
info, _ := ParseOwnerFromName(attr.GetName())
|
|
if !slices.Contains(owners, info.Owner) {
|
|
return authorizer.DecisionDeny, "unsupported owner type", nil
|
|
}
|
|
|
|
switch info.Owner {
|
|
case NamespaceResourceOwner:
|
|
if attr.IsReadOnly() {
|
|
// Everyone can see the namespace
|
|
return authorizer.DecisionAllow, "", nil
|
|
}
|
|
if user.GetOrgRole() == identity.RoleAdmin {
|
|
return authorizer.DecisionAllow, "", nil
|
|
}
|
|
return authorizer.DecisionDeny, "must be an org admin to edit", nil
|
|
|
|
case UserResourceOwner:
|
|
if user.GetIdentifier() == info.Identifier {
|
|
return authorizer.DecisionAllow, "", nil
|
|
}
|
|
return authorizer.DecisionDeny, "your are not the owner of the resource", nil
|
|
|
|
case TeamResourceOwner:
|
|
if a.Teams == nil {
|
|
return authorizer.DecisionDeny, "team checker not configured", err
|
|
}
|
|
ok, err := a.Teams.InTeam(ctx, user, info.Identifier, !attr.IsReadOnly())
|
|
if err != nil {
|
|
return authorizer.DecisionDeny, "error fetching teams", err
|
|
}
|
|
if ok {
|
|
return authorizer.DecisionAllow, "", nil
|
|
}
|
|
return authorizer.DecisionDeny, "you are not a member of the referenced team", nil
|
|
|
|
case UnknownResourceOwner:
|
|
return authorizer.DecisionAllow, "", nil
|
|
}
|
|
|
|
// the owner was not explicitly allowed
|
|
return authorizer.DecisionDeny, "", nil
|
|
}
|