mirror of
https://github.com/paralus/paralus.git
synced 2026-05-06 08:26:53 +00:00
Initial support for authorization
This commit is contained in:
@@ -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)}
|
||||
}
|
||||
|
||||
@@ -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())
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user