mirror of
https://github.com/paralus/paralus.git
synced 2026-05-06 16:36:46 +00:00
175 lines
5.3 KiB
Go
175 lines
5.3 KiB
Go
package authv3
|
|
|
|
import (
|
|
context "context"
|
|
"database/sql"
|
|
"net/http"
|
|
"regexp"
|
|
"strings"
|
|
|
|
"github.com/paralus/paralus/internal/dao"
|
|
"github.com/paralus/paralus/pkg/common"
|
|
rpcv3 "github.com/paralus/paralus/proto/rpc/v3"
|
|
commonpbv3 "github.com/paralus/paralus/proto/types/commonpb/v3"
|
|
"github.com/uptrace/bun"
|
|
"github.com/uptrace/bun/dialect/pgdialect"
|
|
"github.com/uptrace/bun/driver/pgdriver"
|
|
"github.com/urfave/negroni"
|
|
"go.uber.org/zap"
|
|
grpc "google.golang.org/grpc"
|
|
)
|
|
|
|
type authMiddleware struct {
|
|
db *bun.DB
|
|
ac authContext
|
|
opt Option
|
|
}
|
|
|
|
// NewAuthMiddleware creates as a middleware for the HTTP server which
|
|
// does the auth and authz by talking to kratos server and casbin
|
|
func NewAuthMiddleware(al *zap.Logger, opt Option) negroni.Handler {
|
|
sqldb := sql.OpenDB(pgdriver.NewConnector(pgdriver.WithDSN(getDSN())))
|
|
return &authMiddleware{
|
|
ac: SetupAuthContext(al),
|
|
opt: opt,
|
|
db: bun.NewDB(sqldb, pgdialect.New()),
|
|
}
|
|
}
|
|
|
|
type remoteAuthMiddleware struct {
|
|
as rpcv3.AuthClient
|
|
db *bun.DB
|
|
opt Option
|
|
}
|
|
|
|
// NewRemoteAuthMiddleware creates a middleware for the HTTP server
|
|
// which does auth and authz by talking to the auth service exposed by
|
|
// paralus via grpc.
|
|
func NewRemoteAuthMiddleware(al *zap.Logger, as string, opt Option) negroni.Handler {
|
|
sqldb := sql.OpenDB(pgdriver.NewConnector(pgdriver.WithDSN(getDSN())))
|
|
conn, err := grpc.Dial(as, grpc.WithInsecure())
|
|
if err != nil {
|
|
_log.Fatal("Unable to connect to server", err)
|
|
}
|
|
client := rpcv3.NewAuthClient(conn)
|
|
|
|
return &remoteAuthMiddleware{
|
|
as: client,
|
|
opt: opt,
|
|
db: bun.NewDB(sqldb, pgdialect.New()),
|
|
}
|
|
}
|
|
|
|
func serveHTTP(opt Option,
|
|
db *bun.DB,
|
|
isRequestAllowed func(context.Context, *commonpbv3.IsRequestAllowedRequest) (*commonpbv3.IsRequestAllowedResponse, error),
|
|
rw http.ResponseWriter,
|
|
r *http.Request,
|
|
next http.HandlerFunc,
|
|
) {
|
|
for _, ex := range opt.ExcludeURLs {
|
|
match, err := regexp.MatchString(ex, r.URL.Path)
|
|
if err != nil {
|
|
_log.Errorf("failed to match URL expression", err)
|
|
http.Error(rw, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
|
|
return
|
|
}
|
|
if match {
|
|
next(rw, r)
|
|
return
|
|
}
|
|
}
|
|
// Auth is primarily done via grpc endpoints, this is only used
|
|
// for endoints which do not go through grpc As of now, it is just
|
|
// prompt.
|
|
var poResp dao.ProjectOrg
|
|
|
|
if strings.HasPrefix(r.URL.String(), "/v2/debug/prompt/project/") {
|
|
// /v2/debug/prompt/project/:project/cluster/:cluster_name
|
|
splits := strings.Split(r.URL.String(), "/")
|
|
if len(splits) > 5 {
|
|
// we have to fetch the org info for casbin
|
|
res, err := dao.GetProjectOrganization(r.Context(), db, splits[5])
|
|
if err != nil {
|
|
_log.Errorf("Failed to authenticate: unable to find project")
|
|
http.Error(rw, http.StatusText(http.StatusForbidden), http.StatusForbidden)
|
|
return
|
|
}
|
|
_log.Info("found project with organization ", res.Organization)
|
|
poResp = res
|
|
}
|
|
} else {
|
|
// The middleware to only used with routes which does not have
|
|
// a grpc and so fail for any other requests.
|
|
_log.Errorf("Failed to authenticate: not a prompt request")
|
|
http.Error(rw, http.StatusText(http.StatusForbidden), http.StatusForbidden)
|
|
return
|
|
}
|
|
|
|
req := &commonpbv3.IsRequestAllowedRequest{
|
|
Url: r.URL.String(),
|
|
Method: r.Method,
|
|
XSessionToken: r.Header.Get("X-Session-Token"),
|
|
XApiKey: r.Header.Get("X-API-KEYID"),
|
|
XApiToken: r.Header.Get("X-API-TOKEN"),
|
|
Cookie: r.Header.Get("Cookie"),
|
|
Project: poResp.Project,
|
|
Org: poResp.Organization,
|
|
}
|
|
res, err := isRequestAllowed(r.Context(), req)
|
|
if err != nil {
|
|
_log.Errorf("Failed to authenticate a request: %s", err)
|
|
http.Error(rw, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
|
|
return
|
|
}
|
|
|
|
s := res.GetStatus()
|
|
switch s {
|
|
case commonpbv3.RequestStatus_RequestAllowed:
|
|
// update the session data response to be used within prompt
|
|
res.SessionData.Organization = poResp.OrganizationId
|
|
res.SessionData.Partner = poResp.PartnerId
|
|
res.SessionData.Project = &commonpbv3.ProjectData{
|
|
List: []*commonpbv3.ProjectRole{
|
|
{
|
|
Project: poResp.Project,
|
|
ProjectId: poResp.ProjectId,
|
|
},
|
|
},
|
|
}
|
|
ctx := context.WithValue(r.Context(), common.SessionDataKey, res.SessionData)
|
|
next(rw, r.WithContext(ctx))
|
|
return
|
|
case commonpbv3.RequestStatus_RequestMethodOrURLNotAllowed:
|
|
http.Error(rw, res.GetReason(), http.StatusForbidden)
|
|
return
|
|
case commonpbv3.RequestStatus_RequestNotAuthenticated:
|
|
http.Error(rw, res.GetReason(), http.StatusUnauthorized)
|
|
return
|
|
}
|
|
|
|
// status is unknown
|
|
http.Error(rw, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
|
|
}
|
|
|
|
// ServeHTTP function is called by the HTTP server to invoke the
|
|
// middleware
|
|
func (am *authMiddleware) ServeHTTP(rw http.ResponseWriter, r *http.Request, next http.HandlerFunc) {
|
|
serveHTTP(am.opt, am.db, am.ac.IsRequestAllowed, rw, r, next)
|
|
}
|
|
|
|
// ServeHTTP function is called by the HTTP server to invoke the
|
|
// middleware. Same as previous ServeHTTP, but uses
|
|
// remoteAuthMiddleware instead of authMiddleware
|
|
func (am *remoteAuthMiddleware) ServeHTTP(rw http.ResponseWriter, r *http.Request, next http.HandlerFunc) {
|
|
serveHTTP(
|
|
am.opt,
|
|
am.db,
|
|
func(ctx context.Context, isr *commonpbv3.IsRequestAllowedRequest) (*commonpbv3.IsRequestAllowedResponse, error) {
|
|
return am.as.IsRequestAllowed(ctx, isr)
|
|
},
|
|
rw,
|
|
r,
|
|
next)
|
|
}
|