mirror of
https://github.com/1Panel-dev/KubePi.git
synced 2025-11-02 19:34:01 +08:00
428 lines
12 KiB
Go
428 lines
12 KiB
Go
package v1
|
|
|
|
import (
|
|
"bytes"
|
|
"encoding/json"
|
|
"errors"
|
|
"fmt"
|
|
"github.com/KubeOperator/kubepi/internal/api/v1/mfa"
|
|
"io/ioutil"
|
|
"strings"
|
|
|
|
"github.com/KubeOperator/kubepi/internal/api/v1/file"
|
|
"github.com/kataras/iris/v12/middleware/jwt"
|
|
|
|
"github.com/KubeOperator/kubepi/internal/api/v1/chart"
|
|
"github.com/KubeOperator/kubepi/internal/api/v1/cluster"
|
|
"github.com/KubeOperator/kubepi/internal/api/v1/imagerepo"
|
|
"github.com/KubeOperator/kubepi/internal/api/v1/ldap"
|
|
"github.com/KubeOperator/kubepi/internal/api/v1/proxy"
|
|
"github.com/KubeOperator/kubepi/internal/api/v1/role"
|
|
"github.com/KubeOperator/kubepi/internal/api/v1/session"
|
|
"github.com/KubeOperator/kubepi/internal/api/v1/system"
|
|
"github.com/KubeOperator/kubepi/internal/api/v1/user"
|
|
"github.com/KubeOperator/kubepi/internal/api/v1/webkubectl"
|
|
"github.com/KubeOperator/kubepi/internal/api/v1/ws"
|
|
v1 "github.com/KubeOperator/kubepi/internal/model/v1"
|
|
v1Role "github.com/KubeOperator/kubepi/internal/model/v1/role"
|
|
v1System "github.com/KubeOperator/kubepi/internal/model/v1/system"
|
|
"github.com/KubeOperator/kubepi/internal/service/v1/common"
|
|
v1RoleService "github.com/KubeOperator/kubepi/internal/service/v1/role"
|
|
v1RoleBindingService "github.com/KubeOperator/kubepi/internal/service/v1/rolebinding"
|
|
v1SystemService "github.com/KubeOperator/kubepi/internal/service/v1/system"
|
|
pkgV1 "github.com/KubeOperator/kubepi/pkg/api/v1"
|
|
"github.com/KubeOperator/kubepi/pkg/collectons"
|
|
"github.com/KubeOperator/kubepi/pkg/i18n"
|
|
"github.com/asdine/storm/v3"
|
|
"github.com/kataras/iris/v12"
|
|
"github.com/kataras/iris/v12/context"
|
|
"github.com/kataras/iris/v12/core/router"
|
|
"github.com/kataras/iris/v12/sessions"
|
|
)
|
|
|
|
var resourceWhiteList = WhiteList{"sessions", "proxy", "ws", "charts", "webkubectl", "apps", "mfa", "pod"}
|
|
|
|
type WhiteList []string
|
|
|
|
func (w WhiteList) In(name string) bool {
|
|
for i := range w {
|
|
if w[i] == name {
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
func authHandler() iris.Handler {
|
|
return func(ctx *context.Context) {
|
|
var p session.UserProfile
|
|
if ctx.GetHeader("Authorization") != "" {
|
|
pr := jwt.Get(ctx).(*session.UserProfile)
|
|
p = *pr
|
|
|
|
} else {
|
|
p = sessions.Get(ctx).Get("profile").(session.UserProfile)
|
|
}
|
|
if p.Name == "" {
|
|
ctx.Values().Set("message", "please login")
|
|
ctx.StopWithStatus(iris.StatusUnauthorized)
|
|
return
|
|
}
|
|
ctx.Values().Set("profile", p)
|
|
ctx.Next()
|
|
}
|
|
}
|
|
|
|
func langHandler() iris.Handler {
|
|
return func(ctx *context.Context) {
|
|
p := ctx.Values().Get("profile")
|
|
u, ok := p.(session.UserProfile)
|
|
lang := i18n.LanguageZhCN
|
|
if ok {
|
|
lang = u.Language
|
|
}
|
|
ctx.Values().Set("language", lang)
|
|
ctx.Next()
|
|
}
|
|
}
|
|
|
|
func pageHandler() iris.Handler {
|
|
return func(ctx *context.Context) {
|
|
if ctx.URLParamExists(pkgV1.PageSize) && ctx.URLParamExists(pkgV1.PageNum) {
|
|
pageNum, err := ctx.URLParamInt(pkgV1.PageNum)
|
|
if err != nil {
|
|
ctx.Values().Set("message", fmt.Sprintf("page num format err %s", err.Error()))
|
|
ctx.StopWithStatus(iris.StatusBadRequest)
|
|
return
|
|
}
|
|
pageSize, err := ctx.URLParamInt(pkgV1.PageSize)
|
|
if err != nil {
|
|
ctx.Values().Set("message", fmt.Sprintf("page size format err %s", err.Error()))
|
|
ctx.StopWithStatus(iris.StatusBadRequest)
|
|
return
|
|
}
|
|
ctx.Values().Set(pkgV1.PageNum, pageNum)
|
|
ctx.Values().Set(pkgV1.PageSize, pageSize)
|
|
}
|
|
ctx.Next()
|
|
}
|
|
}
|
|
|
|
type logHelper struct {
|
|
Name string `json:"name"`
|
|
Metadata v1.Metadata
|
|
}
|
|
|
|
func logHandler() iris.Handler {
|
|
return func(ctx *context.Context) {
|
|
method := strings.ToLower(ctx.Method())
|
|
if method != "post" && method != "delete" && method != "put" {
|
|
ctx.Next()
|
|
return
|
|
}
|
|
|
|
resourceName := ctx.Values().GetString("resource")
|
|
if resourceName == "" || resourceWhiteList.In(resourceName) {
|
|
ctx.Next()
|
|
return
|
|
}
|
|
|
|
currentPath := ctx.GetCurrentRoute().Path()
|
|
path := strings.Replace(ctx.Request().URL.Path, "/kubepi/api/v1/", "", 1)
|
|
currentPath = strings.Replace(currentPath, "/kubepi/api/v1/", "", 1)
|
|
if strings.HasSuffix(path, "search") {
|
|
ctx.Next()
|
|
return
|
|
}
|
|
|
|
u := ctx.Values().Get("profile")
|
|
profile := u.(session.UserProfile)
|
|
var log v1System.OperationLog
|
|
log.Operator = profile.Name
|
|
log.Operation = method
|
|
|
|
pathResource := strings.Split(path, "/")
|
|
if strings.HasPrefix(currentPath, "clusters/:name") {
|
|
if len(pathResource) < 3 {
|
|
log.OperationDomain = pathResource[0]
|
|
if method != "post" {
|
|
log.SpecificInformation = pathResource[1]
|
|
}
|
|
} else {
|
|
log.OperationDomain = fmt.Sprintf("%s_%s", pathResource[0], pathResource[2])
|
|
if method != "post" {
|
|
if len(pathResource) > 3 {
|
|
log.SpecificInformation = fmt.Sprintf("[%s] %s", pathResource[1], pathResource[3])
|
|
} else {
|
|
log.SpecificInformation = fmt.Sprintf("[%s] %s", pathResource[1], "-")
|
|
}
|
|
}
|
|
}
|
|
} else {
|
|
log.OperationDomain = strings.Split(currentPath, "/")[0]
|
|
if method != "post" {
|
|
if len(pathResource) > 1 {
|
|
log.SpecificInformation = pathResource[1]
|
|
} else {
|
|
log.SpecificInformation = "-"
|
|
}
|
|
}
|
|
}
|
|
|
|
if !strings.Contains(currentPath, "upload") {
|
|
if method == "post" {
|
|
var req logHelper
|
|
data, _ := ctx.GetBody()
|
|
if err := json.Unmarshal(data, &req); err != nil {
|
|
ctx.Next()
|
|
}
|
|
if len(req.Name) == 0 {
|
|
req.Name = req.Metadata.Name
|
|
}
|
|
if strings.HasPrefix(currentPath, "clusters/:name") {
|
|
log.SpecificInformation = fmt.Sprintf("[%s] %s", pathResource[1], req.Name)
|
|
} else {
|
|
log.SpecificInformation = req.Name
|
|
}
|
|
ctx.Request().Body = ioutil.NopCloser(bytes.NewBuffer(data))
|
|
}
|
|
}
|
|
systemService := v1SystemService.NewService()
|
|
go systemService.CreateOperationLog(&log, common.DBOptions{})
|
|
ctx.Next()
|
|
}
|
|
}
|
|
|
|
func resourceExtractHandler() iris.Handler {
|
|
return func(ctx *context.Context) {
|
|
path := ctx.Request().URL.Path
|
|
ss := strings.Split(path, "/")
|
|
// "" "api" "v1" "resource"
|
|
if len(ss) >= 5 {
|
|
ctx.Values().Set("resource", ss[4])
|
|
}
|
|
ctx.Next()
|
|
}
|
|
}
|
|
|
|
func roleHandler() iris.Handler {
|
|
return func(ctx *context.Context) {
|
|
// 查询当前用户的角色
|
|
// 查询角色的 rolebinding 获取 roles
|
|
p := ctx.Values().Get("profile")
|
|
u := p.(session.UserProfile)
|
|
|
|
if u.IsAdministrator {
|
|
ctx.Next()
|
|
return
|
|
}
|
|
roleBindingService := v1RoleBindingService.NewService()
|
|
rbs, err := roleBindingService.GetRoleBindingBySubject(v1Role.Subject{
|
|
Kind: "User",
|
|
Name: u.Name,
|
|
}, common.DBOptions{})
|
|
if err != nil {
|
|
if !errors.As(err, &storm.ErrNotFound) {
|
|
ctx.StatusCode(iris.StatusInternalServerError)
|
|
ctx.Values().Set("message", err.Error())
|
|
return
|
|
}
|
|
}
|
|
roleNameHash := map[string]struct{}{}
|
|
for i := range rbs {
|
|
roleName := rbs[i].RoleRef
|
|
roleNameHash[roleName] = struct{}{}
|
|
}
|
|
var roleNames []string
|
|
for key := range roleNameHash {
|
|
roleNames = append(roleNames, key)
|
|
}
|
|
|
|
roleService := v1RoleService.NewService()
|
|
rs, err := roleService.GetByNames(roleNames, common.DBOptions{})
|
|
if err != nil {
|
|
ctx.StatusCode(iris.StatusInternalServerError)
|
|
ctx.Values().Set("message", err.Error())
|
|
return
|
|
}
|
|
|
|
ctx.Values().Set("roles", rs)
|
|
ctx.Next()
|
|
}
|
|
}
|
|
|
|
func getVerbByRoute(path, method string) string {
|
|
switch strings.ToLower(method) {
|
|
case "put":
|
|
return "update"
|
|
case "delete":
|
|
return "delete"
|
|
case "get":
|
|
if strings.Contains(path, "/:name") {
|
|
return "get"
|
|
} else {
|
|
return "list"
|
|
}
|
|
case "post":
|
|
if strings.HasSuffix(path, "search") {
|
|
return "list"
|
|
}
|
|
return "create"
|
|
|
|
}
|
|
return ""
|
|
}
|
|
|
|
func apiResourceHandler(party iris.Party) iris.Handler {
|
|
return func(ctx *context.Context) {
|
|
//1. 确定所有的api资源有哪些
|
|
apiBuilder := party.(*router.APIBuilder)
|
|
routes := apiBuilder.GetRoutes()
|
|
resourceMap := map[string]*collectons.StringSet{}
|
|
for i := range routes {
|
|
if strings.HasPrefix(routes[i].Path, "/kubepi/api/v1/") {
|
|
ss := strings.Split(routes[i].Path, "/")
|
|
if len(ss) >= 5 {
|
|
resourceName := ss[4]
|
|
//过滤session资源
|
|
if resourceWhiteList.In(resourceName) {
|
|
continue
|
|
}
|
|
if _, ok := resourceMap[resourceName]; !ok {
|
|
resourceMap[resourceName] = collectons.NewStringSet()
|
|
}
|
|
resourceMap[resourceName].Add(getVerbByRoute(routes[i].Path, routes[i].Method))
|
|
}
|
|
}
|
|
}
|
|
displayMap := map[string][]string{}
|
|
for k := range resourceMap {
|
|
verbs := resourceMap[k]
|
|
if len(verbs.ToSlice()) > 0 {
|
|
displayMap[k] = verbs.ToSlice()
|
|
}
|
|
}
|
|
ctx.Values().Set("data", displayMap)
|
|
}
|
|
}
|
|
|
|
func roleAccessHandler() iris.Handler {
|
|
return func(ctx *context.Context) {
|
|
//// 查询角色的 resources
|
|
//// 通过api resource 过滤出来资源主体,method 过滤操作
|
|
p := ctx.Values().Get("profile")
|
|
u := p.(session.UserProfile)
|
|
isInWhiteList := false
|
|
for _, path := range resourceWhiteList {
|
|
if strings.Contains(ctx.Request().URL.Path, fmt.Sprintf("/%s", path)) && path != "sessions" {
|
|
isInWhiteList = true
|
|
break
|
|
}
|
|
}
|
|
if !isInWhiteList {
|
|
// 放通admin权限
|
|
if u.IsAdministrator {
|
|
ctx.Next()
|
|
return
|
|
}
|
|
rs := ctx.Values().Get("roles")
|
|
roles := rs.([]v1Role.Role)
|
|
requestResource := ctx.Values().GetString("resource")
|
|
if requestResource != "" {
|
|
currentRoute := ctx.GetCurrentRoute()
|
|
requestVerb := getVerbByRoute(currentRoute.Path(), currentRoute.Method())
|
|
resourceMatched, methodMatch := matchRoles(requestResource, requestVerb, roles)
|
|
if !(resourceMatched && methodMatch) {
|
|
ctx.StopWithStatus(iris.StatusForbidden)
|
|
ctx.Values().Set("message", []string{"user %s can not access resource %s %s", u.Name, requestResource, requestVerb})
|
|
return
|
|
}
|
|
}
|
|
}
|
|
|
|
ctx.Next()
|
|
}
|
|
}
|
|
|
|
func matchRoles(requestResource, requestMethod string, rs []v1Role.Role) (bool, bool) {
|
|
resourceMatch := false
|
|
methodMatch := false
|
|
for i := range rs {
|
|
for j := range rs[i].Rules {
|
|
for k := range rs[i].Rules[j].Resource {
|
|
if rs[i].Rules[j].Resource[k] == requestResource || rs[i].Rules[j].Resource[k] == "*" {
|
|
resourceMatch = true
|
|
for x := range rs[i].Rules[j].Verbs {
|
|
if rs[i].Rules[j].Verbs[x] == requestMethod || rs[i].Rules[j].Verbs[x] == "*" {
|
|
methodMatch = true
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return resourceMatch, methodMatch
|
|
}
|
|
|
|
func resourceNameInvalidHandler() iris.Handler {
|
|
return func(ctx *context.Context) {
|
|
r := ctx.GetCurrentRoute()
|
|
if strings.Contains(r.Path(), "/:name") {
|
|
resourceName := ctx.Params().GetString("name")
|
|
if resourceName == "" {
|
|
ctx.StatusCode(iris.StatusBadRequest)
|
|
ctx.Values().Set("message", fmt.Sprintf("invalid resource name %s", resourceName))
|
|
return
|
|
}
|
|
}
|
|
ctx.Next()
|
|
}
|
|
}
|
|
|
|
func WarpedJwtHandler() iris.Handler {
|
|
|
|
verifier := jwt.NewVerifier(jwt.HS256, session.JwtSigKey)
|
|
verifier.WithDefaultBlocklist()
|
|
verifyMiddleware := verifier.Verify(func() interface{} {
|
|
return new(session.UserProfile)
|
|
})
|
|
return func(ctx *context.Context) {
|
|
sess := sessions.Get(ctx)
|
|
if sess.Get("profile") != nil {
|
|
ctx.Next()
|
|
return
|
|
}
|
|
verifyMiddleware(ctx)
|
|
}
|
|
}
|
|
|
|
func AddV1Route(app iris.Party) {
|
|
|
|
v1Party := app.Party("/v1")
|
|
v1Party.Use(langHandler())
|
|
v1Party.Use(pageHandler())
|
|
session.Install(v1Party)
|
|
mfa.Install(v1Party)
|
|
authParty := v1Party.Party("")
|
|
|
|
authParty.Use(WarpedJwtHandler())
|
|
authParty.Use(authHandler())
|
|
authParty.Use(resourceExtractHandler())
|
|
authParty.Use(roleHandler())
|
|
authParty.Use(roleAccessHandler())
|
|
authParty.Use(resourceNameInvalidHandler())
|
|
authParty.Use(logHandler())
|
|
authParty.Get("/", apiResourceHandler(authParty))
|
|
user.Install(authParty)
|
|
cluster.Install(authParty)
|
|
role.Install(authParty)
|
|
system.Install(v1Party)
|
|
proxy.Install(authParty)
|
|
ws.Install(authParty)
|
|
chart.Install(authParty)
|
|
webkubectl.Install(authParty, v1Party)
|
|
ldap.Install(authParty)
|
|
imagerepo.Install(authParty)
|
|
file.Install(authParty)
|
|
}
|