12
.github/workflows/release.yml
vendored
12
.github/workflows/release.yml
vendored
@@ -2,7 +2,6 @@ name: CI
|
||||
on:
|
||||
push:
|
||||
tags:
|
||||
- 0.**
|
||||
- v0.**
|
||||
jobs:
|
||||
docker:
|
||||
@@ -26,7 +25,12 @@ jobs:
|
||||
-
|
||||
name: Get the tag name
|
||||
id: get_tag_name
|
||||
run: echo "TAG_NAME=${GITHUB_REF#refs/tags/}" >> $GITHUB_ENV
|
||||
run: |
|
||||
# Extract tag name and remove "v" if present
|
||||
TAG_NAME="${GITHUB_REF#refs/tags/}"
|
||||
VERSION="${TAG_NAME#v}"
|
||||
echo "VERSION=${VERSION}" >> $GITHUB_ENV
|
||||
echo "BUILD_TIME=${(date -u +"%Y-%m-%dT%H:%M:%SZ")}"
|
||||
-
|
||||
name: Build and push
|
||||
uses: docker/build-push-action@v3
|
||||
@@ -35,8 +39,8 @@ jobs:
|
||||
file: "./Dockerfile"
|
||||
platforms: linux/amd64,linux/arm64,linux/arm/v7
|
||||
build-args: |
|
||||
appVersion=${{ env.TAG_NAME }}
|
||||
appVersion=${{ env.VERSION }}
|
||||
tags: |
|
||||
"${{vars.BUILDKIT_IMAGE}}:${{ env.TAG_NAME }}"
|
||||
"${{vars.BUILDKIT_IMAGE}}:${{ env.VERSION }}"
|
||||
"${{vars.BUILDKIT_IMAGE}}:latest"
|
||||
|
||||
|
||||
11
README.md
11
README.md
@@ -92,8 +92,8 @@ docker run --rm --name goma-gateway \
|
||||
```
|
||||
### 4. Healthcheck
|
||||
|
||||
- Goma Gateway readiness: `/readyz`
|
||||
- Routes health check: `/healthz`
|
||||
- Goma Gateway health check: `/health/live`
|
||||
- Routes health check: `health/routes`
|
||||
|
||||
### 5. Simple deployment in docker compose file
|
||||
|
||||
@@ -103,7 +103,7 @@ services:
|
||||
image: jkaninda/goma-gateway
|
||||
command: server
|
||||
healthcheck:
|
||||
test: curl -f http://localhost/readyz || exit 1
|
||||
test: curl -f http://localhost/heath/live || exit 1
|
||||
interval: 30s
|
||||
retries: 5
|
||||
start_period: 20s
|
||||
@@ -119,10 +119,8 @@ Create a config file in this format
|
||||
|
||||
Example of a configuration file
|
||||
```yaml
|
||||
# Goma Gateway configurations
|
||||
## Goma Gateway configurations
|
||||
gateway:
|
||||
########## Global settings
|
||||
listenAddr: :80 #:443 SSL
|
||||
# Proxy write timeout
|
||||
writeTimeout: 15
|
||||
# Proxy read timeout
|
||||
@@ -134,7 +132,6 @@ gateway:
|
||||
## SSL Private Key file
|
||||
sslKeyFile: ''#key.pem
|
||||
# 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"
|
||||
errorLog: "/dev/stderr"
|
||||
|
||||
@@ -19,7 +19,6 @@ Goma Gateway supports :
|
||||
- JWT `client authorization based on the result of a request`
|
||||
- Basic-Auth
|
||||
- Rate limiting middleware
|
||||
- In-Memory Token Bucket based
|
||||
- In-Memory client IP based
|
||||
- Access middleware
|
||||
|
||||
|
||||
@@ -28,8 +28,8 @@ docker run --rm --name goma-gateway \
|
||||
```
|
||||
### 4. Healthcheck
|
||||
|
||||
- Goma Gateway readiness: `/readyz`
|
||||
- Routes health check: `/healthz`
|
||||
- Goma Gateway health check: `/health/live`
|
||||
- Routes health check: `health/routes`
|
||||
|
||||
### 5. Simple deployment in docker compose file
|
||||
|
||||
@@ -46,6 +46,7 @@ services:
|
||||
timeout: 10s
|
||||
ports:
|
||||
- "80:80"
|
||||
- "443:443"
|
||||
volumes:
|
||||
- ./config:/config/
|
||||
```
|
||||
@@ -56,8 +57,6 @@ Example of a configuration file
|
||||
```yaml
|
||||
# Goma Gateway configurations
|
||||
gateway:
|
||||
########## Global settings
|
||||
listenAddr: :80 #:443 SSL
|
||||
# Proxy write timeout
|
||||
writeTimeout: 15
|
||||
# Proxy read timeout
|
||||
@@ -69,7 +68,6 @@ gateway:
|
||||
## SSL Private Key file
|
||||
sslKeyFile: ''#key.pem
|
||||
# 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"
|
||||
errorLog: "/dev/stderr"
|
||||
|
||||
165
docs/route.md
165
docs/route.md
@@ -13,6 +13,46 @@ The Route allows you to match on HTTP traffic and direct it to the backend.
|
||||
### Create a route
|
||||
|
||||
```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
|
||||
# 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
|
||||
@@ -32,119 +72,32 @@ The Route allows you to match on HTTP traffic and direct it to the backend.
|
||||
healthCheck: '' #/internal/health/ready
|
||||
# Route Cors, global cors will be overridden by route
|
||||
cors:
|
||||
# Route Origins Cors, global cors will be overridden by route
|
||||
# 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
|
||||
# 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
|
||||
##### Define route middlewares from middlewares names
|
||||
##### Apply middlewares to the route
|
||||
## The name must be unique
|
||||
## List of middleware name
|
||||
middlewares:
|
||||
- api-forbidden-paths
|
||||
- basic-auth
|
||||
```
|
||||
|
||||
### Full example of route
|
||||
|
||||
```yaml
|
||||
## Goma - simple lightweight API Gateway and Reverse Proxy.
|
||||
# Goma Gateway configurations
|
||||
gateway:
|
||||
########## Global settings
|
||||
listenAddr: 0.0.0.0:80
|
||||
# Proxy write timeout
|
||||
writeTimeout: 15
|
||||
# Proxy read timeout
|
||||
readTimeout: 15
|
||||
# Proxy idle timeout
|
||||
idleTimeout: 60
|
||||
# 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"
|
||||
errorLog: "/dev/stderr"
|
||||
## Returns backend route healthcheck errors
|
||||
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
|
||||
- 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, global cors will be overridden by route
|
||||
origins:
|
||||
- https://dev.example.com
|
||||
- http://localhost:3000
|
||||
- https://example.com
|
||||
# Route Cors headers, route will override global cors
|
||||
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
|
||||
##### Define route middlewares from middlewares names
|
||||
## The name must be unique
|
||||
## List of middleware name
|
||||
middlewares:
|
||||
- api-forbidden-paths
|
||||
- basic-auth
|
||||
# Example of a route | 2
|
||||
- name: Authentication service
|
||||
path: /auth
|
||||
rewrite: /
|
||||
destination: https://example.com
|
||||
healthCheck: /
|
||||
cors: {}
|
||||
middlewares:
|
||||
- api-forbidden-paths
|
||||
# Example of a route | 3
|
||||
- name: Basic auth
|
||||
path: /protected
|
||||
rewrite: /
|
||||
destination: 'http://notification-service:8080'
|
||||
destination: https://example.com
|
||||
healthCheck:
|
||||
cors: {}
|
||||
middlewares: []
|
||||
middlewares:
|
||||
- api-forbidden-paths
|
||||
- basic-auth
|
||||
|
||||
#Defines proxy middlewares
|
||||
# middleware name must be unique
|
||||
@@ -176,26 +129,22 @@ middlewares:
|
||||
# Required headers, if not present in the request, the proxy will return 403
|
||||
requiredHeaders:
|
||||
- Authorization
|
||||
#Sets the request variable to the given value after the authorization request completes.
|
||||
#
|
||||
# Add header to the next request from AuthRequest header, depending on your requirements
|
||||
# Key is AuthRequest's response header Key, and value is Request's header Key
|
||||
# In case you want to get headers from the Authentication service and inject them into the next request's headers
|
||||
#Sets the request variable to the given value after the authorization request completes.
|
||||
#
|
||||
# Add header to the next request from AuthRequest header, depending on your requirements
|
||||
# Key is AuthRequest's response header Key, and value is Request's header Key
|
||||
# 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: X-Auth-UserId
|
||||
userCountryId: X-Auth-UserCountryId
|
||||
# In case you want to get headers from the Authentication service and inject them to the next request params.
|
||||
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
|
||||
# The server will return 403
|
||||
- name: api-forbidden-paths
|
||||
type: access
|
||||
## prevents access paths
|
||||
## prevents access paths
|
||||
paths:
|
||||
- /swagger-ui/*
|
||||
- /v2/swagger-ui/*
|
||||
|
||||
@@ -10,5 +10,6 @@ services:
|
||||
timeout: 10s
|
||||
ports:
|
||||
- "80:80"
|
||||
- "443:443"
|
||||
volumes:
|
||||
- ./config:/config/
|
||||
@@ -4,20 +4,24 @@ metadata:
|
||||
name: goma-config
|
||||
data:
|
||||
goma.yml: |
|
||||
# Goma Gateway configurations
|
||||
gateway:
|
||||
########## Global settings
|
||||
listenAddr: 0.0.0.0:80
|
||||
# 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
|
||||
# Distributed Rate Limiting for Token based across multiple instances is not yet integrated
|
||||
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
|
||||
@@ -59,23 +63,23 @@ data:
|
||||
healthCheck: '' #/internal/health/ready
|
||||
# Route Cors, global cors will be overridden by route
|
||||
cors:
|
||||
# Route Origins Cors, global cors will be overridden by route
|
||||
# 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
|
||||
# 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
|
||||
##### Define route middlewares from middlewares names
|
||||
##### Apply middlewares to the route
|
||||
## The name must be unique
|
||||
## List of middleware name
|
||||
middlewares:
|
||||
- api-forbidden-paths
|
||||
# Example of a route | 3
|
||||
# Example of a route | 2
|
||||
- name: Basic auth
|
||||
path: /protected
|
||||
rewrite: /
|
||||
@@ -116,20 +120,16 @@ data:
|
||||
# Required headers, if not present in the request, the proxy will return 403
|
||||
requiredHeaders:
|
||||
- Authorization
|
||||
#Sets the request variable to the given value after the authorization request completes.
|
||||
#
|
||||
# Add header to the next request from AuthRequest header, depending on your requirements
|
||||
# Key is AuthRequest's response header Key, and value is Request's header Key
|
||||
# In case you want to get headers from the Authentication service and inject them into the next request's headers
|
||||
#Sets the request variable to the given value after the authorization request completes.
|
||||
#
|
||||
# Add header to the next request from AuthRequest header, depending on your requirements
|
||||
# Key is AuthRequest's response header Key, and value is Request's header Key
|
||||
# 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: X-Auth-UserId
|
||||
userCountryId: X-Auth-UserCountryId
|
||||
# In case you want to get headers from the Authentication service and inject them to the next request params.
|
||||
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
|
||||
|
||||
@@ -23,14 +23,14 @@ spec:
|
||||
- containerPort: 80
|
||||
livenessProbe:
|
||||
httpGet:
|
||||
path: /healthz
|
||||
path: /health/live
|
||||
port: 80
|
||||
initialDelaySeconds: 15
|
||||
periodSeconds: 30
|
||||
timeoutSeconds: 10
|
||||
readinessProbe:
|
||||
httpGet:
|
||||
path: /readyz
|
||||
path: /health/live
|
||||
port: 80
|
||||
initialDelaySeconds: 15
|
||||
periodSeconds: 40
|
||||
|
||||
3
goma.yml
3
goma.yml
@@ -1,7 +1,5 @@
|
||||
# Goma Gateway configurations
|
||||
gateway:
|
||||
########## Global settings
|
||||
listenAddr: :80 #:443 SSL
|
||||
# Proxy write timeout
|
||||
writeTimeout: 15
|
||||
# Proxy read timeout
|
||||
@@ -13,7 +11,6 @@ gateway:
|
||||
## SSL Private Key file
|
||||
sslKeyFile: ''#key.pem
|
||||
# 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"
|
||||
errorLog: "/dev/stderr"
|
||||
|
||||
@@ -85,7 +85,6 @@ func initConfig(configFile string) {
|
||||
}
|
||||
conf := &GatewayConfig{
|
||||
GatewayConfig: Gateway{
|
||||
ListenAddr: ":80",
|
||||
WriteTimeout: 15,
|
||||
ReadTimeout: 15,
|
||||
IdleTimeout: 60,
|
||||
|
||||
@@ -49,6 +49,10 @@ func (intercept InterceptErrors) ErrorInterceptor(next http.Handler) http.Handle
|
||||
logger.Error("Backend error")
|
||||
logger.Error("An error occurred from the backend with the status code: %d", rec.statusCode)
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
//Update Origin Cors Headers
|
||||
if allowedOrigin(intercept.Origins, r.Header.Get("Origin")) {
|
||||
w.Header().Set("Access-Control-Allow-Origin", r.Header.Get("Origin"))
|
||||
}
|
||||
w.WriteHeader(rec.statusCode)
|
||||
err := json.NewEncoder(w).Encode(ProxyResponseError{
|
||||
Success: false,
|
||||
|
||||
40
internal/middleware/helpers.go
Normal file
40
internal/middleware/helpers.go
Normal file
@@ -0,0 +1,40 @@
|
||||
/*
|
||||
* Copyright 2024 Jonas Kaninda
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*
|
||||
*/
|
||||
|
||||
package middleware
|
||||
|
||||
import "net/http"
|
||||
|
||||
func getRealIP(r *http.Request) string {
|
||||
if ip := r.Header.Get("X-Real-IP"); ip != "" {
|
||||
return ip
|
||||
}
|
||||
if ip := r.Header.Get("X-Forwarded-For"); ip != "" {
|
||||
return ip
|
||||
}
|
||||
return r.RemoteAddr
|
||||
}
|
||||
func allowedOrigin(origins []string, origin string) bool {
|
||||
for _, o := range origins {
|
||||
if o == origin {
|
||||
return true
|
||||
}
|
||||
continue
|
||||
}
|
||||
return false
|
||||
|
||||
}
|
||||
@@ -34,6 +34,10 @@ 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")
|
||||
//Update Origin Cors Headers
|
||||
if allowedOrigin(jwtAuth.Origins, r.Header.Get("Origin")) {
|
||||
w.Header().Set("Access-Control-Allow-Origin", r.Header.Get("Origin"))
|
||||
}
|
||||
w.WriteHeader(http.StatusUnauthorized)
|
||||
err := json.NewEncoder(w).Encode(ProxyResponseError{
|
||||
Message: http.StatusText(http.StatusUnauthorized),
|
||||
|
||||
@@ -69,6 +69,10 @@ func (rl *RateLimiter) RateLimitMiddleware() mux.MiddlewareFunc {
|
||||
logger.Error("Too many requests from IP: %s %s %s", clientID, r.URL, r.UserAgent())
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
w.WriteHeader(http.StatusTooManyRequests)
|
||||
//Update Origin Cors Headers
|
||||
if allowedOrigin(rl.Origins, r.Header.Get("Origin")) {
|
||||
w.Header().Set("Access-Control-Allow-Origin", r.Header.Get("Origin"))
|
||||
}
|
||||
err := json.NewEncoder(w).Encode(ProxyResponseError{
|
||||
Success: false,
|
||||
Code: http.StatusTooManyRequests,
|
||||
@@ -84,12 +88,3 @@ func (rl *RateLimiter) RateLimitMiddleware() mux.MiddlewareFunc {
|
||||
})
|
||||
}
|
||||
}
|
||||
func getRealIP(r *http.Request) string {
|
||||
if ip := r.Header.Get("X-Real-IP"); ip != "" {
|
||||
return ip
|
||||
}
|
||||
if ip := r.Header.Get("X-Forwarded-For"); ip != "" {
|
||||
return ip
|
||||
}
|
||||
return r.RemoteAddr
|
||||
}
|
||||
|
||||
@@ -30,6 +30,7 @@ type RateLimiter struct {
|
||||
Window time.Duration
|
||||
ClientMap map[string]*Client
|
||||
mu sync.Mutex
|
||||
Origins []string
|
||||
}
|
||||
|
||||
// Client stores request count and window expiration for each client.
|
||||
@@ -39,11 +40,12 @@ type Client struct {
|
||||
}
|
||||
|
||||
// NewRateLimiterWindow creates a new RateLimiter.
|
||||
func NewRateLimiterWindow(requests int, window time.Duration) *RateLimiter {
|
||||
func NewRateLimiterWindow(requests int, window time.Duration, origin []string) *RateLimiter {
|
||||
return &RateLimiter{
|
||||
Requests: requests,
|
||||
Window: window,
|
||||
ClientMap: make(map[string]*Client),
|
||||
Origins: origin,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -69,6 +71,7 @@ type JwtAuth struct {
|
||||
RequiredHeaders []string
|
||||
Headers map[string]string
|
||||
Params map[string]string
|
||||
Origins []string
|
||||
}
|
||||
|
||||
// AuthenticationMiddleware Define struct
|
||||
@@ -94,7 +97,8 @@ type AuthBasic struct {
|
||||
|
||||
// InterceptErrors contains backend status code errors to intercept
|
||||
type InterceptErrors struct {
|
||||
Errors []int
|
||||
Errors []int
|
||||
Origins []string
|
||||
}
|
||||
|
||||
// responseRecorder intercepts the response body and status code
|
||||
|
||||
@@ -35,13 +35,15 @@ func (gatewayServer GatewayServer) Initialize() *mux.Router {
|
||||
// Routes health check
|
||||
if !gateway.DisableHealthCheckStatus {
|
||||
r.HandleFunc("/healthz", heath.HealthCheckHandler).Methods("GET")
|
||||
r.HandleFunc("/health/routes", heath.HealthCheckHandler).Methods("GET")
|
||||
}
|
||||
// Readiness
|
||||
// Health check
|
||||
r.HandleFunc("/health/live", heath.HealthReadyHandler).Methods("GET")
|
||||
r.HandleFunc("/readyz", heath.HealthReadyHandler).Methods("GET")
|
||||
|
||||
if gateway.RateLimiter != 0 {
|
||||
//rateLimiter := middleware.NewRateLimiter(gateway.RateLimiter, time.Minute)
|
||||
limiter := middleware.NewRateLimiterWindow(gateway.RateLimiter, time.Minute) // requests per minute
|
||||
limiter := middleware.NewRateLimiterWindow(gateway.RateLimiter, time.Minute, gateway.Cors.Origins) // requests per minute
|
||||
// Add rate limit middleware to all routes, if defined
|
||||
r.Use(limiter.RateLimitMiddleware())
|
||||
}
|
||||
@@ -111,6 +113,7 @@ func (gatewayServer GatewayServer) Initialize() *mux.Router {
|
||||
RequiredHeaders: jwt.RequiredHeaders,
|
||||
Headers: jwt.Headers,
|
||||
Params: jwt.Params,
|
||||
Origins: gateway.Cors.Origins,
|
||||
}
|
||||
// Apply JWT authentication middleware
|
||||
secureRouter.Use(amw.AuthMiddleware)
|
||||
@@ -161,7 +164,8 @@ func (gatewayServer GatewayServer) Initialize() *mux.Router {
|
||||
r.Use(CORSHandler(gateway.Cors)) // Apply CORS middleware
|
||||
// Apply errorInterceptor middleware
|
||||
interceptErrors := middleware.InterceptErrors{
|
||||
Errors: gateway.InterceptErrors,
|
||||
Errors: gateway.InterceptErrors,
|
||||
Origins: gateway.Cors.Origins,
|
||||
}
|
||||
r.Use(interceptErrors.ErrorInterceptor)
|
||||
return r
|
||||
|
||||
@@ -29,6 +29,7 @@ import (
|
||||
func (gatewayServer GatewayServer) Start(ctx context.Context) error {
|
||||
logger.Info("Initializing routes...")
|
||||
route := gatewayServer.Initialize()
|
||||
logger.Debug("Routes count=%d Middlewares count=%d", len(gatewayServer.gateway.Routes), len(gatewayServer.middlewares))
|
||||
logger.Info("Initializing routes...done")
|
||||
tlsConfig := &tls.Config{}
|
||||
var listenWithTLS = false
|
||||
@@ -41,8 +42,17 @@ func (gatewayServer GatewayServer) Start(ctx context.Context) error {
|
||||
listenWithTLS = true
|
||||
|
||||
}
|
||||
srv := &http.Server{
|
||||
Addr: gatewayServer.gateway.ListenAddr,
|
||||
// HTTP Server
|
||||
httpServer := &http.Server{
|
||||
Addr: ":80",
|
||||
WriteTimeout: time.Second * time.Duration(gatewayServer.gateway.WriteTimeout),
|
||||
ReadTimeout: time.Second * time.Duration(gatewayServer.gateway.ReadTimeout),
|
||||
IdleTimeout: time.Second * time.Duration(gatewayServer.gateway.IdleTimeout),
|
||||
Handler: route, // Pass our instance of gorilla/mux in.
|
||||
}
|
||||
// HTTPS Server
|
||||
httpsServer := &http.Server{
|
||||
Addr: ":443",
|
||||
WriteTimeout: time.Second * time.Duration(gatewayServer.gateway.WriteTimeout),
|
||||
ReadTimeout: time.Second * time.Duration(gatewayServer.gateway.ReadTimeout),
|
||||
IdleTimeout: time.Second * time.Duration(gatewayServer.gateway.IdleTimeout),
|
||||
@@ -53,36 +63,52 @@ func (gatewayServer GatewayServer) Start(ctx context.Context) error {
|
||||
printRoute(gatewayServer.gateway.Routes)
|
||||
}
|
||||
// Set KeepAlive
|
||||
srv.SetKeepAlivesEnabled(!gatewayServer.gateway.DisableKeepAlive)
|
||||
httpServer.SetKeepAlivesEnabled(!gatewayServer.gateway.DisableKeepAlive)
|
||||
httpsServer.SetKeepAlivesEnabled(!gatewayServer.gateway.DisableKeepAlive)
|
||||
go func() {
|
||||
logger.Info("Starting HTTP server listen=0.0.0.0:80")
|
||||
if err := httpServer.ListenAndServe(); err != nil {
|
||||
logger.Fatal("Error starting Goma Gateway HTTP server: %v", err)
|
||||
}
|
||||
}()
|
||||
go func() {
|
||||
logger.Info("Started Goma Gateway server on %v", gatewayServer.gateway.ListenAddr)
|
||||
if listenWithTLS {
|
||||
logger.Info("Server is running securely over HTTPS on %v ", gatewayServer.gateway.ListenAddr)
|
||||
if err := srv.ListenAndServeTLS("", ""); err != nil {
|
||||
logger.Fatal("Error starting Goma Gateway server: %v", err)
|
||||
}
|
||||
} else {
|
||||
if err := srv.ListenAndServe(); err != nil {
|
||||
logger.Fatal("Error starting Goma Gateway server: %v", err)
|
||||
logger.Info("Starting HTTPS server listen=0.0.0.0:443")
|
||||
if err := httpsServer.ListenAndServeTLS("", ""); err != nil {
|
||||
logger.Fatal("Error starting Goma Gateway HTTPS server: %v", err)
|
||||
}
|
||||
}
|
||||
}()
|
||||
var wg sync.WaitGroup
|
||||
wg.Add(1)
|
||||
|
||||
wg.Add(2)
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
<-ctx.Done()
|
||||
shutdownCtx := context.Background()
|
||||
shutdownCtx, cancel := context.WithTimeout(shutdownCtx, 10*time.Second)
|
||||
defer cancel()
|
||||
if err := srv.Shutdown(shutdownCtx); err != nil {
|
||||
_, err := fmt.Fprintf(os.Stderr, "error shutting down Goma Gateway server: %s\n", err)
|
||||
if err := httpServer.Shutdown(shutdownCtx); err != nil {
|
||||
_, err := fmt.Fprintf(os.Stderr, "error shutting down HTTP server: %s\n", err)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
}()
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
<-ctx.Done()
|
||||
shutdownCtx := context.Background()
|
||||
shutdownCtx, cancel := context.WithTimeout(shutdownCtx, 10*time.Second)
|
||||
defer cancel()
|
||||
if listenWithTLS {
|
||||
if err := httpsServer.Shutdown(shutdownCtx); err != nil {
|
||||
_, err := fmt.Fprintf(os.Stderr, "error shutting HTTPS server: %s\n", err)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
}()
|
||||
wg.Wait()
|
||||
return nil
|
||||
|
||||
|
||||
@@ -133,12 +133,10 @@ type Route struct {
|
||||
|
||||
// Gateway contains Goma Proxy Gateway's configs
|
||||
type Gateway struct {
|
||||
// ListenAddr Defines the server listenAddr
|
||||
//
|
||||
//e.g: localhost:8080
|
||||
ListenAddr string `yaml:"listenAddr" env:"GOMA_LISTEN_ADDR, overwrite"`
|
||||
// SSLCertFile SSL Certificate file
|
||||
SSLCertFile string `yaml:"sslCertFile" env:"GOMA_SSL_CERT_FILE, overwrite"`
|
||||
SSLKeyFile string `yaml:"sslKeyFile" env:"GOMA_SSL_KEY_FILE, overwrite"`
|
||||
// SSLKeyFile SSL Private key file
|
||||
SSLKeyFile string `yaml:"sslKeyFile" env:"GOMA_SSL_KEY_FILE, overwrite"`
|
||||
// WriteTimeout defines proxy write timeout
|
||||
WriteTimeout int `yaml:"writeTimeout" env:"GOMA_WRITE_TIMEOUT, overwrite"`
|
||||
// ReadTimeout defines proxy read timeout
|
||||
|
||||
Reference in New Issue
Block a user