From bc86abd8f8caab5d3ca6f29a72941a21738c1e62 Mon Sep 17 00:00:00 2001 From: Jonas Kaninda Date: Wed, 30 Oct 2024 16:38:09 +0100 Subject: [PATCH] chore: move blocklist middleware from route blocklist to middlewares --- pkg/config.go | 56 +++++++++--------- pkg/middleware.go | 22 ++++--- pkg/middleware_test.go | 35 +++++++---- pkg/route.go | 129 +++++++++++++++++++++++------------------ pkg/var.go | 8 ++- 5 files changed, 141 insertions(+), 109 deletions(-) diff --git a/pkg/config.go b/pkg/config.go index dc83b70..4ad163e 100644 --- a/pkg/config.go +++ b/pkg/config.go @@ -30,7 +30,7 @@ var cfg *Gateway type Config struct { file string } -type BasicRule struct { +type BasicRuleMiddleware struct { Username string `yaml:"username"` Password string `yaml:"password"` } @@ -54,10 +54,10 @@ type Cors struct { Headers map[string]string `yaml:"headers"` } -// JWTRuler authentication using HTTP GET method +// JWTRuleMiddleware authentication using HTTP GET method // -// JWTRuler contains the authentication details -type JWTRuler struct { +// JWTRuleMiddleware contains the authentication details +type JWTRuleMiddleware struct { // URL contains the authentication URL, it supports HTTP GET method only. URL string `yaml:"url"` // RequiredHeaders , contains required before sending request to the backend. @@ -84,16 +84,21 @@ type RateLimiter struct { Rule int `yaml:"rule"` } +type AccessRuleMiddleware struct { + ResponseCode int `yaml:"responseCode"` // HTTP Response code +} + // Middleware defined the route middleware type Middleware struct { //Path contains the name of middleware and must be unique Name string `yaml:"name"` // Type contains authentication types // - // basic, jwt, auth0, rateLimit - Type string `yaml:"type"` + // basic, jwt, auth0, rateLimit, access + Type string `yaml:"type"` // Middleware type [basic, jwt, auth0, rateLimit, access] + Paths []string `yaml:"paths"` // Protected paths // Rule contains rule type of - Rule interface{} `yaml:"rule"` + Rule interface{} `yaml:"rule"` // Middleware rule } type MiddlewareName struct { name string `yaml:"name"` @@ -136,7 +141,7 @@ type Route struct { // Eg: [ 403, 405, 500 ] InterceptErrors []int `yaml:"interceptErrors"` // Middlewares Defines route middleware from Middleware names - Middlewares []RouteMiddleware `yaml:"middlewares"` + Middlewares []string `yaml:"middlewares"` } // Gateway contains Goma Proxy Gateway's configs @@ -287,12 +292,7 @@ func initConfig(configFile string) { "Access-Control-Max-Age": "1728000", }, }, - Middlewares: []RouteMiddleware{ - { - Path: "/user", - Rules: []string{"basic-auth"}, - }, - }, + Middlewares: []string{"basic-auth"}, }, { Name: "Hostname example", @@ -308,14 +308,14 @@ func initConfig(configFile string) { { Name: "basic-auth", Type: "basic", - Rule: BasicRule{ + Rule: BasicRuleMiddleware{ Username: "goma", Password: "goma", }, }, { Name: "jwt", Type: "jwt", - Rule: JWTRuler{ + Rule: JWTRuleMiddleware{ URL: "https://www.googleapis.com/auth/userinfo.email", RequiredHeaders: []string{ "Authorization", @@ -361,40 +361,40 @@ func (Gateway) Setup(conf string) *Gateway { return &Gateway{} } -func (middleware Middleware) name() { -} -func ToJWTRuler(input interface{}) (JWTRuler, error) { - jWTRuler := new(JWTRuler) +// getJWTMiddleware returns JWTRuleMiddleware,error +func getJWTMiddleware(input interface{}) (JWTRuleMiddleware, error) { + jWTRuler := new(JWTRuleMiddleware) var bytes []byte bytes, err := yaml.Marshal(input) if err != nil { - return JWTRuler{}, fmt.Errorf("error parsing yaml: %v", err) + return JWTRuleMiddleware{}, fmt.Errorf("error parsing yaml: %v", err) } err = yaml.Unmarshal(bytes, jWTRuler) if err != nil { - return JWTRuler{}, fmt.Errorf("error parsing yaml: %v", err) + return JWTRuleMiddleware{}, fmt.Errorf("error parsing yaml: %v", err) } if jWTRuler.URL == "" { - return JWTRuler{}, fmt.Errorf("error parsing yaml: empty url in jwt auth middleware") + return JWTRuleMiddleware{}, fmt.Errorf("error parsing yaml: empty url in jwt auth middleware") } return *jWTRuler, nil } -func ToBasicAuth(input interface{}) (BasicRule, error) { - basicAuth := new(BasicRule) +// getBasicAuthMiddleware returns BasicRuleMiddleware,error +func getBasicAuthMiddleware(input interface{}) (BasicRuleMiddleware, error) { + basicAuth := new(BasicRuleMiddleware) var bytes []byte bytes, err := yaml.Marshal(input) if err != nil { - return BasicRule{}, fmt.Errorf("error parsing yaml: %v", err) + return BasicRuleMiddleware{}, fmt.Errorf("error parsing yaml: %v", err) } err = yaml.Unmarshal(bytes, basicAuth) if err != nil { - return BasicRule{}, fmt.Errorf("error parsing yaml: %v", err) + return BasicRuleMiddleware{}, fmt.Errorf("error parsing yaml: %v", err) } if basicAuth.Username == "" || basicAuth.Password == "" { - return BasicRule{}, fmt.Errorf("error parsing yaml: empty username/password in %s middleware", basicAuth) + return BasicRuleMiddleware{}, fmt.Errorf("error parsing yaml: empty username/password in %s middleware", basicAuth) } return *basicAuth, nil diff --git a/pkg/middleware.go b/pkg/middleware.go index 0973d6d..b0a4951 100644 --- a/pkg/middleware.go +++ b/pkg/middleware.go @@ -7,7 +7,7 @@ import ( "strings" ) -func searchMiddleware(rules []string, middlewares []Middleware) (Middleware, error) { +func getMiddleware(rules []string, middlewares []Middleware) (Middleware, error) { for _, m := range middlewares { if slices.Contains(rules, m.Name) { return m, nil @@ -17,17 +17,6 @@ func searchMiddleware(rules []string, middlewares []Middleware) (Middleware, err return Middleware{}, errors.New("middleware not found with name: [" + strings.Join(rules, ";") + "]") } -func getMiddleware(rule string, middlewares []Middleware) (Middleware, error) { - for _, m := range middlewares { - if strings.Contains(rule, m.Name) { - - return m, nil - } - continue - } - - return Middleware{}, errors.New("no middleware found with name " + rule) -} type RoutePath struct { route Route @@ -36,3 +25,12 @@ type RoutePath struct { middlewares []Middleware router *mux.Router } + +func doesExist(tyName string) bool { + middlewareList := []string{BasicAuth, JWTAuth, AccessMiddleware} + if slices.Contains(middlewareList, tyName) { + return true + + } + return false +} diff --git a/pkg/middleware_test.go b/pkg/middleware_test.go index 822bb82..7e8161c 100644 --- a/pkg/middleware_test.go +++ b/pkg/middleware_test.go @@ -31,16 +31,29 @@ func TestMiddleware(t *testing.T) { TestInit(t) middlewares := []Middleware{ { - Name: "basic-auth", - Type: "basic", - Rule: BasicRule{ + Name: "basic-auth", + Type: "basic", + Paths: []string{"/", "/admin"}, + Rule: BasicRuleMiddleware{ Username: "goma", Password: "goma", }, - }, { - Name: MidName, - Type: "jwt", - Rule: JWTRuler{ + }, + { + Name: "forbidden path acces", + Type: "access", + Paths: []string{"/", "/admin"}, + Rule: BasicRuleMiddleware{ + Username: "goma", + Password: "goma", + }, + }, + + { + Name: "jwt", + Type: "jwt", + Paths: []string{"/", "/admin"}, + Rule: JWTRuleMiddleware{ URL: "https://www.googleapis.com/auth/userinfo.email", Headers: map[string]string{}, Params: map[string]string{}, @@ -61,21 +74,21 @@ func TestMiddleware(t *testing.T) { func TestReadMiddleware(t *testing.T) { TestMiddleware(t) middlewares := getMiddlewares(t) - middleware, err := searchMiddleware(rules, middlewares) + middleware, err := getMiddleware(rules, middlewares) if err != nil { t.Fatalf("Error searching middleware %s", err.Error()) } switch middleware.Type { case "basic": log.Println("Basic auth") - basicAuth, err := ToBasicAuth(middleware.Rule) + basicAuth, err := getBasicAuthMiddleware(middleware.Rule) if err != nil { log.Fatalln("error:", err) } log.Printf("Username: %s and password: %s\n", basicAuth.Username, basicAuth.Password) case "jwt": log.Println("JWT auth") - jwt, err := ToJWTRuler(middleware.Rule) + jwt, err := getJWTMiddleware(middleware.Rule) if err != nil { log.Fatalln("error:", err) } @@ -89,7 +102,7 @@ func TestReadMiddleware(t *testing.T) { func TestFoundMiddleware(t *testing.T) { middlewares := getMiddlewares(t) - middleware, err := searchMiddleware(rules, middlewares) + middleware, err := getAuthMiddleware("jwt", middlewares) if err != nil { t.Errorf("Error getting middleware %v", err) } diff --git a/pkg/route.go b/pkg/route.go index 218eaa6..46d330b 100644 --- a/pkg/route.go +++ b/pkg/route.go @@ -42,67 +42,87 @@ func (gatewayServer GatewayServer) Initialize() *mux.Router { } for _, route := range gateway.Routes { if route.Path != "" { - blM := middleware.BlockListMiddleware{ - Path: route.Path, - List: route.Blocklist, - } - // Apply route middlewares + + // Apply middlewares to route for _, mid := range route.Middlewares { - if mid.Path != "" { - secureRouter := r.PathPrefix(util.ParseURLPath(route.Path + mid.Path)).Subrouter() - proxyRoute := ProxyRoute{ - path: route.Path, - rewrite: route.Rewrite, - destination: route.Destination, - disableXForward: route.DisableHeaderXForward, - cors: route.Cors, - } - rMiddleware, err := searchMiddleware(mid.Rules, middlewares) + if mid != "" { + // Get Access middleware if it does exist + accessMiddleware, err := getMiddleware([]string{mid}, middlewares) if err != nil { logger.Error("Error: %v", err.Error()) } else { - //Check Authentication middleware - switch rMiddleware.Type { - case "basic": - basicAuth, err := ToBasicAuth(rMiddleware.Rule) - if err != nil { - logger.Error("Error: %s", err.Error()) - } else { - amw := middleware.AuthBasic{ - Username: basicAuth.Username, - Password: basicAuth.Password, - Headers: nil, - Params: nil, - } - // Apply JWT authentication middleware - secureRouter.Use(amw.AuthMiddleware) - secureRouter.Use(CORSHandler(route.Cors)) - secureRouter.PathPrefix("/").Handler(proxyRoute.ProxyHandler()) // Proxy handler - secureRouter.PathPrefix("").Handler(proxyRoute.ProxyHandler()) // Proxy handler + // Apply access middleware + if accessMiddleware.Type == AccessMiddleware { + blM := middleware.BlockListMiddleware{ + Path: route.Path, + List: accessMiddleware.Paths, } - case "jwt": - jwt, err := ToJWTRuler(rMiddleware.Rule) - if err != nil { - logger.Error("Error: %s", err.Error()) - } else { - amw := middleware.JwtAuth{ - AuthURL: jwt.URL, - RequiredHeaders: jwt.RequiredHeaders, - Headers: jwt.Headers, - Params: jwt.Params, + r.Use(blM.BlocklistMiddleware) + + } + + } + // Get route authentication middleware if it does exist + rMiddleware, err := getMiddleware([]string{mid}, middlewares) + if err != nil { + //Error: middleware not found + logger.Error("Error: %v", err.Error()) + } else { + for _, midPath := range rMiddleware.Paths { + proxyRoute := ProxyRoute{ + path: route.Path, + rewrite: route.Rewrite, + destination: route.Destination, + disableXForward: route.DisableHeaderXForward, + cors: route.Cors, + } + secureRouter := r.PathPrefix(util.ParseURLPath(route.Path + midPath)).Subrouter() + //Check Authentication middleware + switch rMiddleware.Type { + case BasicAuth: + basicAuth, err := getBasicAuthMiddleware(rMiddleware.Rule) + if err != nil { + logger.Error("Error: %s", err.Error()) + } else { + amw := middleware.AuthBasic{ + Username: basicAuth.Username, + Password: basicAuth.Password, + Headers: nil, + Params: nil, + } + // Apply JWT authentication middleware + secureRouter.Use(amw.AuthMiddleware) + secureRouter.Use(CORSHandler(route.Cors)) + secureRouter.PathPrefix("/").Handler(proxyRoute.ProxyHandler()) // Proxy handler + secureRouter.PathPrefix("").Handler(proxyRoute.ProxyHandler()) // Proxy handler + } + case JWTAuth: + jwt, err := getJWTMiddleware(rMiddleware.Rule) + if err != nil { + logger.Error("Error: %s", err.Error()) + } else { + amw := middleware.JwtAuth{ + AuthURL: jwt.URL, + RequiredHeaders: jwt.RequiredHeaders, + Headers: jwt.Headers, + Params: jwt.Params, + } + // Apply JWT authentication middleware + secureRouter.Use(amw.AuthMiddleware) + secureRouter.Use(CORSHandler(route.Cors)) + secureRouter.PathPrefix("/").Handler(proxyRoute.ProxyHandler()) // Proxy handler + secureRouter.PathPrefix("").Handler(proxyRoute.ProxyHandler()) // Proxy handler + + } + case "OAuth": + logger.Error("OAuth is not yet implemented") + logger.Info("Auth middleware ignored") + default: + if !doesExist(rMiddleware.Type) { + logger.Error("Unknown middleware type %s", rMiddleware.Type) } - // Apply JWT authentication middleware - secureRouter.Use(amw.AuthMiddleware) - secureRouter.Use(CORSHandler(route.Cors)) - secureRouter.PathPrefix("/").Handler(proxyRoute.ProxyHandler()) // Proxy handler - secureRouter.PathPrefix("").Handler(proxyRoute.ProxyHandler()) // Proxy handler } - case "OAuth": - logger.Error("OAuth is not yet implemented") - logger.Info("Auth middleware ignored") - default: - logger.Error("Unknown middleware type %s", rMiddleware.Type) } @@ -122,9 +142,6 @@ func (gatewayServer GatewayServer) Initialize() *mux.Router { router := r.PathPrefix(route.Path).Subrouter() // Apply route Cors router.Use(CORSHandler(route.Cors)) - // Add block access middleware to route, if defined - router.Use(blM.BlocklistMiddleware) - //Domain/host based request routing if route.Host != "" { router.Host(route.Host).PathPrefix("").Handler(proxyRoute.ProxyHandler()) } else { diff --git a/pkg/var.go b/pkg/var.go index 2dd5a1b..c22d2da 100644 --- a/pkg/var.go +++ b/pkg/var.go @@ -1,5 +1,9 @@ package pkg -const ConfigFile = "/config/goma.yml" -const accessControlAllowOrigin = "Access-Control-Allow-Origin" +const ConfigFile = "/config/goma.yml" // Default configuration file +const accessControlAllowOrigin = "Access-Control-Allow-Origin" // Cors const serverName = "Goma" +const AccessMiddleware = "access" // access middleware +const BasicAuth = "basic" // basic authentication middleware +const JWTAuth = "jwt" // JWT authentication middleware +const OAuth = "OAuth" // OAuth authentication middleware