Merge pull request #121 from jkaninda/refactor
docs: add route and gateway configuration options
This commit is contained in:
@@ -101,10 +101,6 @@ docker pull ghcr.io/jkaninda/goma-gateway
|
|||||||
|
|
||||||
Documentation references Docker Hub, but all examples will work using ghcr.io just as well.
|
Documentation references Docker Hub, but all examples will work using ghcr.io just as well.
|
||||||
|
|
||||||
## Supported Engines
|
|
||||||
|
|
||||||
This image is developed and tested against the Docker CE engine exclusively.
|
|
||||||
While it may work against different implementations, there are no guarantees about support for non-Docker engines.
|
|
||||||
|
|
||||||
## References
|
## References
|
||||||
|
|
||||||
|
|||||||
@@ -1,23 +0,0 @@
|
|||||||
---
|
|
||||||
title: Intro
|
|
||||||
layout: default
|
|
||||||
parent: Middleware
|
|
||||||
nav_order: 1
|
|
||||||
---
|
|
||||||
# Middlewares
|
|
||||||
|
|
||||||
Middleware is a function executed before (or after) the route callback.
|
|
||||||
|
|
||||||
This is a great way to add API authentication checks, or to validate that the user has permission to access the route.
|
|
||||||
|
|
||||||
With Goma you can create your middleware based on the type you want and apply it on your routes
|
|
||||||
|
|
||||||
Goma Gateway supports :
|
|
||||||
|
|
||||||
- Authentication middleware
|
|
||||||
- JWT `client authorization based on the result of a request`
|
|
||||||
- Basic-Auth
|
|
||||||
- OAuth
|
|
||||||
- Rate limiting middleware
|
|
||||||
- In-Memory client IP based
|
|
||||||
- Access middleware
|
|
||||||
@@ -9,7 +9,7 @@ nav_order: 4
|
|||||||
### JWT middleware
|
### JWT middleware
|
||||||
|
|
||||||
As BasicAuth, JWT middleware grants also access to route to authorized users only.
|
As BasicAuth, JWT middleware grants also access to route to authorized users only.
|
||||||
It implements client authorization based on the result of a request.
|
It implements client authorization based on the result of a request using JSON Web Tokens.
|
||||||
|
|
||||||
If the request returns a 200 response code, access is allowed.
|
If the request returns a 200 response code, access is allowed.
|
||||||
If it returns 401 or 403, the access is denied with the corresponding error code. Any other response code returned by the request is considered an error.
|
If it returns 401 or 403, the access is denied with the corresponding error code. Any other response code returned by the request is considered an error.
|
||||||
|
|||||||
29
docs/middleware/overview.md
Normal file
29
docs/middleware/overview.md
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
---
|
||||||
|
title: Overview
|
||||||
|
layout: default
|
||||||
|
parent: Middleware
|
||||||
|
nav_order: 1
|
||||||
|
---
|
||||||
|
# Middlewares
|
||||||
|
|
||||||
|
Middleware functions are executed before or after a route callback, enabling you to extend the behavior of your routes.
|
||||||
|
|
||||||
|
They are an excellent way to implement features like API authentication, access control, or request validation.
|
||||||
|
|
||||||
|
With Goma, you can create custom middleware tailored to your needs and apply them to your routes seamlessly.
|
||||||
|
|
||||||
|
## Supported Middleware Types
|
||||||
|
|
||||||
|
- **Authentication Middleware**
|
||||||
|
- **JWT**: Performs client authorization based on the result of a request using JSON Web Tokens.
|
||||||
|
- **Basic-Auth**: Verifies credentials through Basic Authentication.
|
||||||
|
- **OAuth**: Supports OAuth-based authentication flows.
|
||||||
|
|
||||||
|
- **Rate Limiting Middleware**
|
||||||
|
- **In-Memory Client IP Based**: Throttles requests based on the client’s IP address using an in-memory store.
|
||||||
|
- **Distributed Rate Limiting**: Leverage Redis for scalable, client IP-based rate limits.
|
||||||
|
|
||||||
|
- **Access Middleware**
|
||||||
|
- Validates user permissions or access rights for specific route paths.
|
||||||
|
|
||||||
|
Middleware provides a flexible and powerful way to enhance the functionality, security, and performance of your API.
|
||||||
@@ -8,6 +8,10 @@ nav_order: 5
|
|||||||
|
|
||||||
# Distributed instances
|
# Distributed instances
|
||||||
|
|
||||||
|
Goma Gateway includes built-in support for Redis-based rate limiting, enabling efficient and scalable deployments.
|
||||||
|
|
||||||
|
By leveraging Redis, the Gateway ensures high-performance request throttling and distributed rate limiting across multiple instances, making it ideal for modern, cloud-native architectures.
|
||||||
|
|
||||||
```yaml
|
```yaml
|
||||||
version: "1.0"
|
version: "1.0"
|
||||||
gateway:
|
gateway:
|
||||||
|
|||||||
@@ -31,6 +31,8 @@ gateway:
|
|||||||
- path: /
|
- path: /
|
||||||
name: example
|
name: example
|
||||||
```
|
```
|
||||||
|
## Routes Configuration
|
||||||
|
Below is an example of configuring routes for the Gateway, defining service paths, methods, backends, health checks, and other settings.
|
||||||
|
|
||||||
Create a file in `/etc/goma/extra` using `yaml` or `.yaml` extension.
|
Create a file in `/etc/goma/extra` using `yaml` or `.yaml` extension.
|
||||||
|
|
||||||
@@ -86,6 +88,7 @@ Create a file in `/etc/goma/extra` using `yaml` or `.yaml` extension.
|
|||||||
disableHostFording: true
|
disableHostFording: true
|
||||||
interceptErrors: [404,401]
|
interceptErrors: [404,401]
|
||||||
blockCommonExploits: false
|
blockCommonExploits: false
|
||||||
|
## List of middleware
|
||||||
middlewares:
|
middlewares:
|
||||||
- auth-middleware
|
- auth-middleware
|
||||||
|
|
||||||
|
|||||||
@@ -7,49 +7,51 @@ nav_order: 1
|
|||||||
|
|
||||||
# Gateway
|
# Gateway
|
||||||
|
|
||||||
```yaml
|
The Gateway serves as the entry point to the server. This section provides options to configure the proxy server, define routes, and specify additional routes.
|
||||||
version: 1.0
|
|
||||||
gateway:
|
|
||||||
sslCertFile: /etc/goma/cert.pem
|
|
||||||
sslKeyFile: /etc/goma/key.pem
|
|
||||||
writeTimeout: 15
|
|
||||||
readTimeout: 15
|
|
||||||
idleTimeout: 30
|
|
||||||
# Rate limiting
|
|
||||||
rateLimit: 0
|
|
||||||
accessLog: /dev/Stdout
|
|
||||||
errorLog: /dev/stderr
|
|
||||||
logLevel: info
|
|
||||||
## Add additional routes
|
|
||||||
extraRoutes:
|
|
||||||
# path
|
|
||||||
directory: /etc/goma/extra
|
|
||||||
watch: true
|
|
||||||
disableRouteHealthCheckError: false
|
|
||||||
disableDisplayRouteOnStart: false
|
|
||||||
disableKeepAlive: false
|
|
||||||
disableHealthCheckStatus: false
|
|
||||||
blockCommonExploits: true
|
|
||||||
# Intercept backend errors
|
|
||||||
interceptErrors:
|
|
||||||
- 500
|
|
||||||
cors:
|
|
||||||
origins:
|
|
||||||
- http://localhost:8080
|
|
||||||
- https://example.com
|
|
||||||
headers:
|
|
||||||
Access-Control-Allow-Credentials: "true"
|
|
||||||
Access-Control-Allow-Headers: Origin, Authorization, Accept, Content-Type, Access-Control-Allow-Headers, X-Client-Id, X-Session-Id
|
|
||||||
Access-Control-Max-Age: "1728000"
|
|
||||||
routes: []
|
|
||||||
```
|
|
||||||
## Extra Routes
|
|
||||||
|
|
||||||
The Extra Routes feature allows you to define additional routes by using .yml or .yaml files stored in a specified directory.
|
These settings enable precise control over traffic flow and routing within your deployment.
|
||||||
|
|
||||||
This approach helps you avoid the complexity of managing all routes in a single file.
|
## Configuration Options
|
||||||
|
|
||||||
When dealing with many routes, maintaining them in one file can quickly become unwieldy. With this feature, you can organize your routes into separate files, making them easier to manage and maintain.
|
- **`sslCertFile`** (`string`): Path to the SSL certificate file.
|
||||||
|
- **`sslKeyFile`** (`string`): Path to the SSL certificate private key file.
|
||||||
|
- **`redis`**: Redis configuration settings.
|
||||||
|
- **`writeTimeout`** (`integer`): Timeout for writing responses (in seconds).
|
||||||
|
- **`readTimeout`** (`integer`): Timeout for reading requests (in seconds).
|
||||||
|
- **`idleTimeout`** (`integer`): Timeout for idle connections (in seconds).
|
||||||
|
- **`rateLimit`** (`integer`): Global rate limiting for the proxy.
|
||||||
|
- **`blockCommonExploits`** (`boolean`): Enable or disable blocking of common exploits.
|
||||||
|
- **`accessLog`** (`string`, default: `/dev/stdout`): Path for access logs.
|
||||||
|
- **`errorLog`** (`string`, default: `/dev/stderr`): Path for error logs.
|
||||||
|
- **`logLevel`** (`string`): Log verbosity level (e.g., `info`, `debug`, `error`).
|
||||||
|
- **`disableHealthCheckStatus`** (`boolean`): Enable or disable exposing the health check route status.
|
||||||
|
- **`disableRouteHealthCheckError`** (`boolean`): Enable or disable returning health check error responses for routes.
|
||||||
|
- **`disableDisplayRouteOnStart`** (`boolean`): Enable or disable displaying routes during server startup.
|
||||||
|
- **`disableKeepAlive`** (`boolean`): Enable or disable `keepAlive` for the proxy.
|
||||||
|
- **`enableMetrics`** (`boolean`): Enable or disable server metrics collection.
|
||||||
|
- **`interceptErrors`** (`array of integers`): List of HTTP status codes to intercept for custom handling.
|
||||||
|
|
||||||
|
### CORS Configuration
|
||||||
|
|
||||||
|
Customize Cross-Origin Resource Sharing (CORS) settings for the proxy:
|
||||||
|
|
||||||
|
- **`origins`** (`array of strings`): List of allowed origins.
|
||||||
|
- **`headers`** (`map[string]string`): Custom headers to include in responses.
|
||||||
|
|
||||||
|
### Additional Routes
|
||||||
|
|
||||||
|
Define custom routes for additional flexibility:
|
||||||
|
|
||||||
|
- **`directory`** (`string`): Directory path for serving extra routes.
|
||||||
|
- **`watch`** (`boolean`): Watch the directory for changes and update routes dynamically.
|
||||||
|
|
||||||
|
### Routes
|
||||||
|
|
||||||
|
Define the main routes for the Gateway, enabling routing logic for incoming requests.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Example Configuration
|
||||||
|
|
||||||
```yaml
|
```yaml
|
||||||
version: 1.0
|
version: 1.0
|
||||||
@@ -72,11 +74,13 @@ gateway:
|
|||||||
# Intercept backend errors
|
# Intercept backend errors
|
||||||
interceptErrors:
|
interceptErrors:
|
||||||
- 500
|
- 500
|
||||||
|
- 405
|
||||||
cors:
|
cors:
|
||||||
origins:
|
origins:
|
||||||
- http://localhost:8080
|
- http://localhost:8080
|
||||||
- https://example.com
|
- https://example.com
|
||||||
headers:
|
headers:
|
||||||
|
X-Custom-Header: "Value"
|
||||||
Access-Control-Allow-Credentials: "true"
|
Access-Control-Allow-Credentials: "true"
|
||||||
Access-Control-Allow-Headers: Origin, Authorization, Accept, Content-Type, Access-Control-Allow-Headers, X-Client-Id, X-Session-Id
|
Access-Control-Allow-Headers: Origin, Authorization, Accept, Content-Type, Access-Control-Allow-Headers, X-Client-Id, X-Session-Id
|
||||||
Access-Control-Max-Age: "1728000"
|
Access-Control-Max-Age: "1728000"
|
||||||
|
|||||||
@@ -8,7 +8,9 @@ nav_order: 5
|
|||||||
|
|
||||||
# Healthcheck
|
# Healthcheck
|
||||||
|
|
||||||
Goma comes with routes healthcheck, that can be enabled and disabled.
|
The proxy includes built-in health check routes, which can be easily enabled or disabled based on your requirements.
|
||||||
|
|
||||||
|
These routes allow you to monitor the health and availability of your services.
|
||||||
|
|
||||||
```yaml
|
```yaml
|
||||||
version: 1.0
|
version: 1.0
|
||||||
|
|||||||
@@ -10,6 +10,46 @@ nav_order: 2
|
|||||||
|
|
||||||
The Route allows you to match on HTTP traffic and direct it to the backend.
|
The Route allows you to match on HTTP traffic and direct it to the backend.
|
||||||
|
|
||||||
|
|
||||||
|
## Configuration Options
|
||||||
|
|
||||||
|
This section outlines the available configuration options for defining routes in the Gateway.
|
||||||
|
|
||||||
|
### Route Configuration
|
||||||
|
|
||||||
|
- **`path`** (`string`): The route path (e.g., `/api/v1/resource`).
|
||||||
|
- **`name`** (`string`): A unique name for the route.
|
||||||
|
- **`hosts`** (`list of strings`): A list of allowed hostnames for the route.
|
||||||
|
- **`rewrite`** (`string`): Rewrites the incoming route path to a new path.
|
||||||
|
- **`methods`** (`array of strings`): A list of allowed HTTP methods (e.g., `GET`, `POST`).
|
||||||
|
- **`destination`** (`string`): The backend endpoint for the route.
|
||||||
|
- **`backends`** (`list of strings`): A list of backend services for load balancing.
|
||||||
|
- **`insecureSkipVerify`** (`boolean`): Disables backend TLS certificate verification.
|
||||||
|
|
||||||
|
## Health Check Configuration
|
||||||
|
|
||||||
|
- **`healthCheck`**:
|
||||||
|
- **`path`** (`string`): The health check path (e.g., `/health`).
|
||||||
|
- **`interval`** (`string`, default: `30s`): The interval between health checks.
|
||||||
|
- **`timeout`** (`string`, default: `10s`): The maximum time to wait for a health check response.
|
||||||
|
- **`healthyStatuses`** (`array of integers`): A list of HTTP status codes considered healthy.
|
||||||
|
|
||||||
|
## CORS Configuration
|
||||||
|
|
||||||
|
- **`cors`**:
|
||||||
|
- **`origins`** (`array of strings`): A list of allowed origins for Cross-Origin Resource Sharing (CORS).
|
||||||
|
- **`headers`** (`array of strings`): A list of custom headers to include in responses.
|
||||||
|
|
||||||
|
## Additional Options
|
||||||
|
|
||||||
|
- **`rateLimit`** (`integer`): The maximum number of requests allowed per minute.
|
||||||
|
- **`disableHostFording`** (`boolean`): Disables proxy host forwarding for improved security.
|
||||||
|
- **`interceptErrors`** (`array of integers`): A list of backend error status codes to intercept for custom handling.
|
||||||
|
- **`blockCommonExploits`** (`boolean`): Enables or disables blocking of common exploits.
|
||||||
|
- **`middlewares`** (`array of strings`): A list of middleware names applied to the route.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
### Simple route
|
### Simple route
|
||||||
|
|
||||||
```yaml
|
```yaml
|
||||||
|
|||||||
@@ -16,7 +16,6 @@ gateway:
|
|||||||
logLevel: info # debug, trace, off
|
logLevel: info # debug, trace, off
|
||||||
accessLog: "/dev/Stdout"
|
accessLog: "/dev/Stdout"
|
||||||
errorLog: "/dev/stderr"
|
errorLog: "/dev/stderr"
|
||||||
## Redis connexion for distributed rate limiting, when using multiple instances | It's optional
|
|
||||||
#redis:
|
#redis:
|
||||||
#addr: redis:6379
|
#addr: redis:6379
|
||||||
# password: password
|
# password: password
|
||||||
|
|||||||
@@ -71,6 +71,8 @@ func (health Health) Check() error {
|
|||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// routesHealthCheck creates healthcheck job
|
||||||
func routesHealthCheck(routes []Route) {
|
func routesHealthCheck(routes []Route) {
|
||||||
for _, health := range healthCheckRoutes(routes) {
|
for _, health := range healthCheckRoutes(routes) {
|
||||||
go func() {
|
go func() {
|
||||||
@@ -84,11 +86,14 @@ func routesHealthCheck(routes []Route) {
|
|||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// createHealthCheckJob create healthcheck job
|
||||||
func (health Health) createHealthCheckJob() error {
|
func (health Health) createHealthCheckJob() error {
|
||||||
interval := "30s"
|
interval := "30s"
|
||||||
if len(health.Interval) > 0 {
|
if len(health.Interval) > 0 {
|
||||||
interval = health.Interval
|
interval = health.Interval
|
||||||
}
|
}
|
||||||
|
// create cron expression
|
||||||
expression := fmt.Sprintf("@every %s", interval)
|
expression := fmt.Sprintf("@every %s", interval)
|
||||||
if !util.IsValidCronExpression(expression) {
|
if !util.IsValidCronExpression(expression) {
|
||||||
logger.Error("Health check interval is invalid: %s", interval)
|
logger.Error("Health check interval is invalid: %s", interval)
|
||||||
@@ -113,3 +118,45 @@ func (health Health) createHealthCheckJob() error {
|
|||||||
defer c.Stop()
|
defer c.Stop()
|
||||||
select {}
|
select {}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// healthCheckRoutes creates and returns []Health
|
||||||
|
func healthCheckRoutes(routes []Route) []Health {
|
||||||
|
var healthRoutes []Health
|
||||||
|
for _, route := range routes {
|
||||||
|
if len(route.HealthCheck.Path) != 0 {
|
||||||
|
timeout, _ := util.ParseDuration("")
|
||||||
|
if len(route.HealthCheck.Timeout) > 0 {
|
||||||
|
d1, err1 := util.ParseDuration(route.HealthCheck.Timeout)
|
||||||
|
if err1 != nil {
|
||||||
|
logger.Error("Health check timeout is invalid: %s", route.HealthCheck.Timeout)
|
||||||
|
}
|
||||||
|
timeout = d1
|
||||||
|
}
|
||||||
|
if len(route.Backends) != 0 {
|
||||||
|
for index, backend := range route.Backends {
|
||||||
|
health := Health{
|
||||||
|
Name: fmt.Sprintf("%s - [%d]", route.Name, index),
|
||||||
|
URL: backend + route.HealthCheck.Path,
|
||||||
|
TimeOut: timeout,
|
||||||
|
HealthyStatuses: route.HealthCheck.HealthyStatuses,
|
||||||
|
InsecureSkipVerify: route.InsecureSkipVerify,
|
||||||
|
}
|
||||||
|
healthRoutes = append(healthRoutes, health)
|
||||||
|
}
|
||||||
|
|
||||||
|
} else {
|
||||||
|
health := Health{
|
||||||
|
Name: route.Name,
|
||||||
|
URL: route.Destination + route.HealthCheck.Path,
|
||||||
|
TimeOut: timeout,
|
||||||
|
HealthyStatuses: route.HealthCheck.HealthyStatuses,
|
||||||
|
InsecureSkipVerify: route.InsecureSkipVerify,
|
||||||
|
}
|
||||||
|
healthRoutes = append(healthRoutes, health)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
logger.Debug("Route %s's healthCheck is undefined", route.Name)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return healthRoutes
|
||||||
|
}
|
||||||
|
|||||||
@@ -11,25 +11,20 @@ You may get a copy of the License at
|
|||||||
*/
|
*/
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"crypto/tls"
|
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"github.com/golang-jwt/jwt"
|
|
||||||
"github.com/jedib0t/go-pretty/v6/table"
|
"github.com/jedib0t/go-pretty/v6/table"
|
||||||
"github.com/jkaninda/goma-gateway/pkg/logger"
|
|
||||||
"github.com/jkaninda/goma-gateway/util"
|
|
||||||
"golang.org/x/oauth2"
|
"golang.org/x/oauth2"
|
||||||
"io"
|
"io"
|
||||||
"net/http"
|
"net/http"
|
||||||
"time"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// printRoute prints routes
|
// printRoute prints routes
|
||||||
func printRoute(routes []Route) {
|
func printRoute(routes []Route) {
|
||||||
t := table.NewWriter()
|
t := table.NewWriter()
|
||||||
t.AppendHeader(table.Row{"Name", "Route", "Rewrite", "Destination"})
|
t.AppendHeader(table.Row{"Name", "Path", "Rewrite", "Destination"})
|
||||||
for _, route := range routes {
|
for _, route := range routes {
|
||||||
if len(route.Backends) > 0 {
|
if len(route.Backends) != 0 {
|
||||||
t.AppendRow(table.Row{route.Name, route.Path, route.Rewrite, fmt.Sprintf("backends: [%d]", len(route.Backends))})
|
t.AppendRow(table.Row{route.Name, route.Path, route.Rewrite, fmt.Sprintf("backends: [%d]", len(route.Backends))})
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
@@ -50,21 +45,6 @@ func getRealIP(r *http.Request) string {
|
|||||||
return r.RemoteAddr
|
return r.RemoteAddr
|
||||||
}
|
}
|
||||||
|
|
||||||
// loadTLS loads TLS Certificate
|
|
||||||
func loadTLS(cert, key string) (*tls.Config, error) {
|
|
||||||
if cert == "" && key == "" {
|
|
||||||
return nil, fmt.Errorf("no certificate or key file provided")
|
|
||||||
}
|
|
||||||
serverCert, err := tls.LoadX509KeyPair(cert, key)
|
|
||||||
if err != nil {
|
|
||||||
logger.Error("Error loading server certificate: %v", err)
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
tlsConfig := &tls.Config{
|
|
||||||
Certificates: []tls.Certificate{serverCert},
|
|
||||||
}
|
|
||||||
return tlsConfig, nil
|
|
||||||
}
|
|
||||||
func (oauth *OauthRulerMiddleware) getUserInfo(token *oauth2.Token) (UserInfo, error) {
|
func (oauth *OauthRulerMiddleware) getUserInfo(token *oauth2.Token) (UserInfo, error) {
|
||||||
oauthConfig := oauth2Config(oauth)
|
oauthConfig := oauth2Config(oauth)
|
||||||
// Call the user info endpoint with the token
|
// Call the user info endpoint with the token
|
||||||
@@ -88,64 +68,3 @@ func (oauth *OauthRulerMiddleware) getUserInfo(token *oauth2.Token) (UserInfo, e
|
|||||||
|
|
||||||
return userInfo, nil
|
return userInfo, nil
|
||||||
}
|
}
|
||||||
func createJWT(email, jwtSecret string) (string, error) {
|
|
||||||
// Define JWT claims
|
|
||||||
claims := jwt.MapClaims{
|
|
||||||
"email": email,
|
|
||||||
"exp": jwt.TimeFunc().Add(time.Hour * 24).Unix(), // Token expiration
|
|
||||||
"iss": "Goma-Gateway", // Issuer claim
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create a new token with HS256 signing method
|
|
||||||
token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
|
|
||||||
|
|
||||||
// Sign the token with a secret
|
|
||||||
signedToken, err := token.SignedString([]byte(jwtSecret))
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
|
|
||||||
return signedToken, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// healthCheckRoutes creates []Health
|
|
||||||
func healthCheckRoutes(routes []Route) []Health {
|
|
||||||
var healthRoutes []Health
|
|
||||||
for _, route := range routes {
|
|
||||||
if len(route.HealthCheck.Path) > 0 {
|
|
||||||
timeout, _ := util.ParseDuration("")
|
|
||||||
if len(route.HealthCheck.Timeout) > 0 {
|
|
||||||
d1, err1 := util.ParseDuration(route.HealthCheck.Timeout)
|
|
||||||
if err1 != nil {
|
|
||||||
logger.Error("Health check timeout is invalid: %s", route.HealthCheck.Timeout)
|
|
||||||
}
|
|
||||||
timeout = d1
|
|
||||||
}
|
|
||||||
if len(route.Backends) > 0 {
|
|
||||||
for index, backend := range route.Backends {
|
|
||||||
health := Health{
|
|
||||||
Name: fmt.Sprintf("%s - [%d]", route.Name, index),
|
|
||||||
URL: backend + route.HealthCheck.Path,
|
|
||||||
TimeOut: timeout,
|
|
||||||
HealthyStatuses: route.HealthCheck.HealthyStatuses,
|
|
||||||
InsecureSkipVerify: route.InsecureSkipVerify,
|
|
||||||
}
|
|
||||||
healthRoutes = append(healthRoutes, health)
|
|
||||||
}
|
|
||||||
|
|
||||||
} else {
|
|
||||||
health := Health{
|
|
||||||
Name: route.Name,
|
|
||||||
URL: route.Destination + route.HealthCheck.Path,
|
|
||||||
TimeOut: timeout,
|
|
||||||
HealthyStatuses: route.HealthCheck.HealthyStatuses,
|
|
||||||
InsecureSkipVerify: route.InsecureSkipVerify,
|
|
||||||
}
|
|
||||||
healthRoutes = append(healthRoutes, health)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
logger.Debug("Route %s's healthCheck is undefined", route.Name)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return healthRoutes
|
|
||||||
}
|
|
||||||
|
|||||||
44
internal/jwt.go
Normal file
44
internal/jwt.go
Normal file
@@ -0,0 +1,44 @@
|
|||||||
|
/*
|
||||||
|
* 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 pkg
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/golang-jwt/jwt"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
// createJWT create JWT token
|
||||||
|
func createJWT(email, jwtSecret string) (string, error) {
|
||||||
|
// Define JWT claims
|
||||||
|
claims := jwt.MapClaims{
|
||||||
|
"email": email,
|
||||||
|
"exp": jwt.TimeFunc().Add(time.Hour * 24).Unix(), // Token expiration
|
||||||
|
"iss": "Goma-Gateway", // Issuer claim
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create a new token with HS256 signing method
|
||||||
|
token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
|
||||||
|
|
||||||
|
// Sign the token with a secret
|
||||||
|
signedToken, err := token.SignedString([]byte(jwtSecret))
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
return signedToken, nil
|
||||||
|
}
|
||||||
@@ -52,6 +52,7 @@ var HttpDuration = promauto.NewHistogramVec(prometheus.HistogramOpts{
|
|||||||
Help: "Duration of HTTP requests.",
|
Help: "Duration of HTTP requests.",
|
||||||
}, []string{"name", "path"})
|
}, []string{"name", "path"})
|
||||||
|
|
||||||
|
// PrometheusMiddleware Prometheus http handler middleware, returns http.Handler
|
||||||
func (pr PrometheusRoute) PrometheusMiddleware(next http.Handler) http.Handler {
|
func (pr PrometheusRoute) PrometheusMiddleware(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) {
|
||||||
path := pr.Path
|
path := pr.Path
|
||||||
|
|||||||
@@ -23,6 +23,7 @@ import (
|
|||||||
"slices"
|
"slices"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// getRealIP returns user real IP
|
||||||
func getRealIP(r *http.Request) string {
|
func getRealIP(r *http.Request) string {
|
||||||
if ip := r.Header.Get("X-Real-IP"); ip != "" {
|
if ip := r.Header.Get("X-Real-IP"); ip != "" {
|
||||||
return ip
|
return ip
|
||||||
|
|||||||
@@ -24,6 +24,7 @@ import (
|
|||||||
"github.com/redis/go-redis/v9"
|
"github.com/redis/go-redis/v9"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// redisRateLimiter, handle rateLimit
|
||||||
func redisRateLimiter(clientIP string, rate int) error {
|
func redisRateLimiter(clientIP string, rate int) error {
|
||||||
ctx := context.Background()
|
ctx := context.Background()
|
||||||
|
|
||||||
|
|||||||
@@ -74,7 +74,7 @@ func (proxyRoute ProxyRoute) ProxyHandler() http.HandlerFunc {
|
|||||||
r.Host = targetURL.Host
|
r.Host = targetURL.Host
|
||||||
}
|
}
|
||||||
backendURL, _ := url.Parse(proxyRoute.destination)
|
backendURL, _ := url.Parse(proxyRoute.destination)
|
||||||
if len(proxyRoute.backends) > 0 {
|
if len(proxyRoute.backends) != 0 {
|
||||||
// Select the next backend URL
|
// Select the next backend URL
|
||||||
backendURL = getNextBackend(proxyRoute.backends)
|
backendURL = getNextBackend(proxyRoute.backends)
|
||||||
}
|
}
|
||||||
@@ -87,8 +87,7 @@ func (proxyRoute ProxyRoute) ProxyHandler() http.HandlerFunc {
|
|||||||
InsecureSkipVerify: proxyRoute.insecureSkipVerify,
|
InsecureSkipVerify: proxyRoute.insecureSkipVerify,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
w.Header().Set("Proxied-By", gatewayName) // Set Server name
|
w.Header().Set("Proxied-By", gatewayName)
|
||||||
w.Header().Del("Server") // Remove the Server header
|
|
||||||
// Custom error handler for proxy errors
|
// Custom error handler for proxy errors
|
||||||
proxy.ErrorHandler = ProxyErrorHandler
|
proxy.ErrorHandler = ProxyErrorHandler
|
||||||
proxy.ServeHTTP(w, r)
|
proxy.ServeHTTP(w, r)
|
||||||
|
|||||||
@@ -26,6 +26,7 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// init initializes prometheus metrics
|
||||||
func init() {
|
func init() {
|
||||||
_ = prometheus.Register(metrics.TotalRequests)
|
_ = prometheus.Register(metrics.TotalRequests)
|
||||||
_ = prometheus.Register(metrics.ResponseStatus)
|
_ = prometheus.Register(metrics.ResponseStatus)
|
||||||
@@ -88,6 +89,7 @@ func (gatewayServer GatewayServer) Initialize() *mux.Router {
|
|||||||
logger.Info("Block common exploits enabled")
|
logger.Info("Block common exploits enabled")
|
||||||
r.Use(middlewares.BlockExploitsMiddleware)
|
r.Use(middlewares.BlockExploitsMiddleware)
|
||||||
}
|
}
|
||||||
|
// check if RateLimit is set
|
||||||
if gateway.RateLimit != 0 {
|
if gateway.RateLimit != 0 {
|
||||||
// Add rate limit middlewares to all routes, if defined
|
// Add rate limit middlewares to all routes, if defined
|
||||||
rateLimit := middlewares.RateLimit{
|
rateLimit := middlewares.RateLimit{
|
||||||
@@ -20,6 +20,7 @@ package pkg
|
|||||||
import (
|
import (
|
||||||
"crypto/tls"
|
"crypto/tls"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"github.com/jkaninda/goma-gateway/pkg/logger"
|
||||||
)
|
)
|
||||||
|
|
||||||
func (gatewayServer GatewayServer) initTLS() (*tls.Config, bool, error) {
|
func (gatewayServer GatewayServer) initTLS() (*tls.Config, bool, error) {
|
||||||
@@ -34,3 +35,19 @@ func (gatewayServer GatewayServer) initTLS() (*tls.Config, bool, error) {
|
|||||||
}
|
}
|
||||||
return tlsConfig, true, nil
|
return tlsConfig, true, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// loadTLS loads TLS Certificate
|
||||||
|
func loadTLS(cert, key string) (*tls.Config, error) {
|
||||||
|
if cert == "" && key == "" {
|
||||||
|
return nil, fmt.Errorf("no certificate or key file provided")
|
||||||
|
}
|
||||||
|
serverCert, err := tls.LoadX509KeyPair(cert, key)
|
||||||
|
if err != nil {
|
||||||
|
logger.Error("Error loading server certificate: %v", err)
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
tlsConfig := &tls.Config{
|
||||||
|
Certificates: []tls.Certificate{serverCert},
|
||||||
|
}
|
||||||
|
return tlsConfig, nil
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user