refactor: refactoring of code to meet all golangci-lint requirements

This commit is contained in:
Jonas Kaninda
2024-11-17 05:28:27 +01:00
parent 319634670c
commit c76cf5bd41
24 changed files with 236 additions and 176 deletions

View File

@@ -52,7 +52,7 @@ func CheckConfig(fileName string) error {
}
}
//Check middlewares
// Check middlewares
for index, mid := range c.Middlewares {
if util.HasWhitespace(mid.Name) {
fmt.Printf("Warning: Middleware contains whitespace: %s | index: [%d], please remove whitespace characters\n", mid.Name, index)

View File

@@ -76,7 +76,7 @@ func (GatewayServer) Config(configFile string, ctx context.Context) (*GatewaySer
}
logger.Info("Generating new configuration file...")
//check if config directory does exist
// check if config directory does exist
if !util.FolderExists(ConfigDir) {
err := os.MkdirAll(ConfigDir, os.ModePerm)
if err != nil {

View File

@@ -19,16 +19,16 @@ package pkg
type Cors struct {
// Cors Allowed origins,
//e.g:
// e.g:
//
// - http://localhost:80
//
// - https://example.com
Origins []string `yaml:"origins"`
//
//e.g:
// e.g:
//
//Access-Control-Allow-Origin: '*'
// Access-Control-Allow-Origin: '*'
//
// Access-Control-Allow-Methods: 'GET, POST, PUT, DELETE, OPTIONS'
//

View File

@@ -42,7 +42,7 @@ type Gateway struct {
DisableHealthCheckStatus bool `yaml:"disableHealthCheckStatus"`
// DisableRouteHealthCheckError allows enabling and disabling backend healthcheck errors
DisableRouteHealthCheckError bool `yaml:"disableRouteHealthCheckError"`
//Disable allows enabling and disabling displaying routes on start
// Disable allows enabling and disabling displaying routes on start
DisableDisplayRouteOnStart bool `yaml:"disableDisplayRouteOnStart"`
// DisableKeepAlive allows enabling and disabling KeepALive server
DisableKeepAlive bool `yaml:"disableKeepAlive"`

View File

@@ -32,11 +32,11 @@ func CORSHandler(cors Cors) mux.MiddlewareFunc {
return func(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
// Set CORS headers from the cors config
//Update Cors Headers
// Update Cors Headers
for k, v := range cors.Headers {
w.Header().Set(k, v)
}
//Update Origin Cors Headers
// Update Origin Cors Headers
if allowedOrigin(cors.Origins, r.Header.Get("Origin")) {
// Handle preflight requests (OPTIONS)
if r.Method == "OPTIONS" {
@@ -90,7 +90,7 @@ func (heathRoute HealthCheckRoute) HealthCheckHandler(w http.ResponseWriter, r *
}
wg.Wait() // Wait for all requests to complete
response := HealthCheckResponse{
Status: "healthy", //Goma proxy
Status: "healthy", // Goma proxy
Routes: routes, // Routes health check
}
w.Header().Set("Content-Type", "application/json")

View File

@@ -76,7 +76,7 @@ func (oauth *OauthRulerMiddleware) getUserInfo(token *oauth2.Token) (UserInfo, e
defer func(Body io.ReadCloser) {
err := Body.Close()
if err != nil {
return
}
}(resp.Body)

View File

@@ -18,11 +18,12 @@
package metrics
import (
"net/http"
"strconv"
"github.com/gorilla/mux"
"github.com/prometheus/client_golang/prometheus"
"github.com/prometheus/client_golang/prometheus/promauto"
"net/http"
"strconv"
)
type PrometheusRoute struct {

View File

@@ -19,7 +19,7 @@ package pkg
// Middleware defined the route middlewares
type Middleware struct {
//Path contains the name of middlewares and must be unique
// Path contains the name of middlewares and must be unique
Name string `yaml:"name"`
// Type contains authentication types
//

View File

@@ -33,7 +33,7 @@ func (jwtAuth JwtAuth) AuthMiddleware(next http.Handler) http.Handler {
if r.Header.Get(header) == "" {
logger.Error("Proxy error, missing %s header", header)
w.Header().Set("Content-Type", "application/json")
//check allowed origin
// check allowed origin
if allowedOrigin(jwtAuth.Origins, r.Header.Get("Origin")) {
w.Header().Set("Access-Control-Allow-Origin", r.Header.Get("Origin"))
}
@@ -42,7 +42,6 @@ func (jwtAuth JwtAuth) AuthMiddleware(next http.Handler) http.Handler {
}
}
//token := r.Header.Get("Authorization")
authURL, err := url.Parse(jwtAuth.AuthURL)
if err != nil {
logger.Error("Error parsing auth URL: %v", err)

View File

@@ -29,8 +29,6 @@ func (rl *TokenRateLimiter) RateLimitMiddleware() mux.MiddlewareFunc {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if !rl.Allow() {
logger.Error("Too many requests from IP: %s %s %s", getRealIP(r), r.URL, r.UserAgent())
//RespondWithError(w, http.StatusUnauthorized, http.StatusText(http.StatusUnauthorized), basicAuth.ErrorInterceptor)
// Rate limit exceeded, return a 429 Too Many Requests response
w.WriteHeader(http.StatusTooManyRequests)
_, err := w.Write([]byte(fmt.Sprintf("%d Too many requests, API requests limit exceeded. Please try again later", http.StatusTooManyRequests)))
@@ -75,7 +73,7 @@ func (rl *RateLimiter) RateLimitMiddleware() mux.MiddlewareFunc {
if client.RequestCount > rl.requests {
logger.Error("Too many requests from IP: %s %s %s", clientIP, r.URL, r.UserAgent())
//Update Origin Cors Headers
// Update Origin Cors Headers
if allowedOrigin(rl.origins, r.Header.Get("Origin")) {
w.Header().Set("Access-Control-Allow-Origin", r.Header.Get("Origin"))
}

View File

@@ -26,13 +26,12 @@ import (
// RateLimiter defines requests limit properties.
type RateLimiter struct {
requests int
id string
window time.Duration
clientMap map[string]*Client
mu sync.Mutex
origins []string
//hosts []string
requests int
id string
window time.Duration
clientMap map[string]*Client
mu sync.Mutex
origins []string
redisBased bool
}

View File

@@ -44,7 +44,7 @@ func (proxyRoute ProxyRoute) ProxyHandler() http.HandlerFunc {
}
}
// Set CORS headers from the cors config
//Update Cors Headers
// Update Cors Headers
for k, v := range proxyRoute.cors.Headers {
w.Header().Set(k, v)
}
@@ -87,7 +87,7 @@ func (proxyRoute ProxyRoute) ProxyHandler() http.HandlerFunc {
InsecureSkipVerify: proxyRoute.insecureSkipVerify,
},
}
w.Header().Set("Proxied-By", gatewayName) //Set Server name
w.Header().Set("Proxied-By", gatewayName) // Set Server name
w.Header().Del("Server") // Remove the Server header
// Custom error handler for proxy errors
proxy.ErrorHandler = ProxyErrorHandler

View File

@@ -22,13 +22,12 @@ import (
"github.com/jkaninda/goma-gateway/pkg/logger"
)
func (gatewayServer GatewayServer) initRedis() error {
if gatewayServer.gateway.Redis.Addr == "" {
return nil
func (gatewayServer GatewayServer) initRedis() {
if len(gatewayServer.gateway.Redis.Addr) != 0 {
logger.Info("Initializing Redis...")
middlewares.InitRedis(gatewayServer.gateway.Redis.Addr, gatewayServer.gateway.Redis.Password)
}
logger.Info("Initializing Redis...")
middlewares.InitRedis(gatewayServer.gateway.Redis.Addr, gatewayServer.gateway.Redis.Password)
return nil
}
func (gatewayServer GatewayServer) closeRedis() {

View File

@@ -32,7 +32,7 @@ func init() {
_ = prometheus.Register(metrics.HttpDuration)
}
// Initialize the routes
// Initialize initializes the routes
func (gatewayServer GatewayServer) Initialize() *mux.Router {
gateway := gatewayServer.gateway
m := gatewayServer.middlewares
@@ -40,8 +40,9 @@ func (gatewayServer GatewayServer) Initialize() *mux.Router {
if len(gateway.Redis.Addr) != 0 {
redisBased = true
}
//Routes background healthcheck
// Routes background healthcheck
routesHealthCheck(gateway.Routes)
r := mux.NewRouter()
heath := HealthCheckRoute{
DisableRouteHealthCheckError: gateway.DisableRouteHealthCheckError,
@@ -68,7 +69,7 @@ func (gatewayServer GatewayServer) Initialize() *mux.Router {
if gateway.RateLimit != 0 {
// Add rate limit middlewares to all routes, if defined
rateLimit := middlewares.RateLimit{
Id: "global_rate", //Generate a unique ID for routes
Id: "global_rate", // Generate a unique ID for routes
Requests: gateway.RateLimit,
Window: time.Minute, // requests per minute
Origins: gateway.Cors.Origins,
@@ -80,16 +81,17 @@ func (gatewayServer GatewayServer) Initialize() *mux.Router {
r.Use(limiter.RateLimitMiddleware())
}
for rIndex, route := range gateway.Routes {
if route.Path != "" {
if route.Destination == "" && len(route.Backends) == 0 {
if len(route.Path) != 0 {
// Checks if route destination and backend are empty
if len(route.Destination) == 0 && len(route.Backends) == 0 {
logger.Fatal("Route %s : destination or backends should not be empty", route.Name)
}
// Apply middlewares to route
for _, mid := range route.Middlewares {
if mid != "" {
// Apply middlewares to the route
for _, middleware := range route.Middlewares {
if middleware != "" {
// Get Access middlewares if it does exist
accessMiddleware, err := getMiddleware([]string{mid}, m)
accessMiddleware, err := getMiddleware([]string{middleware}, m)
if err != nil {
logger.Error("Error: %v", err.Error())
} else {
@@ -105,114 +107,12 @@ func (gatewayServer GatewayServer) Initialize() *mux.Router {
}
// Get route authentication middlewares if it does exist
rMiddleware, err := getMiddleware([]string{mid}, m)
routeMiddleware, err := getMiddleware([]string{middleware}, m)
if err != nil {
//Error: middlewares not found
// Error: middlewares 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,
backends: route.Backends,
disableHostFording: route.DisableHostFording,
methods: route.Methods,
cors: route.Cors,
insecureSkipVerify: route.InsecureSkipVerify,
}
secureRouter := r.PathPrefix(util.ParseRoutePath(route.Path, midPath)).Subrouter()
//callBackRouter := r.PathPrefix(util.ParseRoutePath(route.Path, "/callback")).Subrouter()
//Check Authentication middlewares
switch rMiddleware.Type {
case BasicAuth:
basicAuth, err := getBasicAuthMiddleware(rMiddleware.Rule)
if err != nil {
logger.Error("Error: %s", err.Error())
} else {
amw := middlewares.AuthBasic{
Username: basicAuth.Username,
Password: basicAuth.Password,
Headers: nil,
Params: nil,
}
// Apply JWT authentication middlewares
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 := middlewares.JwtAuth{
AuthURL: jwt.URL,
RequiredHeaders: jwt.RequiredHeaders,
Headers: jwt.Headers,
Params: jwt.Params,
Origins: gateway.Cors.Origins,
}
// Apply JWT authentication middlewares
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, "openid":
oauth, err := oAuthMiddleware(rMiddleware.Rule)
if err != nil {
logger.Error("Error: %s", err.Error())
} else {
redirectURL := "/callback" + route.Path
if oauth.RedirectURL != "" {
redirectURL = oauth.RedirectURL
}
amw := middlewares.Oauth{
ClientID: oauth.ClientID,
ClientSecret: oauth.ClientSecret,
RedirectURL: redirectURL,
Scopes: oauth.Scopes,
Endpoint: middlewares.OauthEndpoint{
AuthURL: oauth.Endpoint.AuthURL,
TokenURL: oauth.Endpoint.TokenURL,
UserInfoURL: oauth.Endpoint.UserInfoURL,
},
State: oauth.State,
Origins: gateway.Cors.Origins,
JWTSecret: oauth.JWTSecret,
Provider: oauth.Provider,
}
oauthRuler := oauthRulerMiddleware(amw)
// Check if a cookie path is defined
if oauthRuler.CookiePath == "" {
oauthRuler.CookiePath = route.Path
}
// Check if a RedirectPath is defined
if oauthRuler.RedirectPath == "" {
oauthRuler.RedirectPath = util.ParseRoutePath(route.Path, midPath)
}
if oauthRuler.Provider == "" {
oauthRuler.Provider = "custom"
}
secureRouter.Use(amw.AuthMiddleware)
secureRouter.Use(CORSHandler(route.Cors))
secureRouter.PathPrefix("/").Handler(proxyRoute.ProxyHandler()) // Proxy handler
secureRouter.PathPrefix("").Handler(proxyRoute.ProxyHandler()) // Proxy handler
// Callback route
r.HandleFunc(util.UrlParsePath(redirectURL), oauthRuler.callbackHandler).Methods("GET")
}
default:
if !doesExist(rMiddleware.Type) {
logger.Error("Unknown middlewares type %s", rMiddleware.Type)
}
}
}
attachAuthMiddlewares(route, routeMiddleware, gateway, r)
}
} else {
logger.Error("Error, middlewares path is empty")
@@ -300,3 +200,107 @@ func (gatewayServer GatewayServer) Initialize() *mux.Router {
return r
}
func attachAuthMiddlewares(route Route, routeMiddleware Middleware, gateway Gateway, r *mux.Router) {
for _, middlewarePath := range routeMiddleware.Paths {
proxyRoute := ProxyRoute{
path: route.Path,
rewrite: route.Rewrite,
destination: route.Destination,
backends: route.Backends,
disableHostFording: route.DisableHostFording,
methods: route.Methods,
cors: route.Cors,
insecureSkipVerify: route.InsecureSkipVerify,
}
secureRouter := r.PathPrefix(util.ParseRoutePath(route.Path, middlewarePath)).Subrouter()
// Check Authentication middleware types
switch routeMiddleware.Type {
case BasicAuth:
basicAuth, err := getBasicAuthMiddleware(routeMiddleware.Rule)
if err != nil {
logger.Error("Error: %s", err.Error())
} else {
authBasic := middlewares.AuthBasic{
Username: basicAuth.Username,
Password: basicAuth.Password,
Headers: nil,
Params: nil,
}
// Apply JWT authentication middlewares
secureRouter.Use(authBasic.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(routeMiddleware.Rule)
if err != nil {
logger.Error("Error: %s", err.Error())
} else {
jwtAuth := middlewares.JwtAuth{
AuthURL: jwt.URL,
RequiredHeaders: jwt.RequiredHeaders,
Headers: jwt.Headers,
Params: jwt.Params,
Origins: gateway.Cors.Origins,
}
// Apply JWT authentication middlewares
secureRouter.Use(jwtAuth.AuthMiddleware)
secureRouter.Use(CORSHandler(route.Cors))
secureRouter.PathPrefix("/").Handler(proxyRoute.ProxyHandler()) // Proxy handler
secureRouter.PathPrefix("").Handler(proxyRoute.ProxyHandler()) // Proxy handler
}
case OAuth:
oauth, err := oAuthMiddleware(routeMiddleware.Rule)
if err != nil {
logger.Error("Error: %s", err.Error())
} else {
redirectURL := "/callback" + route.Path
if oauth.RedirectURL != "" {
redirectURL = oauth.RedirectURL
}
amw := middlewares.Oauth{
ClientID: oauth.ClientID,
ClientSecret: oauth.ClientSecret,
RedirectURL: redirectURL,
Scopes: oauth.Scopes,
Endpoint: middlewares.OauthEndpoint{
AuthURL: oauth.Endpoint.AuthURL,
TokenURL: oauth.Endpoint.TokenURL,
UserInfoURL: oauth.Endpoint.UserInfoURL,
},
State: oauth.State,
Origins: gateway.Cors.Origins,
JWTSecret: oauth.JWTSecret,
Provider: oauth.Provider,
}
oauthRuler := oauthRulerMiddleware(amw)
// Check if a cookie path is defined
if oauthRuler.CookiePath == "" {
oauthRuler.CookiePath = route.Path
}
// Check if a RedirectPath is defined
if oauthRuler.RedirectPath == "" {
oauthRuler.RedirectPath = util.ParseRoutePath(route.Path, middlewarePath)
}
if oauthRuler.Provider == "" {
oauthRuler.Provider = "custom"
}
secureRouter.Use(amw.AuthMiddleware)
secureRouter.Use(CORSHandler(route.Cors))
secureRouter.PathPrefix("/").Handler(proxyRoute.ProxyHandler()) // Proxy handler
secureRouter.PathPrefix("").Handler(proxyRoute.ProxyHandler()) // Proxy handler
// Callback route
r.HandleFunc(util.UrlParsePath(redirectURL), oauthRuler.callbackHandler).Methods("GET")
}
default:
if !doesExist(routeMiddleware.Type) {
logger.Error("Unknown middlewares type %s", routeMiddleware.Type)
}
}
}
}

View File

@@ -23,9 +23,9 @@ type Route struct {
Path string `yaml:"path"`
// Name defines route name
Name string `yaml:"name"`
//Host Domain/host based request routing
//Host string `yaml:"host"`
//Hosts Domains/hosts based request routing
// Host Domain/host based request routing
// Host string `yaml:"host"`
// Hosts Domains/hosts based request routing
Hosts []string `yaml:"hosts"`
// Rewrite rewrites route path to desired path
//

View File

@@ -19,7 +19,6 @@ import (
"context"
"crypto/tls"
"errors"
"fmt"
"github.com/jkaninda/goma-gateway/pkg/logger"
"net/http"
"os"
@@ -33,9 +32,7 @@ func (gatewayServer GatewayServer) Start() error {
logger.Info("Initializing routes...")
route := gatewayServer.Initialize()
logger.Debug("Routes count=%d, Middlewares count=%d", len(gatewayServer.gateway.Routes), len(gatewayServer.middlewares))
if err := gatewayServer.initRedis(); err != nil {
return fmt.Errorf("failed to initialize Redis: %w", err)
}
gatewayServer.initRedis()
defer gatewayServer.closeRedis()
tlsConfig, listenWithTLS, err := gatewayServer.initTLS()
@@ -51,9 +48,7 @@ func (gatewayServer GatewayServer) Start() error {
httpsServer := gatewayServer.createServer(":8443", route, tlsConfig)
// Start HTTP/HTTPS servers
if err := gatewayServer.startServers(httpServer, httpsServer, listenWithTLS); err != nil {
return err
}
gatewayServer.startServers(httpServer, httpsServer, listenWithTLS)
// Handle graceful shutdown
return gatewayServer.shutdown(httpServer, httpsServer, listenWithTLS)
@@ -70,7 +65,7 @@ func (gatewayServer GatewayServer) createServer(addr string, handler http.Handle
}
}
func (gatewayServer GatewayServer) startServers(httpServer, httpsServer *http.Server, listenWithTLS bool) error {
func (gatewayServer GatewayServer) startServers(httpServer, httpsServer *http.Server, listenWithTLS bool) {
go func() {
logger.Info("Starting HTTP server on 0.0.0.0:8080")
if err := httpServer.ListenAndServe(); err != nil && !errors.Is(err, http.ErrServerClosed) {
@@ -86,8 +81,6 @@ func (gatewayServer GatewayServer) startServers(httpServer, httpsServer *http.Se
}
}()
}
return nil
}
func (gatewayServer GatewayServer) shutdown(httpServer, httpsServer *http.Server, listenWithTLS bool) error {

View File

@@ -47,7 +47,7 @@ type JWTRuleMiddleware struct {
//
// In case you want to get headers from Authentication service and inject them to next request's params.
//
//e.g: Header X-Auth-UserId to query userId
// e.g: Header X-Auth-UserId to query userId
Params map[string]string `yaml:"params"`
}
type OauthRulerMiddleware struct {
@@ -66,7 +66,7 @@ type OauthRulerMiddleware struct {
RedirectURL string `yaml:"redirectUrl"`
// RedirectPath is the PATH to redirect users after authentication, e.g: /my-protected-path/dashboard
RedirectPath string `yaml:"redirectPath"`
//CookiePath e.g: /my-protected-path or / || by default is applied on a route path
// CookiePath e.g: /my-protected-path or / || by default is applied on a route path
CookiePath string `yaml:"cookiePath"`
// Scope specifies optional requested permissions.
@@ -119,11 +119,10 @@ type GatewayServer struct {
middlewares []Middleware
}
type ProxyRoute struct {
path string
rewrite string
destination string
backends []string
//healthCheck RouteHealthCheck
path string
rewrite string
destination string
backends []string
methods []string
cors Cors
disableHostFording bool