diff --git a/pkg/services/grafana-apiserver/auth/authorizer/org/org_id.go b/pkg/services/grafana-apiserver/auth/authorizer/org/org_id.go new file mode 100644 index 00000000000..b070af3fb68 --- /dev/null +++ b/pkg/services/grafana-apiserver/auth/authorizer/org/org_id.go @@ -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 +} diff --git a/pkg/services/grafana-apiserver/auth/authorizer/org/org_role.go b/pkg/services/grafana-apiserver/auth/authorizer/org/org_role.go new file mode 100644 index 00000000000..d9d8f29f11b --- /dev/null +++ b/pkg/services/grafana-apiserver/auth/authorizer/org/org_role.go @@ -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()) +} diff --git a/pkg/services/grafana-apiserver/auth/authorizer/org/wireset.go b/pkg/services/grafana-apiserver/auth/authorizer/org/wireset.go new file mode 100644 index 00000000000..b213b088597 --- /dev/null +++ b/pkg/services/grafana-apiserver/auth/authorizer/org/wireset.go @@ -0,0 +1,8 @@ +package org + +import "github.com/google/wire" + +var WireSet = wire.NewSet( + ProvideOrgIDAuthorizer, + ProvideOrgRoleAuthorizer, +) diff --git a/pkg/services/grafana-apiserver/auth/authorizer/provider.go b/pkg/services/grafana-apiserver/auth/authorizer/provider.go new file mode 100644 index 00000000000..debff38df73 --- /dev/null +++ b/pkg/services/grafana-apiserver/auth/authorizer/provider.go @@ -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...) +} diff --git a/pkg/services/grafana-apiserver/auth/authorizer/wireset.go b/pkg/services/grafana-apiserver/auth/authorizer/wireset.go new file mode 100644 index 00000000000..5216c1d57cc --- /dev/null +++ b/pkg/services/grafana-apiserver/auth/authorizer/wireset.go @@ -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, +) diff --git a/pkg/services/grafana-apiserver/endpoints/request/request.go b/pkg/services/grafana-apiserver/endpoints/request/request.go index a1d3d177e1f..df8994bccb6 100644 --- a/pkg/services/grafana-apiserver/endpoints/request/request.go +++ b/pkg/services/grafana-apiserver/endpoints/request/request.go @@ -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 } diff --git a/pkg/services/grafana-apiserver/service.go b/pkg/services/grafana-apiserver/service.go index 48c9f33961d..a8409f6b65b 100644 --- a/pkg/services/grafana-apiserver/service.go +++ b/pkg/services/grafana-apiserver/service.go @@ -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) diff --git a/pkg/services/grafana-apiserver/wireset.go b/pkg/services/grafana-apiserver/wireset.go index 8781d754183..f4986e59313 100644 --- a/pkg/services/grafana-apiserver/wireset.go +++ b/pkg/services/grafana-apiserver/wireset.go @@ -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, )