K8s: Add org ID and role authorizers (#75701)

This commit is contained in:
Todd Treece 2023-09-28 18:28:58 -04:00 committed by GitHub
parent 0af7247612
commit c31ddce0b6
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 161 additions and 7 deletions

View File

@ -0,0 +1,50 @@
package org
import (
"context"
"fmt"
"k8s.io/apiserver/pkg/authorization/authorizer"
"github.com/grafana/grafana/pkg/infra/appcontext"
"github.com/grafana/grafana/pkg/infra/log"
grafanarequest "github.com/grafana/grafana/pkg/services/grafana-apiserver/endpoints/request"
"github.com/grafana/grafana/pkg/services/org"
)
var _ authorizer.Authorizer = &OrgIDAuthorizer{}
type OrgIDAuthorizer struct {
log log.Logger
org org.Service
}
func ProvideOrgIDAuthorizer(orgService org.Service) *OrgIDAuthorizer {
return &OrgIDAuthorizer{log: log.New("grafana-apiserver.authorizer.orgid"), org: orgService}
}
func (auth OrgIDAuthorizer) Authorize(ctx context.Context, a authorizer.Attributes) (authorized authorizer.Decision, reason string, err error) {
signedInUser, err := appcontext.User(ctx)
if err != nil {
return authorizer.DecisionDeny, fmt.Sprintf("error getting signed in user: %v", err), nil
}
orgID, ok := grafanarequest.ParseOrgID(a.GetNamespace())
if !ok {
return authorizer.DecisionNoOpinion, "", nil
}
query := org.GetUserOrgListQuery{UserID: signedInUser.UserID}
result, err := auth.org.GetUserOrgList(ctx, &query)
if err != nil {
return authorizer.DecisionDeny, "error getting user org list", err
}
for _, org := range result {
if org.OrgID == orgID {
return authorizer.DecisionAllow, "", nil
}
}
return authorizer.DecisionDeny, fmt.Sprintf("user %d is not a member of org %d", signedInUser.UserID, orgID), nil
}

View File

@ -0,0 +1,56 @@
package org
import (
"context"
"fmt"
"k8s.io/apiserver/pkg/authorization/authorizer"
"github.com/grafana/grafana/pkg/infra/appcontext"
"github.com/grafana/grafana/pkg/infra/log"
"github.com/grafana/grafana/pkg/services/org"
)
var _ authorizer.Authorizer = &OrgIDAuthorizer{}
type OrgRoleAuthorizer struct {
log log.Logger
org org.Service
}
func ProvideOrgRoleAuthorizer(orgService org.Service) *OrgRoleAuthorizer {
return &OrgRoleAuthorizer{log: log.New("grafana-apiserver.authorizer.orgrole"), org: orgService}
}
func (auth OrgRoleAuthorizer) Authorize(ctx context.Context, a authorizer.Attributes) (authorized authorizer.Decision, reason string, err error) {
signedInUser, err := appcontext.User(ctx)
if err != nil {
return authorizer.DecisionDeny, fmt.Sprintf("error getting signed in user: %v", err), nil
}
switch signedInUser.OrgRole {
case org.RoleAdmin:
return authorizer.DecisionAllow, "", nil
case org.RoleEditor:
switch a.GetVerb() {
case "get", "list", "watch", "create", "update", "patch", "delete", "put", "post":
return authorizer.DecisionAllow, "", nil
default:
return authorizer.DecisionDeny, errorMessageForGrafanaOrgRole(string(signedInUser.OrgRole), a), nil
}
case org.RoleViewer:
switch a.GetVerb() {
case "get", "list", "watch":
return authorizer.DecisionAllow, "", nil
default:
return authorizer.DecisionDeny, errorMessageForGrafanaOrgRole(string(signedInUser.OrgRole), a), nil
}
case org.RoleNone:
return authorizer.DecisionDeny, errorMessageForGrafanaOrgRole(string(signedInUser.OrgRole), a), nil
}
return authorizer.DecisionNoOpinion, "", nil
}
func errorMessageForGrafanaOrgRole(grafanaOrgRole string, a authorizer.Attributes) string {
return fmt.Sprintf("Grafana org role (%s) didn't allow %s access on requested resource=%s, path=%s", grafanaOrgRole, a.GetVerb(), a.GetResource(), a.GetPath())
}

View File

@ -0,0 +1,8 @@
package org
import "github.com/google/wire"
var WireSet = wire.NewSet(
ProvideOrgIDAuthorizer,
ProvideOrgRoleAuthorizer,
)

View File

@ -0,0 +1,22 @@
package authorizer
import (
"k8s.io/apiserver/pkg/authentication/user"
"k8s.io/apiserver/pkg/authorization/authorizer"
"k8s.io/apiserver/pkg/authorization/authorizerfactory"
"k8s.io/apiserver/pkg/authorization/union"
"github.com/grafana/grafana/pkg/services/grafana-apiserver/auth/authorizer/org"
)
func ProvideAuthorizer(
orgIDAuthorizer *org.OrgIDAuthorizer,
orgRoleAuthorizer *org.OrgRoleAuthorizer,
) authorizer.Authorizer {
authorizers := []authorizer.Authorizer{
authorizerfactory.NewPrivilegedGroups(user.SystemPrivilegedGroup),
orgIDAuthorizer,
orgRoleAuthorizer,
}
return union.New(authorizers...)
}

View File

@ -0,0 +1,12 @@
package authorizer
import (
"github.com/google/wire"
"github.com/grafana/grafana/pkg/services/grafana-apiserver/auth/authorizer/org"
)
var WireSet = wire.NewSet(
org.WireSet,
ProvideAuthorizer,
)

View File

@ -9,6 +9,10 @@ import (
func OrgIDFrom(ctx context.Context) (int64, bool) {
ns := request.NamespaceValue(ctx)
return ParseOrgID(ns)
}
func ParseOrgID(ns string) (int64, bool) {
if len(ns) < 5 || ns[:4] != "org-" {
return 0, false
}

View File

@ -18,7 +18,7 @@ import (
"k8s.io/apimachinery/pkg/runtime/serializer"
"k8s.io/apiserver/pkg/authentication/authenticator"
"k8s.io/apiserver/pkg/authentication/request/headerrequest"
"k8s.io/apiserver/pkg/authentication/user"
"k8s.io/apiserver/pkg/authorization/authorizer"
openapinamer "k8s.io/apiserver/pkg/endpoints/openapi"
"k8s.io/apiserver/pkg/endpoints/responsewriter"
genericapiserver "k8s.io/apiserver/pkg/server"
@ -101,11 +101,14 @@ type service struct {
rr routing.RouteRegister
handler web.Handler
builders []APIGroupBuilder
authorizer authorizer.Authorizer
}
func ProvideService(
cfg *setting.Cfg,
rr routing.RouteRegister,
authz authorizer.Authorizer,
) (*service, error) {
s := &service{
etcd_servers: cfg.SectionWithEnvOverrides("grafana-apiserver").Key("etcd_servers").Strings(","),
@ -114,6 +117,7 @@ func ProvideService(
dataPath: path.Join(cfg.DataPath, "k8s"),
stopCh: make(chan struct{}),
builders: []APIGroupBuilder{},
authorizer: authz,
}
// This will be used when running as a dskit service
@ -171,8 +175,6 @@ func (s *service) start(ctx context.Context) error {
o.SecureServing.BindPort = 6443
o.Authentication.RemoteKubeConfigFileOptional = true
o.Authorization.RemoteKubeConfigFileOptional = true
o.Authorization.AlwaysAllowPaths = []string{"*"}
o.Authorization.AlwaysAllowGroups = []string{user.SystemPrivilegedGroup, "grafana"}
o.Etcd.StorageConfig.Transport.ServerList = s.etcd_servers
o.Admission = nil
@ -220,6 +222,7 @@ func (s *service) start(ctx context.Context) error {
return err
}
serverConfig.Authorization.Authorizer = s.authorizer
serverConfig.Authentication.Authenticator = authenticator
// Get the list of groups the server will support
@ -283,10 +286,6 @@ func (s *service) start(ctx context.Context) error {
req.Header.Set("X-Remote-User", strconv.FormatInt(signedInUser.UserID, 10))
req.Header.Set("X-Remote-Group", "grafana")
req.Header.Set("X-Remote-Extra-token-name", signedInUser.Name)
req.Header.Set("X-Remote-Extra-org-role", string(signedInUser.OrgRole))
req.Header.Set("X-Remote-Extra-org-id", strconv.FormatInt(signedInUser.OrgID, 10))
req.Header.Set("X-Remote-Extra-user-id", strconv.FormatInt(signedInUser.UserID, 10))
resp := responsewriter.WrapForHTTP1Or2(c.Resp)
prepared.GenericAPIServer.Handler.ServeHTTP(resp, req)

View File

@ -2,6 +2,8 @@ package grafanaapiserver
import (
"github.com/google/wire"
"github.com/grafana/grafana/pkg/services/grafana-apiserver/auth/authorizer"
)
var WireSet = wire.NewSet(
@ -9,4 +11,5 @@ var WireSet = wire.NewSet(
wire.Bind(new(RestConfigProvider), new(*service)),
wire.Bind(new(Service), new(*service)),
wire.Bind(new(APIRegistrar), new(*service)),
authorizer.WireSet,
)