diff --git a/.env.example b/.env.example index 991c595..0c9e6fa 100644 --- a/.env.example +++ b/.env.example @@ -1,8 +1,8 @@ -GOMA_LISTEN_ADDR=localhost:80 -GOMA_CONFIG_FILE=/config/goma.yml -GOMA_ACCESS_LOG=/dev/Stdout -GOMA_ERROR_LOG=/dev/stderr +GOMA_LISTEN_ADDR=0.0.0.0:80 GOMA_WRITE_TIMEOUT=15 GOMA_READ_TIMEOUT=15 GOMA_IDLE_TIMEOUT=30 -GOMA_RATE_LIMITER=10 \ No newline at end of file +GOMA_RATE_LIMITER=10 +GOMA_ACCESS_LOG=/dev/Stdout +GOMA_ERROR_LOG=/dev/stderr +GOMA_CONFIG_FILE=/config/goma.yml diff --git a/README.md b/README.md index fc5b267..ef1b32d 100644 --- a/README.md +++ b/README.md @@ -106,7 +106,7 @@ docker run --rm --name goma-gateway \ Create a config file in this format ## Customize configuration file -Example of configuration file +Example of a configuration file ```yaml ## Goma - simple lightweight API Gateway and Reverse Proxy. # Goma Gateway configurations @@ -119,7 +119,7 @@ gateway: readTimeout: 15 # Proxy idle timeout idleTimeout: 60 - # Proxy rate limit, it's In-Memory Token Bucket + # Proxy rate limit, it's In-Memory IP based # Distributed Rate Limiting for Token based across multiple instances is not yet integrated rateLimiter: 0 accessLog: "/dev/Stdout" @@ -128,6 +128,8 @@ gateway: disableRouteHealthCheckError: false # Disable display routes on start disableDisplayRouteOnStart: false + # disableKeepAlive allows enabling and disabling KeepALive server + disableKeepAlive: false # interceptErrors intercepts backend errors based on defined the status codes interceptErrors: - 405 @@ -194,7 +196,7 @@ gateway: - path: /path-example # Rules defines which specific middleware applies to a route path rules: - - jwtAuth + - jwt # path to protect - path: /admin # Rules defines which specific middleware applies to a route path @@ -204,7 +206,7 @@ gateway: - path: /path-example # Rules defines which specific middleware applies to a route path rules: - - jwtAuth + - jwt - path: /history http: url: http://security-service:8080/security/authUser @@ -236,15 +238,15 @@ gateway: #Defines proxy middlewares middlewares: # Enable Basic auth authorization based - - name: local-auth-basic - # Authentication types | jwtAuth, basicAuth, auth0 + - name: basic-auth + # Authentication types | jwt, basic, OAuth type: basic rule: username: admin password: admin #Enables JWT authorization based on the result of a request and continues the request. - name: google-auth - # Authentication types | jwtAuth, basicAuth, OAuth + # 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 rule: diff --git a/cmd/config/config.go b/cmd/config/config.go index ce6112b..7bca009 100644 --- a/cmd/config/config.go +++ b/cmd/config/config.go @@ -23,7 +23,7 @@ import ( var Cmd = &cobra.Command{ Use: "config", - Short: "Goma configuration", + Short: "Goma Gateway configuration management", Run: func(cmd *cobra.Command, args []string) { if len(args) == 0 { return diff --git a/cmd/root.go b/cmd/root.go index 814ff8b..ebd621b 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -28,7 +28,7 @@ var rootCmd = &cobra.Command{ Use: "goma", Short: "Goma Gateway is a lightweight API Gateway, Reverse Proxy", Long: `.`, - Example: "", + Example: util.MainExample, Version: util.FullVersion(), } diff --git a/goma.yml b/goma.yml index 60afbe4..5283d7f 100644 --- a/goma.yml +++ b/goma.yml @@ -9,7 +9,7 @@ gateway: readTimeout: 15 # Proxy idle timeout idleTimeout: 60 - # Proxy rate limit, it's In-Memory Token Bucket + # Proxy rate limit, it's In-Memory IP based # Distributed Rate Limiting for Token based across multiple instances is not yet integrated rateLimiter: 0 accessLog: "/dev/Stdout" @@ -18,6 +18,8 @@ gateway: disableRouteHealthCheckError: false # Disable display routes on start disableDisplayRouteOnStart: false + # disableKeepAlive allows enabling and disabling KeepALive server + disableKeepAlive: false # interceptErrors intercepts backend errors based on defined the status codes interceptErrors: - 405 @@ -84,7 +86,7 @@ gateway: - path: /path-example # Rules defines which specific middleware applies to a route path rules: - - jwtAuth + - jwt # path to protect - path: /admin # Rules defines which specific middleware applies to a route path @@ -94,7 +96,7 @@ gateway: - path: /path-example # Rules defines which specific middleware applies to a route path rules: - - jwtAuth + - jwt - path: /history http: url: http://security-service:8080/security/authUser @@ -126,7 +128,7 @@ gateway: #Defines proxy middlewares middlewares: # Enable Basic auth authorization based - - name: local-auth-basic + - name: basic-auth # Authentication types | jwt, basic, OAuth type: basic rule: diff --git a/pkg/config.go b/pkg/config.go index 8c82acc..dc83b70 100644 --- a/pkg/config.go +++ b/pkg/config.go @@ -77,6 +77,12 @@ type JWTRuler struct { //e.g: Header X-Auth-UserId to query userId Params map[string]string `yaml:"params"` } +type RateLimiter struct { + // ipBased, tokenBased + Type string `yaml:"type"` + Rate float64 `yaml:"rate"` + Rule int `yaml:"rule"` +} // Middleware defined the route middleware type Middleware struct { @@ -146,22 +152,27 @@ type Gateway struct { // IdleTimeout defines proxy idle timeout IdleTimeout int `yaml:"idleTimeout" env:"GOMA_IDLE_TIMEOUT, overwrite"` // RateLimiter Defines number of request peer minute - RateLimiter int `yaml:"rateLimiter" env:"GOMA_RATE_LIMITER, overwrite"` - AccessLog string `yaml:"accessLog" env:"GOMA_ACCESS_LOG, overwrite"` - ErrorLog string `yaml:"errorLog" env:"GOMA_ERROR_LOG=, overwrite"` - DisableRouteHealthCheckError bool `yaml:"disableRouteHealthCheckError"` - //Disable dispelling routes on start - DisableDisplayRouteOnStart bool `yaml:"disableDisplayRouteOnStart"` - InterceptErrors []int `yaml:"interceptErrors"` - EnableKeepAlive bool `yaml:"enableKeepAlive"` - // Cors contains the proxy global cors + RateLimiter int `yaml:"rateLimiter" env:"GOMA_RATE_LIMITER, overwrite"` + AccessLog string `yaml:"accessLog" env:"GOMA_ACCESS_LOG, overwrite"` + ErrorLog string `yaml:"errorLog" env:"GOMA_ERROR_LOG=, overwrite"` + // DisableRouteHealthCheckError allows enabling and disabling backend healthcheck errors + DisableRouteHealthCheckError bool `yaml:"disableRouteHealthCheckError"` + //Disable allows enabling and disabling displaying routes on start + DisableDisplayRouteOnStart bool `yaml:"disableDisplayRouteOnStart"` + // DisableKeepAlive allows enabling and disabling KeepALive server + DisableKeepAlive bool `yaml:"disableKeepAlive"` + // InterceptErrors holds the status codes to intercept the error from backend + InterceptErrors []int `yaml:"interceptErrors"` + // Cors holds proxy global cors Cors Cors `yaml:"cors"` - // Routes defines the proxy routes + // Routes holds proxy routes Routes []Route `yaml:"routes"` } type GatewayConfig struct { - GatewayConfig Gateway `yaml:"gateway"` - Middlewares []Middleware `yaml:"middlewares"` + // GatewayConfig holds Gateway config + GatewayConfig Gateway `yaml:"gateway"` + // Middlewares holds proxy middlewares + Middlewares []Middleware `yaml:"middlewares"` } // ErrorResponse represents the structure of the JSON error response @@ -365,7 +376,7 @@ func ToJWTRuler(input interface{}) (JWTRuler, error) { return JWTRuler{}, fmt.Errorf("error parsing yaml: %v", err) } if jWTRuler.URL == "" { - return JWTRuler{}, fmt.Errorf("error parsing yaml: empty url in %s auth middleware", jwtAuth) + return JWTRuler{}, fmt.Errorf("error parsing yaml: empty url in jwt auth middleware") } return *jWTRuler, nil diff --git a/pkg/middleware/bloclist.go b/pkg/middleware/bloclist.go index dc443b2..f389a52 100644 --- a/pkg/middleware/bloclist.go +++ b/pkg/middleware/bloclist.go @@ -30,7 +30,7 @@ func (blockList BlockListMiddleware) BlocklistMiddleware(next http.Handler) http return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { for _, block := range blockList.List { if isPathBlocked(r.URL.Path, util.ParseURLPath(blockList.Path+block)) { - logger.Error("Access to %s is forbidden", r.URL.Path) + logger.Warn("%s: access to %s is forbidden", getRealIP(r), r.URL.Path) w.Header().Set("Content-Type", "application/json") w.WriteHeader(http.StatusNotFound) err := json.NewEncoder(w).Encode(ProxyResponseError{ diff --git a/pkg/middleware/middleware.go b/pkg/middleware/middleware.go index e3c2116..a9707e2 100644 --- a/pkg/middleware/middleware.go +++ b/pkg/middleware/middleware.go @@ -65,8 +65,8 @@ type ProxyResponseError struct { Message string `json:"message"` } -// AuthJWT Define struct -type AuthJWT struct { +// JwtAuth Define struct +type JwtAuth struct { AuthURL string RequiredHeaders []string Headers map[string]string @@ -97,9 +97,9 @@ type AuthBasic struct { // AuthMiddleware authenticate the client using JWT // // authorization based on the result of backend's response and continue the request when the client is authorized -func (amw AuthJWT) AuthMiddleware(next http.Handler) http.Handler { +func (jwtAuth JwtAuth) AuthMiddleware(next http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - for _, header := range amw.RequiredHeaders { + for _, header := range jwtAuth.RequiredHeaders { if r.Header.Get(header) == "" { logger.Error("Proxy error, missing %s header", header) w.Header().Set("Content-Type", "application/json") @@ -116,7 +116,7 @@ func (amw AuthJWT) AuthMiddleware(next http.Handler) http.Handler { } } //token := r.Header.Get("Authorization") - authURL, err := url.Parse(amw.AuthURL) + authURL, err := url.Parse(jwtAuth.AuthURL) if err != nil { logger.Error("Error parsing auth URL: %v", err) w.Header().Set("Content-Type", "application/json") @@ -162,7 +162,7 @@ func (amw AuthJWT) AuthMiddleware(next http.Handler) http.Handler { authResp, err := client.Do(authReq) if err != nil || authResp.StatusCode != http.StatusOK { logger.Info("%s %s %s %s", r.Method, r.RemoteAddr, r.URL, r.UserAgent()) - logger.Error("Proxy authentication error") + logger.Warn("Proxy authentication error") w.Header().Set("Content-Type", "application/json") w.WriteHeader(http.StatusUnauthorized) err = json.NewEncoder(w).Encode(ProxyResponseError{ @@ -183,15 +183,15 @@ func (amw AuthJWT) AuthMiddleware(next http.Handler) http.Handler { }(authResp.Body) // Inject specific header tp the current request's header // Add header to the next request from AuthRequest header, depending on your requirements - if amw.Headers != nil { - for k, v := range amw.Headers { + if jwtAuth.Headers != nil { + for k, v := range jwtAuth.Headers { r.Header.Set(v, authResp.Header.Get(k)) } } query := r.URL.Query() // Add query parameters to the next request from AuthRequest header, depending on your requirements - if amw.Params != nil { - for k, v := range amw.Params { + if jwtAuth.Params != nil { + for k, v := range jwtAuth.Params { query.Set(v, authResp.Header.Get(k)) } } @@ -214,7 +214,7 @@ func (basicAuth AuthBasic) AuthMiddleware(next http.Handler) http.Handler { err := json.NewEncoder(w).Encode(ProxyResponseError{ Success: false, Code: http.StatusUnauthorized, - Message: "Unauthorized", + Message: http.StatusText(http.StatusUnauthorized), }) if err != nil { return @@ -229,7 +229,7 @@ func (basicAuth AuthBasic) AuthMiddleware(next http.Handler) http.Handler { err := json.NewEncoder(w).Encode(ProxyResponseError{ Success: false, Code: http.StatusUnauthorized, - Message: "Unauthorized", + Message: http.StatusText(http.StatusUnauthorized), }) if err != nil { return @@ -246,7 +246,7 @@ func (basicAuth AuthBasic) AuthMiddleware(next http.Handler) http.Handler { err := json.NewEncoder(w).Encode(ProxyResponseError{ Success: false, Code: http.StatusUnauthorized, - Message: "Unauthorized", + Message: http.StatusText(http.StatusUnauthorized), }) if err != nil { return @@ -263,7 +263,7 @@ func (basicAuth AuthBasic) AuthMiddleware(next http.Handler) http.Handler { err := json.NewEncoder(w).Encode(ProxyResponseError{ Success: false, Code: http.StatusUnauthorized, - Message: "Unauthorized", + Message: http.StatusText(http.StatusUnauthorized), }) if err != nil { return diff --git a/pkg/route.go b/pkg/route.go index ccb5828..218eaa6 100644 --- a/pkg/route.go +++ b/pkg/route.go @@ -46,7 +46,7 @@ func (gatewayServer GatewayServer) Initialize() *mux.Router { Path: route.Path, List: route.Blocklist, } - // Apply route middleware + // Apply route middlewares for _, mid := range route.Middlewares { if mid.Path != "" { secureRouter := r.PathPrefix(util.ParseURLPath(route.Path + mid.Path)).Subrouter() @@ -63,7 +63,7 @@ func (gatewayServer GatewayServer) Initialize() *mux.Router { } else { //Check Authentication middleware switch rMiddleware.Type { - case basicAuth, "basic": + case "basic": basicAuth, err := ToBasicAuth(rMiddleware.Rule) if err != nil { logger.Error("Error: %s", err.Error()) @@ -80,12 +80,12 @@ func (gatewayServer GatewayServer) Initialize() *mux.Router { secureRouter.PathPrefix("/").Handler(proxyRoute.ProxyHandler()) // Proxy handler secureRouter.PathPrefix("").Handler(proxyRoute.ProxyHandler()) // Proxy handler } - case jwtAuth, "jwt": + case "jwt": jwt, err := ToJWTRuler(rMiddleware.Rule) if err != nil { logger.Error("Error: %s", err.Error()) } else { - amw := middleware.AuthJWT{ + amw := middleware.JwtAuth{ AuthURL: jwt.URL, RequiredHeaders: jwt.RequiredHeaders, Headers: jwt.Headers, @@ -98,7 +98,7 @@ func (gatewayServer GatewayServer) Initialize() *mux.Router { secureRouter.PathPrefix("").Handler(proxyRoute.ProxyHandler()) // Proxy handler } - case OAuth, "auth0": + case "OAuth": logger.Error("OAuth is not yet implemented") logger.Info("Auth middleware ignored") default: diff --git a/pkg/server.go b/pkg/server.go index 446eb61..ea8ac05 100644 --- a/pkg/server.go +++ b/pkg/server.go @@ -40,7 +40,7 @@ func (gatewayServer GatewayServer) Start(ctx context.Context) error { printRoute(gatewayServer.gateway.Routes) } // Set KeepAlive - srv.SetKeepAlivesEnabled(gatewayServer.gateway.EnableKeepAlive) + srv.SetKeepAlivesEnabled(!gatewayServer.gateway.DisableKeepAlive) go func() { logger.Info("Started Goma Gateway server on %v", gatewayServer.gateway.ListenAddr) diff --git a/pkg/var.go b/pkg/var.go index a750893..2dd5a1b 100644 --- a/pkg/var.go +++ b/pkg/var.go @@ -2,7 +2,4 @@ package pkg const ConfigFile = "/config/goma.yml" const accessControlAllowOrigin = "Access-Control-Allow-Origin" -const basicAuth = "basicAuth" -const jwtAuth = "jwtAuth" -const OAuth = "OAuth" const serverName = "Goma" diff --git a/util/constants.go b/util/constants.go index 2bcc2a9..e003539 100644 --- a/util/constants.go +++ b/util/constants.go @@ -29,3 +29,7 @@ func FullVersion() string { } return ver } + +const MainExample = "Initialize config: init config --output config.yml\n" + + "Start server: server \n" + + "Start server with custom config file: server --config config.yml"