From d24625496fee9dd73ddc1419a55236222dd17a89 Mon Sep 17 00:00:00 2001 From: Jonas Kaninda Date: Fri, 8 Nov 2024 22:58:09 +0100 Subject: [PATCH] feat: add limit HTTP methods allowed for a particular route --- README.md | 149 +-------------------------------------------- docs/index.md | 4 +- docs/middleware.md | 1 + docs/route.md | 4 +- goma.yml | 3 +- internal/config.go | 4 +- internal/proxy.go | 13 ++++ internal/route.go | 8 ++- internal/types.go | 9 ++- 9 files changed, 39 insertions(+), 156 deletions(-) diff --git a/README.md b/README.md index 56f3363..84aeae5 100644 --- a/README.md +++ b/README.md @@ -31,10 +31,9 @@ Architecture: - [Github](https://github.com/jkaninda/goma-gateway) ### Documentation is found at - ### Features -It comes with a lot of integrated features, such as: +It's designed to be straightforward and efficient, offering features, like: - RESTFull API Gateway management - Domain/host based request routing @@ -55,6 +54,7 @@ It comes with a lot of integrated features, such as: - Rate limiting - In-Memory Token Bucket based - In-Memory client IP based +- Limit HTTP methods allowed for a particular route. ### Todo: @@ -112,151 +112,6 @@ services: - ./config:/etc/goma/ ``` -Create a config file in this format -## Customize configuration file - -Example of a configuration file -```yaml -## Goma Gateway configurations -gateway: - # Proxy write timeout - writeTimeout: 15 - # Proxy read timeout - readTimeout: 15 - # Proxy idle timeout - idleTimeout: 60 - ## SSL Certificate file - sslCertFile: '' #cert.pem - ## SSL Private Key file - sslKeyFile: ''#key.pem - # Proxy rate limit, it's In-Memory IP based - rateLimiter: 0 - accessLog: "/dev/Stdout" - errorLog: "/dev/stderr" - ## Enable, disable routes health check - disableHealthCheckStatus: false - ## Returns backend route healthcheck errors - disableRouteHealthCheckError: false - # Disable display routes on start - disableDisplayRouteOnStart: false - # disableKeepAlive allows enabling and disabling KeepALive server - disableKeepAlive: false - blockCommonExploits: false - # interceptErrors intercepts backend errors based on defined the status codes - interceptErrors: - - 405 - - 500 - # - 400 - # Proxy Global HTTP Cors - cors: - # Global routes cors for all routes - origins: - - http://localhost:8080 - - https://example.com - # Global routes cors headers for all routes - headers: - Access-Control-Allow-Headers: 'Origin, Authorization, Accept, Content-Type, Access-Control-Allow-Headers, X-Client-Id, X-Session-Id' - Access-Control-Allow-Credentials: 'true' - Access-Control-Max-Age: 1728000 - ##### Define routes - routes: - # Example of a route | 1 - - name: Public - # host Domain/host based request routing - host: "" # Host is optional - path: /public - ## Rewrite a request path - # e.g rewrite: /store to / - rewrite: / - destination: https://example.com - #DisableHeaderXForward Disable X-forwarded header. - # [X-Forwarded-Host, X-Forwarded-For, Host, Scheme ] - # It will not match the backend route, by default, it's disabled - disableHeaderXForward: false - # Internal health check - healthCheck: '' #/internal/health/ready - # Route Cors, global cors will be overridden by route - cors: - # Route Origins Cors, route will override global cors origins - origins: - - https://dev.example.com - - http://localhost:3000 - - https://example.com - # Route Cors headers, route will override global cors headers - headers: - Access-Control-Allow-Methods: 'GET' - Access-Control-Allow-Headers: 'Origin, Authorization, Accept, Content-Type, Access-Control-Allow-Headers, X-Client-Id, X-Session-Id' - Access-Control-Allow-Credentials: 'true' - Access-Control-Max-Age: 1728000 - ##### Apply middlewares to the route - ## The name must be unique - ## List of middleware name - middlewares: - - api-forbidden-paths - # Example of a route | 2 - - name: Basic auth - path: /protected - rewrite: / - destination: https://example.com - healthCheck: - cors: {} - middlewares: - - api-forbidden-paths - - basic-auth - -#Defines proxy middlewares -# middleware name must be unique -middlewares: - # Enable Basic auth authorization based - - name: basic-auth - # Authentication types | jwt, basic, OAuth - type: basic - paths: - - /user - - /admin - - /account - rule: - username: admin - password: admin - #Enables JWT authorization based on the result of a request and continues the request. - - name: google-auth - # Authentication types | jwt, basic, OAuth - # jwt authorization based on the result of backend's response and continue the request when the client is authorized - type: jwt - # Paths to protect - paths: - - /protected-access - - /example-of-jwt - #- /* or wildcard path - rule: - # This is an example URL - url: https://www.googleapis.com/auth/userinfo.email - # Required headers, if not present in the request, the proxy will return 403 - requiredHeaders: - - Authorization - # You can also get headers from the authentication request result and inject them into the next request header or params. - # In case you want to get headers from the authentication service and inject them into the next request headers. - # Set the request variable to the given value after the authorization request completes. - # In case you want to get headers from the authentication service and inject them into the next request headers. - # Key is authentication request response header Key. Value is the next Request header Key. - headers: - userId: Auth-UserId - userCountryId: Auth-UserCountryId - # In case you want to get headers from the Authentication service and inject them to the next request params. - #Key is authentication request response header Key. Value is the next Request parameter Key. - params: - userCountryId: countryId - # The server will return 403 - - name: api-forbidden-paths - type: access - ## prevents access paths - paths: - - /swagger-ui/* - - /v2/swagger-ui/* - - /api-docs/* - - /internal/* - - /actuator/* -``` ## Requirement diff --git a/docs/index.md b/docs/index.md index 8e86777..8cc3cc9 100644 --- a/docs/index.md +++ b/docs/index.md @@ -10,7 +10,7 @@ Goma Gateway is a lightweight API Gateway Management. Goma logo -It comes with a lot of integrated features, such as: +It's designed to be straightforward and efficient, offering features, like: - RESTFull API Gateway management - Domain/host based request routing @@ -31,6 +31,8 @@ It comes with a lot of integrated features, such as: - Rate limiting - In-Memory Token Bucket based - In-Memory client IP based +- Limit HTTP methods allowed for a particular route. + Declare your routes and middlewares as code. diff --git a/docs/middleware.md b/docs/middleware.md index 0488e25..ab1aa39 100644 --- a/docs/middleware.md +++ b/docs/middleware.md @@ -223,6 +223,7 @@ Example of rateLimit middleware path: /protected rewrite: / destination: 'https://example.com' + methods: [POST, PUT, GET] healthCheck: cors: {} middlewares: diff --git a/docs/route.md b/docs/route.md index cabd948..f7fc88c 100644 --- a/docs/route.md +++ b/docs/route.md @@ -16,6 +16,7 @@ The Route allows you to match on HTTP traffic and direct it to the backend. path: /store/cart rewrite: /cart destination: http://cart-service:8080 + methods: [POST, PUT, GET] healthCheck: '' cors: {} middlewares: @@ -39,7 +40,7 @@ gateway: ## SSL Private Key file sslKeyFile: ''#key.pem # Proxy rate limit, it's In-Memory IP based - rateLimiter: 0 + rateLimit: 0 accessLog: "/dev/Stdout" errorLog: "/dev/stderr" ## Enable, disable routes health check @@ -77,6 +78,7 @@ gateway: # e.g rewrite: /store to / rewrite: / destination: https://example.com + methods: [GET] #DisableHeaderXForward Disable X-forwarded header. # [X-Forwarded-Host, X-Forwarded-For, Host, Scheme ] # It will not match the backend route, by default, it's disabled diff --git a/goma.yml b/goma.yml index 19f65a7..57ac839 100644 --- a/goma.yml +++ b/goma.yml @@ -11,7 +11,7 @@ gateway: ## SSL Private Key file sslKeyFile: ''#key.pem # Proxy rate limit, it's In-Memory IP based - rateLimiter: 0 + rateLimit: 0 accessLog: "/dev/Stdout" errorLog: "/dev/stderr" ## Enable, disable routes health check @@ -50,6 +50,7 @@ gateway: # e.g rewrite: /store to / rewrite: / destination: https://example.com + methods: [POST, PUT, GET] #DisableHeaderXForward Disable X-forwarded header. # [X-Forwarded-Host, X-Forwarded-For, Host, Scheme ] # It will not match the backend route, by default, it's disabled diff --git a/internal/config.go b/internal/config.go index 5de1c37..b4c7d8d 100644 --- a/internal/config.go +++ b/internal/config.go @@ -112,6 +112,7 @@ func initConfig(configFile string) { configFile = GetConfigPaths() } conf := &GatewayConfig{ + Version: util.Version, GatewayConfig: Gateway{ WriteTimeout: 15, ReadTimeout: 15, @@ -120,7 +121,7 @@ func initConfig(configFile string) { ErrorLog: "/dev/stderr", DisableRouteHealthCheckError: false, DisableDisplayRouteOnStart: false, - RateLimiter: 0, + RateLimit: 0, InterceptErrors: []int{405, 500}, Cors: Cors{ Origins: []string{"http://localhost:8080", "https://example.com"}, @@ -134,6 +135,7 @@ func initConfig(configFile string) { { Name: "Public", Path: "/public", + Methods: []string{"GET"}, Destination: "https://example.com", Rewrite: "/", HealthCheck: "", diff --git a/internal/proxy.go b/internal/proxy.go index 0680a8f..54cd031 100644 --- a/internal/proxy.go +++ b/internal/proxy.go @@ -21,6 +21,7 @@ import ( "net/http" "net/http/httputil" "net/url" + "slices" "strings" ) @@ -28,6 +29,18 @@ import ( func (proxyRoute ProxyRoute) ProxyHandler() http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { logger.Info("%s %s %s %s", r.Method, getRealIP(r), r.URL, r.UserAgent()) + // Check Method if is allowed + if len(proxyRoute.methods) > 0 { + if !slices.Contains(proxyRoute.methods, r.Method) { + logger.Error("%s Method is not allowed", r.Method) + w.WriteHeader(http.StatusMethodNotAllowed) + _, err := w.Write([]byte(fmt.Sprintf("%s method is not allowed", r.Method))) + if err != nil { + return + } + return + } + } // Set CORS headers from the cors config //Update Cors Headers for k, v := range proxyRoute.cors.Headers { diff --git a/internal/route.go b/internal/route.go index 5497d05..ecfdfac 100644 --- a/internal/route.go +++ b/internal/route.go @@ -45,9 +45,9 @@ func (gatewayServer GatewayServer) Initialize() *mux.Router { logger.Info("Block common exploits enabled") r.Use(middleware.BlockExploitsMiddleware) } - if gateway.RateLimiter != 0 { - //rateLimiter := middleware.NewRateLimiter(gateway.RateLimiter, time.Minute) - limiter := middleware.NewRateLimiterWindow(gateway.RateLimiter, time.Minute, gateway.Cors.Origins) // requests per minute + if gateway.RateLimit != 0 { + //rateLimiter := middleware.NewRateLimiter(gateway.RateLimit, time.Minute) + limiter := middleware.NewRateLimiterWindow(gateway.RateLimit, time.Minute, gateway.Cors.Origins) // requests per minute // Add rate limit middleware to all routes, if defined r.Use(limiter.RateLimitMiddleware()) } @@ -85,6 +85,7 @@ func (gatewayServer GatewayServer) Initialize() *mux.Router { rewrite: route.Rewrite, destination: route.Destination, disableXForward: route.DisableHeaderXForward, + methods: route.Methods, cors: route.Cors, } secureRouter := r.PathPrefix(util.ParseRoutePath(route.Path, midPath)).Subrouter() @@ -189,6 +190,7 @@ func (gatewayServer GatewayServer) Initialize() *mux.Router { path: route.Path, rewrite: route.Rewrite, destination: route.Destination, + methods: route.Methods, disableXForward: route.DisableHeaderXForward, cors: route.Cors, } diff --git a/internal/types.go b/internal/types.go index cf2a468..26963a5 100644 --- a/internal/types.go +++ b/internal/types.go @@ -145,6 +145,9 @@ type Route struct { Destination string `yaml:"destination"` // Cors contains the route cors headers Cors Cors `yaml:"cors"` + //RateLimit int `yaml:"rateLimit"` + // Methods allowed method + Methods []string `yaml:"methods"` // DisableHeaderXForward Disable X-forwarded header. // // [X-Forwarded-Host, X-Forwarded-For, Host, Scheme ] @@ -173,8 +176,8 @@ type Gateway struct { ReadTimeout int `yaml:"readTimeout" env:"GOMA_READ_TIMEOUT, overwrite"` // IdleTimeout defines proxy idle timeout IdleTimeout int `yaml:"idleTimeout" env:"GOMA_IDLE_TIMEOUT, overwrite"` - // RateLimiter Defines the number of request peer minutes - RateLimiter int `yaml:"rateLimiter" env:"GOMA_RATE_LIMITER, overwrite"` + // RateLimit Defines the number of request peer minutes + RateLimit int `yaml:"rateLimit" env:"GOMA_RATE_LIMIT, overwrite"` // BlockCommonExploits enable, disable block common exploits BlockCommonExploits bool `yaml:"blockCommonExploits"` AccessLog string `yaml:"accessLog" env:"GOMA_ACCESS_LOG, overwrite"` @@ -195,6 +198,7 @@ type Gateway struct { Routes []Route `yaml:"routes"` } type GatewayConfig struct { + Version string `yaml:"version"` // GatewayConfig holds Gateway config GatewayConfig Gateway `yaml:"gateway"` // Middlewares holds proxy middlewares @@ -216,6 +220,7 @@ type ProxyRoute struct { path string rewrite string destination string + methods []string cors Cors disableXForward bool }