Merge pull request #18 from jkaninda/refactor

Refactor
This commit is contained in:
2024-10-30 04:57:46 +01:00
committed by GitHub
12 changed files with 71 additions and 55 deletions

View File

@@ -1,8 +1,8 @@
GOMA_LISTEN_ADDR=localhost:80 GOMA_LISTEN_ADDR=0.0.0.0:80
GOMA_CONFIG_FILE=/config/goma.yml
GOMA_ACCESS_LOG=/dev/Stdout
GOMA_ERROR_LOG=/dev/stderr
GOMA_WRITE_TIMEOUT=15 GOMA_WRITE_TIMEOUT=15
GOMA_READ_TIMEOUT=15 GOMA_READ_TIMEOUT=15
GOMA_IDLE_TIMEOUT=30 GOMA_IDLE_TIMEOUT=30
GOMA_RATE_LIMITER=10 GOMA_RATE_LIMITER=10
GOMA_ACCESS_LOG=/dev/Stdout
GOMA_ERROR_LOG=/dev/stderr
GOMA_CONFIG_FILE=/config/goma.yml

View File

@@ -106,7 +106,7 @@ docker run --rm --name goma-gateway \
Create a config file in this format Create a config file in this format
## Customize configuration file ## Customize configuration file
Example of configuration file Example of a configuration file
```yaml ```yaml
## Goma - simple lightweight API Gateway and Reverse Proxy. ## Goma - simple lightweight API Gateway and Reverse Proxy.
# Goma Gateway configurations # Goma Gateway configurations
@@ -119,7 +119,7 @@ gateway:
readTimeout: 15 readTimeout: 15
# Proxy idle timeout # Proxy idle timeout
idleTimeout: 60 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 # Distributed Rate Limiting for Token based across multiple instances is not yet integrated
rateLimiter: 0 rateLimiter: 0
accessLog: "/dev/Stdout" accessLog: "/dev/Stdout"
@@ -128,6 +128,8 @@ gateway:
disableRouteHealthCheckError: false disableRouteHealthCheckError: false
# Disable display routes on start # Disable display routes on start
disableDisplayRouteOnStart: false disableDisplayRouteOnStart: false
# disableKeepAlive allows enabling and disabling KeepALive server
disableKeepAlive: false
# interceptErrors intercepts backend errors based on defined the status codes # interceptErrors intercepts backend errors based on defined the status codes
interceptErrors: interceptErrors:
- 405 - 405
@@ -194,7 +196,7 @@ gateway:
- path: /path-example - path: /path-example
# Rules defines which specific middleware applies to a route path # Rules defines which specific middleware applies to a route path
rules: rules:
- jwtAuth - jwt
# path to protect # path to protect
- path: /admin - path: /admin
# Rules defines which specific middleware applies to a route path # Rules defines which specific middleware applies to a route path
@@ -204,7 +206,7 @@ gateway:
- path: /path-example - path: /path-example
# Rules defines which specific middleware applies to a route path # Rules defines which specific middleware applies to a route path
rules: rules:
- jwtAuth - jwt
- path: /history - path: /history
http: http:
url: http://security-service:8080/security/authUser url: http://security-service:8080/security/authUser
@@ -236,15 +238,15 @@ gateway:
#Defines proxy middlewares #Defines proxy middlewares
middlewares: middlewares:
# Enable Basic auth authorization based # Enable Basic auth authorization based
- name: local-auth-basic - name: basic-auth
# Authentication types | jwtAuth, basicAuth, auth0 # Authentication types | jwt, basic, OAuth
type: basic type: basic
rule: rule:
username: admin username: admin
password: admin password: admin
#Enables JWT authorization based on the result of a request and continues the request. #Enables JWT authorization based on the result of a request and continues the request.
- name: google-auth - 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 # jwt authorization based on the result of backend's response and continue the request when the client is authorized
type: jwt type: jwt
rule: rule:

View File

@@ -23,7 +23,7 @@ import (
var Cmd = &cobra.Command{ var Cmd = &cobra.Command{
Use: "config", Use: "config",
Short: "Goma configuration", Short: "Goma Gateway configuration management",
Run: func(cmd *cobra.Command, args []string) { Run: func(cmd *cobra.Command, args []string) {
if len(args) == 0 { if len(args) == 0 {
return return

View File

@@ -28,7 +28,7 @@ var rootCmd = &cobra.Command{
Use: "goma", Use: "goma",
Short: "Goma Gateway is a lightweight API Gateway, Reverse Proxy", Short: "Goma Gateway is a lightweight API Gateway, Reverse Proxy",
Long: `.`, Long: `.`,
Example: "", Example: util.MainExample,
Version: util.FullVersion(), Version: util.FullVersion(),
} }

View File

@@ -9,7 +9,7 @@ gateway:
readTimeout: 15 readTimeout: 15
# Proxy idle timeout # Proxy idle timeout
idleTimeout: 60 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 # Distributed Rate Limiting for Token based across multiple instances is not yet integrated
rateLimiter: 0 rateLimiter: 0
accessLog: "/dev/Stdout" accessLog: "/dev/Stdout"
@@ -18,6 +18,8 @@ gateway:
disableRouteHealthCheckError: false disableRouteHealthCheckError: false
# Disable display routes on start # Disable display routes on start
disableDisplayRouteOnStart: false disableDisplayRouteOnStart: false
# disableKeepAlive allows enabling and disabling KeepALive server
disableKeepAlive: false
# interceptErrors intercepts backend errors based on defined the status codes # interceptErrors intercepts backend errors based on defined the status codes
interceptErrors: interceptErrors:
- 405 - 405
@@ -84,7 +86,7 @@ gateway:
- path: /path-example - path: /path-example
# Rules defines which specific middleware applies to a route path # Rules defines which specific middleware applies to a route path
rules: rules:
- jwtAuth - jwt
# path to protect # path to protect
- path: /admin - path: /admin
# Rules defines which specific middleware applies to a route path # Rules defines which specific middleware applies to a route path
@@ -94,7 +96,7 @@ gateway:
- path: /path-example - path: /path-example
# Rules defines which specific middleware applies to a route path # Rules defines which specific middleware applies to a route path
rules: rules:
- jwtAuth - jwt
- path: /history - path: /history
http: http:
url: http://security-service:8080/security/authUser url: http://security-service:8080/security/authUser
@@ -126,7 +128,7 @@ gateway:
#Defines proxy middlewares #Defines proxy middlewares
middlewares: middlewares:
# Enable Basic auth authorization based # Enable Basic auth authorization based
- name: local-auth-basic - name: basic-auth
# Authentication types | jwt, basic, OAuth # Authentication types | jwt, basic, OAuth
type: basic type: basic
rule: rule:

View File

@@ -77,6 +77,12 @@ type JWTRuler struct {
//e.g: Header X-Auth-UserId to query userId //e.g: Header X-Auth-UserId to query userId
Params map[string]string `yaml:"params"` 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 // Middleware defined the route middleware
type Middleware struct { type Middleware struct {
@@ -149,18 +155,23 @@ type Gateway struct {
RateLimiter int `yaml:"rateLimiter" env:"GOMA_RATE_LIMITER, overwrite"` RateLimiter int `yaml:"rateLimiter" env:"GOMA_RATE_LIMITER, overwrite"`
AccessLog string `yaml:"accessLog" env:"GOMA_ACCESS_LOG, overwrite"` AccessLog string `yaml:"accessLog" env:"GOMA_ACCESS_LOG, overwrite"`
ErrorLog string `yaml:"errorLog" env:"GOMA_ERROR_LOG=, overwrite"` ErrorLog string `yaml:"errorLog" env:"GOMA_ERROR_LOG=, overwrite"`
// DisableRouteHealthCheckError allows enabling and disabling backend healthcheck errors
DisableRouteHealthCheckError bool `yaml:"disableRouteHealthCheckError"` DisableRouteHealthCheckError bool `yaml:"disableRouteHealthCheckError"`
//Disable dispelling routes on start //Disable allows enabling and disabling displaying routes on start
DisableDisplayRouteOnStart bool `yaml:"disableDisplayRouteOnStart"` 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"` InterceptErrors []int `yaml:"interceptErrors"`
EnableKeepAlive bool `yaml:"enableKeepAlive"` // Cors holds proxy global cors
// Cors contains the proxy global cors
Cors Cors `yaml:"cors"` Cors Cors `yaml:"cors"`
// Routes defines the proxy routes // Routes holds proxy routes
Routes []Route `yaml:"routes"` Routes []Route `yaml:"routes"`
} }
type GatewayConfig struct { type GatewayConfig struct {
// GatewayConfig holds Gateway config
GatewayConfig Gateway `yaml:"gateway"` GatewayConfig Gateway `yaml:"gateway"`
// Middlewares holds proxy middlewares
Middlewares []Middleware `yaml:"middlewares"` Middlewares []Middleware `yaml:"middlewares"`
} }
@@ -365,7 +376,7 @@ func ToJWTRuler(input interface{}) (JWTRuler, error) {
return JWTRuler{}, fmt.Errorf("error parsing yaml: %v", err) return JWTRuler{}, fmt.Errorf("error parsing yaml: %v", err)
} }
if jWTRuler.URL == "" { 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 return *jWTRuler, nil

View File

@@ -30,7 +30,7 @@ func (blockList BlockListMiddleware) BlocklistMiddleware(next http.Handler) http
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
for _, block := range blockList.List { for _, block := range blockList.List {
if isPathBlocked(r.URL.Path, util.ParseURLPath(blockList.Path+block)) { 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.Header().Set("Content-Type", "application/json")
w.WriteHeader(http.StatusNotFound) w.WriteHeader(http.StatusNotFound)
err := json.NewEncoder(w).Encode(ProxyResponseError{ err := json.NewEncoder(w).Encode(ProxyResponseError{

View File

@@ -65,8 +65,8 @@ type ProxyResponseError struct {
Message string `json:"message"` Message string `json:"message"`
} }
// AuthJWT Define struct // JwtAuth Define struct
type AuthJWT struct { type JwtAuth struct {
AuthURL string AuthURL string
RequiredHeaders []string RequiredHeaders []string
Headers map[string]string Headers map[string]string
@@ -97,9 +97,9 @@ type AuthBasic struct {
// AuthMiddleware authenticate the client using JWT // AuthMiddleware authenticate the client using JWT
// //
// authorization based on the result of backend's response and continue the request when the client is authorized // 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) { 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) == "" { if r.Header.Get(header) == "" {
logger.Error("Proxy error, missing %s header", header) logger.Error("Proxy error, missing %s header", header)
w.Header().Set("Content-Type", "application/json") 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") //token := r.Header.Get("Authorization")
authURL, err := url.Parse(amw.AuthURL) authURL, err := url.Parse(jwtAuth.AuthURL)
if err != nil { if err != nil {
logger.Error("Error parsing auth URL: %v", err) logger.Error("Error parsing auth URL: %v", err)
w.Header().Set("Content-Type", "application/json") 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) authResp, err := client.Do(authReq)
if err != nil || authResp.StatusCode != http.StatusOK { if err != nil || authResp.StatusCode != http.StatusOK {
logger.Info("%s %s %s %s", r.Method, r.RemoteAddr, r.URL, r.UserAgent()) 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.Header().Set("Content-Type", "application/json")
w.WriteHeader(http.StatusUnauthorized) w.WriteHeader(http.StatusUnauthorized)
err = json.NewEncoder(w).Encode(ProxyResponseError{ err = json.NewEncoder(w).Encode(ProxyResponseError{
@@ -183,15 +183,15 @@ func (amw AuthJWT) AuthMiddleware(next http.Handler) http.Handler {
}(authResp.Body) }(authResp.Body)
// Inject specific header tp the current request's header // Inject specific header tp the current request's header
// Add header to the next request from AuthRequest header, depending on your requirements // Add header to the next request from AuthRequest header, depending on your requirements
if amw.Headers != nil { if jwtAuth.Headers != nil {
for k, v := range amw.Headers { for k, v := range jwtAuth.Headers {
r.Header.Set(v, authResp.Header.Get(k)) r.Header.Set(v, authResp.Header.Get(k))
} }
} }
query := r.URL.Query() query := r.URL.Query()
// Add query parameters to the next request from AuthRequest header, depending on your requirements // Add query parameters to the next request from AuthRequest header, depending on your requirements
if amw.Params != nil { if jwtAuth.Params != nil {
for k, v := range amw.Params { for k, v := range jwtAuth.Params {
query.Set(v, authResp.Header.Get(k)) 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{ err := json.NewEncoder(w).Encode(ProxyResponseError{
Success: false, Success: false,
Code: http.StatusUnauthorized, Code: http.StatusUnauthorized,
Message: "Unauthorized", Message: http.StatusText(http.StatusUnauthorized),
}) })
if err != nil { if err != nil {
return return
@@ -229,7 +229,7 @@ func (basicAuth AuthBasic) AuthMiddleware(next http.Handler) http.Handler {
err := json.NewEncoder(w).Encode(ProxyResponseError{ err := json.NewEncoder(w).Encode(ProxyResponseError{
Success: false, Success: false,
Code: http.StatusUnauthorized, Code: http.StatusUnauthorized,
Message: "Unauthorized", Message: http.StatusText(http.StatusUnauthorized),
}) })
if err != nil { if err != nil {
return return
@@ -246,7 +246,7 @@ func (basicAuth AuthBasic) AuthMiddleware(next http.Handler) http.Handler {
err := json.NewEncoder(w).Encode(ProxyResponseError{ err := json.NewEncoder(w).Encode(ProxyResponseError{
Success: false, Success: false,
Code: http.StatusUnauthorized, Code: http.StatusUnauthorized,
Message: "Unauthorized", Message: http.StatusText(http.StatusUnauthorized),
}) })
if err != nil { if err != nil {
return return
@@ -263,7 +263,7 @@ func (basicAuth AuthBasic) AuthMiddleware(next http.Handler) http.Handler {
err := json.NewEncoder(w).Encode(ProxyResponseError{ err := json.NewEncoder(w).Encode(ProxyResponseError{
Success: false, Success: false,
Code: http.StatusUnauthorized, Code: http.StatusUnauthorized,
Message: "Unauthorized", Message: http.StatusText(http.StatusUnauthorized),
}) })
if err != nil { if err != nil {
return return

View File

@@ -46,7 +46,7 @@ func (gatewayServer GatewayServer) Initialize() *mux.Router {
Path: route.Path, Path: route.Path,
List: route.Blocklist, List: route.Blocklist,
} }
// Apply route middleware // Apply route middlewares
for _, mid := range route.Middlewares { for _, mid := range route.Middlewares {
if mid.Path != "" { if mid.Path != "" {
secureRouter := r.PathPrefix(util.ParseURLPath(route.Path + mid.Path)).Subrouter() secureRouter := r.PathPrefix(util.ParseURLPath(route.Path + mid.Path)).Subrouter()
@@ -63,7 +63,7 @@ func (gatewayServer GatewayServer) Initialize() *mux.Router {
} else { } else {
//Check Authentication middleware //Check Authentication middleware
switch rMiddleware.Type { switch rMiddleware.Type {
case basicAuth, "basic": case "basic":
basicAuth, err := ToBasicAuth(rMiddleware.Rule) basicAuth, err := ToBasicAuth(rMiddleware.Rule)
if err != nil { if err != nil {
logger.Error("Error: %s", err.Error()) 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
secureRouter.PathPrefix("").Handler(proxyRoute.ProxyHandler()) // Proxy handler secureRouter.PathPrefix("").Handler(proxyRoute.ProxyHandler()) // Proxy handler
} }
case jwtAuth, "jwt": case "jwt":
jwt, err := ToJWTRuler(rMiddleware.Rule) jwt, err := ToJWTRuler(rMiddleware.Rule)
if err != nil { if err != nil {
logger.Error("Error: %s", err.Error()) logger.Error("Error: %s", err.Error())
} else { } else {
amw := middleware.AuthJWT{ amw := middleware.JwtAuth{
AuthURL: jwt.URL, AuthURL: jwt.URL,
RequiredHeaders: jwt.RequiredHeaders, RequiredHeaders: jwt.RequiredHeaders,
Headers: jwt.Headers, Headers: jwt.Headers,
@@ -98,7 +98,7 @@ func (gatewayServer GatewayServer) Initialize() *mux.Router {
secureRouter.PathPrefix("").Handler(proxyRoute.ProxyHandler()) // Proxy handler secureRouter.PathPrefix("").Handler(proxyRoute.ProxyHandler()) // Proxy handler
} }
case OAuth, "auth0": case "OAuth":
logger.Error("OAuth is not yet implemented") logger.Error("OAuth is not yet implemented")
logger.Info("Auth middleware ignored") logger.Info("Auth middleware ignored")
default: default:

View File

@@ -40,7 +40,7 @@ func (gatewayServer GatewayServer) Start(ctx context.Context) error {
printRoute(gatewayServer.gateway.Routes) printRoute(gatewayServer.gateway.Routes)
} }
// Set KeepAlive // Set KeepAlive
srv.SetKeepAlivesEnabled(gatewayServer.gateway.EnableKeepAlive) srv.SetKeepAlivesEnabled(!gatewayServer.gateway.DisableKeepAlive)
go func() { go func() {
logger.Info("Started Goma Gateway server on %v", gatewayServer.gateway.ListenAddr) logger.Info("Started Goma Gateway server on %v", gatewayServer.gateway.ListenAddr)

View File

@@ -2,7 +2,4 @@ package pkg
const ConfigFile = "/config/goma.yml" const ConfigFile = "/config/goma.yml"
const accessControlAllowOrigin = "Access-Control-Allow-Origin" const accessControlAllowOrigin = "Access-Control-Allow-Origin"
const basicAuth = "basicAuth"
const jwtAuth = "jwtAuth"
const OAuth = "OAuth"
const serverName = "Goma" const serverName = "Goma"

View File

@@ -29,3 +29,7 @@ func FullVersion() string {
} }
return ver 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"