Initial support for authorization

This commit is contained in:
Abin Simon
2022-03-22 13:51:46 +05:30
parent 1dce43d607
commit f7ac37ab6f
5 changed files with 89 additions and 18 deletions

View File

@@ -6,8 +6,11 @@ import (
"github.com/RafayLabs/rcloud-base/pkg/enforcer"
logv2 "github.com/RafayLabs/rcloud-base/pkg/log"
"github.com/RafayLabs/rcloud-base/pkg/service"
"github.com/RafayLabs/rcloud-base/pkg/enforcer"
kclient "github.com/ory/kratos-client-go"
"github.com/uptrace/bun"
"gorm.io/driver/postgres"
"gorm.io/gorm"
)
var _log = logv2.GetLogger()
@@ -27,6 +30,7 @@ type Option struct {
type authContext struct {
kc *kclient.APIClient
ks service.ApiKeyService
as service.AuthzService
}
// NewAuthContext setup authentication and authorization dependencies.
@@ -36,6 +40,8 @@ func NewAuthContext(db *bun.DB) authContext {
kratosScheme string
kratosAddr string
)
// TODO: https://github.com/RafayLabs/prompt/pull/3#issuecomment-1073557206
// Where exactly should we be getting these values from?
if v, ok := os.LookupEnv("KRATOS_SCHEME"); ok {
kratosScheme = v
} else {
@@ -51,5 +57,18 @@ func NewAuthContext(db *bun.DB) authContext {
kratosConfig.Servers[0].URL = kratosScheme + "://" + kratosAddr
kc = kclient.NewAPIClient(kratosConfig)
return authContext{kc: kc, ks: service.NewApiKeyService(db)}
gormDb, err := gorm.Open(
postgres.New(postgres.Config{Conn: db.DB}),
&gorm.Config{},
)
if err != nil {
_log.Fatalw("unable to create db connection", "error", err)
}
enforcer, err := enforcer.NewCasbinEnforcer(gormDb).Init()
if err != nil {
_log.Fatalw("unable to init enforcer", "error", err)
}
as := service.NewAuthzService(db, enforcer)
return authContext{kc: kc, as: as, ks: service.NewApiKeyService(db)}
}

View File

@@ -2,9 +2,11 @@ package authv3
import (
context "context"
"reflect"
"strings"
"github.com/RafayLabs/rcloud-base/pkg/gateway"
commonpbv3 "github.com/RafayLabs/rcloud-base/proto/types/commonpb/v3"
commonv3 "github.com/RafayLabs/rcloud-base/proto/types/commonpb/v3"
grpc "google.golang.org/grpc"
codes "google.golang.org/grpc/codes"
"google.golang.org/grpc/metadata"
@@ -21,6 +23,27 @@ func (ac authContext) NewAuthUnaryInterceptor(opt Option) grpc.UnaryServerInterc
}
}
// We have to get the value of org, and project (namespace in
// future) as we will be using this inorder to authorize the
// user's access to different resources
reqValue := reflect.ValueOf(req).Elem()
field := reqValue.FieldByName("Metadata")
var org string
var project string
if field != (reflect.Value{}) {
org = field.Interface().(*commonv3.Metadata).Organization
project = field.Interface().(*commonv3.Metadata).Project
}
// overrides for picking up info when not in default metadata locations
// XXX: This requires any new items which does not follow metadata convention added here
switch strings.Split(info.FullMethod, "/")[1] {
case "rafay.dev.rpc.v3.Project":
project = field.Interface().(*commonv3.Metadata).Name
case "rafay.dev.rpc.v3.Organization":
org = field.Interface().(*commonv3.Metadata).Name
}
md, ok := metadata.FromIncomingContext(ctx)
if !ok {
return nil, status.Error(codes.InvalidArgument, "grpc metadata not exist")
@@ -43,11 +66,13 @@ func (ac authContext) NewAuthUnaryInterceptor(opt Option) grpc.UnaryServerInterc
if len(md.Get("grpcgateway-cookie")) != 0 {
cookie = md.Get("grpcgateway-cookie")[0]
}
acReq := &commonpbv3.IsRequestAllowedRequest{
acReq := &commonv3.IsRequestAllowedRequest{
Url: url,
Method: method,
XSessionToken: token,
Cookie: cookie,
Org: org,
Project: project,
}
res, err := ac.IsRequestAllowed(ctx, nil, acReq)
if err != nil {
@@ -57,12 +82,12 @@ func (ac authContext) NewAuthUnaryInterceptor(opt Option) grpc.UnaryServerInterc
s := res.GetStatus()
switch s {
case commonpbv3.RequestStatus_RequestAllowed:
case commonv3.RequestStatus_RequestAllowed:
ctx := NewSessionContext(ctx, res.SessionData)
return handler(ctx, req)
case commonpbv3.RequestStatus_RequestMethodOrURLNotAllowed:
case commonv3.RequestStatus_RequestMethodOrURLNotAllowed:
return nil, status.Error(codes.PermissionDenied, res.GetReason())
case commonpbv3.RequestStatus_RequestNotAuthenticated:
case commonv3.RequestStatus_RequestNotAuthenticated:
return nil, status.Error(codes.Unauthenticated, res.GetReason())
}

View File

@@ -7,6 +7,7 @@ import (
"strings"
rpcv3 "github.com/RafayLabs/rcloud-base/proto/rpc/user"
authzv1 "github.com/RafayLabs/rcloud-base/proto/types/authz"
commonv3 "github.com/RafayLabs/rcloud-base/proto/types/commonpb/v3"
"github.com/spacemonkeygo/httpsig"
)
@@ -25,10 +26,14 @@ func (ac *authContext) IsRequestAllowed(ctx context.Context, httpreq *http.Reque
}
// Authenticate request
err := ac.authenticate(ctx, httpreq, req, res)
err, succ := ac.authenticate(ctx, httpreq, req, res)
if err != nil {
return nil, err
}
// Don't bother checking authorization if athentication failed
if !succ {
return res, nil
}
// Authorize request
err = ac.authorize(ctx, req, res)
@@ -41,14 +46,14 @@ func (ac *authContext) IsRequestAllowed(ctx context.Context, httpreq *http.Reque
// authenticate validate whether the request is from a legitimate user
// and populate relevant information in res.
func (ac *authContext) authenticate(ctx context.Context, httpreq *http.Request, req *commonv3.IsRequestAllowedRequest, res *commonv3.IsRequestAllowedResponse) error {
func (ac *authContext) authenticate(ctx context.Context, httpreq *http.Request, req *commonv3.IsRequestAllowedRequest, res *commonv3.IsRequestAllowedResponse) (error, bool) {
if len(req.XApiKey) > 0 && len(req.XSessionToken) == 0 {
resp, err := ac.ks.GetByKey(ctx, &rpcv3.ApiKeyRequest{
Id: req.XApiKey,
})
if err != nil {
_log.Infow("unable to get api key", "key", req.XApiKey, "error", err)
return ErrInvalidAPIKey
return ErrInvalidAPIKey, false
}
var kg httpsig.KeyGetterFunc = func(id string) interface{} {
return []byte(resp.Secret)
@@ -58,7 +63,7 @@ func (ac *authContext) authenticate(ctx context.Context, httpreq *http.Request,
verifier.SetRequiredHeaders([]string{"content-md5", "date", "host", "nonce"})
err = verifier.Verify(httpreq)
if err != nil {
return ErrInvalidSignature
return ErrInvalidSignature, false
}
res.Status = commonv3.RequestStatus_RequestAllowed
res.SessionData.Username = resp.Name
@@ -73,9 +78,9 @@ func (ac *authContext) authenticate(ctx context.Context, httpreq *http.Request,
if strings.Contains(err.Error(), "401 Unauthorized") {
res.Status = commonv3.RequestStatus_RequestNotAuthenticated
res.Reason = "no or invalid credentials"
return nil
return nil, false
} else {
return err
return err, false
}
}
if session.GetActive() {
@@ -90,11 +95,33 @@ func (ac *authContext) authenticate(ctx context.Context, httpreq *http.Request,
res.Reason = "no active session"
}
}
return nil
return nil, true
}
// authorize performs authorization of the request and populate
// relevant information in res.
// authorize performs authorization of the request
func (ac *authContext) authorize(ctx context.Context, req *commonv3.IsRequestAllowedRequest, res *commonv3.IsRequestAllowedResponse) error {
// user,namespace,project,org,url(perm),method
// ones that don't have value should be "*"
proj := req.Project
if proj == "" {
proj = "*"
}
org := req.Org
if org == "" {
org = "*"
}
er := authzv1.EnforceRequest{
Params: []string{"u:" + res.SessionData.Username, "*", proj, org, req.Url, req.Method},
}
authenticated, err := ac.as.Enforce(ctx, &er)
if err != nil {
return err
}
if !authenticated.Res {
res.Status = commonv3.RequestStatus_RequestMethodOrURLNotAllowed
res.Reason = "not authorized to perform action"
return nil
}
return nil
}

View File

@@ -82,7 +82,7 @@ func (s *groupService) createGroupRoleRelations(ctx context.Context, db bun.IDB,
var ps []*authzv1.Policy
for _, pnr := range projectNamespaceRoles {
role := pnr.GetRole()
entity, err := pg.GetByName(ctx, db, role, &models.Role{})
entity, err := dao.GetByName(ctx, db, role, &models.Role{})
if err != nil {
return &userv3.Group{}, fmt.Errorf("unable to find role '%v'", role)
}
@@ -145,7 +145,7 @@ func (s *groupService) createGroupRoleRelations(ctx context.Context, db bun.IDB,
if project == "" {
return &userv3.Group{}, fmt.Errorf("no project name provided for role '%v'", roleName)
}
projectId, err := pg.GetProjectId(ctx, s.db, project)
projectId, err := dao.GetProjectId(ctx, s.db, project)
if err != nil {
return &userv3.Group{}, fmt.Errorf("unable to find project '%v'", project)
}

View File

@@ -179,7 +179,7 @@ func (s *userService) createUserRoleRelations(ctx context.Context, db bun.IDB, u
if project == "" {
return &userv3.User{}, fmt.Errorf("no project name provided for role '%v'", roleName)
}
projectId, err := pg.GetProjectId(ctx, db, project)
projectId, err := dao.GetProjectId(ctx, db, project)
if err != nil {
return user, fmt.Errorf("unable to find project '%v'", project)
}