diff --git a/.env.example b/.env.example new file mode 100644 index 0000000..202ec51 --- /dev/null +++ b/.env.example @@ -0,0 +1,8 @@ +HTTP_SERVER_HOST=0.0.0.0 +HTTP_SERVER_PORT=8080 +HTTP_SERVER_READ_TIMEOUT=5s +HTTP_SERVER_READ_HEADER_TIMEOUT=5s +HTTP_SERVER_WRITE_TIMEOUT=5s +HTTP_SERVER_IDLE_TIMEOUT=2m +DB_URI="mongodb://admin:password@localhost:27017/bambooFirewall?authSource=admin&?w=majority&socketTimeoutMS=3000" +LOGGING=false \ No newline at end of file diff --git a/.gitignore b/.gitignore index cfd6bf3..8051283 100644 --- a/.gitignore +++ b/.gitignore @@ -1,47 +1,26 @@ +# If you prefer the allow list template instead of the deny list, see community template: +# https://github.com/github/gitignore/blob/main/community/Golang/Go.AllowList.gitignore +# # Binaries for programs and plugins *.exe *.exe~ *.dll *.so *.dylib -create_admin +_output/ -# Test binary, build with `go test -c` +# Test binary, built with `go test -c` *.test # Output of the go coverage tool, specifically when used with LiteIDE *.out -# Dependency directories (created by `go get`) -/bin/ -/pkg/ +# Dependency directories (remove the comment below to include it) +# vendor/ -# Generated files -*.pb.go -*.pb.gw.go -*.swagger.json +# Go workspace file +go.work +go.work.sum -# IDE-specific files (e.g., Visual Studio Code, IntelliJ IDEA) -.vscode/ -.idea/ - -# Logs and error files -*.log -*.log.* - -# Operating System-generated files -.DS_Store -Thumbs.db - -# Output directories for coverage and profiling tools -/cover -/profile - -# Binary release files -/dist/ - -# Go module cache directory -/go.sum - -# Ignore vendor folder if using dep or Go modules -/vendor/ +# env file +.env \ No newline at end of file diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..cd81d8e --- /dev/null +++ b/Makefile @@ -0,0 +1,24 @@ +SERVER_DIR = ./cmd/server +SERVER_BIN_NAME = bamboo-apiserver +CLI_DIR = ./cmd/bamboofwcli +CLI_BIN_NAME = bbfw + +.PHONY: all-platform +all-platform: + build/build.sh $(SERVER_DIR) $(SERVER_BIN_NAME) all + build/build.sh $(CLI_DIR) $(CLI_BIN_NAME) all + +.PHONY: all +all: build-server build-bbfw + +.PHONY: build-server +build-server: + build/build.sh $(SERVER_DIR) $(SERVER_BIN_NAME) + +.PHONY: build-bbfw +build-bbfw: + build/build.sh $(CLI_DIR) $(CLI_BIN_NAME) + +.PHONY: clean +clean: + build/clean.sh diff --git a/api/handler/gns_handler.go b/api/handler/gns_handler.go deleted file mode 100644 index 49471cd..0000000 --- a/api/handler/gns_handler.go +++ /dev/null @@ -1,54 +0,0 @@ -package handler - -import ( - "net/http" - - "github.com/bamboo-firewall/be/domain" - models "github.com/bamboo-firewall/watcher/model" - "github.com/gin-gonic/gin" -) - -type GNSHandler struct { - GNSUsecase domain.GNSUsecase -} - -func (hh *GNSHandler) convertToCalicoObjectResponse(gns []models.GlobalNetworkSet) []domain.CalicoObjectResponse { - response := make([]domain.CalicoObjectResponse, len(gns)) - for i, item := range gns { - response[i] = domain.CalicoObjectResponse{ - Kind: item.Kind, - ApiVersion: item.ApiVersion, - Metadata: item.Metadata, - Spec: item.Spec, - } - - } - return response -} - -func (hh *GNSHandler) Fetch(c *gin.Context) { - gns, err := hh.GNSUsecase.Fetch(c) - if err != nil { - c.JSON(http.StatusInternalServerError, domain.ErrorResponse{Message: err.Error()}) - return - } - c.JSON(http.StatusOK, hh.convertToCalicoObjectResponse(gns)) -} - -func (hh *GNSHandler) Search(c *gin.Context) { - var request domain.SearchRequest - - err := c.ShouldBind(&request) - if err != nil { - c.JSON(http.StatusBadRequest, domain.ErrorResponse{Message: err.Error()}) - return - } - - gns, err := hh.GNSUsecase.Search(c, request.Options) - - if err != nil { - c.JSON(http.StatusInternalServerError, domain.ErrorResponse{Message: err.Error()}) - return - } - c.JSON(http.StatusOK, hh.convertToCalicoObjectResponse(gns)) -} diff --git a/api/handler/hep_handler.go b/api/handler/hep_handler.go deleted file mode 100644 index 6d1a1fd..0000000 --- a/api/handler/hep_handler.go +++ /dev/null @@ -1,54 +0,0 @@ -package handler - -import ( - "net/http" - - "github.com/bamboo-firewall/be/domain" - models "github.com/bamboo-firewall/watcher/model" - "github.com/gin-gonic/gin" -) - -type HEPHandler struct { - HEPUsecase domain.HEPUsecase -} - -func (hh *HEPHandler) convertToCalicoObjectResponse(heps []models.HostEndPoint) []domain.CalicoObjectResponse { - response := make([]domain.CalicoObjectResponse, len(heps)) - for i, hep := range heps { - response[i] = domain.CalicoObjectResponse{ - Kind: hep.Kind, - ApiVersion: hep.ApiVersion, - Metadata: hep.Metadata, - Spec: hep.Spec, - } - - } - return response -} - -func (hh *HEPHandler) Fetch(c *gin.Context) { - heps, err := hh.HEPUsecase.Fetch(c) - if err != nil { - c.JSON(http.StatusInternalServerError, domain.ErrorResponse{Message: err.Error()}) - return - } - c.JSON(http.StatusOK, hh.convertToCalicoObjectResponse(heps)) -} - -func (hh *HEPHandler) Search(c *gin.Context) { - var request domain.SearchRequest - - err := c.ShouldBind(&request) - if err != nil { - c.JSON(http.StatusBadRequest, domain.ErrorResponse{Message: err.Error()}) - return - } - - heps, err := hh.HEPUsecase.Search(c, request.Options) - - if err != nil { - c.JSON(http.StatusInternalServerError, domain.ErrorResponse{Message: err.Error()}) - return - } - c.JSON(http.StatusOK, hh.convertToCalicoObjectResponse(heps)) -} diff --git a/api/handler/login_handler.go b/api/handler/login_handler.go deleted file mode 100644 index c21216e..0000000 --- a/api/handler/login_handler.go +++ /dev/null @@ -1,57 +0,0 @@ -package handler - -import ( - "net/http" - - "github.com/bamboo-firewall/be/bootstrap" - "github.com/bamboo-firewall/be/domain" - "golang.org/x/crypto/bcrypt" - - "github.com/gin-gonic/gin" -) - -type LoginHandler struct { - LoginUsecase domain.LoginUsecase - Env *bootstrap.Env -} - -func (lc *LoginHandler) Login(c *gin.Context) { - var request domain.LoginRequest - - err := c.ShouldBind(&request) - if err != nil { - c.JSON(http.StatusBadRequest, domain.ErrorResponse{Message: err.Error()}) - return - } - - user, err := lc.LoginUsecase.GetUserByUsername(c, request.Username) - if err != nil { - c.JSON(http.StatusNotFound, domain.ErrorResponse{Message: "User not found"}) - return - } - - if bcrypt.CompareHashAndPassword([]byte(user.Password), []byte(request.Password)) != nil { - c.JSON(http.StatusUnauthorized, domain.ErrorResponse{Message: "Invalid credentials"}) - return - } - - accessToken, err := lc.LoginUsecase.CreateAccessToken(&user, lc.Env.AccessTokenSecret, lc.Env.AccessTokenExpiryHour) - if err != nil { - c.JSON(http.StatusInternalServerError, domain.ErrorResponse{Message: err.Error()}) - return - } - - refreshToken, err := lc.LoginUsecase.CreateRefreshToken(&user, lc.Env.RefreshTokenSecret, lc.Env.RefreshTokenExpiryHour) - if err != nil { - c.JSON(http.StatusInternalServerError, domain.ErrorResponse{Message: err.Error()}) - return - } - - loginResponse := domain.LoginResponse{ - AccessToken: accessToken, - RefreshToken: refreshToken, - User: user, - } - - c.JSON(http.StatusOK, loginResponse) -} diff --git a/api/handler/option_handler.go b/api/handler/option_handler.go deleted file mode 100644 index 6874ef4..0000000 --- a/api/handler/option_handler.go +++ /dev/null @@ -1,42 +0,0 @@ -package handler - -import ( - "net/http" - - "github.com/bamboo-firewall/be/domain" - "github.com/gin-gonic/gin" -) - -type OptionHandler struct { - HEPUsecase domain.HEPUsecase - GNSUsecase domain.GNSUsecase - PolicyUsecase domain.PolicyUsecase -} - -func (hh *OptionHandler) FetchByType(c *gin.Context) { - var request domain.FetchOptionRequest - err := c.ShouldBind(&request) - if err != nil { - c.JSON(http.StatusBadRequest, domain.ErrorResponse{Message: err.Error()}) - return - } - if !(request.Type == domain.CollectionHEP || request.Type == domain.CollectionPolicy || request.Type == domain.CollectionGNS) { - c.JSON(http.StatusNotFound, domain.ErrorResponse{Message: "Type is not in valid!"}) - } - var options []domain.Option - - switch request.Type { - case domain.CollectionHEP: - options, err = hh.HEPUsecase.GetOptions(c, request.Filter, request.Label) - case domain.CollectionGNS: - options, err = hh.GNSUsecase.GetOptions(c, request.Filter, request.Label) - case domain.CollectionPolicy: - options, err = hh.PolicyUsecase.GetOptions(c, request.Filter, request.Label) - } - - if err != nil { - c.JSON(http.StatusInternalServerError, domain.ErrorResponse{Message: err.Error()}) - return - } - c.JSON(http.StatusOK, options) -} diff --git a/api/handler/policy_handler.go b/api/handler/policy_handler.go deleted file mode 100644 index aede602..0000000 --- a/api/handler/policy_handler.go +++ /dev/null @@ -1,54 +0,0 @@ -package handler - -import ( - "net/http" - - "github.com/bamboo-firewall/be/domain" - models "github.com/bamboo-firewall/watcher/model" - "github.com/gin-gonic/gin" -) - -type PolicyHandler struct { - PolicyUsecase domain.PolicyUsecase -} - -func (h *PolicyHandler) convertToCalicoObjectResponse(policies []models.GlobalNetworkPolicies) []domain.CalicoObjectResponse { - response := make([]domain.CalicoObjectResponse, len(policies)) - for i, item := range policies { - response[i] = domain.CalicoObjectResponse{ - Kind: item.Kind, - ApiVersion: item.ApiVersion, - Metadata: item.Metadata, - Spec: item.Spec, - } - - } - return response -} - -func (h *PolicyHandler) Fetch(c *gin.Context) { - items, err := h.PolicyUsecase.Fetch(c) - if err != nil { - c.JSON(http.StatusInternalServerError, domain.ErrorResponse{Message: err.Error()}) - return - } - c.JSON(http.StatusOK, h.convertToCalicoObjectResponse(items)) -} - -func (hh *PolicyHandler) Search(c *gin.Context) { - var request domain.SearchRequest - - err := c.ShouldBind(&request) - if err != nil { - c.JSON(http.StatusBadRequest, domain.ErrorResponse{Message: err.Error()}) - return - } - - policies, err := hh.PolicyUsecase.Search(c, request.Options) - - if err != nil { - c.JSON(http.StatusInternalServerError, domain.ErrorResponse{Message: err.Error()}) - return - } - c.JSON(http.StatusOK, hh.convertToCalicoObjectResponse(policies)) -} diff --git a/api/handler/profile_handler.go b/api/handler/profile_handler.go deleted file mode 100644 index 36aff39..0000000 --- a/api/handler/profile_handler.go +++ /dev/null @@ -1,75 +0,0 @@ -package handler - -import ( - "net/http" - - "github.com/bamboo-firewall/be/domain" - "github.com/gin-gonic/gin" - "golang.org/x/crypto/bcrypt" -) - -type ProfileHandler struct { - ProfileUsecase domain.ProfileUsecase - UserUsecase domain.UserUsecase -} - -func (pc *ProfileHandler) Fetch(c *gin.Context) { - userID := c.GetString("x-user-id") - - profile, err := pc.ProfileUsecase.GetProfileByID(c, userID) - if err != nil { - c.JSON(http.StatusInternalServerError, domain.ErrorResponse{Message: err.Error()}) - return - } - - c.JSON(http.StatusOK, profile) -} - -func (pc *ProfileHandler) Update(c *gin.Context) { - var request domain.RequestUpdateProfile - if err := c.ShouldBindJSON(&request); err != nil { - c.JSON(http.StatusBadRequest, domain.ErrorResponse{Message: err.Error()}) - return - } - - userID := c.GetString("x-user-id") - - user, err := pc.UserUsecase.GetUserByID(c, userID) - - if err != nil { - c.JSON(http.StatusInternalServerError, domain.ErrorResponse{Message: err.Error()}) - return - } - - if request.Name != "" { - user.Name = request.Name - } - - if request.Password != "" { - encryptedPassword, err := bcrypt.GenerateFromPassword( - []byte(request.Password), - bcrypt.DefaultCost, - ) - if err != nil { - c.JSON(http.StatusInternalServerError, domain.ErrorResponse{Message: err.Error()}) - return - } - - request.Password = string(encryptedPassword) - user.Password = request.Password - } - - err = pc.UserUsecase.Update(c, &user) - if err != nil { - c.JSON(http.StatusInternalServerError, domain.ErrorResponse{Message: err.Error()}) - return - } - - profile, err := pc.ProfileUsecase.GetProfileByID(c, userID) - if err != nil { - c.JSON(http.StatusInternalServerError, domain.ErrorResponse{Message: err.Error()}) - return - } - - c.JSON(http.StatusOK, domain.SuccessResponse{Message: "Update profile successfully", Data: profile}) -} diff --git a/api/handler/refresh_token_handler.go b/api/handler/refresh_token_handler.go deleted file mode 100644 index 97361a2..0000000 --- a/api/handler/refresh_token_handler.go +++ /dev/null @@ -1,56 +0,0 @@ -package handler - -import ( - "net/http" - - "github.com/bamboo-firewall/be/bootstrap" - "github.com/bamboo-firewall/be/domain" - - "github.com/gin-gonic/gin" -) - -type RefreshTokenHandler struct { - RefreshTokenUsecase domain.RefreshTokenUsecase - Env *bootstrap.Env -} - -func (rtc *RefreshTokenHandler) RefreshToken(c *gin.Context) { - var request domain.RefreshTokenRequest - - err := c.ShouldBind(&request) - if err != nil { - c.JSON(http.StatusBadRequest, domain.ErrorResponse{Message: err.Error()}) - return - } - - id, err := rtc.RefreshTokenUsecase.ExtractIDFromToken(request.RefreshToken, rtc.Env.RefreshTokenSecret) - if err != nil { - c.JSON(http.StatusUnauthorized, domain.ErrorResponse{Message: "User not found"}) - return - } - - user, err := rtc.RefreshTokenUsecase.GetUserByID(c, id) - if err != nil { - c.JSON(http.StatusUnauthorized, domain.ErrorResponse{Message: "User not found"}) - return - } - - accessToken, err := rtc.RefreshTokenUsecase.CreateAccessToken(&user, rtc.Env.AccessTokenSecret, rtc.Env.AccessTokenExpiryHour) - if err != nil { - c.JSON(http.StatusInternalServerError, domain.ErrorResponse{Message: err.Error()}) - return - } - - refreshToken, err := rtc.RefreshTokenUsecase.CreateRefreshToken(&user, rtc.Env.RefreshTokenSecret, rtc.Env.RefreshTokenExpiryHour) - if err != nil { - c.JSON(http.StatusInternalServerError, domain.ErrorResponse{Message: err.Error()}) - return - } - - refreshTokenResponse := domain.RefreshTokenResponse{ - AccessToken: accessToken, - RefreshToken: refreshToken, - } - - c.JSON(http.StatusOK, refreshTokenResponse) -} diff --git a/api/handler/signup_handler.go b/api/handler/signup_handler.go deleted file mode 100644 index 30ca204..0000000 --- a/api/handler/signup_handler.go +++ /dev/null @@ -1,82 +0,0 @@ -package handler - -import ( - "net/http" - - "github.com/bamboo-firewall/be/bootstrap" - "github.com/bamboo-firewall/be/domain" - "github.com/casbin/casbin/v2" - "github.com/gin-gonic/gin" - "go.mongodb.org/mongo-driver/bson/primitive" - "golang.org/x/crypto/bcrypt" -) - -type SignupHandler struct { - SignupUsecase domain.SignupUsecase - Enforcer *casbin.Enforcer - Env *bootstrap.Env -} - -func (sc *SignupHandler) Signup(c *gin.Context) { - var request domain.SignupRequest - - err := c.ShouldBind(&request) - if err != nil { - c.JSON(http.StatusBadRequest, domain.ErrorResponse{Message: err.Error()}) - return - } - - _, err = sc.SignupUsecase.GetUserByEmail(c, request.Email) - if err == nil { - c.JSON(http.StatusConflict, domain.ErrorResponse{Message: "User already exists with the given email"}) - return - } - - encryptedPassword, err := bcrypt.GenerateFromPassword( - []byte(request.Password), - bcrypt.DefaultCost, - ) - if err != nil { - c.JSON(http.StatusInternalServerError, domain.ErrorResponse{Message: err.Error()}) - return - } - - request.Password = string(encryptedPassword) - - user := domain.User{ - ID: primitive.NewObjectID(), - Name: request.Name, - Username: request.Username, - Email: request.Email, - Password: request.Password, - Role: "devops", // signup-function only for devops - } - - err = sc.SignupUsecase.Create(c, &user) - if err != nil { - c.JSON(http.StatusInternalServerError, domain.ErrorResponse{Message: err.Error()}) - return - } - - accessToken, err := sc.SignupUsecase.CreateAccessToken(&user, sc.Env.AccessTokenSecret, sc.Env.AccessTokenExpiryHour) - if err != nil { - c.JSON(http.StatusInternalServerError, domain.ErrorResponse{Message: err.Error()}) - return - } - - refreshToken, err := sc.SignupUsecase.CreateRefreshToken(&user, sc.Env.RefreshTokenSecret, sc.Env.RefreshTokenExpiryHour) - if err != nil { - c.JSON(http.StatusInternalServerError, domain.ErrorResponse{Message: err.Error()}) - return - } - - sc.Enforcer.AddGroupingPolicy(user.ID.Hex(), user.Role) - - signupResponse := domain.SignupResponse{ - AccessToken: accessToken, - RefreshToken: refreshToken, - User: user, - } - - c.JSON(http.StatusOK, signupResponse) -} diff --git a/api/handler/statistic_handler.go b/api/handler/statistic_handler.go deleted file mode 100644 index ed12445..0000000 --- a/api/handler/statistic_handler.go +++ /dev/null @@ -1,34 +0,0 @@ -package handler - -import ( - "net/http" - - "github.com/bamboo-firewall/be/domain" - "github.com/gin-gonic/gin" -) - -type StatisticHandler struct { - StatisticUsecase domain.StatisticUsecase -} - -func (hh *StatisticHandler) GetSummary(c *gin.Context) { - summary, err := hh.StatisticUsecase.GetSummary(c) - if err != nil { - c.JSON(http.StatusInternalServerError, domain.ErrorResponse{Message: err.Error()}) - return - } - c.JSON(http.StatusOK, domain.SummaryResponse{ - Summary: summary, - }) -} - -func (hh *StatisticHandler) GetProjectSummary(c *gin.Context) { - projectSummary, err := hh.StatisticUsecase.GetProjectSummary(c) - if err != nil { - c.JSON(http.StatusInternalServerError, domain.ErrorResponse{Message: err.Error()}) - return - } - c.JSON(http.StatusOK, domain.ProjectSummaryResponse{ - ProjectSummary: projectSummary, - }) -} diff --git a/api/handler/user_handler.go b/api/handler/user_handler.go deleted file mode 100644 index 55755df..0000000 --- a/api/handler/user_handler.go +++ /dev/null @@ -1,143 +0,0 @@ -package handler - -import ( - "net/http" - - "github.com/bamboo-firewall/be/domain" - "github.com/casbin/casbin/v2" - "github.com/gin-gonic/gin" - "go.mongodb.org/mongo-driver/bson/primitive" - "golang.org/x/crypto/bcrypt" -) - -type UserHandler struct { - UserUsecase domain.UserUsecase - Enforcer *casbin.Enforcer -} - -func (h *UserHandler) Fetch(c *gin.Context) { - items, err := h.UserUsecase.Fetch(c) - if err != nil { - c.JSON(http.StatusInternalServerError, domain.ErrorResponse{Message: err.Error()}) - return - } - c.JSON(http.StatusOK, items) -} - -func (h *UserHandler) Create(c *gin.Context) { - var request domain.CreateUserRequest - - err := c.ShouldBind(&request) - if err != nil { - c.JSON(http.StatusBadRequest, domain.ErrorResponse{Message: err.Error()}) - return - } - - _, err = h.UserUsecase.GetUserByEmail(c, request.Email) - if err == nil { - c.JSON(http.StatusConflict, domain.ErrorResponse{Message: "User already exists with the given email"}) - return - } - - encryptedPassword, err := bcrypt.GenerateFromPassword( - []byte(request.Password), - bcrypt.DefaultCost, - ) - if err != nil { - c.JSON(http.StatusInternalServerError, domain.ErrorResponse{Message: err.Error()}) - return - } - - request.Password = string(encryptedPassword) - - user := domain.User{ - ID: primitive.NewObjectID(), - Name: request.Name, - Username: request.Username, - Email: request.Email, - Password: request.Password, - Role: request.Role, - } - - err = h.UserUsecase.Create(c, &user) - if err != nil { - c.JSON(http.StatusInternalServerError, domain.ErrorResponse{Message: err.Error()}) - return - } - - h.Enforcer.AddGroupingPolicy(user.ID.Hex(), user.Role) - - c.JSON(http.StatusOK, user) -} - -func (h *UserHandler) Update(c *gin.Context) { - var request domain.UpdateUserRequest - - err := c.ShouldBind(&request) - if err != nil { - c.JSON(http.StatusBadRequest, domain.ErrorResponse{Message: err.Error()}) - return - } - - user, err := h.UserUsecase.GetUserByID(c, request.ID) - if err != nil { - c.JSON(http.StatusNotFound, domain.ErrorResponse{Message: "User not found"}) - return - } - - encryptedPassword, err := bcrypt.GenerateFromPassword( - []byte(request.Password), - bcrypt.DefaultCost, - ) - if err != nil { - c.JSON(http.StatusInternalServerError, domain.ErrorResponse{Message: err.Error()}) - return - } - - request.Password = string(encryptedPassword) - - if request.Password != "" { - user.Password = request.Password - } - if request.Name != "" { - user.Name = request.Name - } - if request.Role != "" { - user.Role = request.Role - } - - err = h.UserUsecase.Update(c, &user) - if err != nil { - c.JSON(http.StatusInternalServerError, domain.ErrorResponse{Message: err.Error()}) - return - } - - h.Enforcer.AddGroupingPolicy(user.ID.Hex(), user.Role) - - c.JSON(http.StatusOK, user) -} - -func (h *UserHandler) DeleteById(c *gin.Context) { - var request domain.DeleteUserRequest - - err := c.ShouldBind(&request) - if err != nil { - c.JSON(http.StatusBadRequest, domain.ErrorResponse{Message: err.Error()}) - return - } - - err = h.UserUsecase.DeleteById(c, request.ID) - - if err != nil { - c.JSON(http.StatusInternalServerError, domain.ErrorResponse{Message: err.Error()}) - return - } - - h.Enforcer.RemoveGroupingPolicy(request.ID) - - if err != nil { - c.JSON(http.StatusInternalServerError, domain.ErrorResponse{Message: err.Error()}) - } - - c.JSON(http.StatusOK, domain.SuccessResponse{Message: "User deleted successfully!", Data: nil}) -} diff --git a/api/middleware/casbin_middleware.go b/api/middleware/casbin_middleware.go deleted file mode 100644 index fd1f08c..0000000 --- a/api/middleware/casbin_middleware.go +++ /dev/null @@ -1,40 +0,0 @@ -package middleware - -import ( - "github.com/bamboo-firewall/be/domain" - "github.com/casbin/casbin/v2" - "github.com/gin-gonic/gin" - "net/http" -) - -func Authorize(obj string, act string, enforcer *casbin.Enforcer) gin.HandlerFunc { - return func(c *gin.Context) { - // Get current user/subject - sub, existed := c.Get("x-user-id") - if !existed { - c.AbortWithStatusJSON(http.StatusUnauthorized, domain.ErrorResponse{Message: "The request unauthorized"}) - return - } - - // Load policy from Database - err := enforcer.LoadPolicy() - if err != nil { - c.AbortWithStatusJSON(http.StatusInternalServerError, domain.ErrorResponse{Message: "Failed to load policy"}) - return - } - - // Casbin enforces policy - ok, err := enforcer.Enforce(sub, obj, act) - - if err != nil { - c.AbortWithStatusJSON(http.StatusInternalServerError, domain.ErrorResponse{Message: "Error occurred when authorizing user"}) - return - } - - if !ok { - c.AbortWithStatusJSON(http.StatusForbidden, domain.ErrorResponse{Message: "You are not authorized"}) - return - } - c.Next() - } -} diff --git a/api/middleware/cors_middleware.go b/api/middleware/cors_middleware.go deleted file mode 100644 index c46c338..0000000 --- a/api/middleware/cors_middleware.go +++ /dev/null @@ -1,25 +0,0 @@ -package middleware - -import ( - "github.com/bamboo-firewall/be/bootstrap" - "github.com/gin-gonic/gin" - "net/http" -) - -func CORSMiddleware(env *bootstrap.Env) gin.HandlerFunc { - return func(c *gin.Context) { - // Set CORS headers - c.Writer.Header().Set("Access-Control-Allow-Origin", env.CORSAllowOrigin) - c.Writer.Header().Set("Access-Control-Allow-Methods", env.CORSAllowOMethods) - c.Writer.Header().Set("Access-Control-Allow-Headers", "Content-Type, Authorization") - - // Handle preflight requests - if c.Request.Method == "OPTIONS" { - c.AbortWithStatus(http.StatusNoContent) - return - } - - // Continue to the next middleware - c.Next() - } -} diff --git a/api/middleware/jwt_auth_middleware.go b/api/middleware/jwt_auth_middleware.go deleted file mode 100644 index 787fb55..0000000 --- a/api/middleware/jwt_auth_middleware.go +++ /dev/null @@ -1,37 +0,0 @@ -package middleware - -import ( - "net/http" - "strings" - - "github.com/bamboo-firewall/be/domain" - "github.com/bamboo-firewall/be/internal/tokenutil" - "github.com/gin-gonic/gin" -) - -func JwtAuthMiddleware(secret string) gin.HandlerFunc { - return func(c *gin.Context) { - authHeader := c.Request.Header.Get("Authorization") - t := strings.Split(authHeader, " ") - if len(t) == 2 { - authToken := t[1] - authorized, err := tokenutil.IsAuthorized(authToken, secret) - if authorized { - userID, err := tokenutil.ExtractIDFromToken(authToken, secret) - if err != nil { - c.JSON(http.StatusUnauthorized, domain.ErrorResponse{Message: err.Error()}) - c.Abort() - return - } - c.Set("x-user-id", userID) - c.Next() - return - } - c.JSON(http.StatusUnauthorized, domain.ErrorResponse{Message: err.Error()}) - c.Abort() - return - } - c.JSON(http.StatusUnauthorized, domain.ErrorResponse{Message: "Not authorized"}) - c.Abort() - } -} diff --git a/api/route/gns_route.go b/api/route/gns_route.go deleted file mode 100644 index bb1e07b..0000000 --- a/api/route/gns_route.go +++ /dev/null @@ -1,22 +0,0 @@ -package route - -import ( - "time" - - "github.com/bamboo-firewall/be/api/handler" - "github.com/bamboo-firewall/be/bootstrap" - "github.com/bamboo-firewall/be/domain" - "github.com/bamboo-firewall/be/mongo" - "github.com/bamboo-firewall/be/repository" - "github.com/bamboo-firewall/be/usecase" - "github.com/gin-gonic/gin" -) - -func NewGNSRouter(env *bootstrap.Env, timeout time.Duration, db mongo.Database, group *gin.RouterGroup) { - gr := repository.NewGNSRepository(db, domain.CollectionGNS) - gc := &handler.GNSHandler{ - GNSUsecase: usecase.NewGNSUsecase(gr, timeout), - } - group.POST("/gns/fetch", gc.Fetch) - group.POST("/gns/search", gc.Search) -} diff --git a/api/route/hep_route.go b/api/route/hep_route.go deleted file mode 100644 index e9ed41f..0000000 --- a/api/route/hep_route.go +++ /dev/null @@ -1,22 +0,0 @@ -package route - -import ( - "time" - - "github.com/bamboo-firewall/be/api/handler" - "github.com/bamboo-firewall/be/bootstrap" - "github.com/bamboo-firewall/be/domain" - "github.com/bamboo-firewall/be/mongo" - "github.com/bamboo-firewall/be/repository" - "github.com/bamboo-firewall/be/usecase" - "github.com/gin-gonic/gin" -) - -func NewHEPRouter(env *bootstrap.Env, timeout time.Duration, db mongo.Database, group *gin.RouterGroup) { - hr := repository.NewHEPRepository(db, domain.CollectionHEP) - hc := &handler.HEPHandler{ - HEPUsecase: usecase.NewHEPUsecase(hr, timeout), - } - group.POST("/hep/fetch", hc.Fetch) - group.POST("/hep/search", hc.Search) -} diff --git a/api/route/login_route.go b/api/route/login_route.go deleted file mode 100644 index 23978c1..0000000 --- a/api/route/login_route.go +++ /dev/null @@ -1,22 +0,0 @@ -package route - -import ( - "time" - - "github.com/bamboo-firewall/be/api/handler" - "github.com/bamboo-firewall/be/bootstrap" - "github.com/bamboo-firewall/be/domain" - "github.com/bamboo-firewall/be/mongo" - "github.com/bamboo-firewall/be/repository" - "github.com/bamboo-firewall/be/usecase" - "github.com/gin-gonic/gin" -) - -func NewLoginRouter(env *bootstrap.Env, timeout time.Duration, db mongo.Database, group *gin.RouterGroup) { - ur := repository.NewUserRepository(db, domain.CollectionUser) - lc := &handler.LoginHandler{ - LoginUsecase: usecase.NewLoginUsecase(ur, timeout), - Env: env, - } - group.POST("/login", lc.Login) -} diff --git a/api/route/option_route.go b/api/route/option_route.go deleted file mode 100644 index 9740ec3..0000000 --- a/api/route/option_route.go +++ /dev/null @@ -1,25 +0,0 @@ -package route - -import ( - "time" - - "github.com/bamboo-firewall/be/api/handler" - "github.com/bamboo-firewall/be/bootstrap" - "github.com/bamboo-firewall/be/domain" - "github.com/bamboo-firewall/be/mongo" - "github.com/bamboo-firewall/be/repository" - "github.com/bamboo-firewall/be/usecase" - "github.com/gin-gonic/gin" -) - -func NewOptionRoute(env *bootstrap.Env, timeout time.Duration, db mongo.Database, group *gin.RouterGroup) { - hepRepo := repository.NewHEPRepository(db, domain.CollectionHEP) - gnsRepo := repository.NewGNSRepository(db, domain.CollectionGNS) - policyRepo := repository.NewPolicyRepository(db, domain.CollectionPolicy) - hl := &handler.OptionHandler{ - HEPUsecase: usecase.NewHEPUsecase(hepRepo, timeout), - GNSUsecase: usecase.NewGNSUsecase(gnsRepo, timeout), - PolicyUsecase: usecase.NewPolicyUsecase(policyRepo, timeout), - } - group.POST("/options/fetch", hl.FetchByType) -} diff --git a/api/route/ping_route.go b/api/route/ping_route.go deleted file mode 100644 index 8cd31ff..0000000 --- a/api/route/ping_route.go +++ /dev/null @@ -1,16 +0,0 @@ -package route - -import ( - "net/http" - "time" - - "github.com/bamboo-firewall/be/bootstrap" - "github.com/bamboo-firewall/be/mongo" - "github.com/gin-gonic/gin" -) - -func NewPingRouter(env *bootstrap.Env, timeout time.Duration, db mongo.Database, group *gin.RouterGroup) { - group.GET("/ping", func(ctx *gin.Context) { - ctx.String(http.StatusOK, "pong") - }) -} diff --git a/api/route/policy_route.go b/api/route/policy_route.go deleted file mode 100644 index 096c571..0000000 --- a/api/route/policy_route.go +++ /dev/null @@ -1,22 +0,0 @@ -package route - -import ( - "time" - - "github.com/bamboo-firewall/be/api/handler" - "github.com/bamboo-firewall/be/bootstrap" - "github.com/bamboo-firewall/be/domain" - "github.com/bamboo-firewall/be/mongo" - "github.com/bamboo-firewall/be/repository" - "github.com/bamboo-firewall/be/usecase" - "github.com/gin-gonic/gin" -) - -func NewPolicyRouter(env *bootstrap.Env, timeout time.Duration, db mongo.Database, group *gin.RouterGroup) { - r := repository.NewPolicyRepository(db, domain.CollectionPolicy) - h := &handler.PolicyHandler{ - PolicyUsecase: usecase.NewPolicyUsecase(r, timeout), - } - group.POST("/policy/fetch", h.Fetch) - group.POST("/policy/search", h.Search) -} diff --git a/api/route/profile_route.go b/api/route/profile_route.go deleted file mode 100644 index 8d5f500..0000000 --- a/api/route/profile_route.go +++ /dev/null @@ -1,24 +0,0 @@ -package route - -import ( - "time" - - "github.com/bamboo-firewall/be/api/handler" - "github.com/bamboo-firewall/be/bootstrap" - "github.com/bamboo-firewall/be/domain" - "github.com/bamboo-firewall/be/mongo" - "github.com/bamboo-firewall/be/repository" - "github.com/bamboo-firewall/be/usecase" - "github.com/casbin/casbin/v2" - "github.com/gin-gonic/gin" -) - -func NewProfileRouter(env *bootstrap.Env, timeout time.Duration, db mongo.Database, group *gin.RouterGroup, enforcer *casbin.Enforcer) { - ur := repository.NewUserRepository(db, domain.CollectionUser) - pc := &handler.ProfileHandler{ - ProfileUsecase: usecase.NewProfileUsecase(ur, timeout), - UserUsecase: usecase.NewUserUsecase(ur, enforcer, timeout), - } - group.POST("/profile", pc.Fetch) - group.POST("/profile/update", pc.Update) -} diff --git a/api/route/refresh_token_route.go b/api/route/refresh_token_route.go deleted file mode 100644 index 7763a85..0000000 --- a/api/route/refresh_token_route.go +++ /dev/null @@ -1,23 +0,0 @@ -package route - -import ( - "time" - - "github.com/bamboo-firewall/be/api/handler" - "github.com/bamboo-firewall/be/bootstrap" - "github.com/bamboo-firewall/be/domain" - "github.com/bamboo-firewall/be/mongo" - "github.com/bamboo-firewall/be/repository" - "github.com/bamboo-firewall/be/usecase" - - "github.com/gin-gonic/gin" -) - -func NewRefreshTokenRouter(env *bootstrap.Env, timeout time.Duration, db mongo.Database, group *gin.RouterGroup) { - ur := repository.NewUserRepository(db, domain.CollectionUser) - rtc := &handler.RefreshTokenHandler{ - RefreshTokenUsecase: usecase.NewRefreshTokenUsecase(ur, timeout), - Env: env, - } - group.POST("/refresh", rtc.RefreshToken) -} diff --git a/api/route/route.go b/api/route/route.go deleted file mode 100644 index 1186b7c..0000000 --- a/api/route/route.go +++ /dev/null @@ -1,68 +0,0 @@ -package route - -import ( - "fmt" - "time" - - "github.com/bamboo-firewall/be/api/middleware" - "github.com/bamboo-firewall/be/bootstrap" - "github.com/bamboo-firewall/be/mongo" - "github.com/casbin/casbin/v2" - - mongodbadapter "github.com/casbin/mongodb-adapter/v3" - - "github.com/gin-gonic/gin" -) - -func Setup(env *bootstrap.Env, timeout time.Duration, db mongo.Database, gin *gin.Engine) { - gin.Use(middleware.CORSMiddleware(env)) - - adapterConfig := mongodbadapter.AdapterConfig{ - DatabaseName: env.DBName, - } - - adapter, err := mongodbadapter.NewAdapterByDB(db.Client().MongoClient(), &adapterConfig) - - if err != nil { - panic(err) - } - - enforcer, err := casbin.NewEnforcer("config/rbac_model.conf", adapter) - if err != nil { - panic(fmt.Sprintf("failed to create casbin enforcer: %v", err)) - } - - //add policy - if hasPolicy := enforcer.HasPolicy("admin", "user", "write"); !hasPolicy { - enforcer.AddPolicy("admin", "user", "write") - } - if hasPolicy := enforcer.HasPolicy("admin", "user", "read"); !hasPolicy { - enforcer.AddPolicy("admin", "user", "read") - } - if hasPolicy := enforcer.HasPolicy("devops", "user", "read"); !hasPolicy { - enforcer.AddPolicy("devops", "user", "read") - } - - publicRouter := gin.Group("api/") - // All Public APIs - NewPingRouter(env, timeout, db, publicRouter) - NewSignupRouter(env, timeout, db, publicRouter, enforcer) - NewLoginRouter(env, timeout, db, publicRouter) - NewRefreshTokenRouter(env, timeout, db, publicRouter) - - // Privated APIs - protectedRouter := gin.Group("api/v1/") - protectedRouter.Use(middleware.JwtAuthMiddleware(env.AccessTokenSecret)) - NewProfileRouter(env, timeout, db, protectedRouter, enforcer) - NewOptionRoute(env, timeout, db, protectedRouter) - NewHEPRouter(env, timeout, db, protectedRouter) - NewGNSRouter(env, timeout, db, protectedRouter) - NewPolicyRouter(env, timeout, db, protectedRouter) - NewStatisticRouter(env, timeout, db, protectedRouter) - - // Admin APIs - adminRouter := gin.Group("api/v1/admin/") - adminRouter.Use(middleware.JwtAuthMiddleware(env.AccessTokenSecret)) - adminRouter.Use(middleware.Authorize("user", "write", enforcer)) - NewUserRouter(env, timeout, db, adminRouter, enforcer) -} diff --git a/api/route/signup_route.go b/api/route/signup_route.go deleted file mode 100644 index 6eb79f5..0000000 --- a/api/route/signup_route.go +++ /dev/null @@ -1,24 +0,0 @@ -package route - -import ( - "time" - - "github.com/bamboo-firewall/be/api/handler" - "github.com/bamboo-firewall/be/bootstrap" - "github.com/bamboo-firewall/be/domain" - "github.com/bamboo-firewall/be/mongo" - "github.com/bamboo-firewall/be/repository" - "github.com/bamboo-firewall/be/usecase" - "github.com/casbin/casbin/v2" - "github.com/gin-gonic/gin" -) - -func NewSignupRouter(env *bootstrap.Env, timeout time.Duration, db mongo.Database, group *gin.RouterGroup, enforcer *casbin.Enforcer) { - ur := repository.NewUserRepository(db, domain.CollectionUser) - sc := handler.SignupHandler{ - SignupUsecase: usecase.NewSignupUsecase(ur, timeout), - Enforcer: enforcer, - Env: env, - } - group.POST("/signup", sc.Signup) -} diff --git a/api/route/statistic_route.go b/api/route/statistic_route.go deleted file mode 100644 index df13e48..0000000 --- a/api/route/statistic_route.go +++ /dev/null @@ -1,26 +0,0 @@ -package route - -import ( - "time" - - "github.com/bamboo-firewall/be/api/handler" - "github.com/bamboo-firewall/be/bootstrap" - "github.com/bamboo-firewall/be/domain" - "github.com/bamboo-firewall/be/mongo" - "github.com/bamboo-firewall/be/repository" - "github.com/bamboo-firewall/be/usecase" - "github.com/gin-gonic/gin" -) - -func NewStatisticRouter(env *bootstrap.Env, timeout time.Duration, db mongo.Database, group *gin.RouterGroup) { - hr := repository.NewHEPRepository(db, domain.CollectionHEP) - gr := repository.NewGNSRepository(db, domain.CollectionGNS) - pr := repository.NewPolicyRepository(db, domain.CollectionPolicy) - ur := repository.NewUserRepository(db, domain.CollectionUser) - - h := &handler.StatisticHandler{ - StatisticUsecase: usecase.NewStatisticUsecase(pr, gr, hr, ur, timeout), - } - group.POST("/statistic/summary", h.GetSummary) - group.POST("/statistic/project-summary", h.GetProjectSummary) -} diff --git a/api/route/user_route.go b/api/route/user_route.go deleted file mode 100644 index ac9bcb6..0000000 --- a/api/route/user_route.go +++ /dev/null @@ -1,26 +0,0 @@ -package route - -import ( - "time" - - "github.com/bamboo-firewall/be/api/handler" - "github.com/bamboo-firewall/be/bootstrap" - "github.com/bamboo-firewall/be/domain" - "github.com/bamboo-firewall/be/mongo" - "github.com/bamboo-firewall/be/repository" - "github.com/bamboo-firewall/be/usecase" - "github.com/casbin/casbin/v2" - "github.com/gin-gonic/gin" -) - -func NewUserRouter(env *bootstrap.Env, timeout time.Duration, db mongo.Database, group *gin.RouterGroup, enforcer *casbin.Enforcer) { - repo := repository.NewUserRepository(db, domain.CollectionUser) - hl := &handler.UserHandler{ - UserUsecase: usecase.NewUserUsecase(repo, enforcer, timeout), - Enforcer: enforcer, - } - group.POST("/user/fetch", hl.Fetch) - group.POST("/user/create", hl.Create) - group.POST("/user/update", hl.Update) - group.POST("/user/delete", hl.DeleteById) -} diff --git a/api/v1/dto/gnp.go b/api/v1/dto/gnp.go new file mode 100644 index 0000000..3cd48f4 --- /dev/null +++ b/api/v1/dto/gnp.go @@ -0,0 +1,86 @@ +package dto + +import "time" + +type GlobalNetworkPolicy struct { + ID string `json:"id" yaml:"id"` + UUID string `json:"uuid" yaml:"uuid"` + Version uint `json:"version" yaml:"version"` + Metadata GNPMetadata `json:"metadata,omitempty" yaml:"metadata,omitempty"` + Spec GNPSpec `json:"spec" yaml:"spec"` + Description string `json:"description,omitempty" yaml:"description"` + CreatedAt time.Time `json:"createdAt" yaml:"createdAt"` + UpdatedAt time.Time `json:"updatedAt" yaml:"updatedAt"` +} + +type GNPMetadata struct { + Name string `json:"name" yaml:"name"` + Labels map[string]string `json:"labels,omitempty" yaml:"labels"` +} + +type GNPSpec struct { + Selector string `json:"selector,omitempty" yaml:"selector"` + Ingress []GNPSpecRule `json:"ingress,omitempty" yaml:"ingress"` + Egress []GNPSpecRule `json:"egress,omitempty" yaml:"egress"` +} + +type GNPSpecRule struct { + Metadata map[string]string `json:"metadata,omitempty" yaml:"metadata"` + Action string `json:"action" yaml:"action"` + Protocol string `json:"protocol,omitempty" yaml:"protocol"` + NotProtocol string `json:"notProtocol,omitempty" yaml:"notProtocol"` + IPVersion int `json:"ipVersion" yaml:"ipVersion"` + Source *GNPSpecRuleEntity `json:"source,omitempty" yaml:"source"` + Destination *GNPSpecRuleEntity `json:"destination,omitempty" yaml:"destination"` +} + +type GNPSpecRuleEntity struct { + Selector string `json:"selector,omitempty" yaml:"selector"` + Nets []string `json:"nets,omitempty" yaml:"nets"` + NotNets []string `json:"notNets,omitempty" yaml:"notNets"` + Ports []interface{} `json:"ports,omitempty" yaml:"ports"` + NotPorts []interface{} `json:"notPorts,omitempty" yaml:"notPorts"` +} + +type CreateGlobalNetworkPolicyInput struct { + Metadata GNPMetadataInput `json:"metadata" yaml:"metadata" validate:"required"` + Spec GNPSpecInput `json:"spec" yaml:"spec" validate:"required"` + Description string `json:"description" yaml:"description"` +} + +type GNPMetadataInput struct { + Name string `json:"name" yaml:"name" validate:"required,name"` + Labels map[string]string `json:"labels" yaml:"labels"` +} + +type GNPSpecInput struct { + Selector string `json:"selector" yaml:"selector" validate:"omitempty,selector"` + Ingress []GNPSpecRuleInput `json:"ingress" yaml:"ingress" validate:"omitempty,min=1,dive"` + Egress []GNPSpecRuleInput `json:"egress" yaml:"egress" validate:"omitempty,min=1,dive"` +} + +type GNPSpecRuleInput struct { + Metadata map[string]string `json:"metadata" yaml:"metadata"` + Action string `json:"action" yaml:"action" validate:"required,action"` + Protocol string `json:"protocol" yaml:"protocol" validate:"omitempty,protocol"` + NotProtocol string `json:"notProtocol" yaml:"notProtocol" validate:"omitempty,protocol"` + IPVersion int `json:"ipVersion" yaml:"ipVersion" validate:"required,ip_version"` + Source *GNPSpecRuleEntityInput `json:"source" yaml:"source" validate:"omitempty"` + Destination *GNPSpecRuleEntityInput `json:"destination" yaml:"destination" validate:"omitempty"` +} + +type GNPSpecRuleEntityInput struct { + Selector string `json:"selector" yaml:"selector" validate:"omitempty,selector"` + Nets []string `json:"nets" yaml:"nets" validate:"omitempty,min=1,unique"` + NotNets []string `json:"notNets" yaml:"notNets" validate:"omitempty,min=1,unique"` + Ports []interface{} `json:"ports" yaml:"ports" validate:"omitempty,min=1,unique,dive"` + NotPorts []interface{} `json:"notPorts" yaml:"notPorts" validate:"omitempty,min=1,unique,dive"` +} + +type GetGNPInput struct { + Name string `uri:"name" validate:"required"` +} + +type DeleteGlobalNetworkPolicyInput struct { + Metadata GNPMetadataInput `json:"metadata" yaml:"metadata" validate:"required"` +} diff --git a/api/v1/dto/gns.go b/api/v1/dto/gns.go new file mode 100644 index 0000000..e681153 --- /dev/null +++ b/api/v1/dto/gns.go @@ -0,0 +1,46 @@ +package dto + +import "time" + +type GlobalNetworkSet struct { + ID string `json:"id" yaml:"id"` + UUID string `json:"uuid" yaml:"uuid"` + Version uint `json:"version" yaml:"version"` + Metadata GNSMetadata `json:"metadata" yaml:"metadata"` + Spec GNSSpec `json:"spec" yaml:"spec"` + Description string `json:"description" yaml:"description"` + CreatedAt time.Time `json:"createdAt" yaml:"createdAt"` + UpdatedAt time.Time `json:"updatedAt" yaml:"updatedAt"` +} + +type GNSMetadata struct { + Name string `json:"name" yaml:"name"` + Labels map[string]string `json:"labels" yaml:"labels"` +} + +type GNSSpec struct { + Nets []string `json:"nets" yaml:"nets"` +} + +type CreateGlobalNetworkSetInput struct { + Metadata GNSMetadataInput `json:"metadata" yaml:"metadata" validate:"required"` + Spec GNSSpecInput `json:"spec" yaml:"spec"` + Description string `json:"description" yaml:"description"` +} + +type GNSMetadataInput struct { + Name string `json:"name" yaml:"name" validate:"required,name"` + Labels map[string]string `json:"labels" yaml:"labels"` +} + +type GNSSpecInput struct { + Nets []string `json:"nets" yaml:"nets" validate:"min=1,unique"` +} + +type GetGNSInput struct { + Name string `uri:"name" validate:"required"` +} + +type DeleteGlobalNetworkSetInput struct { + Metadata GNSMetadataInput `json:"metadata" yaml:"metadata" validate:"required"` +} diff --git a/api/v1/dto/hep.go b/api/v1/dto/hep.go new file mode 100644 index 0000000..36d6618 --- /dev/null +++ b/api/v1/dto/hep.go @@ -0,0 +1,109 @@ +package dto + +import ( + "time" +) + +type HostEndpoint struct { + ID string `json:"id" yaml:"id"` + UUID string `json:"uuid" yaml:"uuid"` + Version uint `json:"version" yaml:"version"` + Metadata HostEndpointMetadata `json:"metadata" yaml:"metadata"` + Spec HostEndpointSpec `json:"spec" yaml:"spec"` + Description string `json:"description" yaml:"description"` + CreatedAt time.Time `json:"createdAt" yaml:"createdAt"` + UpdatedAt time.Time `json:"updatedAt" yaml:"updatedAt"` +} + +type HostEndpointMetadata struct { + Name string `json:"name" yaml:"name"` + Labels map[string]string `json:"labels" yaml:"labels"` +} + +type HostEndpointSpec struct { + InterfaceName string `json:"interfaceName" yaml:"interfaceName"` + IPs []string `json:"ips" yaml:"ips"` +} + +type CreateHostEndpointInput struct { + Metadata HostEndpointMetadataInput `json:"metadata" yaml:"metadata" validate:"required"` + Spec HostEndpointSpecInput `json:"spec" yaml:"spec" validate:"required"` + Description string `json:"description" yaml:"description"` +} + +type HostEndpointMetadataInput struct { + Name string `json:"name" yaml:"name" validate:"required,name"` + Labels map[string]string `json:"labels" yaml:"labels"` +} + +type HostEndpointSpecInput struct { + InterfaceName string `json:"interfaceName" yaml:"interfaceName"` + IPs []string `json:"ips" yaml:"ips" validate:"min=1,unique,dive,ip"` +} + +type GetHostEndpointInput struct { + Name string `uri:"name" validate:"required"` +} + +type DeleteHostEndpointInput struct { + Metadata HostEndpointMetadataInput `json:"metadata" yaml:"metadata" validate:"required"` +} + +type FetchHostEndpointPolicyInput struct { + Name string `uri:"name" validate:"required"` +} + +type HostEndpointPolicy struct { + MetaData HostEndPointPolicyMetadata `json:"metadata"` + HEP *HostEndpoint `json:"hostEndpoint"` + ParsedGNPs []*ParsedGNP `json:"parsedGNPs"` + ParsedHEPs []*ParsedHEP `json:"parsedHEPs"` + ParsedGNSs []*ParsedGNS `json:"parsedGNSs"` +} + +type HostEndPointPolicyMetadata struct { + HEPVersions map[string]uint `json:"hepVersions"` + GNPVersions map[string]uint `json:"gnpVersions"` + GNSVersions map[string]uint `json:"gnsVersions"` +} + +type ParsedGNP struct { + UUID string `json:"uuid"` + Version uint `json:"version"` + Name string `json:"name"` + InboundRules []*ParsedRule `json:"inboundRules"` + OutboundRules []*ParsedRule `json:"outboundRules"` +} + +type ParsedRule struct { + Action string `json:"action"` + IPVersion int `json:"ipVersion"` + Protocol string `json:"protocol"` + IsProtocolNegative bool `json:"isProtocolNegative"` + SrcNets []string `json:"srcNets"` + IsSrcNetNegative bool `json:"isSrcNetNegative"` + SrcGNSUUIDs []string `json:"srcGNSUUIDs"` + SrcHEPUUIDs []string `json:"srcHEPUUIDs"` + SrcPorts []string `json:"srcPorts"` + IsSrcPortNegative bool `json:"isSrcPortNegative"` + DstNets []string `json:"dstNets"` + IsDstNetNegative bool `json:"isDstNetNegative"` + DstGNSUUIDs []string `json:"dstGNSUUIDs"` + DstHEPUUIDs []string `json:"dstHEPUUIDs"` + DstPorts []string `json:"dstPorts"` + IsDstPortNegative bool `json:"isDstPortNegative"` +} + +type ParsedHEP struct { + UUID string `json:"uuid"` + Name string `json:"name"` + IPsV4 []string `json:"ipsV4"` + IPsV6 []string `json:"ipsV6"` +} + +type ParsedGNS struct { + UUID string `json:"uuid"` + Name string `json:"name"` + NetsV4 []string `json:"netsV4"` + NetsV6 []string `json:"netsV6"` +} diff --git a/api/v1/handler/gnp.go b/api/v1/handler/gnp.go new file mode 100644 index 0000000..acbe2f0 --- /dev/null +++ b/api/v1/handler/gnp.go @@ -0,0 +1,75 @@ +package handler + +import ( + "context" + "net/http" + + "github.com/gin-gonic/gin" + + "github.com/bamboo-firewall/be/api/v1/dto" + "github.com/bamboo-firewall/be/api/v1/mapper" + "github.com/bamboo-firewall/be/cmd/server/pkg/entity" + "github.com/bamboo-firewall/be/cmd/server/pkg/httpbase" + "github.com/bamboo-firewall/be/cmd/server/pkg/httpbase/ierror" + "github.com/bamboo-firewall/be/domain/model" +) + +type gnpService interface { + Create(ctx context.Context, input *model.CreateGlobalNetworkPolicyInput) (*entity.GlobalNetworkPolicy, *ierror.Error) + Get(ctx context.Context, name string) (*entity.GlobalNetworkPolicy, *ierror.Error) + Delete(ctx context.Context, name string) *ierror.Error +} + +func NewGNP(s gnpService) *gnp { + return &gnp{ + service: s, + } +} + +type gnp struct { + service gnpService +} + +func (h *gnp) Create(c *gin.Context) { + in := new(dto.CreateGlobalNetworkPolicyInput) + if ierr := httpbase.BindInput(c, in); ierr != nil { + httpbase.ReturnErrorResponse(c, ierr) + return + } + + gnsEntity, ierr := h.service.Create(c.Request.Context(), mapper.ToCreateGlobalNetworkPolicyInput(in)) + if ierr != nil { + httpbase.ReturnErrorResponse(c, ierr) + return + } + httpbase.ReturnSuccessResponse(c, http.StatusOK, mapper.ToGlobalNetworkPolicyDTO(gnsEntity)) +} + +func (h *gnp) Get(c *gin.Context) { + in := new(dto.GetGNPInput) + if ierr := httpbase.BindInput(c, in); ierr != nil { + httpbase.ReturnErrorResponse(c, ierr) + return + } + + gnpEntity, ierr := h.service.Get(c.Request.Context(), in.Name) + if ierr != nil { + httpbase.ReturnErrorResponse(c, ierr) + return + } + httpbase.ReturnSuccessResponse(c, http.StatusOK, mapper.ToGlobalNetworkPolicyDTO(gnpEntity)) +} + +func (h *gnp) Delete(c *gin.Context) { + in := new(dto.DeleteGlobalNetworkSetInput) + if ierr := httpbase.BindInput(c, in); ierr != nil { + httpbase.ReturnErrorResponse(c, ierr) + return + } + + if err := h.service.Delete(c.Request.Context(), in.Metadata.Name); err != nil { + httpbase.ReturnErrorResponse(c, err) + return + } + httpbase.ReturnSuccessResponse(c, http.StatusOK, nil) +} diff --git a/api/v1/handler/gns.go b/api/v1/handler/gns.go new file mode 100644 index 0000000..eb80454 --- /dev/null +++ b/api/v1/handler/gns.go @@ -0,0 +1,75 @@ +package handler + +import ( + "context" + "net/http" + + "github.com/gin-gonic/gin" + + "github.com/bamboo-firewall/be/api/v1/dto" + "github.com/bamboo-firewall/be/api/v1/mapper" + "github.com/bamboo-firewall/be/cmd/server/pkg/entity" + "github.com/bamboo-firewall/be/cmd/server/pkg/httpbase" + "github.com/bamboo-firewall/be/cmd/server/pkg/httpbase/ierror" + "github.com/bamboo-firewall/be/domain/model" +) + +type gnsService interface { + Create(ctx context.Context, input *model.CreateGlobalNetworkSetInput) (*entity.GlobalNetworkSet, *ierror.Error) + Get(ctx context.Context, name string) (*entity.GlobalNetworkSet, *ierror.Error) + Delete(ctx context.Context, name string) *ierror.Error +} + +func NewGNS(s gnsService) *gns { + return &gns{ + service: s, + } +} + +type gns struct { + service gnsService +} + +func (h *gns) Create(c *gin.Context) { + in := new(dto.CreateGlobalNetworkSetInput) + if ierr := httpbase.BindInput(c, in); ierr != nil { + httpbase.ReturnErrorResponse(c, ierr) + return + } + + gnsEntity, ierr := h.service.Create(c.Request.Context(), mapper.ToCreateGlobalNetworkSetInput(in)) + if ierr != nil { + httpbase.ReturnErrorResponse(c, ierr) + return + } + httpbase.ReturnSuccessResponse(c, http.StatusOK, mapper.ToGlobalNetworkSetDTO(gnsEntity)) +} + +func (h *gns) Get(c *gin.Context) { + in := new(dto.GetGNSInput) + if ierr := httpbase.BindInput(c, in); ierr != nil { + httpbase.ReturnErrorResponse(c, ierr) + return + } + + gnsEntity, ierr := h.service.Get(c.Request.Context(), in.Name) + if ierr != nil { + httpbase.ReturnErrorResponse(c, ierr) + return + } + httpbase.ReturnSuccessResponse(c, http.StatusOK, mapper.ToGlobalNetworkSetDTO(gnsEntity)) +} + +func (h *gns) Delete(c *gin.Context) { + in := new(dto.DeleteGlobalNetworkSetInput) + if ierr := httpbase.BindInput(c, in); ierr != nil { + httpbase.ReturnErrorResponse(c, ierr) + return + } + + if err := h.service.Delete(c.Request.Context(), in.Metadata.Name); err != nil { + httpbase.ReturnErrorResponse(c, err) + return + } + httpbase.ReturnSuccessResponse(c, http.StatusOK, nil) +} diff --git a/api/v1/handler/hep.go b/api/v1/handler/hep.go new file mode 100644 index 0000000..dadeea4 --- /dev/null +++ b/api/v1/handler/hep.go @@ -0,0 +1,90 @@ +package handler + +import ( + "context" + "net/http" + + "github.com/gin-gonic/gin" + + "github.com/bamboo-firewall/be/api/v1/dto" + "github.com/bamboo-firewall/be/api/v1/mapper" + "github.com/bamboo-firewall/be/cmd/server/pkg/entity" + "github.com/bamboo-firewall/be/cmd/server/pkg/httpbase" + "github.com/bamboo-firewall/be/cmd/server/pkg/httpbase/ierror" + "github.com/bamboo-firewall/be/domain/model" +) + +type hepService interface { + Create(ctx context.Context, input *model.CreateHostEndpointInput) (*entity.HostEndpoint, *ierror.Error) + Get(ctx context.Context, name string) (*entity.HostEndpoint, *ierror.Error) + Delete(ctx context.Context, name string) *ierror.Error + FetchPolicies(ctx context.Context, input *model.FetchHostEndpointPolicyInput) (*model.HostEndPointPolicy, *ierror.Error) +} + +func NewHEP(s hepService) *hep { + return &hep{ + service: s, + } +} + +type hep struct { + service hepService +} + +func (h *hep) Create(c *gin.Context) { + in := new(dto.CreateHostEndpointInput) + if ierr := httpbase.BindInput(c, in); ierr != nil { + httpbase.ReturnErrorResponse(c, ierr) + return + } + + hepEntity, ierr := h.service.Create(c.Request.Context(), mapper.ToCreateHostEndpointInput(in)) + if ierr != nil { + httpbase.ReturnErrorResponse(c, ierr) + return + } + httpbase.ReturnSuccessResponse(c, http.StatusOK, mapper.ToHostEndpointDTO(hepEntity)) +} + +func (h *hep) Get(c *gin.Context) { + in := new(dto.GetHostEndpointInput) + if ierr := httpbase.BindInput(c, in); ierr != nil { + httpbase.ReturnErrorResponse(c, ierr) + return + } + + hepEntity, ierr := h.service.Get(c.Request.Context(), in.Name) + if ierr != nil { + httpbase.ReturnErrorResponse(c, ierr) + return + } + httpbase.ReturnSuccessResponse(c, http.StatusOK, mapper.ToHostEndpointDTO(hepEntity)) +} + +func (h *hep) Delete(c *gin.Context) { + in := new(dto.DeleteHostEndpointInput) + if ierr := httpbase.BindInput(c, in); ierr != nil { + httpbase.ReturnErrorResponse(c, ierr) + return + } + + if err := h.service.Delete(c.Request.Context(), in.Metadata.Name); err != nil { + httpbase.ReturnErrorResponse(c, err) + return + } + httpbase.ReturnSuccessResponse(c, http.StatusOK, nil) +} + +func (h *hep) FetchPolicies(c *gin.Context) { + in := new(dto.FetchHostEndpointPolicyInput) + if ierr := httpbase.BindInput(c, in); ierr != nil { + httpbase.ReturnErrorResponse(c, ierr) + return + } + hostEndpointPolicy, ierr := h.service.FetchPolicies(c.Request.Context(), mapper.ToFetchHostEndPointPolicyInput(in)) + if ierr != nil { + httpbase.ReturnErrorResponse(c, ierr) + return + } + httpbase.ReturnSuccessResponse(c, http.StatusOK, mapper.ToFetchPoliciesOutput(hostEndpointPolicy)) +} diff --git a/api/v1/handler/ping.go b/api/v1/handler/ping.go new file mode 100644 index 0000000..57269cf --- /dev/null +++ b/api/v1/handler/ping.go @@ -0,0 +1,7 @@ +package handler + +import "github.com/gin-gonic/gin" + +func Ping(c *gin.Context) { + c.String(200, "pong") +} diff --git a/api/v1/mapper/gnp.go b/api/v1/mapper/gnp.go new file mode 100644 index 0000000..6ce19e5 --- /dev/null +++ b/api/v1/mapper/gnp.go @@ -0,0 +1,115 @@ +package mapper + +import ( + "github.com/bamboo-firewall/be/api/v1/dto" + "github.com/bamboo-firewall/be/cmd/server/pkg/entity" + "github.com/bamboo-firewall/be/domain/model" +) + +func ToGlobalNetworkPolicyDTO(gnp *entity.GlobalNetworkPolicy) *dto.GlobalNetworkPolicy { + if gnp == nil { + return nil + } + + var specIngress []dto.GNPSpecRule + for _, rule := range gnp.Spec.Ingress { + specIngress = append(specIngress, toRuleDTO(rule)) + } + + var specEgress []dto.GNPSpecRule + for _, rule := range gnp.Spec.Egress { + specEgress = append(specEgress, toRuleDTO(rule)) + } + return &dto.GlobalNetworkPolicy{ + ID: gnp.ID.Hex(), + UUID: gnp.UUID, + Version: gnp.Version, + Metadata: dto.GNPMetadata{ + Name: gnp.Metadata.Name, + Labels: gnp.Metadata.Labels, + }, + Spec: dto.GNPSpec{ + Selector: gnp.Spec.Selector, + Ingress: specIngress, + Egress: specEgress, + }, + Description: gnp.Description, + CreatedAt: gnp.CreatedAt.Local(), + UpdatedAt: gnp.UpdatedAt.Local(), + } +} + +func toRuleDTO(rule entity.GNPSpecRule) dto.GNPSpecRule { + return dto.GNPSpecRule{ + Metadata: rule.Metadata, + Action: rule.Action, + Protocol: rule.Protocol, + NotProtocol: rule.NotProtocol, + IPVersion: int(rule.IPVersion), + Source: toRuleEntityDTO(rule.Source), + Destination: toRuleEntityDTO(rule.Destination), + } +} + +func toRuleEntityDTO(ruleEntity *entity.GNPSpecRuleEntity) *dto.GNPSpecRuleEntity { + if ruleEntity == nil { + return nil + } + return &dto.GNPSpecRuleEntity{ + Selector: ruleEntity.Selector, + Nets: ruleEntity.Nets, + NotNets: ruleEntity.NotNets, + Ports: ruleEntity.Ports, + NotPorts: ruleEntity.NotPorts, + } +} + +func ToCreateGlobalNetworkPolicyInput(in *dto.CreateGlobalNetworkPolicyInput) *model.CreateGlobalNetworkPolicyInput { + var specIngress []model.GNPSpecRuleInput + for _, rule := range in.Spec.Ingress { + specIngress = append(specIngress, toRuleInput(rule)) + } + + var specEgress []model.GNPSpecRuleInput + for _, rule := range in.Spec.Egress { + specEgress = append(specEgress, toRuleInput(rule)) + } + + return &model.CreateGlobalNetworkPolicyInput{ + Metadata: model.GNPMetadataInput{ + Name: in.Metadata.Name, + Labels: in.Metadata.Labels, + }, + Spec: model.GNPSpecInput{ + Selector: in.Spec.Selector, + Ingress: specIngress, + Egress: specEgress, + }, + Description: in.Description, + } +} + +func toRuleInput(rule dto.GNPSpecRuleInput) model.GNPSpecRuleInput { + return model.GNPSpecRuleInput{ + Metadata: rule.Metadata, + Action: rule.Action, + Protocol: rule.Protocol, + NotProtocol: rule.NotProtocol, + IPVersion: rule.IPVersion, + Source: toRuleEntityInput(rule.Source), + Destination: toRuleEntityInput(rule.Destination), + } +} + +func toRuleEntityInput(ruleEntity *dto.GNPSpecRuleEntityInput) *model.GNPSpecRuleEntityInput { + if ruleEntity == nil { + return nil + } + return &model.GNPSpecRuleEntityInput{ + Selector: ruleEntity.Selector, + Nets: ruleEntity.Nets, + NotNets: ruleEntity.NotNets, + Ports: ruleEntity.Ports, + NotPorts: ruleEntity.NotPorts, + } +} diff --git a/api/v1/mapper/gns.go b/api/v1/mapper/gns.go new file mode 100644 index 0000000..6a06238 --- /dev/null +++ b/api/v1/mapper/gns.go @@ -0,0 +1,41 @@ +package mapper + +import ( + "github.com/bamboo-firewall/be/api/v1/dto" + "github.com/bamboo-firewall/be/cmd/server/pkg/entity" + "github.com/bamboo-firewall/be/domain/model" +) + +func ToGlobalNetworkSetDTO(gns *entity.GlobalNetworkSet) *dto.GlobalNetworkSet { + if gns == nil { + return nil + } + return &dto.GlobalNetworkSet{ + ID: gns.ID.Hex(), + UUID: gns.UUID, + Version: gns.Version, + Metadata: dto.GNSMetadata{ + Name: gns.Metadata.Name, + Labels: gns.Metadata.Labels, + }, + Spec: dto.GNSSpec{ + Nets: gns.Spec.Nets, + }, + Description: gns.Description, + CreatedAt: gns.CreatedAt.Local(), + UpdatedAt: gns.UpdatedAt.Local(), + } +} + +func ToCreateGlobalNetworkSetInput(in *dto.CreateGlobalNetworkSetInput) *model.CreateGlobalNetworkSetInput { + return &model.CreateGlobalNetworkSetInput{ + Metadata: model.GNSMetadataInput{ + Name: in.Metadata.Name, + Labels: in.Metadata.Labels, + }, + Spec: model.GNSSpecInput{ + Nets: in.Spec.Nets, + }, + Description: in.Description, + } +} diff --git a/api/v1/mapper/hep.go b/api/v1/mapper/hep.go new file mode 100644 index 0000000..545471a --- /dev/null +++ b/api/v1/mapper/hep.go @@ -0,0 +1,132 @@ +package mapper + +import ( + "github.com/bamboo-firewall/be/api/v1/dto" + "github.com/bamboo-firewall/be/cmd/server/pkg/entity" + "github.com/bamboo-firewall/be/domain/model" +) + +func ToHostEndpointDTO(hep *entity.HostEndpoint) *dto.HostEndpoint { + if hep == nil { + return nil + } + return &dto.HostEndpoint{ + ID: hep.ID.Hex(), + UUID: hep.UUID, + Version: hep.Version, + Metadata: dto.HostEndpointMetadata{ + Name: hep.Metadata.Name, + Labels: hep.Metadata.Labels, + }, + Spec: dto.HostEndpointSpec{ + InterfaceName: hep.Spec.InterfaceName, + IPs: hep.Spec.IPs, + }, + Description: hep.Description, + CreatedAt: hep.CreatedAt.Local(), + UpdatedAt: hep.UpdatedAt.Local(), + } +} + +func ToCreateHostEndpointInput(in *dto.CreateHostEndpointInput) *model.CreateHostEndpointInput { + return &model.CreateHostEndpointInput{ + Metadata: model.HostEndpointMetadataInput{ + Name: in.Metadata.Name, + Labels: in.Metadata.Labels, + }, + Spec: model.HostEndpointSpecInput{ + InterfaceName: in.Spec.InterfaceName, + IPs: in.Spec.IPs, + }, + Description: in.Description, + } +} + +func ToFetchHostEndPointPolicyInput(in *dto.FetchHostEndpointPolicyInput) *model.FetchHostEndpointPolicyInput { + return &model.FetchHostEndpointPolicyInput{ + Name: in.Name, + } +} + +func ToFetchPoliciesOutput(hostEndpointPolicy *model.HostEndPointPolicy) *dto.HostEndpointPolicy { + parsedGNPDTOs := make([]*dto.ParsedGNP, len(hostEndpointPolicy.ParsedGNPs)) + for i, policy := range hostEndpointPolicy.ParsedGNPs { + parsedGNPDTOs[i] = toParsedGNPDTO(policy) + } + parsedHEPDTOs := make([]*dto.ParsedHEP, len(hostEndpointPolicy.ParsedHEPs)) + for i, endpoint := range hostEndpointPolicy.ParsedHEPs { + parsedHEPDTOs[i] = toParsedHEPDTO(endpoint) + } + parsedGNSDTOs := make([]*dto.ParsedGNS, len(hostEndpointPolicy.ParsedGNSs)) + for i, set := range hostEndpointPolicy.ParsedGNSs { + parsedGNSDTOs[i] = toParsedGNSDTO(set) + } + return &dto.HostEndpointPolicy{ + MetaData: dto.HostEndPointPolicyMetadata{ + HEPVersions: hostEndpointPolicy.MetaData.HEPVersions, + GNPVersions: hostEndpointPolicy.MetaData.GNPVersions, + GNSVersions: hostEndpointPolicy.MetaData.GNSVersions, + }, + HEP: ToHostEndpointDTO(hostEndpointPolicy.HEP), + ParsedGNPs: parsedGNPDTOs, + ParsedHEPs: parsedHEPDTOs, + ParsedGNSs: parsedGNSDTOs, + } +} + +func toParsedGNPDTO(parsedGNP *model.ParsedGNP) *dto.ParsedGNP { + var inboundRules []*dto.ParsedRule + for _, rule := range parsedGNP.InboundRules { + inboundRules = append(inboundRules, toParsedRuleDTO(rule)) + } + var outboundRules []*dto.ParsedRule + for _, rule := range parsedGNP.OutboundRules { + outboundRules = append(outboundRules, toParsedRuleDTO(rule)) + } + return &dto.ParsedGNP{ + UUID: parsedGNP.UUID, + Version: parsedGNP.Version, + Name: parsedGNP.Name, + InboundRules: inboundRules, + OutboundRules: outboundRules, + } +} + +func toParsedRuleDTO(parsedRule *model.ParsedRule) *dto.ParsedRule { + return &dto.ParsedRule{ + Action: parsedRule.Action, + IPVersion: parsedRule.IPVersion, + Protocol: parsedRule.Protocol, + IsProtocolNegative: parsedRule.IsProtocolNegative, + SrcNets: parsedRule.SrcNets, + IsSrcNetNegative: parsedRule.IsSrcNetNegative, + SrcGNSUUIDs: parsedRule.SrcGNSUUIDs, + SrcHEPUUIDs: parsedRule.SrcHEPUUIDs, + SrcPorts: parsedRule.SrcPorts, + IsSrcPortNegative: parsedRule.IsSrcPortNegative, + DstNets: parsedRule.DstNets, + IsDstNetNegative: parsedRule.IsDstPortNegative, + DstGNSUUIDs: parsedRule.DstGNSUUIDs, + DstHEPUUIDs: parsedRule.DstHEPUUIDs, + DstPorts: parsedRule.DstPorts, + IsDstPortNegative: parsedRule.IsDstPortNegative, + } +} + +func toParsedHEPDTO(parsedHEP *model.ParsedHEP) *dto.ParsedHEP { + return &dto.ParsedHEP{ + UUID: parsedHEP.UUID, + Name: parsedHEP.Name, + IPsV4: parsedHEP.IPsV4, + IPsV6: parsedHEP.IPsV6, + } +} + +func toParsedGNSDTO(parsedGNS *model.ParsedGNS) *dto.ParsedGNS { + return &dto.ParsedGNS{ + UUID: parsedGNS.UUID, + Name: parsedGNS.Name, + NetsV4: parsedGNS.NetsV4, + NetsV6: parsedGNS.NetsV6, + } +} diff --git a/bootstrap/app.go b/bootstrap/app.go deleted file mode 100644 index d533664..0000000 --- a/bootstrap/app.go +++ /dev/null @@ -1,19 +0,0 @@ -package bootstrap - -import "github.com/bamboo-firewall/be/mongo" - -type Application struct { - Env *Env - Mongo mongo.Client -} - -func App() Application { - app := &Application{} - app.Env = NewEnv(".") - app.Mongo = NewMongoDatabase(app.Env) - return *app -} - -func (app *Application) CloseDBConnection() { - CloseMongoDBConnection(app.Mongo) -} diff --git a/bootstrap/database.go b/bootstrap/database.go deleted file mode 100644 index fc08e5e..0000000 --- a/bootstrap/database.go +++ /dev/null @@ -1,44 +0,0 @@ -package bootstrap - -import ( - "context" - "log" - "time" - - "github.com/bamboo-firewall/be/mongo" -) - -func NewMongoDatabase(env *Env) mongo.Client { - ctx, cancel := context.WithTimeout(context.Background(), time.Duration(env.MongoDbTimeOut)*time.Second) - defer cancel() - - client, err := mongo.NewClient(env.MongoDbURI) - if err != nil { - log.Fatal(err) - } - - err = client.Connect(ctx) - if err != nil { - log.Fatal(err) - } - - err = client.Ping(ctx) - if err != nil { - log.Fatal(err) - } - - return client -} - -func CloseMongoDBConnection(client mongo.Client) { - if client == nil { - return - } - - err := client.Disconnect(context.TODO()) - if err != nil { - log.Fatal(err) - } - - log.Println("Connection to MongoDB closed.") -} diff --git a/bootstrap/env.go b/bootstrap/env.go deleted file mode 100644 index 5a8dc60..0000000 --- a/bootstrap/env.go +++ /dev/null @@ -1,50 +0,0 @@ -package bootstrap - -import ( - "log" - - "github.com/spf13/viper" -) - -type Env struct { - AppEnv string `mapstructure:"APP_ENV" json:"APP_ENV"` - ServerAddress string `mapstructure:"SERVER_ADDRESS" json:"SERVER_ADDRESS"` - ContextTimeout int `mapstructure:"CONTEXT_TIMEOUT" json:"CONTEXT_TIMEOUT"` - DBName string `mapstructure:"DB_NAME" json:"DB_NAME"` - AccessTokenExpiryHour int `mapstructure:"ACCESS_TOKEN_EXPIRY_HOUR" json:"ACCESS_TOKEN_EXPIRY_HOUR"` - RefreshTokenExpiryHour int `mapstructure:"REFRESH_TOKEN_EXPIRY_HOUR" json:"REFRESH_TOKEN_EXPIRY_HOUR"` - AccessTokenSecret string `mapstructure:"ACCESS_TOKEN_SECRET" json:"ACCESS_TOKEN_SECRET"` - RefreshTokenSecret string `mapstructure:"REFRESH_TOKEN_SECRET" json:"REFRESH_TOKEN_SECRET"` - MongoDbURI string `mapstructure:"MONGO_URI" json:"MONGO_URI"` - MongoDbTimeOut int `mapstructure:"MONGO_TIMEOUT" json:"MONGO_TIMEOUT"` - AdminPassword string `mapstructure:"ADMIN_PASSWORD" json:"ADMIN_PASSWORD"` - CORSAllowOrigin string `mapstructure:"CORS_ALLOW_ORIGIN" json:"CORS_ALLOW_ORIGIN"` - CORSAllowOMethods string `mapstructure:"CORS_ALLOW_METHODS" json:"CORS_ALLOW_METHODS"` - AdminAccount string `mapstructure:"ADMIN_ACCOUNT" json:"ADMIN_ACCOUNT"` - EmailDomain string `mapstructure:"EMAIL_DOMAIN" json:"EMAIL_DOMAIN"` -} - -func NewEnv(path string) *Env { - env := Env{} - viper.AddConfigPath(path) - viper.SetConfigType("json") - viper.SetConfigName("config") - - viper.AutomaticEnv() - - err := viper.ReadInConfig() - if err != nil { - log.Fatal("Can't find config file : ", err) - } - - err = viper.Unmarshal(&env) - if err != nil { - log.Fatal("Environment can't be loaded: ", err) - } - - if env.AppEnv == "development" { - log.Println("The App is running in development env") - } - - return &env -} diff --git a/build/build.sh b/build/build.sh new file mode 100755 index 0000000..d25f8f5 --- /dev/null +++ b/build/build.sh @@ -0,0 +1,8 @@ +#!/usr/bin/env bash + +ROOT_PATH="$(cd "$(dirname "$0")/.." && pwd -P)" +BUILD_PATH="${ROOT_PATH}/build" + +source "${BUILD_PATH}/init.sh" + +golang::build_binaries "$@" diff --git a/build/clean.sh b/build/clean.sh new file mode 100755 index 0000000..7fd61f3 --- /dev/null +++ b/build/clean.sh @@ -0,0 +1,12 @@ +#!/usr/bin/env bash + +ROOT_PATH="$(cd "$(dirname "$0")/.." && pwd -P)" +BUILD_PATH="${ROOT_PATH}/build" + +source "${BUILD_PATH}/init.sh" + +cleanup() { + rm -r ${BUILD_OUTPUT_PATH} +} + +cleanup diff --git a/build/golang.sh b/build/golang.sh new file mode 100755 index 0000000..2b736db --- /dev/null +++ b/build/golang.sh @@ -0,0 +1,41 @@ +#!/usr/bin/env bash + +readonly SUPPORTED_PLATFORMS=( + linux/amd64 + linux/arm64 + darwin/amd64 + darwin/arm64 + windows/amd64 + windows/arm64 +) + +golang::build_binaries() { + local -a platforms + if [[ "$3" == "all" ]]; then + platforms=("${SUPPORTED_PLATFORMS[@]}") + else + local host_platform + host_platform=$(golang::host_platform) + platforms+=("${host_platform}") + fi + + for platform in "${platforms[@]}"; do + golang::build_binary_for_platform ${platform} $1 $2 + done +} + +golang::build_binary_for_platform() { + local platform="$1" + local build_dir="$2" + local bin_name="$3" + + GOOS=${platform%%/*} + GOARCH=${platform##*/} + output="${BUILD_CMD_PATH}/${GOOS}/${GOARCH}/${bin_name}" + + CGO_ENABLED=${CGO_ENABLED} GOOS=${GOOS} GOARCH=${GOARCH} ${GO_BUILD} -ldflags="${LDFLAGS}" -o ${output} ${build_dir} +} + +golang::host_platform() { + echo "$(go env GOHOSTOS)/$(go env GOHOSTARCH)" +} diff --git a/build/init.sh b/build/init.sh new file mode 100755 index 0000000..cd5dea6 --- /dev/null +++ b/build/init.sh @@ -0,0 +1,23 @@ +#!/usr/bin/env bash + +BUILD_OUTPUT_PATH="${ROOT_PATH}/_output" +BUILD_CMD_PATH="${BUILD_OUTPUT_PATH}/bin" + +PACKAGE_NAME="github.com/bamboo-firewall/be" + +VERSION="$(git describe --abbrev=0 --tags)" +BRANCH="$(git rev-parse --abbrev-ref HEAD)" +BUILD_TIME="$(date +%Y-%m-%dT%H:%M:%S%z)" + +ORGANIZATION="ATAOCloud" + +LDFLAGS="-s -w -X ${PACKAGE_NAME}/buildinfo.Version=${VERSION} \ + -X ${PACKAGE_NAME}/buildinfo.GitBranch=${BRANCH} \ + -X ${PACKAGE_NAME}/buildinfo.BuildDate=${BUILD_TIME} \ + -X ${PACKAGE_NAME}/buildinfo.Organization=${ORGANIZATION}" + +CGO_ENABLED=0 + +GO_BUILD="go build -buildvcs=false -a -installsuffix cgo" + +source "${BUILD_PATH}/golang.sh" diff --git a/buildinfo/build_info.go b/buildinfo/build_info.go new file mode 100644 index 0000000..73469bc --- /dev/null +++ b/buildinfo/build_info.go @@ -0,0 +1,8 @@ +package buildinfo + +var ( + Version string + GitBranch string + BuildDate string + Organization string +) diff --git a/cmd/bamboofwcli/bamboofwcli.go b/cmd/bamboofwcli/bamboofwcli.go new file mode 100644 index 0000000..02749b1 --- /dev/null +++ b/cmd/bamboofwcli/bamboofwcli.go @@ -0,0 +1,13 @@ +package main + +import ( + "log/slog" + "os" + + "github.com/bamboo-firewall/be/cmd/bamboofwcli/command" +) + +func main() { + slog.SetDefault(slog.New(slog.NewJSONHandler(os.Stdout, &slog.HandlerOptions{Level: slog.LevelDebug}))) + command.Execute() +} diff --git a/cmd/bamboofwcli/command/common/constant.go b/cmd/bamboofwcli/command/common/constant.go new file mode 100644 index 0000000..bbc0704 --- /dev/null +++ b/cmd/bamboofwcli/command/common/constant.go @@ -0,0 +1,5 @@ +package common + +const ( + APIServerENV = "BAMBOOFW_APISERVER_ADDRESS" +) diff --git a/cmd/bamboofwcli/command/common/resource.go b/cmd/bamboofwcli/command/common/resource.go new file mode 100644 index 0000000..c56cb30 --- /dev/null +++ b/cmd/bamboofwcli/command/common/resource.go @@ -0,0 +1,85 @@ +package common + +import ( + "encoding/json" + "fmt" + "io" + "os" + "path/filepath" + "strings" + + "gopkg.in/yaml.v3" + + "github.com/bamboo-firewall/be/cmd/bamboofwcli/command/resouremanager" +) + +type FileExtension string + +const ( + FileExtensionYAML FileExtension = "yaml" + FileExtensionYML FileExtension = "yml" + FileExtensionJSON FileExtension = "json" +) + +func GetResourceMgrByType(resourceType string) (resouremanager.Resource, error) { + switch strings.ToLower(resourceType) { + case "hostendpoint", "hep": + return resouremanager.NewHEP(), nil + case "globalnetworkset", "gns": + return resouremanager.NewGNS(), nil + case "globalnetworkpolicy", "gnp": + return resouremanager.NewGNP(), nil + default: + return nil, fmt.Errorf("unknown resource type: %s", resourceType) + } +} + +type ResourceFile struct { + Name string + Content interface{} +} + +func GetResourceFilesByFileNames[T any](fileNames []string) ([]*ResourceFile, error) { + var resources []*ResourceFile + + for _, fileName := range fileNames { + resource, err := GetResourceFileByFileName[T](fileName) + if err != nil { + return nil, err + } + resources = append(resources, resource) + } + return resources, nil +} + +func GetResourceFileByFileName[T any](fileName string) (*ResourceFile, error) { + var input T + f, err := os.Open(fileName) + if err != nil { + return nil, fmt.Errorf("could not open file %q: %w", fileName, err) + } + defer f.Close() + + contentFile, err := io.ReadAll(f) + if err != nil { + return nil, fmt.Errorf("error reading file %q: %w", fileName, err) + } + fileExtension := filepath.Ext(f.Name()) + switch FileExtension(strings.TrimLeft(fileExtension, ".")) { + case FileExtensionYAML, FileExtensionYML: + if err = yaml.Unmarshal(contentFile, &input); err != nil { + return nil, fmt.Errorf("error parsing file %q: %w", fileName, err) + } + case FileExtensionJSON: + if err = json.Unmarshal(contentFile, &input); err != nil { + return nil, fmt.Errorf("error parsing file %q: %w", fileName, err) + } + default: + return nil, fmt.Errorf("unsupported file extension: %q", fileExtension) + } + + return &ResourceFile{ + Name: fileName, + Content: &input, + }, nil +} diff --git a/cmd/bamboofwcli/command/create.go b/cmd/bamboofwcli/command/create.go new file mode 100644 index 0000000..02868a1 --- /dev/null +++ b/cmd/bamboofwcli/command/create.go @@ -0,0 +1,82 @@ +package command + +import ( + "context" + "fmt" + "os" + + "github.com/spf13/cobra" + + "github.com/bamboo-firewall/be/api/v1/dto" + "github.com/bamboo-firewall/be/cmd/bamboofwcli/command/common" + "github.com/bamboo-firewall/be/cmd/bamboofwcli/command/resouremanager" + "github.com/bamboo-firewall/be/pkg/client" +) + +var fileCreates []string + +var createCMD = &cobra.Command{ + Use: "create [resourceType]", + Short: "Create resources by filename", + Long: `The create command is used to create resources by filename. + + Resource type available: + * HostEndpoint(or hep) + * GlobalNetworkSet(or gns) + * GlobalNetworkPolicy(or gnp)`, + Example: ` # Create a global network policy + bbfwcli create gnp policy.yaml + + # Create many global network policy + bbfwcli create gnp policy1.yaml policy2.yaml`, + Args: cobra.ExactArgs(1), + Run: func(cmd *cobra.Command, args []string) { + if err := create(cmd, args); err != nil { + fmt.Fprintf(os.Stderr, "Error: %v\n", err) + os.Exit(1) + } + }, +} + +func init() { + createCMD.Flags().StringArrayVarP(&fileCreates, "file", "f", []string{}, "file to read") + createCMD.MarkFlagRequired("file") +} + +func create(cmd *cobra.Command, args []string) error { + resourceType := args[0] + resourceMgr, err := common.GetResourceMgrByType(resourceType) + if err != nil { + return err + } + + var resources []*common.ResourceFile + switch resourceMgr.GetResourceType() { + case resouremanager.ResourceTypeHEP: + resources, err = common.GetResourceFilesByFileNames[dto.CreateHostEndpointInput](fileCreates) + case resouremanager.ResourceTypeGNS: + resources, err = common.GetResourceFilesByFileNames[dto.CreateGlobalNetworkSetInput](fileCreates) + case resouremanager.ResourceTypeGNP: + resources, err = common.GetResourceFilesByFileNames[dto.CreateGlobalNetworkPolicyInput](fileCreates) + default: + return fmt.Errorf("unsupported resource type: %s", resourceType) + } + if err != nil { + return err + } + + apiServer := client.NewAPIServer(os.Getenv(common.APIServerENV)) + var numHandled int + for _, r := range resources { + err = resourceMgr.Create(context.Background(), apiServer, r.Content) + if err != nil { + fmt.Printf("Fail to create resource. Error: %v\n", err) + } else { + fmt.Printf("Successsfully created resource from %s\n", r.Name) + numHandled++ + } + } + + fmt.Printf("Total: %d resources. Success: %d. Fail: %d.\n", len(resources), numHandled, len(resources)-numHandled) + return nil +} diff --git a/cmd/bamboofwcli/command/delete.go b/cmd/bamboofwcli/command/delete.go new file mode 100644 index 0000000..92ab660 --- /dev/null +++ b/cmd/bamboofwcli/command/delete.go @@ -0,0 +1,130 @@ +package command + +import ( + "context" + "fmt" + "os" + + "github.com/spf13/cobra" + + "github.com/bamboo-firewall/be/api/v1/dto" + "github.com/bamboo-firewall/be/cmd/bamboofwcli/command/common" + "github.com/bamboo-firewall/be/cmd/bamboofwcli/command/resouremanager" + "github.com/bamboo-firewall/be/pkg/client" +) + +var fileDeletes []string + +var deleteCMD = &cobra.Command{ + Use: "delete [resourceType]", + Short: "Delete resources by name or filename", + Long: `The delete command is used to delete resources by name or filename. + + Resource type available: + * HostEndpoint(or hep) + * GlobalNetworkSet(or gns) + * GlobalNetworkPolicy(or gnp)`, + Example: ` # Delete a policy with name + bbfwcli delete gnp allow_ssh + + # Delete many policy with name + bbfwcli delete hep allow_ssh allow_ping + + # Delete many policy with filename + bbfwcli delete hep allow_ssh.yaml allow_ping.yaml`, + Args: cobra.MinimumNArgs(1), + Run: func(cmd *cobra.Command, args []string) { + if err := deleteResources(cmd, args); err != nil { + fmt.Fprintf(os.Stderr, "Error: %v\n", err) + os.Exit(1) + } + }, +} + +func init() { + deleteCMD.Flags().StringArrayVarP(&fileDeletes, "file", "f", []string{}, "file to read") +} + +func deleteResources(cmd *cobra.Command, args []string) error { + resourceType := args[0] + resourceMgr, err := common.GetResourceMgrByType(resourceType) + if err != nil { + return err + } + var resourcesName []string + if len(args) > 1 { + resourcesName = args[1:] + } + if len(resourcesName) > 0 && len(fileDeletes) > 0 { + return fmt.Errorf("cannot use name resource with file param together") + } else if len(resourcesName) == 0 && len(fileDeletes) == 0 { + return fmt.Errorf("must specify at least one resource to delete") + } + + var resources []*common.ResourceFile + if len(fileDeletes) > 0 { + switch resourceMgr.GetResourceType() { + case resouremanager.ResourceTypeHEP: + resources, err = common.GetResourceFilesByFileNames[dto.DeleteHostEndpointInput](fileDeletes) + case resouremanager.ResourceTypeGNS: + resources, err = common.GetResourceFilesByFileNames[dto.DeleteGlobalNetworkSetInput](fileDeletes) + case resouremanager.ResourceTypeGNP: + resources, err = common.GetResourceFilesByFileNames[dto.DeleteGlobalNetworkPolicyInput](fileDeletes) + default: + return fmt.Errorf("unsupported resource type: %s", resourceType) + } + if err != nil { + return err + } + } else { + for _, name := range resourcesName { + switch resourceMgr.GetResourceType() { + case resouremanager.ResourceTypeHEP: + resources = append(resources, &common.ResourceFile{ + Name: name, + Content: &dto.DeleteHostEndpointInput{ + Metadata: dto.HostEndpointMetadataInput{ + Name: name, + }, + }, + }) + case resouremanager.ResourceTypeGNS: + resources = append(resources, &common.ResourceFile{ + Name: name, + Content: &dto.DeleteGlobalNetworkSetInput{ + Metadata: dto.GNSMetadataInput{ + Name: name, + }, + }, + }) + case resouremanager.ResourceTypeGNP: + resources = append(resources, &common.ResourceFile{ + Name: name, + Content: &dto.DeleteGlobalNetworkPolicyInput{ + Metadata: dto.GNPMetadataInput{ + Name: name, + }, + }, + }) + default: + return fmt.Errorf("unsupported resource type: %s", resourceType) + } + + } + } + + apiServer := client.NewAPIServer(os.Getenv(common.APIServerENV)) + var numHandled int + for _, r := range resources { + err = resourceMgr.Delete(context.Background(), apiServer, r.Content) + if err != nil { + fmt.Printf("fail to delete resource from: %v\n", err) + } else { + fmt.Printf("successsfully deleted resource from %s\n", r.Name) + numHandled++ + } + } + + fmt.Printf("Total: %d resources. Success: %d. Fail: %d.\n", len(resources), numHandled, len(resources)-numHandled) + return nil +} diff --git a/cmd/bamboofwcli/command/get.go b/cmd/bamboofwcli/command/get.go new file mode 100644 index 0000000..974d7b2 --- /dev/null +++ b/cmd/bamboofwcli/command/get.go @@ -0,0 +1,87 @@ +package command + +import ( + "bytes" + "context" + "encoding/json" + "fmt" + "os" + + "github.com/spf13/cobra" + "gopkg.in/yaml.v3" + + "github.com/bamboo-firewall/be/api/v1/dto" + "github.com/bamboo-firewall/be/cmd/bamboofwcli/command/common" + "github.com/bamboo-firewall/be/cmd/bamboofwcli/command/resouremanager" + "github.com/bamboo-firewall/be/pkg/client" +) + +var outputFormat string + +var getCMD = &cobra.Command{ + Use: "get", + Short: "Get resource by name", + Example: ` # Get a global network policy by name + bbfwcli get gnp allow_ssh + + # Get a global network policy by name with json output format + bbfwcli get gnp allow_ssh -o json +`, + Args: cobra.ExactArgs(2), + Run: func(cmd *cobra.Command, args []string) { + if err := get(cmd, args); err != nil { + fmt.Fprintf(os.Stderr, "Error: %v\n", err) + os.Exit(1) + } + }, +} + +func init() { + getCMD.Flags().StringVarP(&outputFormat, "output", "o", "", "output format(yaml|json). Default: yaml") +} + +func get(cmd *cobra.Command, args []string) error { + resourceType := args[0] + resourceMgr, err := common.GetResourceMgrByType(resourceType) + if err != nil { + return err + } + + resourceName := args[1] + + var input interface{} + switch resourceMgr.GetResourceType() { + case resouremanager.ResourceTypeHEP: + input = &dto.GetHostEndpointInput{Name: resourceName} + case resouremanager.ResourceTypeGNS: + input = &dto.GetGNSInput{Name: resourceName} + case resouremanager.ResourceTypeGNP: + input = &dto.GetGNPInput{Name: resourceName} + default: + return fmt.Errorf("unsupported resource type: %s", resourceType) + } + + apiServer := client.NewAPIServer(os.Getenv(common.APIServerENV)) + + resource, err := resourceMgr.Get(context.Background(), apiServer, input) + if err != nil { + return fmt.Errorf("get resource by name %s failed: %v", resourceName, err) + } + + var output []byte + switch common.FileExtension(outputFormat) { + case common.FileExtensionJSON: + output, err = json.MarshalIndent(resource, "", " ") + default: + var buf bytes.Buffer + yamlEncoder := yaml.NewEncoder(&buf) + yamlEncoder.SetIndent(2) + err = yamlEncoder.Encode(resource) + output = buf.Bytes() + } + if err != nil { + return fmt.Errorf("fail to marshal resource. Error: %v\n", err) + } + fmt.Printf("%s\n", string(output)) + return nil +} diff --git a/cmd/bamboofwcli/command/resouremanager/gnp.go b/cmd/bamboofwcli/command/resouremanager/gnp.go new file mode 100644 index 0000000..42d2dd0 --- /dev/null +++ b/cmd/bamboofwcli/command/resouremanager/gnp.go @@ -0,0 +1,33 @@ +package resouremanager + +import ( + "context" + + "github.com/bamboo-firewall/be/api/v1/dto" +) + +func NewGNP() Resource { + return &gnp{} +} + +type gnp struct { +} + +func (p *gnp) Create(ctx context.Context, apiServer APIServer, resource interface{}) error { + r := resource.(*dto.CreateGlobalNetworkPolicyInput) + return apiServer.CreateGNP(ctx, r) +} + +func (p *gnp) Get(ctx context.Context, apiServer APIServer, resource interface{}) (interface{}, error) { + r := resource.(*dto.GetGNPInput) + return apiServer.GetGNP(ctx, r) +} + +func (p *gnp) Delete(ctx context.Context, apiServer APIServer, resource interface{}) error { + r := resource.(*dto.DeleteGlobalNetworkPolicyInput) + return apiServer.DeleteGNP(ctx, r) +} + +func (p *gnp) GetResourceType() ResourceType { + return ResourceTypeGNP +} diff --git a/cmd/bamboofwcli/command/resouremanager/gns.go b/cmd/bamboofwcli/command/resouremanager/gns.go new file mode 100644 index 0000000..665f549 --- /dev/null +++ b/cmd/bamboofwcli/command/resouremanager/gns.go @@ -0,0 +1,33 @@ +package resouremanager + +import ( + "context" + + "github.com/bamboo-firewall/be/api/v1/dto" +) + +func NewGNS() Resource { + return &gns{} +} + +type gns struct { +} + +func (s *gns) Create(ctx context.Context, apiServer APIServer, resource interface{}) error { + r := resource.(*dto.CreateGlobalNetworkSetInput) + return apiServer.CreateGNS(ctx, r) +} + +func (s *gns) Get(ctx context.Context, apiServer APIServer, resource interface{}) (interface{}, error) { + r := resource.(*dto.GetGNSInput) + return apiServer.GetGNS(ctx, r) +} + +func (s *gns) Delete(ctx context.Context, apiServer APIServer, resource interface{}) error { + r := resource.(*dto.DeleteGlobalNetworkSetInput) + return apiServer.DeleteGNS(ctx, r) +} + +func (s *gns) GetResourceType() ResourceType { + return ResourceTypeGNS +} diff --git a/cmd/bamboofwcli/command/resouremanager/hep.go b/cmd/bamboofwcli/command/resouremanager/hep.go new file mode 100644 index 0000000..0f00ffc --- /dev/null +++ b/cmd/bamboofwcli/command/resouremanager/hep.go @@ -0,0 +1,33 @@ +package resouremanager + +import ( + "context" + + "github.com/bamboo-firewall/be/api/v1/dto" +) + +func NewHEP() Resource { + return &hep{} +} + +type hep struct { +} + +func (h *hep) Create(ctx context.Context, apiServer APIServer, resource interface{}) error { + r := resource.(*dto.CreateHostEndpointInput) + return apiServer.CreateHEP(ctx, r) +} + +func (h *hep) Get(ctx context.Context, apiServer APIServer, resource interface{}) (interface{}, error) { + r := resource.(*dto.GetHostEndpointInput) + return apiServer.GetHEP(ctx, r) +} + +func (h *hep) Delete(ctx context.Context, apiServer APIServer, resource interface{}) error { + r := resource.(*dto.DeleteHostEndpointInput) + return apiServer.DeleteHEP(ctx, r) +} + +func (h *hep) GetResourceType() ResourceType { + return ResourceTypeHEP +} diff --git a/cmd/bamboofwcli/command/resouremanager/resource_manager.go b/cmd/bamboofwcli/command/resouremanager/resource_manager.go new file mode 100644 index 0000000..524210c --- /dev/null +++ b/cmd/bamboofwcli/command/resouremanager/resource_manager.go @@ -0,0 +1,35 @@ +package resouremanager + +import ( + "context" + + "github.com/bamboo-firewall/be/api/v1/dto" +) + +type ResourceType int + +const ( + ResourceTypeNone ResourceType = iota + ResourceTypeHEP + ResourceTypeGNS + ResourceTypeGNP +) + +type Resource interface { + Create(ctx context.Context, apiServer APIServer, resource interface{}) error + Get(ctx context.Context, apiServer APIServer, resource interface{}) (interface{}, error) + Delete(ctx context.Context, apiServer APIServer, resource interface{}) error + GetResourceType() ResourceType +} + +type APIServer interface { + CreateHEP(ctx context.Context, input *dto.CreateHostEndpointInput) error + GetHEP(ctx context.Context, input *dto.GetHostEndpointInput) (*dto.HostEndpoint, error) + DeleteHEP(ctx context.Context, input *dto.DeleteHostEndpointInput) error + CreateGNS(ctx context.Context, input *dto.CreateGlobalNetworkSetInput) error + GetGNS(ctx context.Context, input *dto.GetGNSInput) (*dto.GlobalNetworkSet, error) + DeleteGNS(ctx context.Context, input *dto.DeleteGlobalNetworkSetInput) error + CreateGNP(ctx context.Context, input *dto.CreateGlobalNetworkPolicyInput) error + GetGNP(ctx context.Context, input *dto.GetGNPInput) (*dto.GlobalNetworkPolicy, error) + DeleteGNP(ctx context.Context, input *dto.DeleteGlobalNetworkPolicyInput) error +} diff --git a/cmd/bamboofwcli/command/root.go b/cmd/bamboofwcli/command/root.go new file mode 100644 index 0000000..1ef336d --- /dev/null +++ b/cmd/bamboofwcli/command/root.go @@ -0,0 +1,55 @@ +package command + +import ( + "fmt" + "os" + + "github.com/spf13/cobra" +) + +const ( + binaryName = "bbfw" +) + +var rootCMD = &cobra.Command{ + Use: binaryName, + Short: "bamboo firewall cli", + Long: fmt.Sprintf(`BAMBOO Firewall CLI +Description: + The %s is used to manage global policy, + to view and manage host endpoint, global network set configuration.`, binaryName), +} + +func Execute() { + rootCMD.AddCommand(createCMD) + rootCMD.AddCommand(getCMD) + rootCMD.AddCommand(deleteCMD) + rootCMD.AddCommand(versionCMD) + + rootCMD.AddCommand(&cobra.Command{ + Use: "completion", + DisableFlagsInUseLine: true, + Short: "Generate bash completion script for shell(bash, zsh)", + Example: ` # Gen completion for bash shell + bbfwcli completion bash + + # Gen completion for zsh shell + bbfwcli completion zsh`, + Args: cobra.ExactArgs(1), + Run: func(cmd *cobra.Command, args []string) { + switch args[0] { + case "bash": + rootCMD.GenBashCompletionV2(os.Stdout, true) + case "zsh": + rootCMD.GenZshCompletion(os.Stdout) + default: + fmt.Fprintf(os.Stderr, "Unknown shell bash: %s\n", args[0]) + } + }, + }) + + if err := rootCMD.Execute(); err != nil { + fmt.Fprintln(os.Stderr, err) + os.Exit(1) + } +} diff --git a/cmd/bamboofwcli/command/version.go b/cmd/bamboofwcli/command/version.go new file mode 100644 index 0000000..85511b2 --- /dev/null +++ b/cmd/bamboofwcli/command/version.go @@ -0,0 +1,23 @@ +package command + +import ( + "fmt" + "os" + "text/tabwriter" + + "github.com/spf13/cobra" + + "github.com/bamboo-firewall/be/buildinfo" +) + +var versionCMD = &cobra.Command{ + Use: "version", + Short: "Print the version information", + Run: func(cmd *cobra.Command, args []string) { + version := fmt.Sprintf("Version: \t %s \nBranch: \t %s\nBuild: \t %s\nOrganization: \t %s", buildinfo.Version, buildinfo.GitBranch, buildinfo.BuildDate, buildinfo.Organization) + w := tabwriter.NewWriter(os.Stdout, 0, 0, 0, ' ', tabwriter.TabIndent) + fmt.Fprintln(w, version) + w.Flush() + os.Exit(1) + }, +} diff --git a/cmd/create_admin/create_admin.go b/cmd/create_admin/create_admin.go deleted file mode 100644 index 845eeb5..0000000 --- a/cmd/create_admin/create_admin.go +++ /dev/null @@ -1,80 +0,0 @@ -package main - -import ( - "context" - "fmt" - "log" - - "github.com/bamboo-firewall/be/bootstrap" - "github.com/bamboo-firewall/be/domain" - "github.com/casbin/casbin/v2" - mongodbadapter "github.com/casbin/mongodb-adapter/v3" - "go.mongodb.org/mongo-driver/bson" - "go.mongodb.org/mongo-driver/bson/primitive" - "golang.org/x/crypto/bcrypt" -) - -const adminRole = "Admin" - -func main() { - app := bootstrap.App() - env := app.Env - - db := app.Mongo.Database(env.DBName) - defer app.CloseDBConnection() - - // Check if admin user already exists - var admin domain.User - res := db.Collection(domain.CollectionUser).FindOne(context.Background(), bson.M{"email": "admin@example.com"}).Decode(&admin) - if res == nil { - log.Println("Admin user already exists") - return - } - - // Create admin user - encryptedPassword, err := bcrypt.GenerateFromPassword( - []byte(env.AdminPassword), - bcrypt.DefaultCost, - ) - - if err != nil { - log.Println(err) - return - } - - user := domain.User{ - ID: primitive.NewObjectID(), - Name: env.AdminAccount, - Username: env.AdminAccount, - Email: fmt.Sprintf("%s@%s", env.AdminAccount, env.EmailDomain), - Password: string(encryptedPassword), - Role: adminRole, - } - - _, err = db.Collection(domain.CollectionUser).InsertOne(context.Background(), &user) - if err != nil { - log.Println(err) - return - } - - adapterConfig := mongodbadapter.AdapterConfig{ - DatabaseName: env.DBName, - } - - adapter, err := mongodbadapter.NewAdapterByDB(db.Client().MongoClient(), &adapterConfig) - - if err != nil { - log.Println(err) - return - } - - enforcer, err := casbin.NewEnforcer("config/rbac_model.conf", adapter) - if err != nil { - log.Println(err) - return - } - // Add policy for admin - enforcer.AddGroupingPolicy(user.ID.Hex(), user.Role) - - log.Println("Admin user created successfully") -} diff --git a/cmd/main.go b/cmd/main.go deleted file mode 100644 index 3a279fe..0000000 --- a/cmd/main.go +++ /dev/null @@ -1,26 +0,0 @@ -package main - -import ( - "time" - - route "github.com/bamboo-firewall/be/api/route" - "github.com/bamboo-firewall/be/bootstrap" - "github.com/gin-gonic/gin" -) - -func main() { - app := bootstrap.App() - - env := app.Env - - db := app.Mongo.Database(env.DBName) - defer app.CloseDBConnection() - - timeout := time.Duration(env.ContextTimeout) * time.Second - - gin := gin.Default() - - route.Setup(env, timeout, db, gin) - - gin.Run(env.ServerAddress) -} diff --git a/cmd/server/api_server.go b/cmd/server/api_server.go new file mode 100644 index 0000000..b7230e7 --- /dev/null +++ b/cmd/server/api_server.go @@ -0,0 +1,55 @@ +package main + +import ( + "context" + "flag" + "log" + "log/slog" + "os" + "os/signal" + "syscall" + "time" + + "github.com/bamboo-firewall/be/config" +) + +const ( + defaultGracefulTimeout = 30 * time.Second +) + +func main() { + var pathConfig string + flag.StringVar(&pathConfig, "config-file", "", "path to env config file") + flag.Parse() + + cfg, err := config.New(pathConfig) + if err != nil { + slog.Warn("read config from file fail", "error", err) + } + newApp, err := NewApp(cfg) + if err != nil { + log.Fatal(err) + } + go func() { + if err = newApp.Start(); err != nil { + log.Fatal(err) + } + }() + interruptHandle(newApp) +} + +func interruptHandle(app App) { + c := make(chan os.Signal, 1) + signal.Notify(c, os.Interrupt, syscall.SIGINT, syscall.SIGTERM) + + slog.Debug("Listening Signal...") + s := <-c + slog.Info("Shutting down Server ...", "Received signal.", s) + + stopCtx, cancel := context.WithTimeout(context.Background(), defaultGracefulTimeout) + defer cancel() + + if err := app.Stop(stopCtx); err != nil { + log.Fatal(err) + } +} diff --git a/cmd/server/app.go b/cmd/server/app.go new file mode 100644 index 0000000..8e89cd7 --- /dev/null +++ b/cmd/server/app.go @@ -0,0 +1,66 @@ +package main + +import ( + "context" + "errors" + "fmt" + "net/http" + + "github.com/bamboo-firewall/be/cmd/server/pkg/httpbase" + "github.com/bamboo-firewall/be/cmd/server/pkg/repository" + "github.com/bamboo-firewall/be/cmd/server/pkg/storage" + "github.com/bamboo-firewall/be/cmd/server/pkg/validator" + "github.com/bamboo-firewall/be/cmd/server/route" + "github.com/bamboo-firewall/be/config" +) + +type App interface { + Start() error + Stop(ctx context.Context) error +} + +type app struct { + httpServer *httpbase.Server + policyDB *storage.PolicyDB +} + +func NewApp(cfg config.Config) (App, error) { + policy, err := storage.NewPolicyDB(cfg.DBURI) + if err != nil { + return nil, err + } + + validator.Init() + + router := route.RegisterHandler(repository.NewPolicy(policy)) + return &app{ + httpServer: httpbase.NewServer( + fmt.Sprintf("%s:%s", cfg.HTTPServerHost, cfg.HTTPServerPort), + router, + httpbase.ConfigTimeout{ + ReadTimeout: cfg.HTTPServerReadTimeout, + ReadHeaderTimeout: cfg.HTTPServerReadHeaderTimeout, + WriteTimeout: cfg.HTTPServerWriteTimeout, + IdleTimeout: cfg.HTTPServerIdleTimeout, + }, + ), + policyDB: policy, + }, nil +} + +func (a *app) Start() error { + if err := a.httpServer.Start(); err != nil && !errors.Is(err, http.ErrServerClosed) { + return err + } + return nil +} + +func (a *app) Stop(ctx context.Context) error { + if err := a.httpServer.Stop(ctx); err != nil { + return err + } + if err := a.policyDB.Stop(ctx); err != nil { + return err + } + return nil +} diff --git a/cmd/server/middleware/cors.go b/cmd/server/middleware/cors.go new file mode 100644 index 0000000..026796a --- /dev/null +++ b/cmd/server/middleware/cors.go @@ -0,0 +1,18 @@ +package middleware + +import ( + "time" + + "github.com/gin-contrib/cors" + "github.com/gin-gonic/gin" +) + +func CORS() gin.HandlerFunc { + config := cors.Config{ + AllowMethods: []string{"GET", "OPTIONS", "POST", "PUT", "PATCH", "DELETE"}, + AllowHeaders: []string{"Origin", "Content-Length", "Content-Type", "Authorization"}, + AllowAllOrigins: true, + MaxAge: 12 * time.Hour, + } + return cors.New(config) +} diff --git a/cmd/server/pkg/common/errlist/common_err.go b/cmd/server/pkg/common/errlist/common_err.go new file mode 100644 index 0000000..8631675 --- /dev/null +++ b/cmd/server/pkg/common/errlist/common_err.go @@ -0,0 +1,13 @@ +package errlist + +import "github.com/bamboo-firewall/be/cmd/server/pkg/httpbase/ierror" + +var ( + ErrDatabase = ierror.NewCoreError("err_database", "") + + ErrNotFoundHostEndpoint = ierror.NewCoreError("err_not_found_host_endpoint", "") + ErrNotFoundGlobalNetworkPolicy = ierror.NewCoreError("err_not_found_global_network_policy", "") + ErrNotFoundGlobalNetworkSet = ierror.NewCoreError("err_not_found_global_network_set", "") + + ErrUnmarshalFailed = ierror.NewCoreError("err_unmarshal_failed", "") +) diff --git a/cmd/server/pkg/entity/common.go b/cmd/server/pkg/entity/common.go new file mode 100644 index 0000000..e34f656 --- /dev/null +++ b/cmd/server/pkg/entity/common.go @@ -0,0 +1,17 @@ +package entity + +type IPVersion int + +const ( + IPVersion4 IPVersion = 4 + IPVersion6 IPVersion = 6 +) + +type Protocol string + +const ( + ProtocolTCP Protocol = "tcp" + ProtocolUDP Protocol = "udp" + ProtocolICMP Protocol = "icmp" + ProtocolSCTP Protocol = "sctp" +) diff --git a/cmd/server/pkg/entity/gnp.go b/cmd/server/pkg/entity/gnp.go new file mode 100644 index 0000000..0dcae22 --- /dev/null +++ b/cmd/server/pkg/entity/gnp.go @@ -0,0 +1,60 @@ +package entity + +import ( + "time" + + "go.mongodb.org/mongo-driver/bson/primitive" +) + +type RuleAction string + +const ( + RuleActionAllow RuleAction = "allow" + RuleActionDeny RuleAction = "deny" + RuleActionLog RuleAction = "log" + RuleActionPass RuleAction = "pass" +) + +type GlobalNetworkPolicy struct { + ID primitive.ObjectID `bson:"_id"` + UUID string `bson:"uuid"` + Version uint `bson:"version"` + Metadata GNPMetadata `bson:"metadata"` + Spec GNPSpec `bson:"spec"` + Description string `bson:"description"` + CreatedAt time.Time `bson:"created_at"` + UpdatedAt time.Time `bson:"updated_at"` +} + +type GNPMetadata struct { + Name string `bson:"name"` + Labels map[string]string `bson:"labels"` +} + +type GNPSpec struct { + Selector string `bson:"selector,omitempty"` + Ingress []GNPSpecRule `bson:"ingress,omitempty"` + Egress []GNPSpecRule `bson:"egress,omitempty"` +} + +type GNPSpecRule struct { + Metadata map[string]string `bson:"metadata,omitempty"` + Action string `bson:"action"` + IPVersion IPVersion `bson:"ip_version"` + Protocol string `bson:"protocol,omitempty"` + NotProtocol string `bson:"not_protocol,omitempty"` + Source *GNPSpecRuleEntity `bson:"source,omitempty"` + Destination *GNPSpecRuleEntity `bson:"destination,omitempty"` +} + +type GNPSpecRuleEntity struct { + Selector string `bson:"selector,omitempty"` + Nets []string `bson:"nets,omitempty"` + NotNets []string `bson:"not_nets,omitempty"` + Ports []interface{} `bson:"ports,omitempty"` + NotPorts []interface{} `bson:"not_ports,omitempty"` +} + +func (GlobalNetworkPolicy) CollectionName() string { + return "global_network_policy" +} diff --git a/cmd/server/pkg/entity/gns.go b/cmd/server/pkg/entity/gns.go new file mode 100644 index 0000000..6ab1b83 --- /dev/null +++ b/cmd/server/pkg/entity/gns.go @@ -0,0 +1,33 @@ +package entity + +import ( + "time" + + "go.mongodb.org/mongo-driver/bson/primitive" +) + +type GlobalNetworkSet struct { + ID primitive.ObjectID `bson:"_id"` + UUID string `bson:"uuid"` + Version uint `bson:"version"` + Metadata GNSMetadata `bson:"metadata"` + Spec GNSSpec `bson:"spec"` + Description string `bson:"description"` + CreatedAt time.Time `bson:"created_at"` + UpdatedAt time.Time `bson:"updated_at"` +} + +type GNSMetadata struct { + Name string `bson:"name"` + Labels map[string]string `bson:"labels,omitempty"` +} + +type GNSSpec struct { + Nets []string `bson:"nets"` + NetsV4 []string `bson:"nets_v4,omitempty"` + NetsV6 []string `bson:"nets_v6,omitempty"` +} + +func (GlobalNetworkSet) CollectionName() string { + return "global_network_set" +} diff --git a/cmd/server/pkg/entity/hep.go b/cmd/server/pkg/entity/hep.go new file mode 100644 index 0000000..1ba8b8d --- /dev/null +++ b/cmd/server/pkg/entity/hep.go @@ -0,0 +1,34 @@ +package entity + +import ( + "time" + + "go.mongodb.org/mongo-driver/bson/primitive" +) + +type HostEndpoint struct { + ID primitive.ObjectID `bson:"_id"` + UUID string `bson:"uuid"` + Version uint `bson:"version"` + Metadata HostEndpointMetadata `bson:"metadata"` + Spec HostEndpointSpec `bson:"spec"` + Description string `bson:"description"` + CreatedAt time.Time `bson:"created_at"` + UpdatedAt time.Time `bson:"updated_at"` +} + +type HostEndpointMetadata struct { + Name string `bson:"name"` + Labels map[string]string `bson:"labels"` +} + +type HostEndpointSpec struct { + InterfaceName string `bson:"interface_name"` + IPs []string `bson:"ips"` + IPsV4 []string `bson:"ips_v4,omitempty"` + IPsV6 []string `bson:"ips_v6,omitempty"` +} + +func (HostEndpoint) CollectionName() string { + return "host_endpoint" +} diff --git a/cmd/server/pkg/httpbase/const.go b/cmd/server/pkg/httpbase/const.go new file mode 100644 index 0000000..804eab3 --- /dev/null +++ b/cmd/server/pkg/httpbase/const.go @@ -0,0 +1,9 @@ +package httpbase + +const ( + MIMEApplicationJSON = "application/json" +) + +const ( + HeaderContentType = "Content-Type" +) diff --git a/cmd/server/pkg/httpbase/error.go b/cmd/server/pkg/httpbase/error.go new file mode 100644 index 0000000..449f794 --- /dev/null +++ b/cmd/server/pkg/httpbase/error.go @@ -0,0 +1,70 @@ +package httpbase + +import ( + "context" + "net/http" + + "github.com/bamboo-firewall/be/cmd/server/pkg/httpbase/ierror" +) + +const ( + DefaultServerName = ierror.ErrorName("err_internal_server") +) + +// ErrorCode with 5xx status. Begin Code from 1000 -> 1999 +const ( + ErrorCodeInternalServer ierror.ErrorCode = iota + 1000 + ErrorCodeDatabase +) + +// ErrorCode with 4xx status. Begin code from 2000 -> 2999 +const ( + ErrorCodeBadRequest ierror.ErrorCode = iota + 2000 + ErrorCodeNotFound + ErrorCodeValidateRequest + ErrorCodeForBidden + ErrorCodeUnauthorized +) + +func toName(id ierror.ErrorCode) ierror.ErrorName { + switch id { + // 5xx code + case ErrorCodeInternalServer: + return DefaultServerName + case ErrorCodeDatabase: + return "err_database" + // 4xx code + case ErrorCodeBadRequest: + return "err_bad_request" + case ErrorCodeUnauthorized: + return "err_unauthorized" + case ErrorCodeNotFound: + return "err_not_found" + case ErrorCodeValidateRequest: + return "err_validate_request" + default: + return "err_common" + } +} + +var ( + ErrNotFound = func(ctx context.Context, msgID string) *ierror.Error { + return newClientIError(ctx, ErrorCodeNotFound, msgID).SetHTTPStatus(http.StatusNotFound) + } + + ErrBindRequest = func(ctx context.Context, msgID string) *ierror.Error { + return newClientIError(ctx, ErrorCodeBadRequest, msgID).SetHTTPStatus(http.StatusBadRequest) + } + + ErrValidateRequest = func(ctx context.Context, msgID string) *ierror.Error { + return newClientIError(ctx, ErrorCodeBadRequest, msgID).SetHTTPStatus(http.StatusBadRequest) + } + + ErrDatabase = func(ctx context.Context, msgID string) *ierror.Error { + return newClientIError(ctx, ErrorCodeDatabase, msgID).SetHTTPStatus(http.StatusInternalServerError) + } +) + +func newClientIError(ctx context.Context, errorCode ierror.ErrorCode, msgID string) *ierror.Error { + return ierror.NewError(errorCode, toName(errorCode), msgID) +} diff --git a/cmd/server/pkg/httpbase/ginutil.go b/cmd/server/pkg/httpbase/ginutil.go new file mode 100644 index 0000000..b323bfe --- /dev/null +++ b/cmd/server/pkg/httpbase/ginutil.go @@ -0,0 +1,98 @@ +package httpbase + +import ( + "errors" + "log/slog" + "net/http" + + "github.com/gin-gonic/gin" + "github.com/go-playground/validator/v10" + + "github.com/bamboo-firewall/be/cmd/server/pkg/httpbase/ierror" +) + +type ErrorResponse struct { + Error *MinifyError `json:"error"` +} + +type MinifyError struct { + Code ierror.ErrorCode `json:"code"` + Name ierror.ErrorName `json:"name"` + Message string `json:"message"` + Detail interface{} `json:"detail,omitempty"` + SubName string `json:"sub_name,omitempty"` + SubMessage string `json:"sub_message,omitempty"` + TrackID string `json:"track_id,omitempty"` +} + +func ReturnErrorResponse(ctx *gin.Context, err *ierror.Error) { + minifyErr := &MinifyError{ + Code: err.Code, + Name: err.Name, + Message: err.Message, + Detail: err.Detail, + } + if err.HTTPStatusCode >= http.StatusInternalServerError { + slog.Error("something wrong", "error", err) + } + ctx.JSON(err.HTTPStatusCode, ErrorResponse{ + Error: minifyErr, + }) +} + +func ReturnSuccessResponse(ctx *gin.Context, status int, data interface{}) { + if data == nil { + ctx.Status(status) + } else { + ctx.JSON(status, data) + } +} + +func BindInput(ctx *gin.Context, input interface{}) *ierror.Error { + err := ctx.ShouldBindUri(input) + if err != nil { + return bindError(ctx, err) + } + err = ctx.ShouldBindHeader(input) + if err != nil { + return bindError(ctx, err) + } + err = ctx.ShouldBind(input) + if err != nil { + return bindError(ctx, err) + } + if err = defaultValidator.Struct(input); err != nil { + return validateError(ctx, err) + } + return nil +} + +func bindError(ctx *gin.Context, err error) *ierror.Error { + return ErrBindRequest(ctx, "BindFailed").SetDetail(err.Error()) +} + +type validatorErrorDetail struct { + Field string `json:"field"` + RejectedValue interface{} `json:"rejected_value"` + Tag string `json:"tag"` + Param string `json:"param,omitempty"` +} + +func validateError(ctx *gin.Context, err error) *ierror.Error { + var errs validator.ValidationErrors + ok := errors.As(err, &errs) + if !ok { + return ErrValidateRequest(ctx, "ValidateFailed").SetDetail(err.Error()) + } + + errDetails := make([]validatorErrorDetail, 0, len(errs)) + for _, verr := range errs { + errDetails = append(errDetails, validatorErrorDetail{ + Field: verr.Field(), + RejectedValue: verr.Value(), + Tag: verr.Tag(), + Param: verr.Param(), + }) + } + return ErrValidateRequest(ctx, "ValidateFailed").SetDetail(errDetails) +} diff --git a/cmd/server/pkg/httpbase/ierror/core_error.go b/cmd/server/pkg/httpbase/ierror/core_error.go new file mode 100644 index 0000000..43d6c58 --- /dev/null +++ b/cmd/server/pkg/httpbase/ierror/core_error.go @@ -0,0 +1,29 @@ +package ierror + +import "fmt" + +func NewCoreError(name string, message string) *CoreError { + return &CoreError{ + Name: name, + Message: message, + } +} + +type CoreError struct { + Name string `json:"name"` + Message string `json:"message"` + Child error `json:"-"` +} + +func (e *CoreError) Error() string { + if e.Child != nil { + return fmt.Sprintf("%s:%s child(%s)", e.Name, e.Message, e.Child) + } else { + return fmt.Sprintf("%s:%s", e.Name, e.Message) + } +} + +func (e *CoreError) WithChild(child error) *CoreError { + e.Child = child + return e +} diff --git a/cmd/server/pkg/httpbase/ierror/error.go b/cmd/server/pkg/httpbase/ierror/error.go new file mode 100644 index 0000000..ca00cc8 --- /dev/null +++ b/cmd/server/pkg/httpbase/ierror/error.go @@ -0,0 +1,43 @@ +package ierror + +import "fmt" + +type ErrorCode uint16 +type ErrorName string + +func NewError(code ErrorCode, name ErrorName, message string) *Error { + return &Error{ + Code: code, + Name: name, + Message: message, + } +} + +type Error struct { + Code ErrorCode `json:"code"` + Name ErrorName `json:"name"` + Message string `json:"message"` + Detail interface{} `json:"detail,omitempty"` + + SubError *CoreError `json:"-"` + HTTPStatusCode int `json:"-"` +} + +func (e *Error) Error() string { + return fmt.Sprintf("%d:%s msg[%s] subErr[%s]", e.Code, e.Name, e.Message, e.SubError) +} + +func (e *Error) SetSubError(subErr *CoreError) *Error { + e.SubError = subErr + return e +} + +func (e *Error) SetHTTPStatus(status int) *Error { + e.HTTPStatusCode = status + return e +} + +func (e *Error) SetDetail(detail interface{}) *Error { + e.Detail = detail + return e +} diff --git a/cmd/server/pkg/httpbase/server.go b/cmd/server/pkg/httpbase/server.go new file mode 100644 index 0000000..604c15a --- /dev/null +++ b/cmd/server/pkg/httpbase/server.go @@ -0,0 +1,65 @@ +package httpbase + +import ( + "context" + "log/slog" + "net/http" + "time" +) + +const ( + defaultReadTimeout = 15 * time.Second + defaultReadHeaderTimeout = 15 * time.Second + defaultWriteTimeout = 15 * time.Second + defaultIdleTimeout = 60 * time.Second +) + +type ConfigTimeout struct { + ReadTimeout time.Duration + ReadHeaderTimeout time.Duration + WriteTimeout time.Duration + IdleTimeout time.Duration +} + +type Server struct { + server *http.Server +} + +func NewServer(addr string, mux http.Handler, cfg ConfigTimeout, opts ...serverOption) *Server { + if cfg.ReadTimeout <= 0 { + cfg.ReadTimeout = defaultReadTimeout + } + if cfg.ReadHeaderTimeout <= 0 { + cfg.ReadHeaderTimeout = defaultReadHeaderTimeout + } + if cfg.WriteTimeout <= 0 { + cfg.WriteTimeout = defaultWriteTimeout + } + if cfg.IdleTimeout <= 0 { + cfg.IdleTimeout = defaultIdleTimeout + } + httpServer := &http.Server{ + Addr: addr, + Handler: mux, + ReadTimeout: cfg.ReadTimeout, + ReadHeaderTimeout: cfg.ReadHeaderTimeout, + WriteTimeout: cfg.WriteTimeout, + IdleTimeout: cfg.IdleTimeout, + } + s := &Server{ + server: httpServer, + } + for _, o := range opts { + o(s) + } + return s +} + +func (s *Server) Start() error { + return s.server.ListenAndServe() +} + +func (s *Server) Stop(ctx context.Context) error { + slog.Info("Stop http server") + return s.server.Shutdown(ctx) +} diff --git a/cmd/server/pkg/httpbase/server_option.go b/cmd/server/pkg/httpbase/server_option.go new file mode 100644 index 0000000..9327b89 --- /dev/null +++ b/cmd/server/pkg/httpbase/server_option.go @@ -0,0 +1,117 @@ +package httpbase + +import ( + "context" + "crypto/tls" + "net" + "net/http" + "time" +) + +type serverOption func(s *Server) + +// WithDisableGeneralOptionsHandler if true, passes "OPTIONS *" requests to the Handler, +// otherwise responds with 200 OK and Content-Length: 0. +func WithDisableGeneralOptionsHandler(diable bool) serverOption { + return func(s *Server) { + s.server.DisableGeneralOptionsHandler = diable + } +} + +// WithMaxHeaderBytes controls the maximum number of bytes the +// server will read parsing the request header's keys and +// values, including the request line. It does not limit the +// size of the request body. +// If zero, DefaultMaxHeaderBytes is used. +func WithMaxHeaderBytes(maxBytes int) serverOption { + return func(s *Server) { + if maxBytes > 0 { + s.server.MaxHeaderBytes = maxBytes + } + } +} + +// WithTLSConfig optionally provides a TLS configuration for use +// by ServeTLS and ListenAndServeTLS. Note that this value is +// cloned by ServeTLS and ListenAndServeTLS, so it's not +// possible to modify the configuration with methods like +// tls.Config.SetSessionTicketKeys. To use +// SetSessionTicketKeys, use Server.Serve with a TLS Listener +// instead. +func WithTLSConfig(cnf *tls.Config) serverOption { + return func(s *Server) { + if cnf != nil { + s.server.TLSConfig = cnf + } + } +} + +// WithTLSNextProto optionally specifies a function to take over +// ownership of the provided TLS connection when an ALPN +// protocol upgrade has occurred. The map key is the protocol +// name negotiated. The Handler argument should be used to +// handle HTTP requests and will initialize the Request's TLS +// and RemoteAddr if not already set. The connection is +// automatically closed when the function returns. +// If TLSNextProto is not nil, HTTP/2 support is not enabled +// automatically. +func WithTLSNextProto(tlsNextProto map[string]func(*http.Server, *tls.Conn, http.Handler)) serverOption { + return func(s *Server) { + s.server.TLSNextProto = tlsNextProto + } +} + +// WithConnState specifies an optional callback function that is +// called when a client connection changes state. See the +// ConnState type and associated constants for details. +func WithConnState(connState func(net.Conn, http.ConnState)) serverOption { + return func(s *Server) { + s.server.ConnState = connState + } +} + +// WithBaseContent optionally specifies a function that returns +// the base context for incoming requests on this server. +// The provided Listener is the specific Listener that's +// about to start accepting requests. +// If BaseContext is nil, the default is context.Background(). +// If non-nil, it must return a non-nil context. +// DEPRECATED: Use BaseContext instead. +func WithBaseContent(baseContext func(net.Listener) context.Context) serverOption { + return func(s *Server) { + s.server.BaseContext = baseContext + } +} + +// WithBaseContext optionally specifies a function that returns +// the base context for incoming requests on this server. +// The provided Listener is the specific Listener that's +// about to start accepting requests. +// If BaseContext is nil, the default is context.Background(). +// If non-nil, it must return a non-nil context. +func WithBaseContext(baseContext func(net.Listener) context.Context) serverOption { + return func(s *Server) { + s.server.BaseContext = baseContext + } +} + +// WithConnContext optionally specifies a function that modifies +// the context used for a new connection c. The provided ctx +// is derived from the base context and has a ServerContextKey +// value. +func WithConnContext(connContext func(ctx context.Context, c net.Conn) context.Context) serverOption { + return func(s *Server) { + s.server.ConnContext = connContext + } +} + +// WithHandlerTimeout use httpbase.TimeoutHandler to handle request timeout +// if handlerTimeout is zero, it will be ignored +// WARN: sse, websocket, long-polling, etc... should not use this option +func WithHandlerTimeout(handlerTimeout time.Duration) serverOption { + return func(s *Server) { + if handlerTimeout > 0 { + s.server.Handler = http.TimeoutHandler(s.server.Handler, handlerTimeout, "server timeout") + } + } +} diff --git a/cmd/server/pkg/httpbase/validator.go b/cmd/server/pkg/httpbase/validator.go new file mode 100644 index 0000000..2b0bc4c --- /dev/null +++ b/cmd/server/pkg/httpbase/validator.go @@ -0,0 +1,15 @@ +package httpbase + +import "github.com/go-playground/validator/v10" + +var ( + defaultValidator = validator.New() +) + +func RegisterValidator(tag string, fn validator.Func) error { + return defaultValidator.RegisterValidation(tag, fn) +} + +func RegisterStructValidation(fn validator.StructLevelFunc, types ...interface{}) { + defaultValidator.RegisterStructValidation(fn, types...) +} diff --git a/cmd/server/pkg/net/ip.go b/cmd/server/pkg/net/ip.go new file mode 100644 index 0000000..1b0401d --- /dev/null +++ b/cmd/server/pkg/net/ip.go @@ -0,0 +1,64 @@ +package net + +import ( + "encoding/json" + "net" +) + +type IP struct { + net.IP +} + +func (i IP) MarshalJSON() ([]byte, error) { + s, err := i.MarshalText() + if err != nil { + return nil, err + } + return json.Marshal(string(s)) +} + +func (i *IP) UnmarshalJSON(b []byte) error { + var s string + if err := json.Unmarshal(b, &s); err != nil { + return err + } + if err := i.UnmarshalText([]byte(s)); err != nil { + return err + } + if ipv4 := i.To4(); ipv4 != nil { + i.IP = ipv4 + } + return nil +} + +func ParseIP(s string) *IP { + addr := net.ParseIP(s) + if addr == nil { + return nil + } + if addr4 := addr.To4(); addr4 != nil { + addr = addr4 + } + return &IP{addr} +} + +func (i IP) Version() int { + if i.To4() != nil { + return 4 + } else if i.To16() != nil { + return 6 + } + return 0 +} + +func (i IP) Network() *IPNet { + ipnet := &IPNet{} + if ipv4 := i.IP.To4(); ipv4 != nil { + ipnet.IP = ipv4 + ipnet.Mask = net.CIDRMask(net.IPv4len*8, net.IPv4len*8) + } else { + ipnet.IP = i.IP + ipnet.Mask = net.CIDRMask(net.IPv6len*8, net.IPv6len*8) + } + return ipnet +} diff --git a/cmd/server/pkg/net/net.go b/cmd/server/pkg/net/net.go new file mode 100644 index 0000000..d4bf557 --- /dev/null +++ b/cmd/server/pkg/net/net.go @@ -0,0 +1,67 @@ +package net + +import ( + "encoding/json" + "net" +) + +type IPNet struct { + net.IPNet +} + +func (i IPNet) MarshalJSON() ([]byte, error) { + return json.Marshal(i.String()) +} + +func (i *IPNet) UnmarshalJSON(b []byte) error { + var s string + if err := json.Unmarshal(b, &s); err != nil { + return err + } + ip, ipnet, err := ParseCIDROrIP(s) + if err != nil { + return err + } + i.IP = ip.IP + i.Mask = ipnet.Mask + return nil +} + +func (i IPNet) Version() int { + if i.IP.To4() != nil { + return 4 + } else if i.IP.To16() != nil { + return 6 + } + return 0 +} + +func ParseCIDR(cidr string) (*IP, *IPNet, error) { + netIP, netIPNet, err := net.ParseCIDR(cidr) + if netIPNet == nil || err != nil { + return nil, nil, err + } + ip := &IP{netIP} + ipNet := &IPNet{*netIPNet} + + if ipv4 := ip.IP.To4(); ipv4 != nil { + ip.IP = ipv4 + } + return ip, ipNet, nil +} + +func ParseCIDROrIP(cidr string) (*IP, *IPNet, error) { + // parse CIDR + ip, ipnet, err := ParseCIDR(cidr) + if err == nil { + return ip, ipnet, nil + } + + // if err, parse ip + ip = ParseIP(cidr) + if ip == nil { + return nil, nil, err + } + ipnet = ip.Network() + return ip, ipnet, nil +} diff --git a/cmd/server/pkg/repository/gnp.go b/cmd/server/pkg/repository/gnp.go new file mode 100644 index 0000000..aca8735 --- /dev/null +++ b/cmd/server/pkg/repository/gnp.go @@ -0,0 +1,62 @@ +package repository + +import ( + "context" + "errors" + + "go.mongodb.org/mongo-driver/bson" + "go.mongodb.org/mongo-driver/mongo" + "go.mongodb.org/mongo-driver/mongo/options" + + "github.com/bamboo-firewall/be/cmd/server/pkg/common/errlist" + "github.com/bamboo-firewall/be/cmd/server/pkg/entity" + "github.com/bamboo-firewall/be/cmd/server/pkg/httpbase/ierror" +) + +func (r *PolicyDB) UpsertGroupPolicy(ctx context.Context, gnp *entity.GlobalNetworkPolicy) *ierror.CoreError { + filter := bson.D{{Key: "_id", Value: gnp.ID}} + update := bson.D{{Key: "$set", Value: gnp}} + opts := options.Update().SetUpsert(true) + _, err := r.mongo.Database.Collection(gnp.CollectionName()).UpdateOne(ctx, filter, update, opts) + if err != nil { + return errlist.ErrDatabase.WithChild(err) + } + + return nil +} + +func (r *PolicyDB) GetGNPByName(ctx context.Context, name string) (*entity.GlobalNetworkPolicy, *ierror.CoreError) { + filter := bson.D{{Key: "metadata.name", Value: name}} + + gnp := new(entity.GlobalNetworkPolicy) + err := r.mongo.Database.Collection(gnp.CollectionName()).FindOne(ctx, filter).Decode(gnp) + if err != nil { + if errors.Is(err, mongo.ErrNoDocuments) { + return nil, errlist.ErrNotFoundGlobalNetworkPolicy + } + return nil, errlist.ErrDatabase.WithChild(err) + } + return gnp, nil +} + +func (r *PolicyDB) DeleteGNPByName(ctx context.Context, name string) *ierror.CoreError { + filter := bson.D{{Key: "metadata.name", Value: name}} + + _, err := r.mongo.Database.Collection(entity.GlobalNetworkPolicy{}.CollectionName()).DeleteOne(ctx, filter) + if err != nil { + return errlist.ErrDatabase.WithChild(err) + } + return nil +} + +func (r *PolicyDB) ListGNP(ctx context.Context) ([]*entity.GlobalNetworkPolicy, *ierror.CoreError) { + policies := make([]*entity.GlobalNetworkPolicy, 0) + cursor, err := r.mongo.Database.Collection(entity.GlobalNetworkPolicy{}.CollectionName()).Find(ctx, bson.D{}) + if err != nil { + return nil, errlist.ErrDatabase.WithChild(err) + } + if err = cursor.All(ctx, &policies); err != nil { + return nil, errlist.ErrUnmarshalFailed.WithChild(err) + } + return policies, nil +} diff --git a/cmd/server/pkg/repository/gns.go b/cmd/server/pkg/repository/gns.go new file mode 100644 index 0000000..9ebfeed --- /dev/null +++ b/cmd/server/pkg/repository/gns.go @@ -0,0 +1,62 @@ +package repository + +import ( + "context" + "errors" + + "go.mongodb.org/mongo-driver/bson" + "go.mongodb.org/mongo-driver/mongo" + "go.mongodb.org/mongo-driver/mongo/options" + + "github.com/bamboo-firewall/be/cmd/server/pkg/common/errlist" + "github.com/bamboo-firewall/be/cmd/server/pkg/entity" + "github.com/bamboo-firewall/be/cmd/server/pkg/httpbase/ierror" +) + +func (r *PolicyDB) UpsertGNS(ctx context.Context, gns *entity.GlobalNetworkSet) *ierror.CoreError { + filter := bson.D{{Key: "_id", Value: gns.ID}} + update := bson.D{{Key: "$set", Value: gns}} + opts := options.Update().SetUpsert(true) + _, err := r.mongo.Database.Collection(gns.CollectionName()).UpdateOne(ctx, filter, update, opts) + if err != nil { + return errlist.ErrDatabase.WithChild(err) + } + + return nil +} + +func (r *PolicyDB) GetGNSByName(ctx context.Context, name string) (*entity.GlobalNetworkSet, *ierror.CoreError) { + filter := bson.D{{Key: "metadata.name", Value: name}} + + gns := new(entity.GlobalNetworkSet) + err := r.mongo.Database.Collection(gns.CollectionName()).FindOne(ctx, filter).Decode(gns) + if err != nil { + if errors.Is(err, mongo.ErrNoDocuments) { + return nil, errlist.ErrNotFoundGlobalNetworkSet + } + return nil, errlist.ErrDatabase.WithChild(err) + } + return gns, nil +} + +func (r *PolicyDB) DeleteGNSByName(ctx context.Context, name string) *ierror.CoreError { + filter := bson.D{{Key: "metadata.name", Value: name}} + + _, err := r.mongo.Database.Collection(entity.GlobalNetworkSet{}.CollectionName()).DeleteOne(ctx, filter) + if err != nil { + return errlist.ErrDatabase.WithChild(err) + } + return nil +} + +func (r *PolicyDB) ListGNS(ctx context.Context) ([]*entity.GlobalNetworkSet, *ierror.CoreError) { + sets := make([]*entity.GlobalNetworkSet, 0) + cursor, err := r.mongo.Database.Collection(entity.GlobalNetworkSet{}.CollectionName()).Find(ctx, bson.D{}) + if err != nil { + return nil, errlist.ErrDatabase.WithChild(err) + } + if err = cursor.All(ctx, &sets); err != nil { + return nil, errlist.ErrUnmarshalFailed.WithChild(err) + } + return sets, nil +} diff --git a/cmd/server/pkg/repository/hep.go b/cmd/server/pkg/repository/hep.go new file mode 100644 index 0000000..b5dc82c --- /dev/null +++ b/cmd/server/pkg/repository/hep.go @@ -0,0 +1,62 @@ +package repository + +import ( + "context" + "errors" + + "go.mongodb.org/mongo-driver/bson" + "go.mongodb.org/mongo-driver/mongo" + "go.mongodb.org/mongo-driver/mongo/options" + + "github.com/bamboo-firewall/be/cmd/server/pkg/common/errlist" + "github.com/bamboo-firewall/be/cmd/server/pkg/entity" + "github.com/bamboo-firewall/be/cmd/server/pkg/httpbase/ierror" +) + +func (r *PolicyDB) UpsertHostEndpoint(ctx context.Context, hep *entity.HostEndpoint) *ierror.CoreError { + filter := bson.D{{Key: "_id", Value: hep.ID}} + update := bson.D{{Key: "$set", Value: hep}} + opts := options.Update().SetUpsert(true) + _, err := r.mongo.Database.Collection(hep.CollectionName()).UpdateOne(ctx, filter, update, opts) + if err != nil { + return errlist.ErrDatabase.WithChild(err) + } + + return nil +} + +func (r *PolicyDB) GetHostEndpointByName(ctx context.Context, name string) (*entity.HostEndpoint, *ierror.CoreError) { + filter := bson.D{{Key: "metadata.name", Value: name}} + + hep := new(entity.HostEndpoint) + err := r.mongo.Database.Collection(hep.CollectionName()).FindOne(ctx, filter).Decode(hep) + if err != nil { + if errors.Is(err, mongo.ErrNoDocuments) { + return nil, errlist.ErrNotFoundHostEndpoint + } + return nil, errlist.ErrDatabase.WithChild(err) + } + return hep, nil +} + +func (r *PolicyDB) DeleteHostEndpointByName(ctx context.Context, name string) *ierror.CoreError { + filter := bson.D{{Key: "metadata.name", Value: name}} + + _, err := r.mongo.Database.Collection(entity.HostEndpoint{}.CollectionName()).DeleteOne(ctx, filter) + if err != nil { + return errlist.ErrDatabase.WithChild(err) + } + return nil +} + +func (r *PolicyDB) ListHostEndpoints(ctx context.Context) ([]*entity.HostEndpoint, *ierror.CoreError) { + heps := make([]*entity.HostEndpoint, 0) + cursor, err := r.mongo.Database.Collection(entity.HostEndpoint{}.CollectionName()).Find(ctx, bson.D{}) + if err != nil { + return nil, errlist.ErrDatabase.WithChild(err) + } + if err = cursor.All(ctx, &heps); err != nil { + return nil, errlist.ErrUnmarshalFailed.WithChild(err) + } + return heps, nil +} diff --git a/cmd/server/pkg/repository/policy_db.go b/cmd/server/pkg/repository/policy_db.go new file mode 100644 index 0000000..3fdc72b --- /dev/null +++ b/cmd/server/pkg/repository/policy_db.go @@ -0,0 +1,11 @@ +package repository + +import "github.com/bamboo-firewall/be/cmd/server/pkg/storage" + +type PolicyDB struct { + mongo *storage.PolicyDB +} + +func NewPolicy(mongo *storage.PolicyDB) *PolicyDB { + return &PolicyDB{mongo: mongo} +} diff --git a/cmd/server/pkg/selector/parser/ast.go b/cmd/server/pkg/selector/parser/ast.go new file mode 100644 index 0000000..9a37919 --- /dev/null +++ b/cmd/server/pkg/selector/parser/ast.go @@ -0,0 +1,294 @@ +package parser + +import ( + "strings" +) + +// Labels defines the interface of labels that can be used by selector +type Labels interface { + // Get returns value and presence of the given labelName + Get(labelName string) (string, bool) +} + +type MapAsLabels map[string]string + +func (m MapAsLabels) Get(labelName string) (value string, present bool) { + value, present = m[labelName] + return +} + +type node interface { + Evaluate(labels Labels) bool + collectFragments(fragments []string) []string +} + +type selectorRoot struct { + root node + cachedString *string +} + +func (r *selectorRoot) EvaluateLabels(labels Labels) bool { + return r.root.Evaluate(labels) +} + +func (r *selectorRoot) Evaluate(labels map[string]string) bool { + return r.EvaluateLabels(MapAsLabels(labels)) +} + +func (r *selectorRoot) String() string { + if r.cachedString == nil { + fragments := r.root.collectFragments([]string{}) + joined := strings.Join(fragments, "") + r.cachedString = &joined + } + return *r.cachedString +} + +type LabelEqValueNode struct { + LabelName string + Value string +} + +func (node *LabelEqValueNode) Evaluate(labels Labels) bool { + val, ok := labels.Get(node.LabelName) + if !ok { + return false + } + return val == node.Value +} + +func (node *LabelEqValueNode) collectFragments(fragments []string) []string { + return appendLabelOpAndQuotedString(fragments, node.LabelName, " == ", node.Value) +} + +type LabelContainsValueNode struct { + LabelName string + Value string +} + +func (node *LabelContainsValueNode) Evaluate(labels Labels) bool { + val, ok := labels.Get(node.LabelName) + if !ok { + return false + } + return strings.Contains(val, node.Value) +} + +func (node *LabelContainsValueNode) collectFragments(fragments []string) []string { + return appendLabelOpAndQuotedString(fragments, node.LabelName, " contains ", node.Value) +} + +type LabelStartsWithValueNode struct { + LabelName string + Value string +} + +func (node *LabelStartsWithValueNode) Evaluate(labels Labels) bool { + val, ok := labels.Get(node.LabelName) + if ok { + return strings.HasPrefix(val, node.Value) + } + return false +} + +func (node *LabelStartsWithValueNode) collectFragments(fragments []string) []string { + return appendLabelOpAndQuotedString(fragments, node.LabelName, " starts with ", node.Value) +} + +type LabelEndsWithValueNode struct { + LabelName string + Value string +} + +func (node *LabelEndsWithValueNode) Evaluate(labels Labels) bool { + val, ok := labels.Get(node.LabelName) + if ok { + return strings.HasSuffix(val, node.Value) + } + return false +} + +func (node *LabelEndsWithValueNode) collectFragments(fragments []string) []string { + return appendLabelOpAndQuotedString(fragments, node.LabelName, " ends with ", node.Value) +} + +type LabelNeValueNode struct { + LabelName string + Value string +} + +func (node *LabelNeValueNode) Evaluate(labels Labels) bool { + val, ok := labels.Get(node.LabelName) + if ok { + return val != node.Value + } + return true +} + +func (node *LabelNeValueNode) collectFragments(fragments []string) []string { + return appendLabelOpAndQuotedString(fragments, node.LabelName, " != ", node.Value) +} + +func appendLabelOpAndQuotedString(fragments []string, label, op, s string) []string { + var quote string + if strings.Contains(s, `"`) { + quote = `'` + } else { + quote = `"` + } + return append(fragments, label, op, quote, s, quote) +} + +type LabelInSetNode struct { + LabelName string + Value StringSet +} + +func (node *LabelInSetNode) Evaluate(labels Labels) bool { + val, ok := labels.Get(node.LabelName) + if ok { + return node.Value.Contains(val) + } + return false +} + +func (node *LabelInSetNode) collectFragments(fragments []string) []string { + return collectInOpFragments(fragments, node.LabelName, "in", node.Value) +} + +type LabelNotInSetNode struct { + LabelName string + Value StringSet +} + +func (node *LabelNotInSetNode) Evaluate(labels Labels) bool { + val, ok := labels.Get(node.LabelName) + if ok { + return !node.Value.Contains(val) + } + return true +} + +func (node *LabelNotInSetNode) collectFragments(fragments []string) []string { + return collectInOpFragments(fragments, node.LabelName, "not in", node.Value) +} + +func collectInOpFragments(fragments []string, labelName, op string, values StringSet) []string { + var quote string + fragments = append(fragments, labelName, " ", op, " {") + first := true + for _, s := range values { + if strings.Contains(s, `"`) { + quote = `'` + } else { + quote = `"` + } + if !first { + fragments = append(fragments, ", ") + } else { + first = false + } + fragments = append(fragments, quote, s, quote) + } + fragments = append(fragments, "}") + return fragments +} + +type HasNode struct { + LabelName string +} + +func (node *HasNode) Evaluate(labels Labels) bool { + _, ok := labels.Get(node.LabelName) + if ok { + return true + } + return false +} + +func (node *HasNode) collectFragments(fragments []string) []string { + return append(fragments, "has(", node.LabelName, ")") +} + +type NotNode struct { + Operand node +} + +func (node *NotNode) Evaluate(labels Labels) bool { + return !node.Operand.Evaluate(labels) +} + +func (node *NotNode) collectFragments(fragments []string) []string { + fragments = append(fragments, "!") + return node.Operand.collectFragments(fragments) +} + +type AndNode struct { + Operands []node +} + +func (node *AndNode) Evaluate(labels Labels) bool { + for _, operand := range node.Operands { + if !operand.Evaluate(labels) { + return false + } + } + return true +} + +func (node *AndNode) collectFragments(fragments []string) []string { + fragments = append(fragments, "(") + fragments = node.Operands[0].collectFragments(fragments) + for _, op := range node.Operands[1:] { + fragments = append(fragments, " && ") + fragments = op.collectFragments(fragments) + } + fragments = append(fragments, ")") + return fragments +} + +type OrNode struct { + Operands []node +} + +func (node *OrNode) Evaluate(labels Labels) bool { + for _, operand := range node.Operands { + if operand.Evaluate(labels) { + return true + } + } + return false +} + +func (node *OrNode) collectFragments(fragments []string) []string { + fragments = append(fragments, "(") + fragments = node.Operands[0].collectFragments(fragments) + for _, op := range node.Operands[1:] { + fragments = append(fragments, " || ") + fragments = op.collectFragments(fragments) + } + fragments = append(fragments, ")") + return fragments +} + +type AllNode struct { +} + +func (node *AllNode) Evaluate(Labels) bool { + return true +} + +func (node *AllNode) collectFragments(fragments []string) []string { + return append(fragments, "all()") +} + +type GlobalNode struct { +} + +func (node *GlobalNode) Evaluate(labels Labels) bool { + return true +} + +func (node *GlobalNode) collectFragments(fragments []string) []string { + return append(fragments, "global()") +} diff --git a/cmd/server/pkg/selector/parser/parser.go b/cmd/server/pkg/selector/parser/parser.go new file mode 100644 index 0000000..fe7694f --- /dev/null +++ b/cmd/server/pkg/selector/parser/parser.go @@ -0,0 +1,220 @@ +package parser + +import ( + "errors" + "fmt" + + "github.com/bamboo-firewall/be/cmd/server/pkg/selector/tokenizer" +) + +func Parse(selector string) (*selectorRoot, error) { + tokens, err := tokenizer.Tokenize(selector) + if err != nil { + return nil, err + } + if tokens[0].Kind == tokenizer.TokenEOF { + return &selectorRoot{root: &AllNode{}}, nil + } + + // The "||" operator has the lowest precedence so we start with that. + sel, remainingTokens, err := parseOrExpression(tokens) + if err != nil { + return nil, err + } + // EOF token + if len(remainingTokens) != 1 { + err = fmt.Errorf("unexpected content at the end of selector %+v", remainingTokens) + return nil, err + } + return &selectorRoot{root: sel}, err +} + +// parseOrExpression parses a one or more "&&" terms, separated by "||" operators. +func parseOrExpression(tokens []tokenizer.Token) (sel node, remainingTokens []tokenizer.Token, err error) { + // look for the first expression + andNodes := make([]node, 0) + sel, remainingTokens, err = parseAndExpression(tokens) + if err != nil { + return + } + andNodes = append(andNodes, sel) + + // Then loop looking for "||" followed by an + for { + switch remainingTokens[0].Kind { + case tokenizer.TokenOr: + remainingTokens = remainingTokens[1:] + sel, remainingTokens, err = parseAndExpression(remainingTokens) + if err != nil { + return + } + andNodes = append(andNodes, sel) + default: + if len(andNodes) == 1 { + sel = andNodes[0] + } else { + sel = &OrNode{andNodes} + } + return + } + } +} + +func parseAndExpression(tokens []tokenizer.Token) (sel node, remainingTokens []tokenizer.Token, err error) { + // Look for first operator + opNodes := make([]node, 0) + sel, remainingTokens, err = parseOperator(tokens) + if err != nil { + return + } + opNodes = append(opNodes, sel) + + // Then loop looking for "&&" followed by another operator + for { + switch remainingTokens[0].Kind { + case tokenizer.TokenAnd: + remainingTokens = remainingTokens[1:] + sel, remainingTokens, err = parseOperator(remainingTokens) + if err != nil { + return + } + opNodes = append(opNodes, sel) + default: + if len(opNodes) == 1 { + sel = opNodes[0] + } else { + sel = &AndNode{opNodes} + } + return + } + } +} + +func parseOperator(tokens []tokenizer.Token) (sel node, remainingTokens []tokenizer.Token, err error) { + if len(tokens) == 0 { + err = errors.New("unexpected and of a string looking for op") + return + } + // First, collapse any leading "!" operators to a single boolean + negated := false + for { + if tokens[0].Kind == tokenizer.TokenNot { + negated = !negated + tokens = tokens[1:] + } else { + break + } + } + + // Then, look for the various types of operator + switch tokens[0].Kind { + case tokenizer.TokenHas: + sel = &HasNode{tokens[0].Value.(string)} + remainingTokens = tokens[1:] + case tokenizer.TokenAll: + sel = &AllNode{} + remainingTokens = tokens[1:] + case tokenizer.TokenGlobal: + sel = &GlobalNode{} + remainingTokens = tokens[1:] + case tokenizer.TokenLabel: + // should have an operator and a literal. + if len(tokens) < 3 { + err = errors.New(fmt.Sprint("unexpected end of string in middle of op", tokens)) + return + } + switch tokens[1].Kind { + case tokenizer.TokenEq: + if tokens[2].Kind == tokenizer.TokenStringLiteral { + sel = &LabelEqValueNode{tokens[0].Value.(string), tokens[2].Value.(string)} + remainingTokens = tokens[3:] + } else { + err = errors.New("expected string") + } + case tokenizer.TokenNe: + if tokens[2].Kind == tokenizer.TokenStringLiteral { + sel = &LabelNeValueNode{tokens[0].Value.(string), tokens[2].Value.(string)} + remainingTokens = tokens[3:] + } else { + err = errors.New("expected string") + } + case tokenizer.TokenContains: + if tokens[2].Kind == tokenizer.TokenStringLiteral { + sel = &LabelContainsValueNode{tokens[0].Value.(string), tokens[2].Value.(string)} + remainingTokens = tokens[3:] + } else { + err = errors.New("expected string") + } + case tokenizer.TokenStartWith: + if tokens[2].Kind == tokenizer.TokenStringLiteral { + sel = &LabelStartsWithValueNode{tokens[0].Value.(string), tokens[2].Value.(string)} + remainingTokens = tokens[3:] + } else { + err = errors.New("expected string") + } + case tokenizer.TokenEndsWith: + if tokens[2].Kind == tokenizer.TokenStringLiteral { + sel = &LabelEndsWithValueNode{tokens[0].Value.(string), tokens[2].Value.(string)} + remainingTokens = tokens[3:] + } else { + err = errors.New("expected string") + } + case tokenizer.TokenIn, tokenizer.TokenNotIn: + if tokens[2].Kind == tokenizer.TokenLBrace { + remainingTokens = tokens[3:] + var values []string + for { + if remainingTokens[0].Kind == tokenizer.TokenStringLiteral { + values = append(values, remainingTokens[0].Value.(string)) + remainingTokens = remainingTokens[1:] + if remainingTokens[0].Kind == tokenizer.TokenComma { + remainingTokens = remainingTokens[1:] + } else { + break + } + } else { + break + } + } + if remainingTokens[0].Kind != tokenizer.TokenRBrace { + err = errors.New("expected }") + } else { + // Skip over the } + remainingTokens = remainingTokens[1:] + + labelName := tokens[0].Value.(string) + set := ConvertToStringSetInPlace(values) + if tokens[1].Kind == tokenizer.TokenIn { + sel = &LabelInSetNode{LabelName: labelName, Value: set} + } else { + sel = &LabelNotInSetNode{LabelName: labelName, Value: set} + } + } + } else { + err = errors.New("expected set literal") + } + default: + err = errors.New(fmt.Sprint("expected == or != not ", tokens[1])) + } + case tokenizer.TokenLParen: + // We hit a paren, skip past it, then recurse + sel, remainingTokens, err = parseOrExpression(tokens[1:]) + if err != nil { + return + } + // After parsing the nested expression, there should be a matching paren + if len(remainingTokens) < 1 || remainingTokens[0].Kind != tokenizer.TokenRParen { + err = errors.New("expected )") + return + } + remainingTokens = remainingTokens[1:] + default: + err = errors.New(fmt.Sprint("unexpected token: ", tokens[0])) + return + } + if negated && err == nil { + sel = &NotNode{sel} + } + return + +} diff --git a/cmd/server/pkg/selector/parser/parser_test.go b/cmd/server/pkg/selector/parser/parser_test.go new file mode 100644 index 0000000..c2b4cad --- /dev/null +++ b/cmd/server/pkg/selector/parser/parser_test.go @@ -0,0 +1,21 @@ +package parser + +import ( + "testing" +) + +func TestSomething(t *testing.T) { + //input := "! has(my-label) || my-label starts with 'prod' && role in {'frontend','business'} && type == 'production'" + input := "role in {'agent'} && project == 'atao'" + sel, err := Parse(input) + if err != nil { + t.Fatal(err) + } + t.Log(sel.Evaluate(map[string]string{"role": "agent", "project": "atao"})) + + //tokens, err := tokenizer.Tokenize(input) + //if err != nil { + // t.Fatal(err) + //} + //t.Log(tokens) +} diff --git a/cmd/server/pkg/selector/parser/string_set.go b/cmd/server/pkg/selector/parser/string_set.go new file mode 100644 index 0000000..2274d30 --- /dev/null +++ b/cmd/server/pkg/selector/parser/string_set.go @@ -0,0 +1,34 @@ +package parser + +import "sort" + +type StringSet []string + +func (ss StringSet) Contains(s string) bool { + idx := sort.SearchStrings(ss, s) + return idx < len(ss) && ss[idx] == s +} + +func (ss StringSet) SliceCopy() []string { + if ss == nil { + return nil + } + cp := make([]string, len(ss), len(ss)) + copy(cp, ss) + return cp +} + +func ConvertToStringSetInPlace(s []string) StringSet { + if len(s) <= 1 { + return s + } + sort.Strings(s) + out := s[0:1] + for _, v := range s[1:] { + if v == out[len(out)-1] { + continue + } + out = append(out, v) + } + return out +} diff --git a/cmd/server/pkg/selector/selector.go b/cmd/server/pkg/selector/selector.go new file mode 100644 index 0000000..6795f7e --- /dev/null +++ b/cmd/server/pkg/selector/selector.go @@ -0,0 +1,16 @@ +package selector + +import "github.com/bamboo-firewall/be/cmd/server/pkg/selector/parser" + +type Selector interface { + // Evaluate evaluates the selector against the given labels expressed as a concrete map + Evaluate(labels map[string]string) bool + + // String returns a string that represents this selector + String() string +} + +// Parse a string representation of a selector expression into a Selector. +func Parse(selector string) (Selector, error) { + return parser.Parse(selector) +} diff --git a/cmd/server/pkg/selector/tokenizer/tokenizer.go b/cmd/server/pkg/selector/tokenizer/tokenizer.go new file mode 100644 index 0000000..22430e8 --- /dev/null +++ b/cmd/server/pkg/selector/tokenizer/tokenizer.go @@ -0,0 +1,195 @@ +package tokenizer + +import ( + "errors" + "fmt" + "regexp" + "strings" +) + +type tokenKind uint8 + +const ( + TokenNone tokenKind = iota + TokenLabel + TokenStringLiteral + TokenLBrace + TokenRBrace + TokenComma + TokenEq + TokenNe + TokenIn + TokenNot + TokenNotIn + TokenContains + TokenStartWith + TokenEndsWith + TokenAll + TokenHas + TokenLParen + TokenRParen + TokenAnd + TokenOr + TokenGlobal + TokenEOF +) + +const whitespace = " \t" + +type Token struct { + Kind tokenKind + Value interface{} +} + +const ( + labelKeyMatcher = `[a-zA-Z0-9_./-]{1,512}` +) + +var ( + identifierRegex = regexp.MustCompile("^" + labelKeyMatcher) + containsRegex = regexp.MustCompile(`^contains`) + startsWithRegex = regexp.MustCompile(`^starts\s*with`) + endsWithRegex = regexp.MustCompile(`^ends\s*with`) + hasRegex = regexp.MustCompile(`^has\(\s*(` + labelKeyMatcher + `)\s*\)`) + allRegex = regexp.MustCompile(`^all\(\s*\)`) + notInRegex = regexp.MustCompile(`^not\s*in\b`) + inRegex = regexp.MustCompile(`^in\b`) + globalRegex = regexp.MustCompile(`^global\(\s*\)`) +) + +// Tokenize transform string to token slice +func Tokenize(s string) ([]Token, error) { + var tokens []Token + for { + startLen := len(s) + s = strings.TrimLeft(s, whitespace) + if len(s) == 0 { + tokens = append(tokens, Token{Kind: TokenEOF, Value: nil}) + break + } + var lastTokenKind = TokenNone + if len(tokens) > 0 { + lastTokenKind = tokens[len(tokens)-1].Kind + } + switch s[0] { + case '(': + tokens = append(tokens, Token{Kind: TokenLParen, Value: nil}) + s = s[1:] + case ')': + tokens = append(tokens, Token{Kind: TokenRParen, Value: nil}) + s = s[1:] + case '"': + s = s[1:] + index := strings.Index(s, `"`) + if index == -1 { + return nil, errors.New("unterminated double quote") + } + value := s[0:index] + tokens = append(tokens, Token{Kind: TokenStringLiteral, Value: value}) + s = s[index+1:] + case '\'': + s = s[1:] + index := strings.Index(s, `'`) + if index == -1 { + return nil, errors.New("unterminated single quote") + } + value := s[0:index] + tokens = append(tokens, Token{Kind: TokenStringLiteral, Value: value}) + s = s[index+1:] + case '{': + tokens = append(tokens, Token{Kind: TokenLBrace, Value: nil}) + s = s[1:] + case '}': + tokens = append(tokens, Token{Kind: TokenRBrace, Value: nil}) + s = s[1:] + case ',': + tokens = append(tokens, Token{Kind: TokenComma, Value: nil}) + s = s[1:] + case '=': + if len(s) > 1 && s[1] == '=' { + tokens = append(tokens, Token{Kind: TokenEq, Value: nil}) + s = s[2:] + } else { + return nil, errors.New("expect ==") + } + case '!': + if len(s) > 1 && s[1] == '=' { + tokens = append(tokens, Token{Kind: TokenNe, Value: nil}) + s = s[2:] + } else { + tokens = append(tokens, Token{Kind: TokenNot, Value: nil}) + s = s[1:] + } + case '&': + if len(s) > 1 && s[1] == '&' { + tokens = append(tokens, Token{Kind: TokenAnd, Value: nil}) + s = s[2:] + } else { + return nil, errors.New("expect &&") + } + case '|': + if len(s) > 1 && s[1] == '|' { + tokens = append(tokens, Token{Kind: TokenOr, Value: nil}) + s = s[2:] + } else { + return nil, errors.New("expect ||") + } + default: + // Handle less-simple cases with regex matches. We're already stripped any whitespace + if lastTokenKind == TokenLabel { + // IF we just saw a label, look for a contains/starts with/ends with operator instead if another label + if idxs := containsRegex.FindStringIndex(s); idxs != nil { + // "contains" + tokens = append(tokens, Token{Kind: TokenContains, Value: nil}) + s = s[idxs[1]:] + } else if idxs = startsWithRegex.FindStringIndex(s); idxs != nil { + // "starts with" + tokens = append(tokens, Token{Kind: TokenStartWith, Value: nil}) + s = s[idxs[1]:] + } else if idxs = endsWithRegex.FindStringIndex(s); idxs != nil { + // "ends with" + tokens = append(tokens, Token{Kind: TokenEndsWith, Value: nil}) + s = s[idxs[1]:] + } else if idxs = notInRegex.FindStringIndex(s); idxs != nil { + // "not in" + tokens = append(tokens, Token{Kind: TokenNotIn, Value: nil}) + s = s[idxs[1]:] + } else if idxs = inRegex.FindStringIndex(s); idxs != nil { + // "in" + tokens = append(tokens, Token{Kind: TokenIn, Value: nil}) + s = s[idxs[1]:] + } else { + return nil, fmt.Errorf("unexpected characters after label '%v', was expecting an operator", tokens[len(tokens)-1].Value) + } + } else if idxs := hasRegex.FindStringSubmatchIndex(s); idxs != nil { + // "has(label)" + wholeMatchEnd := idxs[1] + labelNameMatchStart := idxs[2] + labelNameMatchEnd := idxs[3] + labelName := s[labelNameMatchStart:labelNameMatchEnd] + tokens = append(tokens, Token{Kind: TokenHas, Value: labelName}) + s = s[wholeMatchEnd:] + } else if idxs = allRegex.FindStringIndex(s); idxs != nil { + // "all" + tokens = append(tokens, Token{Kind: TokenAll, Value: nil}) + s = s[idxs[1]:] + } else if idxs = globalRegex.FindStringIndex(s); idxs != nil { + // "global" + tokens = append(tokens, Token{Kind: TokenGlobal, Value: nil}) + s = s[idxs[1]:] + } else if idxs = identifierRegex.FindStringIndex(s); idxs != nil { + // "label" + wholeMatchEnd := idxs[1] + identifier := s[0:wholeMatchEnd] + tokens = append(tokens, Token{Kind: TokenLabel, Value: identifier}) + s = s[wholeMatchEnd:] + } else { + return nil, errors.New("unexpected characters") + } + } + if len(s) >= startLen { + return nil, errors.New("infinite loop detected in tokenizer") + } + } + return tokens, nil +} diff --git a/cmd/server/pkg/storage/mongo.go b/cmd/server/pkg/storage/mongo.go new file mode 100644 index 0000000..0c6661a --- /dev/null +++ b/cmd/server/pkg/storage/mongo.go @@ -0,0 +1,37 @@ +package storage + +import ( + "context" + "log/slog" + + "go.mongodb.org/mongo-driver/mongo" + "go.mongodb.org/mongo-driver/mongo/options" + "go.mongodb.org/mongo-driver/mongo/readpref" + "go.mongodb.org/mongo-driver/x/mongo/driver/connstring" +) + +type PolicyDB struct { + Database *mongo.Database +} + +func NewPolicyDB(uri string) (*PolicyDB, error) { + opts := options.Client() + opts.ApplyURI(uri) + cs, cErr := connstring.ParseAndValidate(uri) + if cErr != nil { + return nil, cErr + } + client, err := mongo.Connect(context.Background(), opts) + if err != nil { + return nil, err + } + if err = client.Ping(context.Background(), readpref.Primary()); err != nil { + return nil, err + } + return &PolicyDB{Database: client.Database(cs.Database)}, nil +} + +func (pm *PolicyDB) Stop(ctx context.Context) error { + slog.Info("Stop policy mongo") + return pm.Database.Client().Disconnect(ctx) +} diff --git a/cmd/server/pkg/validator/validator.go b/cmd/server/pkg/validator/validator.go new file mode 100644 index 0000000..f902336 --- /dev/null +++ b/cmd/server/pkg/validator/validator.go @@ -0,0 +1,232 @@ +package validator + +import ( + "fmt" + "log/slog" + "regexp" + "slices" + "strconv" + "strings" + + "github.com/go-playground/validator/v10" + + "github.com/bamboo-firewall/be/api/v1/dto" + "github.com/bamboo-firewall/be/cmd/server/pkg/entity" + "github.com/bamboo-firewall/be/cmd/server/pkg/httpbase" + "github.com/bamboo-firewall/be/cmd/server/pkg/net" + "github.com/bamboo-firewall/be/cmd/server/pkg/selector" +) + +func registerValidator(tagName string, validatorFunc validator.Func) { + if err := httpbase.RegisterValidator(tagName, validatorFunc); err != nil { + panic(err) + } else { + slog.Debug("registered validator", "tag", tagName) + } +} + +func registerStructValidation(fn validator.StructLevelFunc, in ...interface{}) { + httpbase.RegisterStructValidation(fn, in...) +} + +func Init() { + registerValidator("name", validateName) + registerValidator("selector", validateSelector) + registerValidator("action", validateAction) + registerValidator("ip_version", validateIPVersion) + registerValidator("protocol", validateProtocol) + registerValidator("port", validatePort) + + registerValidator("net", validateIPNetwork) + registerValidator("cidr", validateCIDR) + registerValidator("ip", validateIP) + + registerStructValidation(validateGNPSpecInput, dto.GNPSpecInput{}) + registerStructValidation(validateGNPSpecRuleInput, dto.GNPSpecRuleInput{}) + registerStructValidation(validateGNPSpecRuleEntityInput, dto.GNPSpecRuleEntityInput{}) + registerStructValidation(validateGNSSpecInput, dto.GNSSpecInput{}) +} + +var nameRegex = regexp.MustCompile(`^[a-z_-]+$`) + +func validateName(fl validator.FieldLevel) bool { + return nameRegex.MatchString(fl.Field().String()) +} + +func validateSelector(fl validator.FieldLevel) bool { + sel := fl.Field().Interface().(string) + _, err := selector.Parse(sel) + if err != nil { + return false + } + return true +} + +func validateAction(fl validator.FieldLevel) bool { + action := fl.Field().Interface().(string) + return slices.Contains( + []entity.RuleAction{entity.RuleActionAllow, entity.RuleActionDeny, entity.RuleActionLog, entity.RuleActionPass}, + entity.RuleAction(strings.ToLower(action)), + ) +} + +func validateIPVersion(fl validator.FieldLevel) bool { + ipVersion := fl.Field().Interface().(int) + return slices.Contains([]entity.IPVersion{entity.IPVersion4, entity.IPVersion6}, entity.IPVersion(ipVersion)) +} + +func validateProtocol(fl validator.FieldLevel) bool { + protocol := fl.Field().Interface().(string) + return slices.Contains([]entity.Protocol{entity.ProtocolTCP, entity.ProtocolUDP, entity.ProtocolICMP, entity.ProtocolSCTP}, entity.Protocol(strings.ToLower(protocol))) +} + +func validateCIDR(fl validator.FieldLevel) bool { + n := fl.Field().String() + _, _, err := net.ParseCIDROrIP(n) + return err == nil +} + +func validateIPNetwork(fl validator.FieldLevel) bool { + n := fl.Field().String() + ip, ipnet, err := net.ParseCIDROrIP(n) + if err != nil { + return false + } + return ip.String() == ipnet.IP.String() +} + +func validateIP(fl validator.FieldLevel) bool { + return net.ParseIP(fl.Field().String()) != nil +} + +var portRangeRegex = regexp.MustCompile(`^(\d+):(\d+)$`) + +const ( + portRangeMin int = 0 + portRangeMax int = 65535 +) + +// validatePort port range 0-65535 +func validatePort(fl validator.FieldLevel) bool { + if portNumber, ok := fl.Field().Interface().(float64); ok { + if int(portNumber) < portRangeMin || int(portNumber) > portRangeMax { + return false + } + } else if portRange, ok := fl.Field().Interface().(string); ok { + portsMatch := portRangeRegex.FindStringSubmatch(portRange) + if portsMatch == nil { + return false + } + portStart, err := strconv.ParseUint(portsMatch[1], 10, 16) + if err != nil { + return false + } + portEnd, err := strconv.ParseUint(portsMatch[2], 10, 16) + if err != nil { + return false + } + if portStart > portEnd { + return false + } + } else { + return false + } + return true +} + +func validateGNPSpecInput(sl validator.StructLevel) { + input := sl.Current().Interface().(dto.GNPSpecInput) + if len(input.Ingress) == 0 && len(input.Egress) == 0 { + sl.ReportError(input.Ingress, "ingress", "Egress", "require ingress or egress", "") + } +} + +func validateGNPSpecRuleInput(sl validator.StructLevel) { + input := sl.Current().Interface().(dto.GNPSpecRuleInput) + if input.Protocol != "" && input.NotProtocol != "" { + sl.ReportError(input.NotProtocol, "notProtocol", "NotProtocol", "cannot use notProtocol with protocol", "") + } + if input.Protocol != "" || input.NotProtocol != "" { + if (input.Protocol != "" && !isProtocolSupportPort(input.Protocol)) || (input.NotProtocol != "" && !isProtocolSupportPort(input.NotProtocol)) { + if input.Source != nil { + if len(input.Source.Ports) > 0 { + sl.ReportError(input.Source.Ports, "notPorts", "NotPorts", "protocol not support ports", "") + } + if len(input.Source.NotPorts) > 0 { + sl.ReportError(input.Source.NotPorts, "notPorts", "NotPorts", "protocol not support ports", "") + } + } + + if input.Destination != nil { + if len(input.Destination.Ports) > 0 { + sl.ReportError(input.Destination.Ports, "notPorts", "NotPorts", "protocol not support ports", "") + } + if len(input.Destination.NotPorts) > 0 { + sl.ReportError(input.Destination.NotPorts, "notPorts", "NotPorts", "protocol not support ports", "") + } + } + } + } + + if input.Source != nil { + isNetSameIPVersion(sl, input.IPVersion, input.Source.Nets) + isNetSameIPVersion(sl, input.IPVersion, input.Source.NotNets) + } + if input.Destination != nil { + isNetSameIPVersion(sl, input.IPVersion, input.Destination.Nets) + isNetSameIPVersion(sl, input.IPVersion, input.Destination.NotNets) + } +} + +func isProtocolSupportPort(protocol string) bool { + return slices.Contains([]entity.Protocol{entity.ProtocolTCP, entity.ProtocolUDP, entity.ProtocolSCTP}, entity.Protocol(strings.ToLower(protocol))) +} + +func isNetSameIPVersion(sl validator.StructLevel, ipVersion int, nets []string) { + for i, ipNetwork := range nets { + ip, ipnet, err := net.ParseCIDROrIP(ipNetwork) + if err != nil { + sl.ReportError(ipNetwork, fmt.Sprintf("nets[%d]", i), "", "net", "") + continue + } + if ip.String() != ipnet.IP.String() { + sl.ReportError(ipNetwork, fmt.Sprintf("nets[%d]", i), "", "ip network is invalid", "") + } + if ip.Version() != ipVersion { + sl.ReportError(ipNetwork, fmt.Sprintf("nets[%d]", i), "", "not match with ipVersion", "") + } + } +} + +func validateGNPSpecRuleEntityInput(sl validator.StructLevel) { + input := sl.Current().Interface().(dto.GNPSpecRuleEntityInput) + if len(input.Nets) > 0 && len(input.NotNets) > 0 { + sl.ReportError(input.NotNets, "notNets", "NotNets", "cannot use notNets with nets", "") + } + if len(input.NotPorts) > 0 && len(input.NotPorts) > 0 { + sl.ReportError(input.NotPorts, "notPorts", "NotPorts", "cannot use notPorts with ports", "") + } +} + +func validateGNSSpecInput(sl validator.StructLevel) { + input := sl.Current().Interface().(dto.GNSSpecInput) + cidrMap := make(map[string]struct{}) + for i, netString := range input.Nets { + ip, ipnet, err := net.ParseCIDROrIP(netString) + if err != nil { + sl.ReportError(netString, fmt.Sprintf("nets[%d]", i), "", "cidr", "") + continue + } + var netV4V6 string + if ip.String() == ipnet.IP.String() { + netV4V6 = ipnet.String() + } else { + netV4V6 = ip.Network().String() + } + if _, ok := cidrMap[netV4V6]; ok { + sl.ReportError(netV4V6, fmt.Sprintf("nets[%d]", i), "", "duplicate", "") + } else { + cidrMap[netV4V6] = struct{}{} + } + } +} diff --git a/cmd/server/route/route.go b/cmd/server/route/route.go new file mode 100644 index 0000000..94c6843 --- /dev/null +++ b/cmd/server/route/route.go @@ -0,0 +1,45 @@ +package route + +import ( + "net/http" + + "github.com/gin-gonic/gin" + + "github.com/bamboo-firewall/be/api/v1/handler" + "github.com/bamboo-firewall/be/cmd/server/middleware" + "github.com/bamboo-firewall/be/cmd/server/pkg/repository" + "github.com/bamboo-firewall/be/domain/service" +) + +func RegisterHandler(repo *repository.PolicyDB) http.Handler { + router := gin.New() + + router.Use(gin.Recovery()) + router.Use(middleware.CORS()) + router.GET("/api/v1/ping", handler.Ping) + + { + hepHandler := handler.NewHEP(service.NewHEP(repo)) + router.POST("/api/v1/hostEndpoints", hepHandler.Create) + router.GET("/api/v1/hostEndpoints/byName/:name", hepHandler.Get) + router.DELETE("/api/v1/hostEndpoints", hepHandler.Delete) + + router.GET("/api/internal/v1/hostEndpoints/byName/:name/fetchPolicies", hepHandler.FetchPolicies) + } + + { + gnpHandler := handler.NewGNP(service.NewGNP(repo)) + router.POST("/api/v1/globalNetworkPolicies", gnpHandler.Create) + router.GET("/api/v1/globalNetworkPolicies/byName/:name", gnpHandler.Get) + router.DELETE("/api/v1/globalNetworkPolicies", gnpHandler.Delete) + } + + { + gnsHandler := handler.NewGNS(service.NewGNS(repo)) + router.POST("/api/v1/globalNetworkSets", gnsHandler.Create) + router.GET("/api/v1/globalNetworkSets/byName/:name", gnsHandler.Get) + router.DELETE("/api/v1/globalNetworkSets", gnsHandler.Delete) + } + + return router +} diff --git a/config.json b/config.json deleted file mode 100644 index 4ff4798..0000000 --- a/config.json +++ /dev/null @@ -1,18 +0,0 @@ -{ - "APP_ENV": "development", - "SERVER_ADDRESS": ":9091", - "PORT": "9091", - "DB_NAME": "bamboofw", - "CONTEXT_TIMEOUT": "2", - "ACCESS_TOKEN_EXPIRY_HOUR": "2", - "REFRESH_TOKEN_EXPIRY_HOUR": "168", - "ACCESS_TOKEN_SECRET": "jzZU3qXB6QEArUPLFi3g", - "REFRESH_TOKEN_SECRET": "kdrqZnivLJ5UoNq3ZkYT", - "MONGO_URI": "mongodb://mongou:mongopass@mongodb:27017/?authSource=admin", - "ADMIN_PASSWORD": "n9dR63zeCAxEPY92", - "CORS_ALLOW_METHODS": "POST, GET, OPTIONS, PUT, DELETE", - "CORS_ALLOW_ORIGIN": "*", - "MONGO_TIMEOUT": 10, - "EMAIL_DOMAIN": "example.com", - "ADMIN_ACCOUNT": "admin" -} \ No newline at end of file diff --git a/config/config.go b/config/config.go new file mode 100644 index 0000000..4529f86 --- /dev/null +++ b/config/config.go @@ -0,0 +1,38 @@ +package config + +import ( + "time" + + "github.com/spf13/viper" +) + +type Config struct { + HTTPServerHost string + HTTPServerPort string + HTTPServerReadTimeout time.Duration + HTTPServerReadHeaderTimeout time.Duration + HTTPServerWriteTimeout time.Duration + HTTPServerIdleTimeout time.Duration + DBURI string + Logging bool +} + +func New(path string) (Config, error) { + viper.AutomaticEnv() + if path != "" { + viper.SetConfigFile(path) + if err := viper.ReadInConfig(); err != nil { + return Config{}, err + } + } + return Config{ + HTTPServerHost: viper.GetString("HTTP_SERVER_HOST"), + HTTPServerPort: viper.GetString("HTTP_SERVER_PORT"), + HTTPServerReadTimeout: viper.GetDuration("HTTP_SERVER_READ_TIMEOUT"), + HTTPServerReadHeaderTimeout: viper.GetDuration("HTTP_SERVER_READ_HEADER_TIMEOUT"), + HTTPServerWriteTimeout: viper.GetDuration("HTTP_SERVER_WRITE_TIMEOUT"), + HTTPServerIdleTimeout: viper.GetDuration("HTTP_SERVER_IDLE_TIMEOUT"), + DBURI: viper.GetString("DB_URI"), + Logging: viper.GetBool("LOGGING"), + }, nil +} diff --git a/config/rbac_model.conf b/config/rbac_model.conf deleted file mode 100644 index 71159e3..0000000 --- a/config/rbac_model.conf +++ /dev/null @@ -1,14 +0,0 @@ -[request_definition] -r = sub, obj, act - -[policy_definition] -p = sub, obj, act - -[role_definition] -g = _, _ - -[policy_effect] -e = some(where (p.eft == allow)) - -[matchers] -m = g(r.sub, p.sub) && r.obj == p.obj && r.act == p.act \ No newline at end of file diff --git a/deployment/.env b/deployment/.env deleted file mode 100644 index 60153a0..0000000 --- a/deployment/.env +++ /dev/null @@ -1,18 +0,0 @@ -APP_ENV=development -SERVER_ADDRESS=:9091 -PORT=9091 -DB_NAME=bamboofw -DB_PORT=27017 -DB_ROOT_USER=mongou -DB_ROOT_PASS=mongopass -CONTEXT_TIMEOUT=2 -ACCESS_TOKEN_EXPIRY_HOUR=2 -REFRESH_TOKEN_EXPIRY_HOUR=168 -ACCESS_TOKEN_SECRET=jzZU3qXB6QEArUPLFi3g -REFRESH_TOKEN_SECRET=kdrqZnivLJ5UoNq3ZkYT -MONGO_URI=mongodb://mongou:mongopass@mongodb:27017/bamboofw?authSource=admin -CORS_ALLOW_METHODS="POST, GET, OPTIONS, PUT, DELETE" -CORS_ALLOW_ORIGIN="*" -MONGO_TIMEOUT=10 -EMAIL_DOMAIN=example.com -ADMIN_ACCOUNT=admin diff --git a/deployment/Dockerfile b/deployment/Dockerfile deleted file mode 100644 index 5a083ab..0000000 --- a/deployment/Dockerfile +++ /dev/null @@ -1,21 +0,0 @@ -FROM golang:1.20 as builder - -WORKDIR /go/src/github.com/bamboo-firewall/be -COPY go.mod go.sum ./ -RUN go mod download -COPY . . -RUN CGO_ENABLED=0 GOOS=linux go build -o /bin/service /go/src/github.com/bamboo-firewall/be/cmd/main.go - -FROM alpine:3.15 -WORKDIR /app -ENV TZ=Asia/Ho_Chi_Minh -RUN mkdir config -COPY --from=builder /bin/service /app/service -COPY --from=builder /go/src/github.com/bamboo-firewall/be/config.json /app/config.json -COPY --from=builder /go/src/github.com/bamboo-firewall/be/config/rbac_model.conf /app/config/rbac_model.conf -# add package for handle timezone in alpine -RUN apk add tzdata \ - && ln -snf /usr/share/zoneinfo/$TZ /etc/localtime && echo $TZ > /etc/timezone \ - && chmod u+x /app/service - -CMD [ "/app/service" ] \ No newline at end of file diff --git a/deployment/docker-compose.yaml b/deployment/docker-compose.yaml deleted file mode 100644 index af0104a..0000000 --- a/deployment/docker-compose.yaml +++ /dev/null @@ -1,31 +0,0 @@ -version: "3.8" - -services: - api: - build: - context: ../ - dockerfile: deployment/Dockerfile - image: bamboo-api - container_name: bamboo-api - restart: unless-stopped - env_file: .env - ports: - - "$PORT:$PORT" - depends_on: - - mongodb - - mongodb: - image: mongo:6.0 - container_name: bamboo-mongodb - restart: unless-stopped - env_file: .env - environment: - - MONGO_INITDB_ROOT_USERNAME=$DB_ROOT_USER - - MONGO_INITDB_ROOT_PASSWORD=$DB_ROOT_PASS - ports: - - "$DB_PORT:$DB_PORT" - volumes: - - dbdata:/data/db - -volumes: - dbdata: diff --git a/docker/Dockerfile b/docker/Dockerfile new file mode 100644 index 0000000..28933af --- /dev/null +++ b/docker/Dockerfile @@ -0,0 +1,15 @@ +FROM golang:1.23 + +WORKDIR /src + +RUN --mount=type=cache,target=/go/pkg/mod/ \ + --mount=type=bind,source=go.sum,target=go.sum \ + --mount=type=bind,source=go.mod,target=go.mod \ + go mod download + + +RUN --mount=type=bind,target=. \ + go build --ldflags "-s -w" -o /bin/server ./cmd/server + +CMD ["/bin/server"] + diff --git a/docker/docker-compose.yaml b/docker/docker-compose.yaml new file mode 100644 index 0000000..18fe8d4 --- /dev/null +++ b/docker/docker-compose.yaml @@ -0,0 +1,27 @@ +services: + mongo: + image: mongo:7.0.14 + restart: always + environment: + MONGO_INITDB_ROOT_USERNAME: root + MONGO_INITDB_ROOT_PASSWORD: change_me + volumes: + - mongodata:/data/db + + app: + build: + dockerfile: ./docker/Dockerfile + context: ../ + ports: + - 8080:8080 + environment: + HTTP_SERVER_HOST: "0.0.0.0" + HTTP_SERVER_PORT: "8080" + HTTP_SERVER_READ_TIMEOUT: "5s" + HTTP_SERVER_READ_HEADER_TIMEOUT: "5s" + HTTP_SERVER_WRITE_TIMEOUT: "5s" + HTTP_SERVER_IDLE_TIMEOUT: "5s" + DB_URI: "mongodb://root:change_me@mongo:27017/admin" + LOGGING: True +volumes: + mongodata: diff --git a/domain/calico_object_response.go b/domain/calico_object_response.go deleted file mode 100644 index b4a275c..0000000 --- a/domain/calico_object_response.go +++ /dev/null @@ -1,8 +0,0 @@ -package domain - -type CalicoObjectResponse struct { - Kind string `json:"kind"` - ApiVersion string `json:"apiVersion"` - Metadata interface{} `json:"metadata,omitempty"` - Spec interface{} `json:"spec,omitempty"` -} diff --git a/domain/error_response.go b/domain/error_response.go deleted file mode 100644 index ef263a0..0000000 --- a/domain/error_response.go +++ /dev/null @@ -1,5 +0,0 @@ -package domain - -type ErrorResponse struct { - Message string `json:"message"` -} diff --git a/domain/gns.go b/domain/gns.go deleted file mode 100644 index f287551..0000000 --- a/domain/gns.go +++ /dev/null @@ -1,25 +0,0 @@ -package domain - -import ( - "context" - - models "github.com/bamboo-firewall/watcher/model" - "go.mongodb.org/mongo-driver/bson" -) - -const ( - CollectionGNS = "globalnetworksets" -) - -type GNSRepository interface { - Fetch(c context.Context) ([]models.GlobalNetworkSet, error) - Search(c context.Context, options bson.M) ([]models.GlobalNetworkSet, error) - GetTotal(c context.Context) (int64, error) - AggGroupBy(c context.Context, query bson.M, key string, jsonPath string) ([]Option, error) -} - -type GNSUsecase interface { - Fetch(c context.Context) ([]models.GlobalNetworkSet, error) - Search(c context.Context, options []Option) ([]models.GlobalNetworkSet, error) - GetOptions(c context.Context, filter []Option, key string) ([]Option, error) -} diff --git a/domain/hep.go b/domain/hep.go deleted file mode 100644 index 2f21145..0000000 --- a/domain/hep.go +++ /dev/null @@ -1,26 +0,0 @@ -package domain - -import ( - "context" - - models "github.com/bamboo-firewall/watcher/model" - "go.mongodb.org/mongo-driver/bson" -) - -const ( - CollectionHEP = "hostendpoints" -) - -type HEPRepository interface { - Fetch(c context.Context) ([]models.HostEndPoint, error) - Search(c context.Context, options bson.M) ([]models.HostEndPoint, error) - GetTotal(c context.Context) (int64, error) - GetProjectSummary(c context.Context) ([]ProjectSummary, error) - AggGroupBy(c context.Context, query bson.M, key string, jsonPath string) ([]Option, error) -} - -type HEPUsecase interface { - Fetch(c context.Context) ([]models.HostEndPoint, error) - Search(c context.Context, options []Option) ([]models.HostEndPoint, error) - GetOptions(c context.Context, filter []Option, key string) ([]Option, error) -} diff --git a/domain/jwt_custom.go b/domain/jwt_custom.go deleted file mode 100644 index 5107a72..0000000 --- a/domain/jwt_custom.go +++ /dev/null @@ -1,16 +0,0 @@ -package domain - -import ( - "github.com/golang-jwt/jwt/v4" -) - -type JwtCustomClaims struct { - Name string `json:"name"` - ID string `json:"id"` - jwt.StandardClaims -} - -type JwtCustomRefreshClaims struct { - ID string `json:"id"` - jwt.StandardClaims -} diff --git a/domain/login.go b/domain/login.go deleted file mode 100644 index de6f6db..0000000 --- a/domain/login.go +++ /dev/null @@ -1,23 +0,0 @@ -package domain - -import ( - "context" -) - -type LoginRequest struct { - Username string `form:"username" binding:"required"` - Password string `form:"password" binding:"required"` -} - -type LoginResponse struct { - AccessToken string `json:"accessToken"` - RefreshToken string `json:"refreshToken"` - User User `json:"user"` -} - -type LoginUsecase interface { - GetUserByEmail(c context.Context, email string) (User, error) - GetUserByUsername(c context.Context, username string) (User, error) - CreateAccessToken(user *User, secret string, expiry int) (accessToken string, err error) - CreateRefreshToken(user *User, secret string, expiry int) (refreshToken string, err error) -} diff --git a/domain/metadata.go b/domain/metadata.go deleted file mode 100644 index d9f1b54..0000000 --- a/domain/metadata.go +++ /dev/null @@ -1,10 +0,0 @@ -package domain - -import "time" - -type Metadata struct { - Name string `bson:"name"` - UID string `bson:"uid"` - CreationTimestamp time.Time `bson:"creationTimestamp"` - Labels map[string]string `bson:"labels"` -} diff --git a/domain/model/gnp.go b/domain/model/gnp.go new file mode 100644 index 0000000..8d88bc2 --- /dev/null +++ b/domain/model/gnp.go @@ -0,0 +1,36 @@ +package model + +type CreateGlobalNetworkPolicyInput struct { + Metadata GNPMetadataInput + Spec GNPSpecInput + Description string +} + +type GNPMetadataInput struct { + Name string + Labels map[string]string +} + +type GNPSpecInput struct { + Selector string + Ingress []GNPSpecRuleInput + Egress []GNPSpecRuleInput +} + +type GNPSpecRuleInput struct { + Metadata map[string]string + Action string + Protocol string + NotProtocol string + IPVersion int + Source *GNPSpecRuleEntityInput + Destination *GNPSpecRuleEntityInput +} + +type GNPSpecRuleEntityInput struct { + Selector string + Nets []string + NotNets []string + Ports []interface{} + NotPorts []interface{} +} diff --git a/domain/model/gns.go b/domain/model/gns.go new file mode 100644 index 0000000..6db1c99 --- /dev/null +++ b/domain/model/gns.go @@ -0,0 +1,16 @@ +package model + +type CreateGlobalNetworkSetInput struct { + Metadata GNSMetadataInput `json:"metadata" validate:"required"` + Spec GNSSpecInput `json:"spec"` + Description string `json:"description"` +} + +type GNSMetadataInput struct { + Name string `json:"name" validate:"required"` + Labels map[string]string `json:"labels"` +} + +type GNSSpecInput struct { + Nets []string `json:"nets"` +} diff --git a/domain/model/hep.go b/domain/model/hep.go new file mode 100644 index 0000000..3dd9e53 --- /dev/null +++ b/domain/model/hep.go @@ -0,0 +1,85 @@ +package model + +import "github.com/bamboo-firewall/be/cmd/server/pkg/entity" + +type CreateHostEndpointInput struct { + Metadata HostEndpointMetadataInput + Spec HostEndpointSpecInput + Description string +} + +type HostEndpointMetadataInput struct { + Name string + Labels map[string]string +} + +type HostEndpointSpecInput struct { + InterfaceName string + IPs []string + Ports []HostEndpointSpecPortInput +} + +type HostEndpointSpecPortInput struct { + Name string + Port int + Protocol string +} + +type FetchHostEndpointPolicyInput struct { + Name string +} + +type HostEndPointPolicy struct { + MetaData HostEndPointPolicyMetadata + HEP *entity.HostEndpoint + ParsedGNPs []*ParsedGNP + ParsedHEPs []*ParsedHEP + ParsedGNSs []*ParsedGNS +} + +type HostEndPointPolicyMetadata struct { + GNPVersions map[string]uint + HEPVersions map[string]uint + GNSVersions map[string]uint +} + +type ParsedGNP struct { + UUID string + Version uint + Name string + InboundRules []*ParsedRule + OutboundRules []*ParsedRule +} + +type ParsedRule struct { + Action string + IPVersion int + Protocol string + IsProtocolNegative bool + SrcNets []string + IsSrcNetNegative bool + SrcGNSUUIDs []string + SrcHEPUUIDs []string + SrcPorts []string + IsSrcPortNegative bool + DstNets []string + IsDstNetNegative bool + DstGNSUUIDs []string + DstHEPUUIDs []string + DstPorts []string + IsDstPortNegative bool +} + +type ParsedHEP struct { + UUID string + Name string + IPsV4 []string + IPsV6 []string +} + +type ParsedGNS struct { + UUID string + Name string + NetsV4 []string + NetsV6 []string +} diff --git a/domain/option.go b/domain/option.go deleted file mode 100644 index 3369557..0000000 --- a/domain/option.go +++ /dev/null @@ -1,10 +0,0 @@ -package domain - -const ( - CollectionOption = "options" -) - -type Option struct { - Key string `bson:"key" json:"key"` - Value string `bson:"value" json:"value"` -} diff --git a/domain/option_request.go b/domain/option_request.go deleted file mode 100644 index bcb3e1d..0000000 --- a/domain/option_request.go +++ /dev/null @@ -1,7 +0,0 @@ -package domain - -type FetchOptionRequest struct { - Type string `json:"type" binding:"required"` - Label string `json:"label" binding:"required"` - Filter []Option `json:"filter" binding:"required"` -} diff --git a/domain/policy.go b/domain/policy.go deleted file mode 100644 index 6ced08f..0000000 --- a/domain/policy.go +++ /dev/null @@ -1,25 +0,0 @@ -package domain - -import ( - "context" - - models "github.com/bamboo-firewall/watcher/model" - "go.mongodb.org/mongo-driver/bson" -) - -const ( - CollectionPolicy = "globalnetworkpolicies" -) - -type PolicyRepository interface { - Fetch(c context.Context) ([]models.GlobalNetworkPolicies, error) - Search(c context.Context, options bson.M) ([]models.GlobalNetworkPolicies, error) - GetTotal(c context.Context) (int64, error) - AggGroupBy(c context.Context, query bson.M, key string, jsonPath string) ([]Option, error) -} - -type PolicyUsecase interface { - Fetch(c context.Context) ([]models.GlobalNetworkPolicies, error) - Search(c context.Context, options []Option) ([]models.GlobalNetworkPolicies, error) - GetOptions(c context.Context, filter []Option, key string) ([]Option, error) -} diff --git a/domain/profile.go b/domain/profile.go deleted file mode 100644 index 747bd8e..0000000 --- a/domain/profile.go +++ /dev/null @@ -1,19 +0,0 @@ -package domain - -import "context" - -type Profile struct { - UserId string `json:"user_id"` - Name string `json:"name"` - Email string `json:"email"` - Role string `json:"role"` -} - -type RequestUpdateProfile struct { - Name string `json:"name"` - Password string `json:"password"` -} - -type ProfileUsecase interface { - GetProfileByID(c context.Context, userID string) (*Profile, error) -} diff --git a/domain/refresh_token.go b/domain/refresh_token.go deleted file mode 100644 index 6374abd..0000000 --- a/domain/refresh_token.go +++ /dev/null @@ -1,21 +0,0 @@ -package domain - -import ( - "context" -) - -type RefreshTokenRequest struct { - RefreshToken string `form:"refreshToken" binding:"required"` -} - -type RefreshTokenResponse struct { - AccessToken string `json:"accessToken"` - RefreshToken string `json:"refreshToken"` -} - -type RefreshTokenUsecase interface { - GetUserByID(c context.Context, id string) (User, error) - CreateAccessToken(user *User, secret string, expiry int) (accessToken string, err error) - CreateRefreshToken(user *User, secret string, expiry int) (refreshToken string, err error) - ExtractIDFromToken(requestToken string, secret string) (string, error) -} diff --git a/domain/search_request.go b/domain/search_request.go deleted file mode 100644 index cae4e7d..0000000 --- a/domain/search_request.go +++ /dev/null @@ -1,5 +0,0 @@ -package domain - -type SearchRequest struct { - Options []Option `json:"options" binding:"required"` -} diff --git a/domain/service/gnp.go b/domain/service/gnp.go new file mode 100644 index 0000000..4a667d6 --- /dev/null +++ b/domain/service/gnp.go @@ -0,0 +1,118 @@ +package service + +import ( + "context" + "errors" + "time" + + "github.com/google/uuid" + "go.mongodb.org/mongo-driver/bson/primitive" + + "github.com/bamboo-firewall/be" + "github.com/bamboo-firewall/be/cmd/server/pkg/common/errlist" + "github.com/bamboo-firewall/be/cmd/server/pkg/entity" + "github.com/bamboo-firewall/be/cmd/server/pkg/httpbase" + "github.com/bamboo-firewall/be/cmd/server/pkg/httpbase/ierror" + "github.com/bamboo-firewall/be/cmd/server/pkg/repository" + "github.com/bamboo-firewall/be/domain/model" +) + +func NewGNP(policyMongo *repository.PolicyDB) *gnp { + return &gnp{ + storage: policyMongo, + } +} + +type gnp struct { + storage be.Storage +} + +func (ds *gnp) Create(ctx context.Context, input *model.CreateGlobalNetworkPolicyInput) (*entity.GlobalNetworkPolicy, *ierror.Error) { + // ToDo: use transaction and lock row + gnpExisted, coreErr := ds.storage.GetGNPByName(ctx, input.Metadata.Name) + if coreErr != nil && !errors.Is(coreErr, errlist.ErrNotFoundGlobalNetworkPolicy) { + return nil, httpbase.ErrDatabase(ctx, "get global network policy failed").SetSubError(coreErr) + } + + var specIngress []entity.GNPSpecRule + for _, rule := range input.Spec.Ingress { + specIngress = append(specIngress, modelToRule(rule)) + } + + var specEgress []entity.GNPSpecRule + for _, rule := range input.Spec.Egress { + specEgress = append(specEgress, modelToRule(rule)) + } + + gnpEntity := &entity.GlobalNetworkPolicy{ + ID: primitive.NewObjectID(), + UUID: uuid.New().String(), + Version: 1, + Metadata: entity.GNPMetadata{ + Name: input.Metadata.Name, + Labels: input.Metadata.Labels, + }, + Spec: entity.GNPSpec{ + Selector: input.Spec.Selector, + Ingress: specIngress, + Egress: specEgress, + }, + Description: input.Description, + CreatedAt: time.Now(), + UpdatedAt: time.Now(), + } + if gnpExisted != nil { + gnpEntity.ID = gnpExisted.ID + gnpEntity.UUID = gnpExisted.UUID + gnpEntity.Version = gnpExisted.Version + 1 + gnpEntity.CreatedAt = gnpExisted.CreatedAt + } + + if coreErr = ds.storage.UpsertGroupPolicy(ctx, gnpEntity); coreErr != nil { + return nil, httpbase.ErrDatabase(ctx, "create global network failed").SetSubError(coreErr) + } + return gnpEntity, nil +} + +func (ds *gnp) Get(ctx context.Context, name string) (*entity.GlobalNetworkPolicy, *ierror.Error) { + gnpEntity, coreErr := ds.storage.GetGNPByName(ctx, name) + if coreErr != nil { + if errors.Is(coreErr, errlist.ErrNotFoundGlobalNetworkPolicy) { + return nil, httpbase.ErrNotFound(ctx, "not found").SetSubError(coreErr) + } + return nil, httpbase.ErrDatabase(ctx, "get global network policy failed").SetSubError(coreErr) + } + return gnpEntity, nil +} + +func (ds *gnp) Delete(ctx context.Context, name string) *ierror.Error { + if coreErr := ds.storage.DeleteGNPByName(ctx, name); coreErr != nil { + return httpbase.ErrDatabase(ctx, "delete global network policy failed").SetSubError(coreErr) + } + return nil +} + +func modelToRule(rule model.GNPSpecRuleInput) entity.GNPSpecRule { + return entity.GNPSpecRule{ + Metadata: rule.Metadata, + Action: rule.Action, + Protocol: rule.Protocol, + NotProtocol: rule.NotProtocol, + IPVersion: entity.IPVersion(rule.IPVersion), + Source: modelToRuleEntity(rule.Source), + Destination: modelToRuleEntity(rule.Destination), + } +} + +func modelToRuleEntity(ruleEntity *model.GNPSpecRuleEntityInput) *entity.GNPSpecRuleEntity { + if ruleEntity == nil { + return nil + } + return &entity.GNPSpecRuleEntity{ + Selector: ruleEntity.Selector, + Nets: ruleEntity.Nets, + NotNets: ruleEntity.NotNets, + Ports: ruleEntity.Ports, + NotPorts: ruleEntity.NotPorts, + } +} diff --git a/domain/service/gns.go b/domain/service/gns.go new file mode 100644 index 0000000..3ea16e7 --- /dev/null +++ b/domain/service/gns.go @@ -0,0 +1,108 @@ +package service + +import ( + "context" + "errors" + "log/slog" + "time" + + "github.com/google/uuid" + "go.mongodb.org/mongo-driver/bson/primitive" + + "github.com/bamboo-firewall/be" + "github.com/bamboo-firewall/be/cmd/server/pkg/common/errlist" + "github.com/bamboo-firewall/be/cmd/server/pkg/entity" + "github.com/bamboo-firewall/be/cmd/server/pkg/httpbase" + "github.com/bamboo-firewall/be/cmd/server/pkg/httpbase/ierror" + "github.com/bamboo-firewall/be/cmd/server/pkg/net" + "github.com/bamboo-firewall/be/cmd/server/pkg/repository" + "github.com/bamboo-firewall/be/domain/model" +) + +func NewGNS(policyMongo *repository.PolicyDB) *gns { + return &gns{ + storage: policyMongo, + } +} + +type gns struct { + storage be.Storage +} + +func (ds *gns) Create(ctx context.Context, input *model.CreateGlobalNetworkSetInput) (*entity.GlobalNetworkSet, *ierror.Error) { + // ToDo: use transaction and lock row + gnsExisted, coreErr := ds.storage.GetGNSByName(ctx, input.Metadata.Name) + if coreErr != nil && !errors.Is(coreErr, errlist.ErrNotFoundGlobalNetworkSet) { + return nil, httpbase.ErrDatabase(ctx, "get global network set failed").SetSubError(coreErr) + } + + netsV4, netsV6 := exactNets(input.Spec.Nets) + gnsEntity := &entity.GlobalNetworkSet{ + ID: primitive.NewObjectID(), + UUID: uuid.New().String(), + Version: 1, + Metadata: entity.GNSMetadata{ + Name: input.Metadata.Name, + Labels: input.Metadata.Labels, + }, + Spec: entity.GNSSpec{ + Nets: input.Spec.Nets, + NetsV4: netsV4, + NetsV6: netsV6, + }, + Description: input.Description, + CreatedAt: time.Now(), + UpdatedAt: time.Now(), + } + if gnsExisted != nil { + gnsEntity.ID = gnsExisted.ID + gnsEntity.UUID = gnsExisted.UUID + gnsEntity.Version = gnsExisted.Version + 1 + gnsEntity.CreatedAt = gnsExisted.CreatedAt + } + + if coreErr = ds.storage.UpsertGNS(ctx, gnsEntity); coreErr != nil { + return nil, httpbase.ErrDatabase(ctx, "create global network set failed").SetSubError(coreErr) + } + return gnsEntity, nil +} + +func exactNets(nets []string) (netsV4 []string, netsV6 []string) { + for _, netString := range nets { + ip, ipnet, err := net.ParseCIDROrIP(netString) + if err != nil { + slog.Warn("malformed net", "net", netString) + continue + } + var netV4V6 string + if ip.String() == ipnet.IP.String() { + netV4V6 = ipnet.String() + } else { + netV4V6 = ip.Network().String() + } + if ip.Version() == int(entity.IPVersion4) { + netsV4 = append(netsV4, netV4V6) + } else if ip.Version() == int(entity.IPVersion6) { + netsV6 = append(netsV6, netV4V6) + } + } + return +} + +func (ds *gns) Get(ctx context.Context, name string) (*entity.GlobalNetworkSet, *ierror.Error) { + gnsEntity, coreErr := ds.storage.GetGNSByName(ctx, name) + if coreErr != nil { + if errors.Is(coreErr, errlist.ErrNotFoundGlobalNetworkSet) { + return nil, httpbase.ErrNotFound(ctx, "not found").SetSubError(coreErr) + } + return nil, httpbase.ErrDatabase(ctx, "get global network set failed").SetSubError(coreErr) + } + return gnsEntity, nil +} + +func (ds *gns) Delete(ctx context.Context, name string) *ierror.Error { + if coreErr := ds.storage.DeleteGNSByName(ctx, name); coreErr != nil { + return httpbase.ErrDatabase(ctx, "delete global network set failed").SetSubError(coreErr) + } + return nil +} diff --git a/domain/service/hep.go b/domain/service/hep.go new file mode 100644 index 0000000..9dbbd96 --- /dev/null +++ b/domain/service/hep.go @@ -0,0 +1,374 @@ +package service + +import ( + "context" + "errors" + "fmt" + "log/slog" + "time" + + "github.com/google/uuid" + "go.mongodb.org/mongo-driver/bson/primitive" + + "github.com/bamboo-firewall/be" + "github.com/bamboo-firewall/be/cmd/server/pkg/common/errlist" + "github.com/bamboo-firewall/be/cmd/server/pkg/entity" + "github.com/bamboo-firewall/be/cmd/server/pkg/httpbase" + "github.com/bamboo-firewall/be/cmd/server/pkg/httpbase/ierror" + "github.com/bamboo-firewall/be/cmd/server/pkg/net" + "github.com/bamboo-firewall/be/cmd/server/pkg/repository" + "github.com/bamboo-firewall/be/cmd/server/pkg/selector" + "github.com/bamboo-firewall/be/domain/model" +) + +func NewHEP(policyMongo *repository.PolicyDB) *hep { + return &hep{ + storage: policyMongo, + } +} + +type hep struct { + storage be.Storage +} + +func (ds *hep) Create(ctx context.Context, input *model.CreateHostEndpointInput) (*entity.HostEndpoint, *ierror.Error) { + // ToDo: use transaction and lock row + hepExisted, coreErr := ds.storage.GetHostEndpointByName(ctx, input.Metadata.Name) + if coreErr != nil && !errors.Is(coreErr, errlist.ErrNotFoundHostEndpoint) { + return nil, httpbase.ErrDatabase(ctx, "get host endpoint failed").SetSubError(coreErr) + } + + ipsV4, ipsV6 := exactIPs(input.Spec.IPs) + hepEntity := &entity.HostEndpoint{ + ID: primitive.NewObjectID(), + UUID: uuid.New().String(), + Version: 1, + Metadata: entity.HostEndpointMetadata{ + Name: input.Metadata.Name, + Labels: input.Metadata.Labels, + }, + Spec: entity.HostEndpointSpec{ + InterfaceName: input.Spec.InterfaceName, + IPs: input.Spec.IPs, + IPsV4: ipsV4, + IPsV6: ipsV6, + }, + Description: input.Description, + CreatedAt: time.Now(), + UpdatedAt: time.Now(), + } + if hepExisted != nil { + hepEntity.ID = hepExisted.ID + hepEntity.UUID = hepExisted.UUID + hepEntity.Version = hepExisted.Version + 1 + hepEntity.CreatedAt = hepExisted.CreatedAt + } + + if coreErr = ds.storage.UpsertHostEndpoint(ctx, hepEntity); coreErr != nil { + return nil, httpbase.ErrDatabase(ctx, "create host endpoint failed").SetSubError(coreErr) + } + return hepEntity, nil +} + +func exactIPs(ips []string) (ipsV4, ipsV6 []string) { + for _, ipString := range ips { + ip := net.ParseIP(ipString) + if ip == nil { + slog.Warn("malformed ip", "ip", ipString) + continue + } + if ip.Version() == int(entity.IPVersion4) { + ipsV4 = append(ipsV4, ip.String()) + } else if ip.Version() == int(entity.IPVersion6) { + ipsV6 = append(ipsV6, ip.String()) + } + } + return +} + +func (ds *hep) Get(ctx context.Context, name string) (*entity.HostEndpoint, *ierror.Error) { + hepEntity, coreErr := ds.storage.GetHostEndpointByName(ctx, name) + if coreErr != nil { + if errors.Is(coreErr, errlist.ErrNotFoundHostEndpoint) { + return nil, httpbase.ErrNotFound(ctx, "not found").SetSubError(coreErr) + } + return nil, httpbase.ErrDatabase(ctx, "get host endpoint failed").SetSubError(coreErr) + } + return hepEntity, nil +} + +func (ds *hep) Delete(ctx context.Context, name string) *ierror.Error { + if coreErr := ds.storage.DeleteHostEndpointByName(ctx, name); coreErr != nil { + return httpbase.ErrDatabase(ctx, "delete host endpoint failed").SetSubError(coreErr) + } + return nil +} + +func (ds *hep) FetchPolicies(ctx context.Context, input *model.FetchHostEndpointPolicyInput) (*model.HostEndPointPolicy, *ierror.Error) { + hepEntity, coreErr := ds.storage.GetHostEndpointByName(ctx, input.Name) + if coreErr != nil { + if errors.Is(coreErr, errlist.ErrNotFoundHostEndpoint) { + return nil, httpbase.ErrNotFound(ctx, "not found").SetSubError(coreErr) + } + return nil, httpbase.ErrDatabase(ctx, "get host endpoint failed").SetSubError(coreErr) + } + + heps, coreErr := ds.storage.ListHostEndpoints(ctx) + if coreErr != nil { + return nil, httpbase.ErrDatabase(ctx, "list host endpoint failed").SetSubError(coreErr) + } + + gnps, err := ds.storage.ListGNP(ctx) + if err != nil { + return nil, httpbase.ErrDatabase(ctx, "list global network policy failed").SetSubError(coreErr) + } + + gnss, err := ds.storage.ListGNS(ctx) + if err != nil { + return nil, httpbase.ErrDatabase(ctx, "list global network set failed").SetSubError(coreErr) + } + + var ( + parsedGNPs []*model.ParsedGNP + gnpVersions = make(map[string]uint) + ) + + rp := &ruleParser{ + parsedHEPsMap: make(map[string]struct{}), + hepVersions: make(map[string]uint), + parsedGNSsMap: make(map[string]struct{}), + gnsVersions: make(map[string]uint), + } + + for _, policy := range gnps { + sel, errParse := selector.Parse(policy.Spec.Selector) + if errParse != nil { + slog.Warn("malformed selector", "policy_uuid", policy.UUID, "selector", policy.Spec.Selector, "err", errParse) + continue + } + if !sel.Evaluate(hepEntity.Metadata.Labels) { + continue + } + gnpVersions[policy.UUID] = policy.Version + + inboundRules := make([]*model.ParsedRule, 0) + outboundRules := make([]*model.ParsedRule, 0) + for _, rule := range policy.Spec.Ingress { + inboundRules = append(inboundRules, rp.parseRule(policy, &rule, heps, gnss)) + } + for _, rule := range policy.Spec.Egress { + outboundRules = append(outboundRules, rp.parseRule(policy, &rule, heps, gnss)) + } + parsedGNPs = append(parsedGNPs, &model.ParsedGNP{ + UUID: policy.UUID, + Version: policy.Version, + Name: policy.Metadata.Name, + InboundRules: inboundRules, + OutboundRules: outboundRules, + }) + } + + return &model.HostEndPointPolicy{ + MetaData: model.HostEndPointPolicyMetadata{ + GNPVersions: gnpVersions, + HEPVersions: rp.hepVersions, + GNSVersions: rp.gnsVersions, + }, + HEP: hepEntity, + ParsedGNPs: parsedGNPs, + ParsedHEPs: rp.parsedHEPs, + ParsedGNSs: rp.parsedGNSs, + }, nil +} + +type ruleParser struct { + parsedHEPs []*model.ParsedHEP + parsedHEPsMap map[string]struct{} + hepVersions map[string]uint + parsedGNSs []*model.ParsedGNS + parsedGNSsMap map[string]struct{} + gnsVersions map[string]uint +} + +func (r *ruleParser) parseRule(policy *entity.GlobalNetworkPolicy, rule *entity.GNPSpecRule, heps []*entity.HostEndpoint, gnss []*entity.GlobalNetworkSet) *model.ParsedRule { + var ( + protocol string + isProtocolNegative bool + srcGNSUUIDs []string + srcHEPUUIDs []string + srcNets []string + isSrcNetNegative bool + srcPorts []string + isSrcPortNegative bool + dstGNSUUIDs []string + dstHEPUUIDs []string + dstNets []string + isDstNetNegative bool + dstPorts []string + isDstPortNegative bool + ) + if rule.Protocol != "" { + protocol = rule.Protocol + isProtocolNegative = false + } else if rule.NotProtocol != "" { + protocol = rule.NotProtocol + isProtocolNegative = true + } + + // get global network set match if selector is available + if rule.Source != nil { + if len(rule.Source.Selector) > 0 { + for { + selSource, errParseSource := selector.Parse(rule.Source.Selector) + if errParseSource != nil { + slog.Warn("malformed selector in source", "policy_uuid", policy.UUID, "selector", rule.Source.Selector, "err", errParseSource) + break + } + for _, ep := range heps { + if !selSource.Evaluate(ep.Metadata.Labels) { + continue + } + if !((rule.IPVersion == entity.IPVersion4 && len(ep.Spec.IPsV4) > 0) || (rule.IPVersion == entity.IPVersion6 && len(ep.Spec.IPsV6) > 0)) { + continue + } + srcHEPUUIDs = append(srcHEPUUIDs, ep.UUID) + if _, ok := r.parsedHEPsMap[ep.UUID]; !ok { + r.parsedHEPsMap[ep.UUID] = struct{}{} + r.hepVersions[ep.UUID] = ep.Version + r.parsedHEPs = append(r.parsedHEPs, entityToParsedHEP(ep)) + } + } + for _, set := range gnss { + if !selSource.Evaluate(set.Metadata.Labels) { + continue + } + if !((rule.IPVersion == entity.IPVersion4 && len(set.Spec.NetsV4) > 0) || (rule.IPVersion == entity.IPVersion6 && len(set.Spec.NetsV6) > 0)) { + continue + } + srcGNSUUIDs = append(srcGNSUUIDs, set.UUID) + if _, ok := r.parsedGNSsMap[set.UUID]; !ok { + r.parsedGNSsMap[set.UUID] = struct{}{} + r.gnsVersions[set.UUID] = set.Version + r.parsedGNSs = append(r.parsedGNSs, entityToParsedGNS(set)) + } + } + break + } + } + + if len(rule.Source.Nets) > 0 { + srcNets = rule.Source.Nets + isSrcNetNegative = false + } else if len(rule.Source.Nets) > 0 { + srcNets = rule.Source.NotNets + isSrcNetNegative = true + } + if len(rule.Source.Ports) > 0 { + srcPorts = convertPorts(rule.Source.Ports) + isSrcPortNegative = false + } else if len(rule.Source.NotPorts) > 0 { + srcPorts = convertPorts(rule.Source.NotPorts) + isSrcPortNegative = true + } + } + // get global network set match if selector is available + if rule.Destination != nil { + if len(rule.Destination.Selector) > 0 { + for { + selDst, errParseDst := selector.Parse(rule.Destination.Selector) + if errParseDst != nil { + slog.Warn("malformed selector in destination", "policy_uuid", policy.UUID, "selector", rule.Source.Selector, "err", errParseDst) + break + } + for _, ep := range heps { + if !selDst.Evaluate(ep.Metadata.Labels) { + continue + } + if !((rule.IPVersion == entity.IPVersion4 && len(ep.Spec.IPsV4) > 0) || (rule.IPVersion == entity.IPVersion6 && len(ep.Spec.IPsV6) > 0)) { + continue + } + dstHEPUUIDs = append(dstHEPUUIDs, ep.UUID) + if _, ok := r.parsedHEPsMap[ep.UUID]; !ok { + r.parsedHEPsMap[ep.UUID] = struct{}{} + r.hepVersions[ep.UUID] = ep.Version + r.parsedHEPs = append(r.parsedHEPs, entityToParsedHEP(ep)) + } + } + for _, set := range gnss { + if !selDst.Evaluate(set.Metadata.Labels) { + continue + } + if !((rule.IPVersion == entity.IPVersion4 && len(set.Spec.NetsV4) > 0) || (rule.IPVersion == entity.IPVersion6 && len(set.Spec.NetsV6) > 0)) { + continue + } + dstGNSUUIDs = append(dstGNSUUIDs, set.Metadata.Name) + if _, ok := r.parsedGNSsMap[set.UUID]; !ok { + r.parsedGNSsMap[set.UUID] = struct{}{} + r.gnsVersions[set.UUID] = set.Version + r.parsedGNSs = append(r.parsedGNSs, entityToParsedGNS(set)) + } + } + break + } + } + + if len(rule.Destination.Nets) > 0 { + dstNets = rule.Destination.Nets + isDstNetNegative = false + } else if len(rule.Destination.NotNets) > 0 { + dstNets = rule.Destination.NotNets + isDstNetNegative = true + } + if len(rule.Destination.Ports) > 0 { + dstPorts = convertPorts(rule.Destination.Ports) + isDstPortNegative = false + } else if len(rule.Destination.NotPorts) > 0 { + dstPorts = convertPorts(rule.Destination.NotPorts) + isDstPortNegative = true + } + } + return &model.ParsedRule{ + Action: rule.Action, + IPVersion: int(rule.IPVersion), + Protocol: protocol, + IsProtocolNegative: isProtocolNegative, + SrcGNSUUIDs: srcGNSUUIDs, + SrcHEPUUIDs: srcHEPUUIDs, + SrcNets: srcNets, + IsSrcNetNegative: isSrcNetNegative, + SrcPorts: srcPorts, + IsSrcPortNegative: isSrcPortNegative, + DstGNSUUIDs: dstGNSUUIDs, + DstHEPUUIDs: dstHEPUUIDs, + DstNets: dstNets, + IsDstNetNegative: isDstNetNegative, + DstPorts: dstPorts, + IsDstPortNegative: isDstPortNegative, + } +} + +func entityToParsedHEP(hep *entity.HostEndpoint) *model.ParsedHEP { + return &model.ParsedHEP{ + UUID: hep.UUID, + Name: hep.Metadata.Name, + IPsV4: hep.Spec.IPsV4, + IPsV6: hep.Spec.IPsV6, + } +} + +func entityToParsedGNS(set *entity.GlobalNetworkSet) *model.ParsedGNS { + return &model.ParsedGNS{ + UUID: set.UUID, + Name: set.Metadata.Name, + NetsV4: set.Spec.NetsV4, + NetsV6: set.Spec.NetsV6, + } +} + +func convertPorts(ports []interface{}) []string { + var portStrings []string + for _, port := range ports { + portStrings = append(portStrings, fmt.Sprint(port)) + } + return portStrings +} diff --git a/domain/signup.go b/domain/signup.go deleted file mode 100644 index 149cdf1..0000000 --- a/domain/signup.go +++ /dev/null @@ -1,26 +0,0 @@ -package domain - -import ( - "context" -) - -type SignupRequest struct { - Name string `form:"name" binding:"required"` - Username string `form:"username" binding:"required"` - Email string `form:"email" binding:"required,email"` - Password string `form:"password" binding:"required"` -} - -type SignupResponse struct { - AccessToken string `json:"accessToken"` - RefreshToken string `json:"refreshToken"` - User User `json:"user"` -} - -type SignupUsecase interface { - Create(c context.Context, user *User) error - GetUserByUsername(c context.Context, username string) (User, error) - GetUserByEmail(c context.Context, email string) (User, error) - CreateAccessToken(user *User, secret string, expiry int) (accessToken string, err error) - CreateRefreshToken(user *User, secret string, expiry int) (refreshToken string, err error) -} diff --git a/domain/statistic.go b/domain/statistic.go deleted file mode 100644 index 2ad2ee2..0000000 --- a/domain/statistic.go +++ /dev/null @@ -1,28 +0,0 @@ -package domain - -import "context" - -type Summary struct { - TotalGlobalNetworkSet int64 `json:"total_global_network_set"` - TotalPolicy int64 `json:"total_policy"` - TotalHostEndpoint int64 `json:"total_host_endpoint"` - TotalUser int64 `json:"total_user"` -} - -type ProjectSummary struct { - ProjectName string `json:"project_name"` - Total int64 `json:"total"` -} - -type SummaryResponse struct { - Summary Summary `json:"summary"` -} - -type ProjectSummaryResponse struct { - ProjectSummary []ProjectSummary `json:"project_summary"` -} - -type StatisticUsecase interface { - GetSummary(c context.Context) (Summary, error) - GetProjectSummary(c context.Context) ([]ProjectSummary, error) -} diff --git a/domain/success_response.go b/domain/success_response.go deleted file mode 100644 index 44388f3..0000000 --- a/domain/success_response.go +++ /dev/null @@ -1,6 +0,0 @@ -package domain - -type SuccessResponse struct { - Message string `json:"message"` - Data interface{} `json:"data"` -} diff --git a/domain/user.go b/domain/user.go deleted file mode 100644 index ba32fe4..0000000 --- a/domain/user.go +++ /dev/null @@ -1,60 +0,0 @@ -package domain - -import ( - "context" - - "go.mongodb.org/mongo-driver/bson/primitive" -) - -const ( - CollectionUser = "users" -) - -type CreateUserRequest struct { - Name string `json:"name" binding:"required|email"` - Email string `json:"email" binding:"required"` - Username string `json:"username" binding:"required"` - Role string `json:"role" binding:"required"` - Password string `json:"password" binding:"required"` -} - -type UpdateUserRequest struct { - ID string `json:"id" binding:"required"` - Name string `json:"name"` - Role string `json:"role"` - Password string `json:"password"` -} - -type DeleteUserRequest struct { - ID string `json:"id" binding:"required"` -} - -type User struct { - ID primitive.ObjectID `bson:"_id"` - Username string `bson:"username" json:"username"` - Name string `bson:"name" json:"name"` - Email string `bson:"email" json:"email"` - Role string `bson:"role" json:"role"` - Password string `bson:"password" json:"-"` -} - -type UserRepository interface { - Create(c context.Context, user *User) error - Update(c context.Context, user *User) error - DeleteById(c context.Context, id string) error - Fetch(c context.Context) ([]User, error) - GetByEmail(c context.Context, email string) (User, error) - GetByUsername(c context.Context, username string) (User, error) - GetByID(c context.Context, id string) (User, error) - GetTotal(c context.Context) (int64, error) -} - -type UserUsecase interface { - Create(c context.Context, user *User) error - Update(c context.Context, user *User) error - Fetch(c context.Context) ([]User, error) - DeleteById(c context.Context, id string) error - GetUserByEmail(c context.Context, email string) (User, error) - GetUserByUsername(c context.Context, username string) (User, error) - GetUserByID(c context.Context, id string) (User, error) -} diff --git a/gnp.go b/gnp.go new file mode 100644 index 0000000..d010d21 --- /dev/null +++ b/gnp.go @@ -0,0 +1,13 @@ +package be + +type MetaData struct { + Annotations map[string]string +} + +type GlobalNetworkPolicy struct { + ApiVersion string + Kind string + MetaData MetaData + Name string + UUID string +} diff --git a/go.mod b/go.mod index e9cf1df..c0097fd 100644 --- a/go.mod +++ b/go.mod @@ -1,63 +1,69 @@ module github.com/bamboo-firewall/be -go 1.20 +go 1.22.6 require ( - github.com/casbin/casbin/v2 v2.71.1 - github.com/casbin/mongodb-adapter/v3 v3.5.0 - github.com/gin-gonic/gin v1.9.1 - github.com/golang-jwt/jwt/v4 v4.5.0 - github.com/spf13/viper v1.16.0 - go.mongodb.org/mongo-driver v1.12.0 - golang.org/x/crypto v0.11.0 + github.com/gin-contrib/cors v1.7.2 + github.com/gin-gonic/gin v1.10.0 + github.com/go-playground/validator/v10 v10.20.0 + github.com/google/go-cmp v0.6.0 + github.com/google/uuid v1.6.0 + github.com/spf13/cobra v1.8.1 + github.com/spf13/viper v1.19.0 + github.com/stretchr/testify v1.9.0 + go.mongodb.org/mongo-driver v1.16.1 + gopkg.in/yaml.v3 v3.0.1 ) require ( - github.com/bamboo-firewall/watcher v0.0.1 - github.com/projectcalico/api v0.0.0-20230602153125-fb7148692637 // indirect -) - -require ( - github.com/Knetic/govaluate v3.0.1-0.20171022003610-9aa49832a739+incompatible // indirect - github.com/bytedance/sonic v1.9.1 // indirect - github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311 // indirect - github.com/fsnotify/fsnotify v1.6.0 // indirect - github.com/gabriel-vasile/mimetype v1.4.2 // indirect + github.com/bytedance/sonic v1.11.6 // indirect + github.com/bytedance/sonic/loader v0.1.1 // indirect + github.com/cloudwego/base64x v0.1.4 // indirect + github.com/cloudwego/iasm v0.2.0 // indirect + github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect + github.com/fsnotify/fsnotify v1.7.0 // indirect + github.com/gabriel-vasile/mimetype v1.4.3 // indirect github.com/gin-contrib/sse v0.1.0 // indirect github.com/go-playground/locales v0.14.1 // indirect github.com/go-playground/universal-translator v0.18.1 // indirect - github.com/go-playground/validator/v10 v10.14.0 // indirect github.com/goccy/go-json v0.10.2 // indirect github.com/golang/snappy v0.0.4 // indirect github.com/hashicorp/hcl v1.0.0 // indirect + github.com/inconshreveable/mousetrap v1.1.0 // indirect github.com/json-iterator/go v1.1.12 // indirect - github.com/klauspost/compress v1.16.7 // indirect - github.com/klauspost/cpuid/v2 v2.2.4 // indirect - github.com/leodido/go-urn v1.2.4 // indirect + github.com/klauspost/compress v1.17.2 // indirect + github.com/klauspost/cpuid/v2 v2.2.7 // indirect + github.com/leodido/go-urn v1.4.0 // indirect github.com/magiconair/properties v1.8.7 // indirect - github.com/mattn/go-isatty v0.0.19 // indirect + github.com/mattn/go-isatty v0.0.20 // indirect github.com/mitchellh/mapstructure v1.5.0 // indirect github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect github.com/modern-go/reflect2 v1.0.2 // indirect github.com/montanaflynn/stats v0.7.1 // indirect - github.com/pelletier/go-toml/v2 v2.0.8 // indirect - github.com/spf13/afero v1.9.5 // indirect - github.com/spf13/cast v1.5.1 // indirect - github.com/spf13/jwalterweatherman v1.1.0 // indirect + github.com/pelletier/go-toml/v2 v2.2.2 // indirect + github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect + github.com/sagikazarmark/locafero v0.4.0 // indirect + github.com/sagikazarmark/slog-shim v0.1.0 // indirect + github.com/sourcegraph/conc v0.3.0 // indirect + github.com/spf13/afero v1.11.0 // indirect + github.com/spf13/cast v1.6.0 // indirect github.com/spf13/pflag v1.0.5 // indirect - github.com/subosito/gotenv v1.4.2 // indirect + github.com/subosito/gotenv v1.6.0 // indirect github.com/twitchyliquid64/golang-asm v0.15.1 // indirect - github.com/ugorji/go/codec v1.2.11 // indirect + github.com/ugorji/go/codec v1.2.12 // indirect github.com/xdg-go/pbkdf2 v1.0.0 // indirect github.com/xdg-go/scram v1.1.2 // indirect github.com/xdg-go/stringprep v1.0.4 // indirect - github.com/youmark/pkcs8 v0.0.0-20201027041543-1326539a0a0a // indirect - golang.org/x/arch v0.3.0 // indirect - golang.org/x/net v0.10.0 // indirect - golang.org/x/sync v0.3.0 // indirect - golang.org/x/sys v0.10.0 // indirect - golang.org/x/text v0.11.0 // indirect - google.golang.org/protobuf v1.30.0 // indirect + github.com/youmark/pkcs8 v0.0.0-20181117223130-1be2e3e5546d // indirect + go.uber.org/atomic v1.9.0 // indirect + go.uber.org/multierr v1.9.0 // indirect + golang.org/x/arch v0.8.0 // indirect + golang.org/x/crypto v0.23.0 // indirect + golang.org/x/exp v0.0.0-20230905200255-921286631fa9 // indirect + golang.org/x/net v0.25.0 // indirect + golang.org/x/sync v0.7.0 // indirect + golang.org/x/sys v0.20.0 // indirect + golang.org/x/text v0.15.0 // indirect + google.golang.org/protobuf v1.34.1 // indirect gopkg.in/ini.v1 v1.67.0 // indirect - gopkg.in/yaml.v3 v3.0.1 // indirect ) diff --git a/go.sum b/go.sum index 0e0735f..1fa5996 100644 --- a/go.sum +++ b/go.sum @@ -1,188 +1,67 @@ -cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= -cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= -cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU= -cloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6AU= -cloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY= -cloud.google.com/go v0.44.3/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY= -cloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc= -cloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0= -cloud.google.com/go v0.50.0/go.mod h1:r9sluTvynVuxRIOHXQEHMFffphuXHOMZMycpNR5e6To= -cloud.google.com/go v0.52.0/go.mod h1:pXajvRH/6o3+F9jDHZWQ5PbGhn+o8w9qiu/CffaVdO4= -cloud.google.com/go v0.53.0/go.mod h1:fp/UouUEsRkN6ryDKNW/Upv/JBKnv6WDthjR6+vze6M= -cloud.google.com/go v0.54.0/go.mod h1:1rq2OEkV3YMf6n/9ZvGWI3GWw0VoqH/1x2nd8Is/bPc= -cloud.google.com/go v0.56.0/go.mod h1:jr7tqZxxKOVYizybht9+26Z/gUq7tiRzu+ACVAMbKVk= -cloud.google.com/go v0.57.0/go.mod h1:oXiQ6Rzq3RAkkY7N6t3TcE6jE+CIBBbA36lwQ1JyzZs= -cloud.google.com/go v0.62.0/go.mod h1:jmCYTdRCQuc1PHIIJ/maLInMho30T/Y0M4hTdTShOYc= -cloud.google.com/go v0.65.0/go.mod h1:O5N8zS7uWy9vkA9vayVHs65eM1ubvY4h553ofrNHObY= -cloud.google.com/go v0.72.0/go.mod h1:M+5Vjvlc2wnp6tjzE102Dw08nGShTscUx2nZMufOKPI= -cloud.google.com/go v0.74.0/go.mod h1:VV1xSbzvo+9QJOxLDaJfTjx5e+MePCpCWwvftOeQmWk= -cloud.google.com/go v0.75.0/go.mod h1:VGuuCn7PG0dwsd5XPVm2Mm3wlh3EL55/79EKB6hlPTY= -cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o= -cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE= -cloud.google.com/go/bigquery v1.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvftPBK2Dvzc= -cloud.google.com/go/bigquery v1.5.0/go.mod h1:snEHRnqQbz117VIFhE8bmtwIDY80NLUZUMb4Nv6dBIg= -cloud.google.com/go/bigquery v1.7.0/go.mod h1://okPTzCYNXSlb24MZs83e2Do+h+VXtc4gLoIoXIAPc= -cloud.google.com/go/bigquery v1.8.0/go.mod h1:J5hqkt3O0uAFnINi6JXValWIb1v0goeZM77hZzJN/fQ= -cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE= -cloud.google.com/go/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1b3c64qFpCk= -cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I= -cloud.google.com/go/pubsub v1.1.0/go.mod h1:EwwdRX2sKPjnvnqCa270oGRyludottCI76h+R3AArQw= -cloud.google.com/go/pubsub v1.2.0/go.mod h1:jhfEVHT8odbXTkndysNHCcx0awwzvfOlguIAii9o8iA= -cloud.google.com/go/pubsub v1.3.1/go.mod h1:i+ucay31+CNRpDW4Lu78I4xXG+O1r/MAHgjpRVR+TSU= -cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw= -cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0ZeosJ0Rtdos= -cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohlUTyfDhBk= -cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs= -cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0= -cloud.google.com/go/storage v1.14.0/go.mod h1:GrKmX003DSIwi9o29oFT7YDnHYwZoctc3fOKtUw0Xmo= -dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= -github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= -github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= -github.com/Knetic/govaluate v3.0.1-0.20171022003610-9aa49832a739+incompatible h1:1G1pk05UrOh0NlF1oeaaix1x8XzrfjIDK47TY0Zehcw= -github.com/Knetic/govaluate v3.0.1-0.20171022003610-9aa49832a739+incompatible/go.mod h1:r7JcOSlj0wfOMncg0iLm8Leh48TZaKVeNIfJntJ2wa0= -github.com/bamboo-firewall/watcher v0.0.1 h1:R91bQUpIOzoH0Dnon05w5aarvaeYq9d3yhz9Hvxfw9c= -github.com/bamboo-firewall/watcher v0.0.1/go.mod h1:35Mob5LW4G4X6NEdNd1qISSjzwr12zYm/fVOtmTlIW0= -github.com/bytedance/sonic v1.5.0/go.mod h1:ED5hyg4y6t3/9Ku1R6dU/4KyJ48DZ4jPhfY1O2AihPM= -github.com/bytedance/sonic v1.9.1 h1:6iJ6NqdoxCDr6mbY8h18oSO+cShGSMRGCEo7F2h0x8s= -github.com/bytedance/sonic v1.9.1/go.mod h1:i736AoUSYt75HyZLoJW9ERYxcy6eaN6h4BZXU064P/U= -github.com/casbin/casbin/v2 v2.71.1 h1:LRHyqM0S1LzM/K59PmfUIN0ZJfLgcOjL4OhOQI/FNXU= -github.com/casbin/casbin/v2 v2.71.1/go.mod h1:vByNa/Fchek0KZUgG5wEsl7iFsiviAYKRtgrQfcJqHg= -github.com/casbin/mongodb-adapter/v3 v3.5.0 h1:WacrRWP0PfKgwo/+m5a81tsyDG7LODaLcecZr5zFHuc= -github.com/casbin/mongodb-adapter/v3 v3.5.0/go.mod h1:R5491PozS7Nx4dnHRSTu9CzRsJZ62IZrzAaC7PFych8= -github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= -github.com/chenzhuoyu/base64x v0.0.0-20211019084208-fb5309c8db06/go.mod h1:DH46F32mSOjUmXrMHnKwZdA8wcEefY7UVqBKYGjpdQY= -github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311 h1:qSGYFH7+jGhDF8vLC+iwCD4WpbV1EBDSzWkJODFLams= -github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311/go.mod h1:b583jCggY9gE99b6G5LEC39OIiVsWj+R97kbl5odCEk= -github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= -github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= -github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= -github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= -github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= -github.com/cncf/udpa/go v0.0.0-20200629203442-efcf912fb354/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= -github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= +github.com/bytedance/sonic v1.11.6 h1:oUp34TzMlL+OY1OUWxHqsdkgC/Zfc85zGqw9siXjrc0= +github.com/bytedance/sonic v1.11.6/go.mod h1:LysEHSvpvDySVdC2f87zGWf6CIKJcAvqab1ZaiQtds4= +github.com/bytedance/sonic/loader v0.1.1 h1:c+e5Pt1k/cy5wMveRDyk2X4B9hF4g7an8N3zCYjJFNM= +github.com/bytedance/sonic/loader v0.1.1/go.mod h1:ncP89zfokxS5LZrJxl5z0UJcsk4M4yY2JpfqGeCtNLU= +github.com/cloudwego/base64x v0.1.4 h1:jwCgWpFanWmN8xoIUHa2rtzmkd5J2plF/dnLS6Xd/0Y= +github.com/cloudwego/base64x v0.1.4/go.mod h1:0zlkT4Wn5C6NdauXdJRhSKRlJvmclQ1hhJgA0rcu/8w= +github.com/cloudwego/iasm v0.2.0 h1:1KNIy1I1H9hNNFEEH3DVnI4UujN+1zjpuk6gwHLTssg= +github.com/cloudwego/iasm v0.2.0/go.mod h1:8rXZaNYT2n95jn+zTI1sDr+IgcD2GVs0nlbbQPiEFhY= +github.com/cpuguy83/go-md2man/v2 v2.0.4/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= -github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= -github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= -github.com/envoyproxy/go-control-plane v0.9.7/go.mod h1:cwu0lG7PUMfa9snN8LXBig5ynNVH9qI8YYLbd1fK2po= -github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk= -github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= -github.com/frankban/quicktest v1.14.4 h1:g2rn0vABPOOXmZUj+vbmUp0lPoXEMuhTpIluN0XL9UY= -github.com/fsnotify/fsnotify v1.6.0 h1:n+5WquG0fcWoWp6xPWfHdbskMCQaFnG6PfBrh1Ky4HY= -github.com/fsnotify/fsnotify v1.6.0/go.mod h1:sl3t1tCWJFWoRz9R8WJCbQihKKwmorjAbSClcnxKAGw= -github.com/gabriel-vasile/mimetype v1.4.2 h1:w5qFW6JKBz9Y393Y4q372O9A7cUSequkh1Q7OhCmWKU= -github.com/gabriel-vasile/mimetype v1.4.2/go.mod h1:zApsH/mKG4w07erKIaJPFiX0Tsq9BFQgN3qGY5GnNgA= +github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= +github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8= +github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0= +github.com/fsnotify/fsnotify v1.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nosvA= +github.com/fsnotify/fsnotify v1.7.0/go.mod h1:40Bi/Hjc2AVfZrqy+aj+yEI+/bRxZnMJyTJwOpGvigM= +github.com/gabriel-vasile/mimetype v1.4.3 h1:in2uUcidCuFcDKtdcBxlR0rJ1+fsokWf+uqxgUFjbI0= +github.com/gabriel-vasile/mimetype v1.4.3/go.mod h1:d8uq/6HKRL6CGdk+aubisF/M5GcPfT7nKyLpA0lbSSk= +github.com/gin-contrib/cors v1.7.2 h1:oLDHxdg8W/XDoN/8zamqk/Drgt4oVZDvaV0YmvVICQw= +github.com/gin-contrib/cors v1.7.2/go.mod h1:SUJVARKgQ40dmrzgXEVxj2m7Ig1v1qIboQkPDTQ9t2E= github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE= github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI= -github.com/gin-gonic/gin v1.9.1 h1:4idEAncQnU5cB7BeOkPtxjfCSye0AAm1R0RVIqJ+Jmg= -github.com/gin-gonic/gin v1.9.1/go.mod h1:hPrL7YrpYKXt5YId3A/Tnip5kqbEAP+KLuI3SUcPTeU= -github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= -github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= -github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= +github.com/gin-gonic/gin v1.10.0 h1:nTuyha1TYqgedzytsKYqna+DfLos46nTv2ygFy86HFU= +github.com/gin-gonic/gin v1.10.0/go.mod h1:4PMNQiOhvDRa013RKVbsiNwoyezlm2rm0uX/T7kzp5Y= github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s= +github.com/go-playground/assert/v2 v2.2.0/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4= github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA= github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY= github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY= github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY= -github.com/go-playground/validator/v10 v10.14.0 h1:vgvQWe3XCz3gIeFDm/HnTIbj6UGmg/+t63MyGU2n5js= -github.com/go-playground/validator/v10 v10.14.0/go.mod h1:9iXMNT7sEkjXb0I+enO7QXmzG6QCsPWY4zveKFVRSyU= +github.com/go-playground/validator/v10 v10.20.0 h1:K9ISHbSaI0lyB2eWMPJo+kOS/FBExVwjEviJTixqxL8= +github.com/go-playground/validator/v10 v10.20.0/go.mod h1:dbuPbCMFw/DrkbEynArYaCwl3amGuJotoKCe95atGMM= github.com/goccy/go-json v0.10.2 h1:CrxCmQqYDkv1z7lO7Wbh2HN93uovUHgrECaO5ZrCXAU= github.com/goccy/go-json v0.10.2/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I= -github.com/golang-jwt/jwt/v4 v4.5.0 h1:7cYmW1XlMY7h7ii7UhUyChSgS5wUJEnm9uZVTGqOWzg= -github.com/golang-jwt/jwt/v4 v4.5.0/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0= -github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= -github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= -github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= -github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= -github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= -github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= -github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y= -github.com/golang/mock v1.4.0/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= -github.com/golang/mock v1.4.1/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= -github.com/golang/mock v1.4.3/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= -github.com/golang/mock v1.4.4 h1:l75CXGRSwbaYNpl/Z2X1XIIAMSCquvXgpVZDhwEIJsc= -github.com/golang/mock v1.4.4/go.mod h1:l3mdAwkq5BuhzHwde/uurv3sEJeZMXNpwsxVWU71h+4= -github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= -github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= -github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= -github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= -github.com/golang/protobuf v1.3.4/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= -github.com/golang/protobuf v1.3.5/go.mod h1:6O5/vntMXwX2lRkT1hjjk0nAC1IDOTvTlVgjlRvqsdk= -github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= -github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= -github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= -github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= -github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= -github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8= -github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= -github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= -github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= -github.com/golang/snappy v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= github.com/golang/snappy v0.0.4 h1:yAGX7huGHXlcLOEtBnF4w7FQwA26wojNCwOYAEhLjQM= github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= -github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= -github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= -github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= -github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= -github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= -github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.4.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= +github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= +github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= -github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= -github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= -github.com/google/martian/v3 v3.1.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= -github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= -github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= -github.com/google/pprof v0.0.0-20191218002539-d4f498aebedc/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= -github.com/google/pprof v0.0.0-20200212024743-f11f1df84d12/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= -github.com/google/pprof v0.0.0-20200229191704-1ebb73c60ed3/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= -github.com/google/pprof v0.0.0-20200430221834-fc25d7d30c6d/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= -github.com/google/pprof v0.0.0-20200708004538-1a94d8640e99/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= -github.com/google/pprof v0.0.0-20201023163331-3e6fc7fc9c4c/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= -github.com/google/pprof v0.0.0-20201203190320-1bf35d6f28c2/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= -github.com/google/pprof v0.0.0-20201218002935-b9804c9f04c2/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= -github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= -github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= -github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= -github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= -github.com/googleapis/google-cloud-go-testing v0.0.0-20200911160855-bcd43fbb19e8/go.mod h1:dvDLG8qkwmyD9a/MJJN3XJcT3xFxOKAvTZGvuZmac9g= -github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= -github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= +github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= +github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4= github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= -github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= -github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= +github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= +github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= -github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= -github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk= -github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= -github.com/klauspost/compress v1.13.6/go.mod h1:/3/Vjq9QcHkK5uEr5lBEmyoZ1iFhe47etQ6QUkpK6sk= -github.com/klauspost/compress v1.16.7 h1:2mk3MPGNzKyxErAw8YaohYh69+pa4sIQSC0fPGCFR9I= -github.com/klauspost/compress v1.16.7/go.mod h1:ntbaceVETuRiXiv4DpjP66DpAtAGkEQskQzEyD//IeE= +github.com/klauspost/compress v1.17.2 h1:RlWWUY/Dr4fL8qk9YG7DTZ7PDgME2V4csBXA8L/ixi4= +github.com/klauspost/compress v1.17.2/go.mod h1:ntbaceVETuRiXiv4DpjP66DpAtAGkEQskQzEyD//IeE= github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg= -github.com/klauspost/cpuid/v2 v2.2.4 h1:acbojRNwl3o09bUq+yDCtZFc1aiwaAAxtcn8YkZXnvk= -github.com/klauspost/cpuid/v2 v2.2.4/go.mod h1:RVVoqg1df56z8g3pUjL/3lE5UfnlrJX8tyFgg4nqhuY= -github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg= -github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= +github.com/klauspost/cpuid/v2 v2.2.7 h1:ZWSB3igEs+d0qvnxR/ZBzXVmxkgt8DdzP6m9pfuVLDM= +github.com/klauspost/cpuid/v2 v2.2.7/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws= +github.com/knz/go-libedit v1.10.1/go.mod h1:MZTVkCWyz0oBc7JOWP3wNAzd002ZbM/5hgShxwh4x8M= github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= -github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= -github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= +github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= -github.com/leodido/go-urn v1.2.4 h1:XlAE/cm/ms7TE/VMVoduSpNBoyc2dOxHs5MZSwAN63Q= -github.com/leodido/go-urn v1.2.4/go.mod h1:7ZrI8mTSeBSHl/UaRyKQW1qZeMgak41ANeCNaVckg+4= +github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= +github.com/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ= +github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI= github.com/magiconair/properties v1.8.7 h1:IeQXZAiQcpL9mgcAe1Nu6cX9LLw6ExEHKjN0VQdvPDY= github.com/magiconair/properties v1.8.7/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0= -github.com/mattn/go-isatty v0.0.19 h1:JITubQf0MOLdlGRuRq+jtsDlekdYPia9ZFsB8h/APPA= -github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= +github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= +github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY= github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= @@ -190,402 +69,114 @@ github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= -github.com/montanaflynn/stats v0.0.0-20171201202039-1bf9dbcd8cbe/go.mod h1:wL8QJuTMNUDYhXwkmfOly8iTdp5TEcJFWZD2D7SIkUc= github.com/montanaflynn/stats v0.7.1 h1:etflOAAHORrCC44V+aR6Ftzort912ZU+YLiSTuV8eaE= github.com/montanaflynn/stats v0.7.1/go.mod h1:etXPPgVO6n31NxCd9KQUMvCM+ve0ruNzt6R8Bnaayow= -github.com/nxadm/tail v1.4.8 h1:nPr65rt6Y5JFSKQO7qToXr7pePgD6Gwiw05lkbyAQTE= -github.com/onsi/ginkgo v1.16.4 h1:29JGrr5oVBm5ulCWet69zQkzWipVXIol6ygQUe/EzNc= -github.com/onsi/gomega v1.23.0 h1:/oxKu9c2HVap+F3PfKort2Hw5DEU+HGlW8n+tguWsys= -github.com/pelletier/go-toml/v2 v2.0.8 h1:0ctb6s9mE31h0/lhu+J6OPmVeDxJn+kYnJc2jZR9tGQ= -github.com/pelletier/go-toml/v2 v2.0.8/go.mod h1:vuYfssBdrU2XDZ9bYydBu6t+6a6PYNcZljzZR9VXg+4= -github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= -github.com/pkg/sftp v1.13.1/go.mod h1:3HaPG6Dq1ILlpPZRO0HVMrsydcdLt6HRDccSgb87qRg= -github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pelletier/go-toml/v2 v2.2.2 h1:aYUidT7k73Pcl9nb2gScu7NSrKCSHIDE89b3+6Wq+LM= +github.com/pelletier/go-toml/v2 v2.2.2/go.mod h1:1t835xjRzz80PqgE6HHgN2JOsmgYu/h4qDAS4n929Rs= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/projectcalico/api v0.0.0-20230602153125-fb7148692637 h1:F48and+6vKJsRMl95Y/XKVik0Kwhos8YShTH9Fsdqlw= -github.com/projectcalico/api v0.0.0-20230602153125-fb7148692637/go.mod h1:d3yVTVhVHDawgeKrru/ZZD8QLEtiKQciUaAwnua47Qg= -github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= -github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= +github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= +github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8= -github.com/spf13/afero v1.9.5 h1:stMpOSZFs//0Lv29HduCmli3GUfpFoF3Y1Q/aXj/wVM= -github.com/spf13/afero v1.9.5/go.mod h1:UBogFpq8E9Hx+xc5CNTTEpTnuHVmXDwZcZcE1eb/UhQ= -github.com/spf13/cast v1.5.1 h1:R+kOtfhWQE6TVQzY+4D7wJLBgkdVasCEFxSUBYBYIlA= -github.com/spf13/cast v1.5.1/go.mod h1:b9PdjNptOpzXr7Rq1q9gJML/2cdGQAo69NKzQ10KN48= -github.com/spf13/jwalterweatherman v1.1.0 h1:ue6voC5bR5F8YxI5S67j9i582FU4Qvo2bmqnqMYADFk= -github.com/spf13/jwalterweatherman v1.1.0/go.mod h1:aNWZUN0dPAAO/Ljvb5BEdw96iTZ0EXowPYD95IqWIGo= +github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs= +github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= +github.com/sagikazarmark/locafero v0.4.0 h1:HApY1R9zGo4DBgr7dqsTH/JJxLTTsOt7u6keLGt6kNQ= +github.com/sagikazarmark/locafero v0.4.0/go.mod h1:Pe1W6UlPYUk/+wc/6KFhbORCfqzgYEpgQ3O5fPuL3H4= +github.com/sagikazarmark/slog-shim v0.1.0 h1:diDBnUNK9N/354PgrxMywXnAwEr1QZcOr6gto+ugjYE= +github.com/sagikazarmark/slog-shim v0.1.0/go.mod h1:SrcSrq8aKtyuqEI1uvTDTK1arOWRIczQRv+GVI1AkeQ= +github.com/sourcegraph/conc v0.3.0 h1:OQTbbt6P72L20UqAkXXuLOj79LfEanQ+YQFNpLA9ySo= +github.com/sourcegraph/conc v0.3.0/go.mod h1:Sdozi7LEKbFPqYX2/J+iBAM6HpqSLTASQIKqDmF7Mt0= +github.com/spf13/afero v1.11.0 h1:WJQKhtpdm3v2IzqG8VMqrr6Rf3UYpEF239Jy9wNepM8= +github.com/spf13/afero v1.11.0/go.mod h1:GH9Y3pIexgf1MTIWtNGyogA5MwRIDXGUr+hbWNoBjkY= +github.com/spf13/cast v1.6.0 h1:GEiTHELF+vaR5dhz3VqZfFSzZjYbgeKDpBxQVS4GYJ0= +github.com/spf13/cast v1.6.0/go.mod h1:ancEpBxwJDODSW/UG4rDrAqiKolqNNh2DX3mk86cAdo= +github.com/spf13/cobra v1.8.1 h1:e5/vxKd/rZsfSJMUX1agtjeTDf+qv1/JdBF8gg5k9ZM= +github.com/spf13/cobra v1.8.1/go.mod h1:wHxEcudfqmLYa8iTfL+OuZPbBZkmvliBWKIezN3kD9Y= github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= -github.com/spf13/viper v1.16.0 h1:rGGH0XDZhdUOryiDWjmIvUSWpbNqisK8Wk0Vyefw8hc= -github.com/spf13/viper v1.16.0/go.mod h1:yg78JgCJcbrQOvV9YLXgkLaZqUidkY9K+Dd1FofRzQg= +github.com/spf13/viper v1.19.0 h1:RWq5SEjt8o25SROyN3z2OrDB9l7RPd3lwTWU8EcEdcI= +github.com/spf13/viper v1.19.0/go.mod h1:GQUN9bilAbhU/jgc1bKs99f/suXKeUMct8Adx5+Ntkg= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= -github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= +github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= -github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= -github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= -github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= -github.com/stretchr/testify v1.8.3 h1:RP3t2pwF7cMEbC1dqtB6poj3niw/9gnV4Cjg5oW5gtY= -github.com/stretchr/testify v1.8.3/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= -github.com/subosito/gotenv v1.4.2 h1:X1TuBLAMDFbaTAChgCBLu3DU3UPyELpnF2jjJ2cz/S8= -github.com/subosito/gotenv v1.4.2/go.mod h1:ayKnFf/c6rvx/2iiLrJUk1e6plDbT3edrFNGqEflhK0= +github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= +github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= +github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= +github.com/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8= +github.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSWPKKo0FU= github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI= github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08= -github.com/ugorji/go/codec v1.2.11 h1:BMaWp1Bb6fHwEtbplGBGJ498wD+LKlNSl25MjdZY4dU= -github.com/ugorji/go/codec v1.2.11/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg= +github.com/ugorji/go/codec v1.2.12 h1:9LC83zGrHhuUA9l16C9AHXAqEV/2wBQ4nkvumAE65EE= +github.com/ugorji/go/codec v1.2.12/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg= github.com/xdg-go/pbkdf2 v1.0.0 h1:Su7DPu48wXMwC3bs7MCNG+z4FhcyEuz5dlvchbq0B0c= github.com/xdg-go/pbkdf2 v1.0.0/go.mod h1:jrpuAogTd400dnrH08LKmI/xc1MbPOebTwRqcT5RDeI= github.com/xdg-go/scram v1.1.2 h1:FHX5I5B4i4hKRVRBCFRxq1iQRej7WO3hhBuJf+UUySY= github.com/xdg-go/scram v1.1.2/go.mod h1:RT/sEzTbU5y00aCK8UOx6R7YryM0iF1N2MOmC3kKLN4= github.com/xdg-go/stringprep v1.0.4 h1:XLI/Ng3O1Atzq0oBs3TWm+5ZVgkq2aqdlvP9JtoZ6c8= github.com/xdg-go/stringprep v1.0.4/go.mod h1:mPGuuIYwz7CmR2bT9j4GbQqutWS1zV24gijq1dTyGkM= +github.com/youmark/pkcs8 v0.0.0-20181117223130-1be2e3e5546d h1:splanxYIlg+5LfHAM6xpdFEAYOk8iySO56hMFq6uLyA= github.com/youmark/pkcs8 v0.0.0-20181117223130-1be2e3e5546d/go.mod h1:rHwXgn7JulP+udvsHwJoVG1YGAP6VLg4y9I5dyZdqmA= -github.com/youmark/pkcs8 v0.0.0-20201027041543-1326539a0a0a h1:fZHgsYlfvtyqToslyjUt3VOPF4J7aK/3MPcK7xp3PDk= -github.com/youmark/pkcs8 v0.0.0-20201027041543-1326539a0a0a/go.mod h1:ul22v+Nro/R083muKhosV54bj5niojjWZvU8xrevuH4= -github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= -github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= -github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= -github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= -go.mongodb.org/mongo-driver v1.12.0 h1:aPx33jmn/rQuJXPQLZQ8NtfPQG8CaqgLThFtqRb0PiE= -go.mongodb.org/mongo-driver v1.12.0/go.mod h1:AZkxhPnFJUoH7kZlFkVKucV20K387miPfm7oimrSmK0= -go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= -go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8= -go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= -go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= -go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= -go.opencensus.io v0.22.5/go.mod h1:5pWMHQbX5EPX2/62yrJeAkowc+lfs/XD7Uxpq3pI6kk= +go.mongodb.org/mongo-driver v1.16.1 h1:rIVLL3q0IHM39dvE+z2ulZLp9ENZKThVfuvN/IiN4l8= +go.mongodb.org/mongo-driver v1.16.1/go.mod h1:oB6AhJQvFQL4LEHyXi6aJzQJtBiTQHiAd83l0GdFaiw= +go.uber.org/atomic v1.9.0 h1:ECmE8Bn/WFTYwEW/bpKD3M8VtR/zQVbavAoalC1PYyE= +go.uber.org/atomic v1.9.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= +go.uber.org/multierr v1.9.0 h1:7fIwc/ZtS0q++VgcfqFDxSBZVv/Xo49/SYnDFupUwlI= +go.uber.org/multierr v1.9.0/go.mod h1:X2jQV1h+kxSjClGpnseKVIxpmcjrj7MNnI0bnlfKTVQ= golang.org/x/arch v0.0.0-20210923205945-b76863e36670/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8= -golang.org/x/arch v0.3.0 h1:02VY4/ZcO/gBOH6PUaoiptASxtXU10jazRCP865E97k= -golang.org/x/arch v0.3.0/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8= +golang.org/x/arch v0.8.0 h1:3wRIsP3pM4yUptoR96otTUOXI367OS0+c9eeRi9doIc= +golang.org/x/arch v0.8.0/go.mod h1:FEVrYAQjsQXMVJ1nsMoVVXPZg6p2JE2mx8psSWTDQys= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= -golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= -golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= -golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= -golang.org/x/crypto v0.0.0-20200302210943-78000ba7a073/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= -golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= -golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= -golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= -golang.org/x/crypto v0.0.0-20220722155217-630584e8d5aa/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= -golang.org/x/crypto v0.11.0 h1:6Ewdq3tDic1mg5xRO4milcWCfMVQhI4NkqWWvqejpuA= -golang.org/x/crypto v0.11.0/go.mod h1:xgJhtzW8F9jGdVFWZESrid1U1bjeNy4zgy5cRr/CIio= -golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= -golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= -golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= -golang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek= -golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY= -golang.org/x/exp v0.0.0-20191129062945-2f5052295587/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= -golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= -golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= -golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM= -golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU= -golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= -golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= -golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= -golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= -golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= -golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= -golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= -golang.org/x/lint v0.0.0-20190909230951-414d861bb4ac/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= -golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= -golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f/go.mod h1:5qLYkcX4OjUUV8bRuDixDT3tpyyb+LUpUlRWLxfhWrs= -golang.org/x/lint v0.0.0-20200130185559-910be7a94367/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= -golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= -golang.org/x/lint v0.0.0-20201208152925-83fdc39ff7b5/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= -golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE= -golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o= -golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc= -golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY= -golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= -golang.org/x/mod v0.1.1-0.20191107180719-034126e5016b/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= -golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= -golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= -golang.org/x/mod v0.4.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= -golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/crypto v0.23.0 h1:dIJU/v2J8Mdglj/8rJ6UUOM3Zc9zLZxVZwwxMooUSAI= +golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8= +golang.org/x/exp v0.0.0-20230905200255-921286631fa9 h1:GoHiUyI/Tp2nVkLI2mCxVkOjsbSXD66ic0XW0js0R9g= +golang.org/x/exp v0.0.0-20230905200255-921286631fa9/go.mod h1:S2oDrQGGwySpoQPVqRShND87VCbxmc6bL1Yd2oYrm6k= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= -golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= -golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= -golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= -golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= -golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20190628185345-da137c7871d7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20200222125558-5a598a2470a0/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20200301022130-244492dfa37a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= -golang.org/x/net v0.0.0-20200501053045-e0ff5e5a1de5/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= -golang.org/x/net v0.0.0-20200506145744-7e3656a0809f/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= -golang.org/x/net v0.0.0-20200513185701-a91f0712d120/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= -golang.org/x/net v0.0.0-20200520182314-0ba52f642ac2/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= -golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= -golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= -golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= -golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= -golang.org/x/net v0.0.0-20201031054903-ff519b6c9102/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= -golang.org/x/net v0.0.0-20201209123823-ac852fbbde11/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= -golang.org/x/net v0.0.0-20201224014010-6772e930b67b/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= -golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= -golang.org/x/net v0.10.0 h1:X2//UzNDwYmtCLn7To6G58Wr6f5ahEAQgKNzv9Y951M= -golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= -golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= -golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= -golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= -golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= -golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= -golang.org/x/oauth2 v0.0.0-20200902213428-5d25da1a8d43/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= -golang.org/x/oauth2 v0.0.0-20201109201403-9fd604954f58/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= -golang.org/x/oauth2 v0.0.0-20201208152858-08078c50e5b5/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= -golang.org/x/oauth2 v0.0.0-20210218202405-ba52d332ba99/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= -golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/net v0.25.0 h1:d/OCCoBEUq33pjydKrGQhw7IlUPI2Oylr+8qLx49kac= +golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.3.0 h1:ftCYgMx6zT/asHUrPw8BLLscYtGznsLAnjq5RH9P66E= -golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y= -golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sync v0.7.0 h1:YsImfSBoP9QPYL0xyKJPq0gcaJdG3rInoqxTWbfQu9M= +golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200113162924-86b910548bc1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200331124033-c3d80250170d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200501052902-10377860bb8e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200511232937-7e40ca221e25/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200515095857-1151b9dac4a9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200523222454-059865788121/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200905004654-be1d3432aa8f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20201201145000-ef89a241ccb3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210104204734-6f8348627aad/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210225134936-a50acf3fe073/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210423185535-09eb48e85fd7/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220704084225-05e143d24a9e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220908164124-27713097b956/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.10.0 h1:SqMFp9UcQJZa+pmYuAKjd9xq1f0j5rLcDIk0mj4qAsA= -golang.org/x/sys v0.10.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.20.0 h1:Od9JTbYCk261bKm4M/mw7AklTlFYIa0bIp9BgSm1S8Y= +golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= -golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= -golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= -golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ= -golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= -golang.org/x/text v0.11.0 h1:LAntKIrcmeSKERyiOh0XMV39LXS8IE9UL2yP7+f5ij4= -golang.org/x/text v0.11.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= -golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= -golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= -golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/text v0.15.0 h1:h1V/4gjBv8v9cjcR6+AR5+/cIYK5N/WAgiv4xlsEtAk= +golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= -golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= -golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= -golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= -golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= -golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= -golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= -golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= -golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= -golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= -golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= -golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= -golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191113191852-77e3bb0ad9e7/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191115202509-3a792d9c32b2/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191125144606-a911d9008d1f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191130070609-6e064ea0cf2d/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191216173652-a0e659d51361/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20191227053925-7b8e75db28f4/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200117161641-43d50277825c/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200122220014-bf1340f18c4a/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200204074204-1cc6d1ef6c74/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200207183749-b753a1ba74fa/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200212150539-ea181f53ac56/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200224181240-023911ca70b2/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200227222343-706bc42d1f0d/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200304193943-95d2e580d8eb/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw= -golang.org/x/tools v0.0.0-20200312045724-11d5b4c81c7d/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw= -golang.org/x/tools v0.0.0-20200331025713-a30bf2db82d4/go.mod h1:Sl4aGygMT6LrqrWclx+PTx3U+LnKx/seiNR+3G19Ar8= -golang.org/x/tools v0.0.0-20200501065659-ab2804fb9c9d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= -golang.org/x/tools v0.0.0-20200512131952-2bc93b1c0c88/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= -golang.org/x/tools v0.0.0-20200515010526-7d3b6ebf133d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= -golang.org/x/tools v0.0.0-20200618134242-20370b0cb4b2/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= -golang.org/x/tools v0.0.0-20200729194436-6467de6f59a7/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= -golang.org/x/tools v0.0.0-20200804011535-6c149bb5ef0d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= -golang.org/x/tools v0.0.0-20200825202427-b303f430e36d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= -golang.org/x/tools v0.0.0-20200904185747-39188db58858/go.mod h1:Cj7w3i3Rnn0Xh82ur9kSqwfTHTeVxaDqrfMjpcNT6bE= -golang.org/x/tools v0.0.0-20201110124207-079ba7bd75cd/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= -golang.org/x/tools v0.0.0-20201201161351-ac6f37ff4c2a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= -golang.org/x/tools v0.0.0-20201208233053-a543418bbed2/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= -golang.org/x/tools v0.0.0-20210105154028-b0ab187a4818/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= -golang.org/x/tools v0.0.0-20210108195828-e2f9c7f1fc8e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= -golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0= golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE= -google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M= -google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= -google.golang.org/api v0.9.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= -google.golang.org/api v0.13.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= -google.golang.org/api v0.14.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= -google.golang.org/api v0.15.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= -google.golang.org/api v0.17.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= -google.golang.org/api v0.18.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= -google.golang.org/api v0.19.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= -google.golang.org/api v0.20.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= -google.golang.org/api v0.22.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= -google.golang.org/api v0.24.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE= -google.golang.org/api v0.28.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE= -google.golang.org/api v0.29.0/go.mod h1:Lcubydp8VUV7KeIHD9z2Bys/sm/vGKnG1UHuDBSrHWM= -google.golang.org/api v0.30.0/go.mod h1:QGmEvQ87FHZNiUVJkT14jQNYJ4ZJjdRF23ZXz5138Fc= -google.golang.org/api v0.35.0/go.mod h1:/XrVsuzM0rZmrsbjJutiuftIzeuTQcEeaYcSk/mQ1dg= -google.golang.org/api v0.36.0/go.mod h1:+z5ficQTmoYpPn8LCUNVpK5I7hwkpjbcgqA7I34qYtE= -google.golang.org/api v0.40.0/go.mod h1:fYKFpnQN0DsDSKRVRcQSDQNtqWPfM9i+zNPxepjRCQ8= -google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= -google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= -google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= -google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0= -google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= -google.golang.org/appengine v1.6.6/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= -google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= -google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= -google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= -google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= -google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= -google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= -google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= -google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= -google.golang.org/genproto v0.0.0-20190911173649-1774047e7e51/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8= -google.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= -google.golang.org/genproto v0.0.0-20191115194625-c23dd37a84c9/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= -google.golang.org/genproto v0.0.0-20191216164720-4f79533eabd1/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= -google.golang.org/genproto v0.0.0-20191230161307-f3c370f40bfb/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= -google.golang.org/genproto v0.0.0-20200115191322-ca5a22157cba/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= -google.golang.org/genproto v0.0.0-20200122232147-0452cf42e150/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= -google.golang.org/genproto v0.0.0-20200204135345-fa8e72b47b90/go.mod h1:GmwEX6Z4W5gMy59cAlVYjN9JhxgbQH6Gn+gFDQe2lzA= -google.golang.org/genproto v0.0.0-20200212174721-66ed5ce911ce/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20200224152610-e50cd9704f63/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20200228133532-8c2c7df3a383/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20200305110556-506484158171/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20200312145019-da6875a35672/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20200331122359-1ee6d9798940/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20200430143042-b979b6f78d84/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20200511104702-f5ebc3bea380/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20200515170657-fc4c6c6a6587/go.mod h1:YsZOwe1myG/8QRHRsmBRE1LrgQY60beZKjly0O1fX9U= -google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= -google.golang.org/genproto v0.0.0-20200618031413-b414f8b61790/go.mod h1:jDfRM7FcilCzHH/e9qn6dsT145K34l5v+OpcnNgKAAA= -google.golang.org/genproto v0.0.0-20200729003335-053ba62fc06f/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20200804131852-c06518451d9c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20200825200019-8632dd797987/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20200904004341-0bd0a958aa1d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20201109203340-2640f1f9cdfb/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20201201144952-b05cb90ed32e/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20201210142538-e3217bee35cc/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20201214200347-8c77b98c765d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20210108203827-ffc7fda8c3d7/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20210226172003-ab064af71705/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= -google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= -google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= -google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= -google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= -google.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= -google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= -google.golang.org/grpc v1.27.1/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= -google.golang.org/grpc v1.28.0/go.mod h1:rpkK4SK4GF4Ach/+MFLZUBavHOvF2JJB5uozKKal+60= -google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk= -google.golang.org/grpc v1.30.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= -google.golang.org/grpc v1.31.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= -google.golang.org/grpc v1.31.1/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= -google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc= -google.golang.org/grpc v1.34.0/go.mod h1:WotjhfgOW/POjDeRt8vscBtXq+2VjORFy659qA51WJ8= -google.golang.org/grpc v1.35.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= -google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= -google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= -google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= -google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= -google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= -google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= -google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= -google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= -google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4= -google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= -google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= -google.golang.org/protobuf v1.30.0 h1:kPPoIgf3TsEvrm0PFe15JQ+570QVxYzEvvHqChK+cng= -google.golang.org/protobuf v1.30.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= +google.golang.org/protobuf v1.34.1 h1:9ddQBjfCyZPOHPUiPxpYESBLc+T8P3E+Vo4IbKZgFWg= +google.golang.org/protobuf v1.34.1/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY= -gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA= gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= -gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ= -gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= -honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= -honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= -honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= -honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= -honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= -honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= -rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8= +nullprogram.com/x/optparse v1.0.0/go.mod h1:KdyPE+Igbe0jQUrVfMqDMeJQIJZEuyV7pjYmp6pbG50= rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4= -rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0= -rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA= diff --git a/hep.go b/hep.go new file mode 100644 index 0000000..707ee9a --- /dev/null +++ b/hep.go @@ -0,0 +1,4 @@ +package be + +type HostEndPoint struct { +} diff --git a/internal/optionutil/optionutil.go b/internal/optionutil/optionutil.go deleted file mode 100644 index e82e740..0000000 --- a/internal/optionutil/optionutil.go +++ /dev/null @@ -1,19 +0,0 @@ -package optionutil - -import ( - "strings" - - "github.com/bamboo-firewall/be/domain" - "go.mongodb.org/mongo-driver/bson" -) - -func ConvertToBsonM(options []domain.Option, mapping map[string]string) bson.M { - query := bson.M{} - for _, item := range options { - key := strings.Split(mapping[item.Key], "$") - if len(key) > 1 { - query[key[1]] = item.Value - } - } - return query -} diff --git a/internal/tokenutil/tokenutil.go b/internal/tokenutil/tokenutil.go deleted file mode 100644 index 1f2189c..0000000 --- a/internal/tokenutil/tokenutil.go +++ /dev/null @@ -1,75 +0,0 @@ -package tokenutil - -import ( - "fmt" - "time" - - "github.com/bamboo-firewall/be/domain" - jwt "github.com/golang-jwt/jwt/v4" -) - -func CreateAccessToken(user *domain.User, secret string, expiry int) (accessToken string, err error) { - exp := time.Now().Add(time.Hour * time.Duration(expiry)).Unix() - claims := &domain.JwtCustomClaims{ - Name: user.Name, - ID: user.ID.Hex(), - StandardClaims: jwt.StandardClaims{ - ExpiresAt: exp, - }, - } - token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims) - t, err := token.SignedString([]byte(secret)) - if err != nil { - return "", err - } - return t, err -} - -func CreateRefreshToken(user *domain.User, secret string, expiry int) (refreshToken string, err error) { - claimsRefresh := &domain.JwtCustomRefreshClaims{ - ID: user.ID.Hex(), - StandardClaims: jwt.StandardClaims{ - ExpiresAt: time.Now().Add(time.Hour * time.Duration(expiry)).Unix(), - }, - } - token := jwt.NewWithClaims(jwt.SigningMethodHS256, claimsRefresh) - rt, err := token.SignedString([]byte(secret)) - if err != nil { - return "", err - } - return rt, err -} - -func IsAuthorized(requestToken string, secret string) (bool, error) { - _, err := jwt.Parse(requestToken, func(token *jwt.Token) (interface{}, error) { - if _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok { - return nil, fmt.Errorf("Unexpected signing method: %v", token.Header["alg"]) - } - return []byte(secret), nil - }) - if err != nil { - return false, err - } - return true, nil -} - -func ExtractIDFromToken(requestToken string, secret string) (string, error) { - token, err := jwt.Parse(requestToken, func(token *jwt.Token) (interface{}, error) { - if _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok { - return nil, fmt.Errorf("Unexpected signing method: %v", token.Header["alg"]) - } - return []byte(secret), nil - }) - - if err != nil { - return "", err - } - - claims, ok := token.Claims.(jwt.MapClaims) - - if !ok && !token.Valid { - return "", fmt.Errorf("Invalid Token") - } - - return claims["id"].(string), nil -} diff --git a/mongo/mongo.go b/mongo/mongo.go deleted file mode 100644 index 4e5d710..0000000 --- a/mongo/mongo.go +++ /dev/null @@ -1,207 +0,0 @@ -package mongo - -import ( - "context" - "errors" - "reflect" - "time" - - "go.mongodb.org/mongo-driver/bson/bsoncodec" - "go.mongodb.org/mongo-driver/bson/bsonrw" - "go.mongodb.org/mongo-driver/bson/bsontype" - "go.mongodb.org/mongo-driver/mongo" - "go.mongodb.org/mongo-driver/mongo/options" - "go.mongodb.org/mongo-driver/mongo/readpref" -) - -type Database interface { - Collection(string) Collection - Client() Client -} - -type Collection interface { - FindOne(context.Context, interface{}) SingleResult - InsertOne(context.Context, interface{}) (interface{}, error) - InsertMany(context.Context, []interface{}) ([]interface{}, error) - DeleteOne(context.Context, interface{}) (int64, error) - Find(context.Context, interface{}, ...*options.FindOptions) (Cursor, error) - CountDocuments(context.Context, interface{}, ...*options.CountOptions) (int64, error) - Aggregate(context.Context, interface{}) (Cursor, error) - UpdateOne(context.Context, interface{}, interface{}, ...*options.UpdateOptions) (*mongo.UpdateResult, error) - UpdateMany(context.Context, interface{}, interface{}, ...*options.UpdateOptions) (*mongo.UpdateResult, error) -} - -type SingleResult interface { - Decode(interface{}) error -} - -type Cursor interface { - Close(context.Context) error - Next(context.Context) bool - Decode(interface{}) error - All(context.Context, interface{}) error -} - -type Client interface { - Database(string) Database - Connect(context.Context) error - Disconnect(context.Context) error - StartSession() (mongo.Session, error) - MongoClient() *mongo.Client - UseSession(ctx context.Context, fn func(mongo.SessionContext) error) error - Ping(context.Context) error -} - -type mongoClient struct { - cl *mongo.Client -} -type mongoDatabase struct { - db *mongo.Database -} -type mongoCollection struct { - coll *mongo.Collection -} - -type mongoSingleResult struct { - sr *mongo.SingleResult -} - -type mongoCursor struct { - mc *mongo.Cursor -} - -type mongoSession struct { - mongo.Session -} - -type nullawareDecoder struct { - defDecoder bsoncodec.ValueDecoder - zeroValue reflect.Value -} - -func (d *nullawareDecoder) DecodeValue(dctx bsoncodec.DecodeContext, vr bsonrw.ValueReader, val reflect.Value) error { - if vr.Type() != bsontype.Null { - return d.defDecoder.DecodeValue(dctx, vr, val) - } - - if !val.CanSet() { - return errors.New("value not settable") - } - if err := vr.ReadNull(); err != nil { - return err - } - // Set the zero value of val's type: - val.Set(d.zeroValue) - return nil -} - -func NewClient(connection string) (Client, error) { - - time.Local = time.UTC - c, err := mongo.NewClient(options.Client().ApplyURI(connection)) - - return &mongoClient{cl: c}, err - -} - -func (mc *mongoClient) Ping(ctx context.Context) error { - return mc.cl.Ping(ctx, readpref.Primary()) -} - -func (mc *mongoClient) Database(dbName string) Database { - db := mc.cl.Database(dbName) - return &mongoDatabase{db: db} -} - -func (mc *mongoClient) UseSession(ctx context.Context, fn func(mongo.SessionContext) error) error { - return mc.cl.UseSession(ctx, fn) -} - -func (mc *mongoClient) StartSession() (mongo.Session, error) { - session, err := mc.cl.StartSession() - return &mongoSession{session}, err -} - -func (mc *mongoClient) MongoClient() *mongo.Client { - return mc.cl -} - -func (mc *mongoClient) Connect(ctx context.Context) error { - return mc.cl.Connect(ctx) -} - -func (mc *mongoClient) Disconnect(ctx context.Context) error { - return mc.cl.Disconnect(ctx) -} - -func (md *mongoDatabase) Collection(colName string) Collection { - collection := md.db.Collection(colName) - return &mongoCollection{coll: collection} -} - -func (md *mongoDatabase) Client() Client { - client := md.db.Client() - return &mongoClient{cl: client} -} - -func (mc *mongoCollection) FindOne(ctx context.Context, filter interface{}) SingleResult { - singleResult := mc.coll.FindOne(ctx, filter) - return &mongoSingleResult{sr: singleResult} -} - -func (mc *mongoCollection) UpdateOne(ctx context.Context, filter interface{}, update interface{}, opts ...*options.UpdateOptions) (*mongo.UpdateResult, error) { - return mc.coll.UpdateOne(ctx, filter, update, opts[:]...) -} - -func (mc *mongoCollection) InsertOne(ctx context.Context, document interface{}) (interface{}, error) { - id, err := mc.coll.InsertOne(ctx, document) - return id.InsertedID, err -} - -func (mc *mongoCollection) InsertMany(ctx context.Context, document []interface{}) ([]interface{}, error) { - res, err := mc.coll.InsertMany(ctx, document) - return res.InsertedIDs, err -} - -func (mc *mongoCollection) DeleteOne(ctx context.Context, filter interface{}) (int64, error) { - count, err := mc.coll.DeleteOne(ctx, filter) - return count.DeletedCount, err -} - -func (mc *mongoCollection) Find(ctx context.Context, filter interface{}, opts ...*options.FindOptions) (Cursor, error) { - findResult, err := mc.coll.Find(ctx, filter, opts...) - return &mongoCursor{mc: findResult}, err -} - -func (mc *mongoCollection) Aggregate(ctx context.Context, pipeline interface{}) (Cursor, error) { - aggregateResult, err := mc.coll.Aggregate(ctx, pipeline) - return &mongoCursor{mc: aggregateResult}, err -} - -func (mc *mongoCollection) UpdateMany(ctx context.Context, filter interface{}, update interface{}, opts ...*options.UpdateOptions) (*mongo.UpdateResult, error) { - return mc.coll.UpdateMany(ctx, filter, update, opts[:]...) -} - -func (mc *mongoCollection) CountDocuments(ctx context.Context, filter interface{}, opts ...*options.CountOptions) (int64, error) { - return mc.coll.CountDocuments(ctx, filter, opts...) -} - -func (sr *mongoSingleResult) Decode(v interface{}) error { - return sr.sr.Decode(v) -} - -func (mr *mongoCursor) Close(ctx context.Context) error { - return mr.mc.Close(ctx) -} - -func (mr *mongoCursor) Next(ctx context.Context) bool { - return mr.mc.Next(ctx) -} - -func (mr *mongoCursor) Decode(v interface{}) error { - return mr.mc.Decode(v) -} - -func (mr *mongoCursor) All(ctx context.Context, result interface{}) error { - return mr.mc.All(ctx, result) -} diff --git a/pkg/client/client_request.go b/pkg/client/client_request.go new file mode 100644 index 0000000..702e6fc --- /dev/null +++ b/pkg/client/client_request.go @@ -0,0 +1,11 @@ +package client + +import "github.com/bamboo-firewall/be/pkg/http" + +type apiServer struct { + client *http.Client +} + +func NewAPIServer(address string) *apiServer { + return &apiServer{client: http.NewClient(address)} +} diff --git a/pkg/client/gnp.go b/pkg/client/gnp.go new file mode 100644 index 0000000..7e73810 --- /dev/null +++ b/pkg/client/gnp.go @@ -0,0 +1,73 @@ +package client + +import ( + "bytes" + "context" + "encoding/json" + "fmt" + "net/http" + + "github.com/bamboo-firewall/be/api/v1/dto" + "github.com/bamboo-firewall/be/cmd/server/pkg/httpbase" +) + +func (c *apiServer) CreateGNP(ctx context.Context, input *dto.CreateGlobalNetworkPolicyInput) error { + inputBytes, _ := json.Marshal(input) + res := c.client.NewRequest(). + SetSubURL("/api/v1/globalNetworkPolicies"). + SetHeader(httpbase.HeaderContentType, httpbase.MIMEApplicationJSON). + SetBody(bytes.NewReader(inputBytes)). + SetMethod(http.MethodPost). + DoRequest(ctx) + + if res.Err != nil { + return fmt.Errorf("failed to create globalnetworkpolicy: %w", res.Err) + } + + if res.StatusCode != http.StatusOK { + return fmt.Errorf("unexpected status code when create globalnetworkpolicy, status code: %d, response: %s", res.StatusCode, res.Body) + } + + return nil +} + +func (c *apiServer) GetGNP(ctx context.Context, input *dto.GetGNPInput) (*dto.GlobalNetworkPolicy, error) { + res := c.client.NewRequest(). + SetSubURL(fmt.Sprintf("/api/v1/globalNetworkPolicies/byName/%s", input.Name)). + SetMethod(http.MethodGet). + DoRequest(ctx) + + if res.Err != nil { + return nil, fmt.Errorf("failed to get globalnetworkpolicy by name: %w", res.Err) + } + + if res.StatusCode != http.StatusOK { + return nil, fmt.Errorf("unexpected status code when get globalnetworkpolicy by name, status code: %d, response: %s", res.StatusCode, res.Body) + } + + var gnp *dto.GlobalNetworkPolicy + if err := json.Unmarshal(res.Body, &gnp); err != nil { + return nil, fmt.Errorf("failed to unmarshal when get globalnetworkpolicy by name, response: %s, err: %w", string(res.Body), err) + } + return gnp, nil +} + +func (c *apiServer) DeleteGNP(ctx context.Context, input *dto.DeleteGlobalNetworkPolicyInput) error { + inputBytes, _ := json.Marshal(input) + res := c.client.NewRequest(). + SetSubURL("/api/v1/globalNetworkPolicies"). + SetHeader(httpbase.HeaderContentType, httpbase.MIMEApplicationJSON). + SetBody(bytes.NewReader(inputBytes)). + SetMethod(http.MethodDelete). + DoRequest(ctx) + + if res.Err != nil { + return fmt.Errorf("failed to delete globalnetworkpolicy: %w", res.Err) + } + + if res.StatusCode != http.StatusOK { + return fmt.Errorf("unexpected status code when delete globalnetworkpolicy, status code: %d, response: %s", res.StatusCode, res.Body) + } + + return nil +} diff --git a/pkg/client/gns.go b/pkg/client/gns.go new file mode 100644 index 0000000..fc5a558 --- /dev/null +++ b/pkg/client/gns.go @@ -0,0 +1,73 @@ +package client + +import ( + "bytes" + "context" + "encoding/json" + "fmt" + "net/http" + + "github.com/bamboo-firewall/be/api/v1/dto" + "github.com/bamboo-firewall/be/cmd/server/pkg/httpbase" +) + +func (c *apiServer) CreateGNS(ctx context.Context, input *dto.CreateGlobalNetworkSetInput) error { + inputBytes, _ := json.Marshal(input) + res := c.client.NewRequest(). + SetSubURL("/api/v1/globalNetworkSets"). + SetHeader(httpbase.HeaderContentType, httpbase.MIMEApplicationJSON). + SetBody(bytes.NewReader(inputBytes)). + SetMethod(http.MethodPost). + DoRequest(ctx) + + if res.Err != nil { + return fmt.Errorf("failed to create globalnetworkset: %w", res.Err) + } + + if res.StatusCode != http.StatusOK { + return fmt.Errorf("unexpected status code when create globalnetworkset, status code: %d, response: %s", res.StatusCode, res.Body) + } + + return nil +} + +func (c *apiServer) GetGNS(ctx context.Context, input *dto.GetGNSInput) (*dto.GlobalNetworkSet, error) { + res := c.client.NewRequest(). + SetSubURL(fmt.Sprintf("/api/v1/globalNetworkSets/byName/%s", input.Name)). + SetMethod(http.MethodGet). + DoRequest(ctx) + + if res.Err != nil { + return nil, fmt.Errorf("failed to get globalnetworkset by name: %w", res.Err) + } + + if res.StatusCode != http.StatusOK { + return nil, fmt.Errorf("unexpected status code when get globalnetworkset by name, status code: %d, response: %s", res.StatusCode, res.Body) + } + + var gns *dto.GlobalNetworkSet + if err := json.Unmarshal(res.Body, &gns); err != nil { + return nil, fmt.Errorf("failed to unmarshal when get globalnetworkset by name, response: %s, err: %w", string(res.Body), err) + } + return gns, nil +} + +func (c *apiServer) DeleteGNS(ctx context.Context, input *dto.DeleteGlobalNetworkSetInput) error { + inputBytes, _ := json.Marshal(input) + res := c.client.NewRequest(). + SetSubURL("/api/v1/globalNetworkSets"). + SetHeader(httpbase.HeaderContentType, httpbase.MIMEApplicationJSON). + SetBody(bytes.NewReader(inputBytes)). + SetMethod(http.MethodDelete). + DoRequest(ctx) + + if res.Err != nil { + return fmt.Errorf("failed to delete globalnetworkset: %w", res.Err) + } + + if res.StatusCode != http.StatusOK { + return fmt.Errorf("unexpected status code when delete globalnetworkset, status code: %d, response: %s", res.StatusCode, res.Body) + } + + return nil +} diff --git a/pkg/client/hep.go b/pkg/client/hep.go new file mode 100644 index 0000000..3b1112c --- /dev/null +++ b/pkg/client/hep.go @@ -0,0 +1,73 @@ +package client + +import ( + "bytes" + "context" + "encoding/json" + "fmt" + "net/http" + + "github.com/bamboo-firewall/be/api/v1/dto" + "github.com/bamboo-firewall/be/cmd/server/pkg/httpbase" +) + +func (c *apiServer) CreateHEP(ctx context.Context, input *dto.CreateHostEndpointInput) error { + inputBytes, _ := json.Marshal(input) + res := c.client.NewRequest(). + SetSubURL("/api/v1/hostEndpoints"). + SetHeader(httpbase.HeaderContentType, httpbase.MIMEApplicationJSON). + SetBody(bytes.NewReader(inputBytes)). + SetMethod(http.MethodPost). + DoRequest(ctx) + + if res.Err != nil { + return fmt.Errorf("failed to create hostendpoint: %w", res.Err) + } + + if res.StatusCode != http.StatusOK { + return fmt.Errorf("unexpected status code when create hostendpoint, status code: %d, response: %s", res.StatusCode, res.Body) + } + + return nil +} + +func (c *apiServer) GetHEP(ctx context.Context, input *dto.GetHostEndpointInput) (*dto.HostEndpoint, error) { + res := c.client.NewRequest(). + SetSubURL(fmt.Sprintf("/api/v1/hostEndpoints/byName/%s", input.Name)). + SetMethod(http.MethodGet). + DoRequest(ctx) + + if res.Err != nil { + return nil, fmt.Errorf("failed to get hostendpoint by name: %w", res.Err) + } + + if res.StatusCode != http.StatusOK { + return nil, fmt.Errorf("unexpected status code when get hostendpoint by name, status code: %d, response: %s", res.StatusCode, res.Body) + } + + var hep *dto.HostEndpoint + if err := json.Unmarshal(res.Body, &hep); err != nil { + return nil, fmt.Errorf("failed to unmarshal when get hostendpoint by name, response: %s, err: %w", string(res.Body), err) + } + return hep, nil +} + +func (c *apiServer) DeleteHEP(ctx context.Context, input *dto.DeleteHostEndpointInput) error { + inputBytes, _ := json.Marshal(input) + res := c.client.NewRequest(). + SetSubURL("/api/v1/hostEndpoints"). + SetHeader(httpbase.HeaderContentType, httpbase.MIMEApplicationJSON). + SetBody(bytes.NewReader(inputBytes)). + SetMethod(http.MethodDelete). + DoRequest(ctx) + + if res.Err != nil { + return fmt.Errorf("failed to delete hostendpoint: %w", res.Err) + } + + if res.StatusCode != http.StatusOK { + return fmt.Errorf("unexpected status code when delete hostendpoint, status code: %d, response: %s", res.StatusCode, res.Body) + } + + return nil +} diff --git a/pkg/http/client.go b/pkg/http/client.go new file mode 100644 index 0000000..ec9b413 --- /dev/null +++ b/pkg/http/client.go @@ -0,0 +1,36 @@ +package http + +import ( + "net/http" + "time" +) + +const ( + defaultTimeout = 10 * time.Second +) + +type Client struct { + baseURL string + httpClient *http.Client +} + +func NewClient(baseURL string, opts ...clientOption) *Client { + httpClient := &http.Client{ + Timeout: defaultTimeout, + } + + client := &Client{ + httpClient: httpClient, + baseURL: baseURL, + } + + for _, opt := range opts { + opt(client) + } + + return client +} + +func (c *Client) NewRequest() *Request { + return NewRequest(c) +} diff --git a/pkg/http/client_option.go b/pkg/http/client_option.go new file mode 100644 index 0000000..e726ec9 --- /dev/null +++ b/pkg/http/client_option.go @@ -0,0 +1,64 @@ +package http + +import ( + "net/http" + "time" +) + +type clientOption func(s *Client) + +// WithTransport specifies the mechanism by which individual +// HTTP requests are made. +// If nil, DefaultTransport is used. +func WithTransport(transport http.RoundTripper) clientOption { + return func(s *Client) { + if transport != nil { + s.httpClient.Transport = transport + } + } +} + +// WithCheckRedirect specifies the policy for handling redirects. +// If CheckRedirect is not nil, the client calls it before +// following an HTTP redirect. The arguments req and via are +// the upcoming request and the requests made already, oldest +// first. If CheckRedirect returns an error, the Client's Get +// method returns both the previous Response (with its Body +// closed) and CheckRedirect's error (wrapped in an url.Error) +// instead of issuing the Request req. +// As a special case, if CheckRedirect returns ErrUseLastResponse, +// then the most recent response is returned with its body +// unclosed, along with a nil error. +// +// If CheckRedirect is nil, the Client uses its default policy, +// which is to stop after 10 consecutive requests. +func WithCheckRedirect(checkRedirect func(req *http.Request, via []*http.Request) error) clientOption { + return func(s *Client) { + if checkRedirect != nil { + s.httpClient.CheckRedirect = checkRedirect + } + } +} + +// WithJar specifies the cookie jar. +// +// The Jar is used to insert relevant cookies into every +// outbound Request and is updated with the cookie values +// of every inbound Response. The Jar is consulted for every +// redirect that the Client follows. +// +// If Jar is nil, cookies are only sent if they are explicitly +// set on the Request. +func WithJar(jar http.CookieJar) clientOption { + return func(s *Client) { + s.httpClient.Jar = jar + } +} + +// WithTimeout set timeout for Client +// Default is 10s +func WithTimeout(timeout time.Duration) clientOption { + return func(s *Client) { + s.httpClient.Timeout = timeout + } +} diff --git a/pkg/http/request.go b/pkg/http/request.go new file mode 100644 index 0000000..cbb7823 --- /dev/null +++ b/pkg/http/request.go @@ -0,0 +1,147 @@ +package http + +import ( + "context" + "fmt" + "io" + "net/http" + "net/url" +) + +func NewRequest(c *Client) *Request { + return &Request{ + c: c, + baseURL: c.baseURL, + params: make(url.Values), + headers: make(http.Header), + } +} + +type Request struct { + c *Client + baseURL string + subURL string + params url.Values + headers http.Header + method string + body io.Reader +} + +type Result struct { + Body []byte + Err error + StatusCode int +} + +func (r *Request) SetBaseURL(baseURL string) *Request { + r.baseURL = baseURL + return r +} + +func (r *Request) SetSubURL(subURL string) *Request { + r.subURL = subURL + return r +} + +func (r *Request) AddParamsFromValues(params url.Values) *Request { + for k, vv := range params { + for _, v := range vv { + r.params.Add(k, v) + } + } + return r +} + +func (r *Request) SetParams(params map[string]string) *Request { + for p, v := range params { + r.SetParam(p, v) + } + return r +} + +func (r *Request) SetParam(param, value string) *Request { + r.params.Set(param, value) + return r +} + +func (r *Request) AddHeaders(headers http.Header) *Request { + for k, hh := range headers { + for _, h := range hh { + r.headers.Add(k, h) + } + } + return r +} + +func (r *Request) SetHeaders(headers map[string]string) *Request { + for h, v := range headers { + r.SetHeader(h, v) + } + return r +} + +func (r *Request) SetHeader(header, value string) *Request { + r.headers.Set(header, value) + return r +} + +func (r *Request) SetMethod(method string) *Request { + r.method = method + return r +} + +func (r *Request) SetBody(body io.Reader) *Request { + r.body = body + return r +} + +func (r *Request) URL() (*url.URL, error) { + finalURL, err := url.Parse(r.baseURL) + if err != nil { + return nil, err + } + finalURL.Path = r.subURL + query := url.Values{} + for key, values := range r.params { + for _, value := range values { + query.Add(key, value) + } + } + finalURL.RawQuery = query.Encode() + return finalURL, nil +} + +func (r *Request) DoRequest(ctx context.Context) *Result { + finalURL, err := r.URL() + if err != nil { + return &Result{ + Err: fmt.Errorf("baseURL is not correct format: %w", err), + } + } + req, err := http.NewRequestWithContext(ctx, r.method, finalURL.String(), r.body) + if err != nil { + return &Result{ + Err: fmt.Errorf("failed to make request: %w", err), + } + } + req.Header = r.headers + res, err := r.c.httpClient.Do(req) + if err != nil { + return &Result{ + Err: fmt.Errorf("failed to do request: %w", err), + } + } + defer res.Body.Close() + body, err := io.ReadAll(res.Body) + if err != nil { + return &Result{ + Err: fmt.Errorf("failed to read response body: %w", err), + StatusCode: res.StatusCode, + } + } + return &Result{ + Body: body, + Err: nil, + StatusCode: res.StatusCode, + } +} diff --git a/pkg/http/request_test.go b/pkg/http/request_test.go new file mode 100644 index 0000000..6024c04 --- /dev/null +++ b/pkg/http/request_test.go @@ -0,0 +1,196 @@ +package http + +import ( + "bytes" + "context" + "errors" + "io" + "net/http" + "net/http/httptest" + "net/url" + "testing" + + "github.com/google/go-cmp/cmp" + "github.com/stretchr/testify/assert" +) + +// mockReader mock for body reader +type mockReader struct{} + +// Read interface implement io.Reader and always return error +func (m mockReader) Read(p []byte) (int, error) { + return 0, errors.New("simulated read error") +} + +func (m mockReader) Close() error { + return nil +} + +func TestDoRequest(t *testing.T) { + c := NewClient("") + tests := []struct { + name string + ctx context.Context + baseURL string + subURL string + addParams url.Values + setParams map[string]string + addHeaders http.Header + setHeaders map[string]string + method string + body string + readResponseBodyFail bool + err error + statusCode int + }{ + { + name: "parse base url fail", + baseURL: ":http://localhost", + err: errors.New("parse base url fail"), + }, + { + name: "new request fail", + err: errors.New("new request fail"), + }, + { + name: "do request fail", + ctx: context.Background(), + baseURL: "http://localhost.xyz", + err: errors.New("do request fail"), + }, + { + name: "do request fail", + ctx: context.Background(), + readResponseBodyFail: true, + err: errors.New("read response body fail"), + }, + { + name: "new request success with setParams", + ctx: context.Background(), + setParams: map[string]string{ + "abc": "xyz", + }, + err: nil, + statusCode: http.StatusOK, + }, + { + name: "new request success with setHeaders", + ctx: context.Background(), + setHeaders: map[string]string{ + "AccessToken": "aaaabbbbb", + }, + err: nil, + statusCode: http.StatusOK, + }, + { + name: "new request success with all", + ctx: context.Background(), + addParams: url.Values{ + "ids": []string{"1", "2", "3"}, + "abc": []string{"xyz"}, + }, + setParams: map[string]string{ + "abc": "xyz", + }, + addHeaders: http.Header{ + "header1": []string{"value1"}, + }, + setHeaders: map[string]string{ + "AccessToken": "aaaabbbbb", + "header1": "value2", + }, + method: http.MethodPost, + body: `{"aaaa":"bbbb"}`, + statusCode: http.StatusOK, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + requestParams := make(url.Values) + if tt.addParams != nil { + for k, vv := range tt.addParams { + for _, v := range vv { + requestParams.Add(k, v) + } + } + } + for k, v := range tt.setParams { + requestParams.Set(k, v) + } + + requestHeaders := make(http.Header) + if tt.addHeaders != nil { + for k, vv := range tt.addHeaders { + for _, v := range vv { + requestHeaders.Add(k, v) + } + } + } + for k, v := range tt.setHeaders { + requestHeaders.Set(k, v) + } + + var bodyReader io.Reader + if tt.body != "" { + bodyReader = bytes.NewBufferString(tt.body) + } + handler := func(w http.ResponseWriter, r *http.Request) { + if tt.readResponseBodyFail { + w.Header().Set("Content-Length", "1") + return + } + if len(requestParams) > 0 { + for k, v := range requestParams { + if !cmp.Equal(v, r.URL.Query()[k]) { + w.WriteHeader(http.StatusBadRequest) + return + } + } + } + + if len(requestHeaders) > 0 { + for k, h := range requestHeaders { + if !cmp.Equal(h, r.Header[k]) { + w.WriteHeader(http.StatusBadRequest) + return + } + } + } + + if tt.body != "" { + body, err := io.ReadAll(r.Body) + if err != nil { + w.WriteHeader(http.StatusBadRequest) + return + } + if string(body) != tt.body { + w.WriteHeader(http.StatusBadRequest) + return + } + } + } + + server := httptest.NewServer(http.HandlerFunc(handler)) + defer server.Close() + if tt.baseURL == "" { + tt.baseURL = server.URL + } + + result := c.NewRequest(). + SetBaseURL(tt.baseURL). + SetSubURL(tt.subURL). + AddParamsFromValues(tt.addParams). + SetParams(tt.setParams). + AddHeaders(tt.addHeaders). + SetHeaders(tt.setHeaders). + SetMethod(tt.method). + SetBody(bodyReader). + DoRequest(tt.ctx) + if tt.err != nil { + assert.Error(t, result.Err) + return + } + assert.Equal(t, tt.statusCode, result.StatusCode) + }) + } +} diff --git a/repository/gns_repository.go b/repository/gns_repository.go deleted file mode 100644 index ae9c84f..0000000 --- a/repository/gns_repository.go +++ /dev/null @@ -1,122 +0,0 @@ -package repository - -import ( - "context" - - models "github.com/bamboo-firewall/watcher/model" - - "github.com/bamboo-firewall/be/domain" - "github.com/bamboo-firewall/be/mongo" - "go.mongodb.org/mongo-driver/bson" - "go.mongodb.org/mongo-driver/bson/primitive" - "go.mongodb.org/mongo-driver/mongo/options" -) - -type gnsRepository struct { - database mongo.Database - collection string -} - -func (r *gnsRepository) GetTotal(c context.Context) (int64, error) { - collection := r.database.Collection(r.collection) - total, err := collection.CountDocuments(c, bson.D{}) - return total, err -} - -func (gr *gnsRepository) Fetch(c context.Context) ([]models.GlobalNetworkSet, error) { - collection := gr.database.Collection(gr.collection) - - opts := options.Find().SetProjection(bson.D{{Key: "password", Value: 0}}) - cursor, err := collection.Find(c, bson.D{}, opts) - - if err != nil { - return nil, err - } - - var gns []models.GlobalNetworkSet - - err = cursor.All(c, &gns) - if gns == nil { - return []models.GlobalNetworkSet{}, err - } - - return gns, err -} - -func (gr *gnsRepository) Search(c context.Context, searchOptions bson.M) ([]models.GlobalNetworkSet, error) { - collection := gr.database.Collection(gr.collection) - - opts := options.Find() - cursor, err := collection.Find(c, searchOptions, opts) - - if err != nil { - return nil, err - } - - var networkset []models.GlobalNetworkSet - - err = cursor.All(c, &networkset) - if networkset == nil { - return []models.GlobalNetworkSet{}, err - } - - return networkset, err -} - -func (gr *gnsRepository) AggGroupBy(c context.Context, filter bson.M, key string, jsonPath string) ([]domain.Option, error) { - collection := gr.database.Collection(gr.collection) - - pipeline := []bson.M{ - { - "$match": filter, - }, - { - "$group": bson.M{ - "_id": bson.TypeNull, - key: bson.M{ - "$addToSet": jsonPath, - }, - }, - }, - { - "$project": bson.M{ - "_id": 0, - key: 1, - }, - }, - } - - cursor, err := collection.Aggregate(c, pipeline) - if err != nil { - return nil, err - } - - var results []bson.M - if err := cursor.All(c, &results); err != nil { - return nil, err - } - var options []domain.Option - if len(results) == 0 { - return []domain.Option{}, nil - } - for _, item := range results[0][key].(primitive.A) { - option := domain.Option{ - Key: key, - Value: item.(string), - } - options = append(options, option) - } - - if options == nil { - return []domain.Option{}, err - } - - return options, err -} - -func NewGNSRepository(db mongo.Database, collection string) domain.GNSRepository { - return &gnsRepository{ - database: db, - collection: collection, - } -} diff --git a/repository/hep_repository.go b/repository/hep_repository.go deleted file mode 100644 index e966bea..0000000 --- a/repository/hep_repository.go +++ /dev/null @@ -1,157 +0,0 @@ -package repository - -import ( - "context" - - models "github.com/bamboo-firewall/watcher/model" - - "github.com/bamboo-firewall/be/domain" - "github.com/bamboo-firewall/be/mongo" - "go.mongodb.org/mongo-driver/bson" - "go.mongodb.org/mongo-driver/bson/primitive" - "go.mongodb.org/mongo-driver/mongo/options" -) - -type hepRepository struct { - database mongo.Database - collection string -} - -func (hr *hepRepository) GetProjectSummary(c context.Context) ([]domain.ProjectSummary, error) { - collection := hr.database.Collection(hr.collection) - pipeline := []bson.M{ - { - "$group": bson.M{ - "_id": bson.M{ - "project": "$metadata.labels.project", - }, - "count": bson.M{ - "$sum": 1, - }, - }, - }, - { - "$project": bson.M{ - "ProjectName": "$_id.project", - "Total": "$count", - }, - }, - } - - cursor, err := collection.Aggregate(c, pipeline) - if err != nil { - return nil, err - } - - var projectSummary []domain.ProjectSummary - err = cursor.All(c, &projectSummary) - - return projectSummary, err -} - -func (hr *hepRepository) GetTotal(c context.Context) (int64, error) { - collection := hr.database.Collection(hr.collection) - total, err := collection.CountDocuments(c, bson.D{}) - return total, err -} - -func (hr *hepRepository) Fetch(c context.Context) ([]models.HostEndPoint, error) { - collection := hr.database.Collection(hr.collection) - - opts := options.Find() - cursor, err := collection.Find(c, bson.D{}, opts) - - if err != nil { - return nil, err - } - - var heps []models.HostEndPoint - - err = cursor.All(c, &heps) - if heps == nil { - return []models.HostEndPoint{}, err - } - - return heps, err -} - -func (hr *hepRepository) Search(c context.Context, searchOptions bson.M) ([]models.HostEndPoint, error) { - collection := hr.database.Collection(hr.collection) - - opts := options.Find() - cursor, err := collection.Find(c, searchOptions, opts) - - if err != nil { - return nil, err - } - - var heps []models.HostEndPoint - - err = cursor.All(c, &heps) - if heps == nil { - return []models.HostEndPoint{}, err - } - - return heps, err -} - -func (hr *hepRepository) AggGroupBy(c context.Context, filter bson.M, key string, jsonPath string) ([]domain.Option, error) { - collection := hr.database.Collection(hr.collection) - - pipeline := []bson.M{ - { - "$match": filter, - }, - { - "$unwind": "$spec.expectedIPs", // unwind array - }, - { - "$group": bson.M{ - "_id": bson.TypeNull, - key: bson.M{ - "$addToSet": jsonPath, - }, - }, - }, - { - "$project": bson.M{ - "_id": 0, - key: 1, - }, - }, - } - - cursor, err := collection.Aggregate(c, pipeline) - if err != nil { - return nil, err - } - - var results []bson.M - if err := cursor.All(c, &results); err != nil { - return nil, err - } - var options []domain.Option - if len(results) == 0 { - return []domain.Option{}, nil - } - for _, item := range results[0][key].(primitive.A) { - option := domain.Option{ - Key: key, - Value: item.(string), - } - options = append(options, option) - } - - if options == nil { - return []domain.Option{}, err - } - - return options, err -} - -func NewHEPRepository(db mongo.Database, collection string) domain.HEPRepository { - return &hepRepository{ - database: db, - collection: collection, - } -} diff --git a/repository/policy_repository.go b/repository/policy_repository.go deleted file mode 100644 index 15a0ccf..0000000 --- a/repository/policy_repository.go +++ /dev/null @@ -1,122 +0,0 @@ -package repository - -import ( - "context" - - models "github.com/bamboo-firewall/watcher/model" - - "github.com/bamboo-firewall/be/domain" - "github.com/bamboo-firewall/be/mongo" - "go.mongodb.org/mongo-driver/bson" - "go.mongodb.org/mongo-driver/bson/primitive" - "go.mongodb.org/mongo-driver/mongo/options" -) - -type policyRepository struct { - database mongo.Database - collection string -} - -func (r *policyRepository) GetTotal(c context.Context) (int64, error) { - collection := r.database.Collection(r.collection) - total, err := collection.CountDocuments(c, bson.D{}) - return total, err -} - -func (r *policyRepository) Fetch(c context.Context) ([]models.GlobalNetworkPolicies, error) { - collection := r.database.Collection(r.collection) - - opts := options.Find().SetProjection(bson.D{{Key: "password", Value: 0}}) - cursor, err := collection.Find(c, bson.D{}, opts) - - if err != nil { - return nil, err - } - - var policies []models.GlobalNetworkPolicies - - err = cursor.All(c, &policies) - if policies == nil { - return []models.GlobalNetworkPolicies{}, err - } - - return policies, err -} - -func (r *policyRepository) Search(c context.Context, searchOptions bson.M) ([]models.GlobalNetworkPolicies, error) { - collection := r.database.Collection(r.collection) - - opts := options.Find() - cursor, err := collection.Find(c, searchOptions, opts) - - if err != nil { - return nil, err - } - - var policies []models.GlobalNetworkPolicies - - err = cursor.All(c, &policies) - if policies == nil { - return []models.GlobalNetworkPolicies{}, err - } - - return policies, err -} - -func (r *policyRepository) AggGroupBy(c context.Context, filter bson.M, key string, jsonPath string) ([]domain.Option, error) { - collection := r.database.Collection(r.collection) - - pipeline := []bson.M{ - { - "$match": filter, - }, - { - "$group": bson.M{ - "_id": bson.TypeNull, - key: bson.M{ - "$addToSet": jsonPath, - }, - }, - }, - { - "$project": bson.M{ - "_id": 0, - key: 1, - }, - }, - } - - cursor, err := collection.Aggregate(c, pipeline) - if err != nil { - return nil, err - } - - var results []bson.M - if err := cursor.All(c, &results); err != nil { - return nil, err - } - var options []domain.Option - if len(results) == 0 { - return []domain.Option{}, nil - } - for _, item := range results[0][key].(primitive.A) { - option := domain.Option{ - Key: key, - Value: item.(string), - } - options = append(options, option) - } - - if options == nil { - return []domain.Option{}, err - } - - return options, err -} - -func NewPolicyRepository(db mongo.Database, collection string) domain.PolicyRepository { - return &policyRepository{ - database: db, - collection: collection, - } -} diff --git a/repository/user_repository.go b/repository/user_repository.go deleted file mode 100644 index 3948d81..0000000 --- a/repository/user_repository.go +++ /dev/null @@ -1,101 +0,0 @@ -package repository - -import ( - "context" - - "github.com/bamboo-firewall/be/domain" - "github.com/bamboo-firewall/be/mongo" - "go.mongodb.org/mongo-driver/bson" - "go.mongodb.org/mongo-driver/bson/primitive" - "go.mongodb.org/mongo-driver/mongo/options" -) - -type userRepository struct { - database mongo.Database - collection string -} - -func (ur *userRepository) Update(c context.Context, user *domain.User) error { - collection := ur.database.Collection(ur.collection) - _, err := collection.UpdateOne(c, bson.M{"_id": user.ID}, bson.M{"$set": user}) - return err -} - -func (ur *userRepository) DeleteById(c context.Context, id string) error { - collection := ur.database.Collection(ur.collection) - idHex, err := primitive.ObjectIDFromHex(id) - if err != nil { - return err - } - _, err = collection.DeleteOne(c, bson.M{"_id": idHex}) - return err -} - -func (ur *userRepository) GetTotal(c context.Context) (int64, error) { - collection := ur.database.Collection(ur.collection) - total, err := collection.CountDocuments(c, bson.D{}) - return total, err -} - -func (ur *userRepository) Create(c context.Context, user *domain.User) error { - collection := ur.database.Collection(ur.collection) - - _, err := collection.InsertOne(c, user) - - return err -} - -func (ur *userRepository) Fetch(c context.Context) ([]domain.User, error) { - collection := ur.database.Collection(ur.collection) - - opts := options.Find().SetProjection(bson.D{{Key: "password", Value: 0}}) - cursor, err := collection.Find(c, bson.D{}, opts) - - if err != nil { - return nil, err - } - - var users []domain.User - - err = cursor.All(c, &users) - if users == nil { - return []domain.User{}, err - } - - return users, err -} - -func (ur *userRepository) GetByEmail(c context.Context, email string) (domain.User, error) { - collection := ur.database.Collection(ur.collection) - var user domain.User - err := collection.FindOne(c, bson.M{"email": email}).Decode(&user) - return user, err -} - -func (ur *userRepository) GetByUsername(c context.Context, username string) (domain.User, error) { - collection := ur.database.Collection(ur.collection) - var user domain.User - err := collection.FindOne(c, bson.M{"username": username}).Decode(&user) - return user, err -} - -func (ur *userRepository) GetByID(c context.Context, id string) (domain.User, error) { - collection := ur.database.Collection(ur.collection) - - var user domain.User - - idHex, err := primitive.ObjectIDFromHex(id) - if err != nil { - return user, err - } - - err = collection.FindOne(c, bson.M{"_id": idHex}).Decode(&user) - return user, err -} - -func NewUserRepository(db mongo.Database, collection string) domain.UserRepository { - return &userRepository{ - database: db, - collection: collection, - } -} diff --git a/storage.go b/storage.go new file mode 100644 index 0000000..8fadcd0 --- /dev/null +++ b/storage.go @@ -0,0 +1,23 @@ +package be + +import ( + "context" + + "github.com/bamboo-firewall/be/cmd/server/pkg/entity" + "github.com/bamboo-firewall/be/cmd/server/pkg/httpbase/ierror" +) + +type Storage interface { + UpsertHostEndpoint(ctx context.Context, hep *entity.HostEndpoint) *ierror.CoreError + GetHostEndpointByName(ctx context.Context, name string) (*entity.HostEndpoint, *ierror.CoreError) + DeleteHostEndpointByName(ctx context.Context, name string) *ierror.CoreError + ListHostEndpoints(ctx context.Context) ([]*entity.HostEndpoint, *ierror.CoreError) + UpsertGroupPolicy(ctx context.Context, gnp *entity.GlobalNetworkPolicy) *ierror.CoreError + GetGNPByName(ctx context.Context, name string) (*entity.GlobalNetworkPolicy, *ierror.CoreError) + DeleteGNPByName(ctx context.Context, name string) *ierror.CoreError + ListGNP(ctx context.Context) ([]*entity.GlobalNetworkPolicy, *ierror.CoreError) + UpsertGNS(ctx context.Context, gns *entity.GlobalNetworkSet) *ierror.CoreError + GetGNSByName(ctx context.Context, name string) (*entity.GlobalNetworkSet, *ierror.CoreError) + DeleteGNSByName(ctx context.Context, name string) *ierror.CoreError + ListGNS(ctx context.Context) ([]*entity.GlobalNetworkSet, *ierror.CoreError) +} diff --git a/usecase/gns_usecase.go b/usecase/gns_usecase.go deleted file mode 100644 index 8271c45..0000000 --- a/usecase/gns_usecase.go +++ /dev/null @@ -1,48 +0,0 @@ -package usecase - -import ( - "context" - "time" - - models "github.com/bamboo-firewall/watcher/model" - - "github.com/bamboo-firewall/be/domain" - "github.com/bamboo-firewall/be/internal/optionutil" -) - -type gnsUsecase struct { - gnsRepository domain.GNSRepository - contextTimeout time.Duration -} - -var GNSMapping = map[string]string{ - "name": "$metadata.name", - "zone": "$metadata.labels.zone", -} - -func (gu *gnsUsecase) Fetch(c context.Context) ([]models.GlobalNetworkSet, error) { - ctx, cancel := context.WithTimeout(c, gu.contextTimeout) - defer cancel() - return gu.gnsRepository.Fetch(ctx) -} - -func (u *gnsUsecase) Search(c context.Context, options []domain.Option) ([]models.GlobalNetworkSet, error) { - ctx, cancel := context.WithTimeout(c, u.contextTimeout) - defer cancel() - findOptions := optionutil.ConvertToBsonM(options, GNSMapping) - return u.gnsRepository.Search(ctx, findOptions) -} - -func (u *gnsUsecase) GetOptions(c context.Context, filter []domain.Option, key string) ([]domain.Option, error) { - ctx, cancel := context.WithTimeout(c, u.contextTimeout) - defer cancel() - query := optionutil.ConvertToBsonM(filter, GNSMapping) - return u.gnsRepository.AggGroupBy(ctx, query, key, GNSMapping[key]) -} - -func NewGNSUsecase(gnsRepository domain.GNSRepository, timeout time.Duration) domain.GNSUsecase { - return &gnsUsecase{ - gnsRepository: gnsRepository, - contextTimeout: timeout, - } -} diff --git a/usecase/hep_usecase.go b/usecase/hep_usecase.go deleted file mode 100644 index 70382a5..0000000 --- a/usecase/hep_usecase.go +++ /dev/null @@ -1,52 +0,0 @@ -package usecase - -import ( - "context" - "time" - - models "github.com/bamboo-firewall/watcher/model" - - "github.com/bamboo-firewall/be/domain" - "github.com/bamboo-firewall/be/internal/optionutil" -) - -type hepUsecase struct { - hepRepository domain.HEPRepository - contextTimeout time.Duration -} - -var HEPMapping = map[string]string{ - "name": "$spec.node", - "ip": "$spec.expectedIPs", - "namespace": "$metadata.labels.namespace", - "project": "$metadata.labels.project", - "role": "$metadata.labels.role", - "zone": "$metadata.labels.zone", -} - -func (hu *hepUsecase) Fetch(c context.Context) ([]models.HostEndPoint, error) { - ctx, cancel := context.WithTimeout(c, hu.contextTimeout) - defer cancel() - return hu.hepRepository.Fetch(ctx) -} - -func (hu *hepUsecase) Search(c context.Context, options []domain.Option) ([]models.HostEndPoint, error) { - ctx, cancel := context.WithTimeout(c, hu.contextTimeout) - defer cancel() - query := optionutil.ConvertToBsonM(options, HEPMapping) - return hu.hepRepository.Search(ctx, query) -} - -func (hu *hepUsecase) GetOptions(c context.Context, filter []domain.Option, key string) ([]domain.Option, error) { - ctx, cancel := context.WithTimeout(c, hu.contextTimeout) - defer cancel() - query := optionutil.ConvertToBsonM(filter, HEPMapping) - return hu.hepRepository.AggGroupBy(ctx, query, key, HEPMapping[key]) -} - -func NewHEPUsecase(hepRepository domain.HEPRepository, timeout time.Duration) domain.HEPUsecase { - return &hepUsecase{ - hepRepository: hepRepository, - contextTimeout: timeout, - } -} diff --git a/usecase/login_usecase.go b/usecase/login_usecase.go deleted file mode 100644 index 8d10a33..0000000 --- a/usecase/login_usecase.go +++ /dev/null @@ -1,41 +0,0 @@ -package usecase - -import ( - "context" - "time" - - "github.com/bamboo-firewall/be/domain" - "github.com/bamboo-firewall/be/internal/tokenutil" -) - -type loginUsecase struct { - userRepository domain.UserRepository - contextTimeout time.Duration -} - -func NewLoginUsecase(userRepository domain.UserRepository, timeout time.Duration) domain.LoginUsecase { - return &loginUsecase{ - userRepository: userRepository, - contextTimeout: timeout, - } -} - -func (lu *loginUsecase) GetUserByEmail(c context.Context, email string) (domain.User, error) { - ctx, cancel := context.WithTimeout(c, lu.contextTimeout) - defer cancel() - return lu.userRepository.GetByEmail(ctx, email) -} - -func (lu *loginUsecase) GetUserByUsername(c context.Context, username string) (domain.User, error) { - ctx, cancel := context.WithTimeout(c, lu.contextTimeout) - defer cancel() - return lu.userRepository.GetByUsername(ctx, username) -} - -func (lu *loginUsecase) CreateAccessToken(user *domain.User, secret string, expiry int) (accessToken string, err error) { - return tokenutil.CreateAccessToken(user, secret, expiry) -} - -func (lu *loginUsecase) CreateRefreshToken(user *domain.User, secret string, expiry int) (refreshToken string, err error) { - return tokenutil.CreateRefreshToken(user, secret, expiry) -} diff --git a/usecase/policy_usecase.go b/usecase/policy_usecase.go deleted file mode 100644 index e1e7a59..0000000 --- a/usecase/policy_usecase.go +++ /dev/null @@ -1,47 +0,0 @@ -package usecase - -import ( - "context" - "time" - - models "github.com/bamboo-firewall/watcher/model" - - "github.com/bamboo-firewall/be/domain" - "github.com/bamboo-firewall/be/internal/optionutil" -) - -type policyUsecase struct { - policyRepository domain.PolicyRepository - contextTimeout time.Duration -} - -var PolicyMapping = map[string]string{ - "name": "$metadata.name", -} - -func (u *policyUsecase) Search(c context.Context, options []domain.Option) ([]models.GlobalNetworkPolicies, error) { - ctx, cancel := context.WithTimeout(c, u.contextTimeout) - defer cancel() - findOptions := optionutil.ConvertToBsonM(options, PolicyMapping) - return u.policyRepository.Search(ctx, findOptions) -} - -func (u *policyUsecase) Fetch(c context.Context) ([]models.GlobalNetworkPolicies, error) { - ctx, cancel := context.WithTimeout(c, u.contextTimeout) - defer cancel() - return u.policyRepository.Fetch(ctx) -} - -func (u *policyUsecase) GetOptions(c context.Context, filter []domain.Option, key string) ([]domain.Option, error) { - ctx, cancel := context.WithTimeout(c, u.contextTimeout) - defer cancel() - query := optionutil.ConvertToBsonM(filter, PolicyMapping) - return u.policyRepository.AggGroupBy(ctx, query, key, PolicyMapping[key]) -} - -func NewPolicyUsecase(policyRepository domain.PolicyRepository, timeout time.Duration) domain.PolicyUsecase { - return &policyUsecase{ - policyRepository: policyRepository, - contextTimeout: timeout, - } -} diff --git a/usecase/profile_usecase.go b/usecase/profile_usecase.go deleted file mode 100644 index 310411c..0000000 --- a/usecase/profile_usecase.go +++ /dev/null @@ -1,32 +0,0 @@ -package usecase - -import ( - "context" - "time" - - "github.com/bamboo-firewall/be/domain" -) - -type profileUsecase struct { - userRepository domain.UserRepository - contextTimeout time.Duration -} - -func NewProfileUsecase(userRepository domain.UserRepository, timeout time.Duration) domain.ProfileUsecase { - return &profileUsecase{ - userRepository: userRepository, - contextTimeout: timeout, - } -} - -func (pu *profileUsecase) GetProfileByID(c context.Context, userID string) (*domain.Profile, error) { - ctx, cancel := context.WithTimeout(c, pu.contextTimeout) - defer cancel() - - user, err := pu.userRepository.GetByID(ctx, userID) - if err != nil { - return nil, err - } - - return &domain.Profile{UserId: user.ID.Hex(), Name: user.Name, Email: user.Email, Role: user.Role}, nil -} diff --git a/usecase/refresh_token_usecase.go b/usecase/refresh_token_usecase.go deleted file mode 100644 index 72f27fc..0000000 --- a/usecase/refresh_token_usecase.go +++ /dev/null @@ -1,39 +0,0 @@ -package usecase - -import ( - "context" - "time" - - "github.com/bamboo-firewall/be/domain" - "github.com/bamboo-firewall/be/internal/tokenutil" -) - -type refreshTokenUsecase struct { - userRepository domain.UserRepository - contextTimeout time.Duration -} - -func NewRefreshTokenUsecase(userRepository domain.UserRepository, timeout time.Duration) domain.RefreshTokenUsecase { - return &refreshTokenUsecase{ - userRepository: userRepository, - contextTimeout: timeout, - } -} - -func (rtu *refreshTokenUsecase) GetUserByID(c context.Context, email string) (domain.User, error) { - ctx, cancel := context.WithTimeout(c, rtu.contextTimeout) - defer cancel() - return rtu.userRepository.GetByID(ctx, email) -} - -func (rtu *refreshTokenUsecase) CreateAccessToken(user *domain.User, secret string, expiry int) (accessToken string, err error) { - return tokenutil.CreateAccessToken(user, secret, expiry) -} - -func (rtu *refreshTokenUsecase) CreateRefreshToken(user *domain.User, secret string, expiry int) (refreshToken string, err error) { - return tokenutil.CreateRefreshToken(user, secret, expiry) -} - -func (rtu *refreshTokenUsecase) ExtractIDFromToken(requestToken string, secret string) (string, error) { - return tokenutil.ExtractIDFromToken(requestToken, secret) -} diff --git a/usecase/signup_usecase.go b/usecase/signup_usecase.go deleted file mode 100644 index 75fe489..0000000 --- a/usecase/signup_usecase.go +++ /dev/null @@ -1,47 +0,0 @@ -package usecase - -import ( - "context" - "time" - - "github.com/bamboo-firewall/be/domain" - "github.com/bamboo-firewall/be/internal/tokenutil" -) - -type signupUsecase struct { - userRepository domain.UserRepository - contextTimeout time.Duration -} - -func NewSignupUsecase(userRepository domain.UserRepository, timeout time.Duration) domain.SignupUsecase { - return &signupUsecase{ - userRepository: userRepository, - contextTimeout: timeout, - } -} - -func (su *signupUsecase) Create(c context.Context, user *domain.User) error { - ctx, cancel := context.WithTimeout(c, su.contextTimeout) - defer cancel() - return su.userRepository.Create(ctx, user) -} - -func (su *signupUsecase) GetUserByEmail(c context.Context, email string) (domain.User, error) { - ctx, cancel := context.WithTimeout(c, su.contextTimeout) - defer cancel() - return su.userRepository.GetByEmail(ctx, email) -} - -func (su *signupUsecase) GetUserByUsername(c context.Context, username string) (domain.User, error) { - ctx, cancel := context.WithTimeout(c, su.contextTimeout) - defer cancel() - return su.userRepository.GetByUsername(ctx, username) -} - -func (su *signupUsecase) CreateAccessToken(user *domain.User, secret string, expiry int) (accessToken string, err error) { - return tokenutil.CreateAccessToken(user, secret, expiry) -} - -func (su *signupUsecase) CreateRefreshToken(user *domain.User, secret string, expiry int) (refreshToken string, err error) { - return tokenutil.CreateRefreshToken(user, secret, expiry) -} diff --git a/usecase/statistic_usecase.go b/usecase/statistic_usecase.go deleted file mode 100644 index 0d188e9..0000000 --- a/usecase/statistic_usecase.go +++ /dev/null @@ -1,71 +0,0 @@ -package usecase - -import ( - "context" - "time" - - "github.com/bamboo-firewall/be/domain" -) - -type statisticUsecase struct { - policyRepository domain.PolicyRepository - gnsRepository domain.GNSRepository - hepRepository domain.HEPRepository - userRepository domain.UserRepository - contextTimeout time.Duration -} - -// GetSummary implements domain.StatisticUsecase. -func (su *statisticUsecase) GetSummary(c context.Context) (domain.Summary, error) { - ctx, cancel := context.WithTimeout(c, su.contextTimeout) - defer cancel() - totalGns, err := su.gnsRepository.GetTotal(ctx) - if err != nil { - return domain.Summary{}, err - } - totalHep, err := su.hepRepository.GetTotal(ctx) - if err != nil { - return domain.Summary{}, err - } - totalPolicy, err := su.policyRepository.GetTotal(ctx) - if err != nil { - return domain.Summary{}, err - } - totalUser, err := su.userRepository.GetTotal(ctx) - if err != nil { - return domain.Summary{}, err - } - - return domain.Summary{ - TotalGlobalNetworkSet: totalGns, - TotalHostEndpoint: totalHep, - TotalPolicy: totalPolicy, - TotalUser: totalUser, - }, err -} - -func (su *statisticUsecase) GetProjectSummary(c context.Context) ([]domain.ProjectSummary, error) { - ctx, cancel := context.WithTimeout(c, su.contextTimeout) - defer cancel() - projects, err := su.hepRepository.GetProjectSummary(ctx) - if err != nil { - return nil, err - } - return projects, nil -} - -func NewStatisticUsecase( - policyRepository domain.PolicyRepository, - gnsRepository domain.GNSRepository, - hepRepository domain.HEPRepository, - userRepository domain.UserRepository, - timeout time.Duration, -) domain.StatisticUsecase { - return &statisticUsecase{ - policyRepository: policyRepository, - gnsRepository: gnsRepository, - hepRepository: hepRepository, - userRepository: userRepository, - contextTimeout: timeout, - } -} diff --git a/usecase/user_usecase.go b/usecase/user_usecase.go deleted file mode 100644 index c4de0bd..0000000 --- a/usecase/user_usecase.go +++ /dev/null @@ -1,64 +0,0 @@ -package usecase - -import ( - "context" - "time" - - "github.com/bamboo-firewall/be/domain" - "github.com/casbin/casbin/v2" -) - -type userUsecase struct { - userRepository domain.UserRepository - contextTimeout time.Duration -} - -// Update implements domain.UserUsecase. -func (su *userUsecase) Update(c context.Context, user *domain.User) error { - ctx, cancel := context.WithTimeout(c, su.contextTimeout) - defer cancel() - return su.userRepository.Update(ctx, user) -} - -func (su *userUsecase) DeleteById(c context.Context, id string) error { - ctx, cancel := context.WithTimeout(c, su.contextTimeout) - defer cancel() - return su.userRepository.DeleteById(ctx, id) -} - -func (su *userUsecase) Create(c context.Context, user *domain.User) error { - ctx, cancel := context.WithTimeout(c, su.contextTimeout) - defer cancel() - return su.userRepository.Create(ctx, user) -} - -func (su *userUsecase) Fetch(c context.Context) ([]domain.User, error) { - ctx, cancel := context.WithTimeout(c, su.contextTimeout) - defer cancel() - return su.userRepository.Fetch(ctx) -} - -func (su *userUsecase) GetUserByEmail(c context.Context, email string) (domain.User, error) { - ctx, cancel := context.WithTimeout(c, su.contextTimeout) - defer cancel() - return su.userRepository.GetByEmail(ctx, email) -} - -func (su *userUsecase) GetUserByUsername(c context.Context, username string) (domain.User, error) { - ctx, cancel := context.WithTimeout(c, su.contextTimeout) - defer cancel() - return su.userRepository.GetByUsername(ctx, username) -} - -func (su *userUsecase) GetUserByID(c context.Context, id string) (domain.User, error) { - ctx, cancel := context.WithTimeout(c, su.contextTimeout) - defer cancel() - return su.userRepository.GetByID(ctx, id) -} - -func NewUserUsecase(userRepository domain.UserRepository, enforcer *casbin.Enforcer, timeout time.Duration) domain.UserUsecase { - return &userUsecase{ - userRepository: userRepository, - contextTimeout: timeout, - } -}