12
README.md
12
README.md
@@ -54,9 +54,7 @@ It's designed to be straightforward and efficient, offering features, like:
|
||||
- JWT `client authorization based on the result of a request`
|
||||
- Basic-Auth
|
||||
- OAuth
|
||||
- Rate limiting
|
||||
- In-Memory Token Bucket based
|
||||
- In-Memory client IP based
|
||||
- Rate limiting, In-Memory client IP based
|
||||
- Limit HTTP methods allowed for a particular route.
|
||||
|
||||
### Todo:
|
||||
@@ -121,12 +119,20 @@ services:
|
||||
- [x] Windows
|
||||
|
||||
Please download the binary from the [release page](https://github.com/jkaninda/goma-gateway/releases).
|
||||
Init configs:
|
||||
|
||||
```shell
|
||||
./goma config init --output config.yml
|
||||
```
|
||||
|
||||
To run
|
||||
```shell
|
||||
./goma server --config config.yml
|
||||
```
|
||||
|
||||
|
||||
|
||||
|
||||
## Deployment
|
||||
|
||||
- Docker
|
||||
|
||||
@@ -20,6 +20,9 @@ It's designed to be straightforward and efficient, offering features, like:
|
||||
- Cross-Origin Resource Sharing (CORS)
|
||||
- Custom Headers
|
||||
- Backend Errors interceptor
|
||||
- Logging
|
||||
- Metrics
|
||||
- Supports Load Balancing, round-robin algorithm
|
||||
- Support TLS
|
||||
- Block common exploits middleware
|
||||
- Patterns to detect SQL injection attempts
|
||||
@@ -28,9 +31,7 @@ It's designed to be straightforward and efficient, offering features, like:
|
||||
- JWT `client authorization based on the result of a request`
|
||||
- Basic-Auth
|
||||
- OAuth
|
||||
- Rate limiting
|
||||
- In-Memory Token Bucket based
|
||||
- In-Memory client IP based
|
||||
- Rate limiting, In-Memory client IP based
|
||||
- Limit HTTP methods allowed for a particular route.
|
||||
|
||||
|
||||
|
||||
@@ -4,7 +4,7 @@ metadata:
|
||||
name: goma-config
|
||||
data:
|
||||
goma.yml: |
|
||||
# Goma Gateway configurations
|
||||
# Goma Gateway configurations
|
||||
version: 1.0
|
||||
gateway:
|
||||
# Proxy write timeout
|
||||
@@ -19,6 +19,7 @@ data:
|
||||
sslKeyFile: ''#key.pem
|
||||
# Proxy rate limit, it's In-Memory IP based
|
||||
rateLimit: 0
|
||||
logLevel: info # debug, trace
|
||||
accessLog: "/dev/Stdout"
|
||||
errorLog: "/dev/stderr"
|
||||
## Enable, disable routes health check
|
||||
@@ -50,10 +51,10 @@ data:
|
||||
##### Define routes
|
||||
routes:
|
||||
# Example of a route | 1
|
||||
- name: Public # Name is optional
|
||||
- path: /
|
||||
name: Public # Name is optional
|
||||
# host Domain/host based request routing
|
||||
host: "" # Host is optional
|
||||
path: /public
|
||||
hosts: [] # Hosts are optional
|
||||
## Rewrite a request path
|
||||
# e.g rewrite: /store to /
|
||||
rewrite: /
|
||||
@@ -64,8 +65,6 @@ data:
|
||||
# [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, route will override global cors origins
|
||||
@@ -85,17 +84,27 @@ data:
|
||||
middlewares:
|
||||
- api-forbidden-paths
|
||||
# Example of a route | 2
|
||||
- name: Basic auth
|
||||
path: /protected
|
||||
- path: /protected
|
||||
name: Basic auth
|
||||
rewrite: /
|
||||
destination: https://example.com
|
||||
methods: []
|
||||
destination: ''
|
||||
backends:
|
||||
- https://example.com
|
||||
- https://example2.com
|
||||
methods:
|
||||
- GET
|
||||
# Route healthcheck
|
||||
healthCheck:
|
||||
path: /health/live
|
||||
interval: 30
|
||||
timeout: 10
|
||||
healthyStatuses:
|
||||
- 200
|
||||
- 404
|
||||
cors: {}
|
||||
middlewares:
|
||||
- api-forbidden-paths
|
||||
- basic-auth
|
||||
|
||||
#Defines proxy middlewares
|
||||
# middleware name must be unique
|
||||
middlewares:
|
||||
@@ -147,4 +156,44 @@ data:
|
||||
- /v2/swagger-ui/*
|
||||
- /api-docs/*
|
||||
- /internal/*
|
||||
- /actuator/*
|
||||
- /actuator/*
|
||||
- name: oauth-google
|
||||
type: oauth
|
||||
paths:
|
||||
- /protected
|
||||
- /example-of-oauth
|
||||
rule:
|
||||
clientId: xxx
|
||||
clientSecret: xxx
|
||||
provider: google
|
||||
endpoint:
|
||||
userInfoUrl: ""
|
||||
redirectUrl: http://localhost:8080/callback
|
||||
redirectPath: ""
|
||||
cookiePath: ""
|
||||
scopes:
|
||||
- https://www.googleapis.com/auth/userinfo.email
|
||||
- https://www.googleapis.com/auth/userinfo.profile
|
||||
state: randomStateString
|
||||
jwtSecret: your-strong-jwt-secret | It's optional
|
||||
- name: oauth-authentik
|
||||
type: oauth
|
||||
paths:
|
||||
- /protected
|
||||
- /example-of-oauth
|
||||
rule:
|
||||
clientId: xxx
|
||||
clientSecret: xxx
|
||||
provider: custom
|
||||
endpoint:
|
||||
authUrl: https://authentik.example.com/application/o/authorize/
|
||||
tokenUrl: https://authentik.example.com/application/o/token/
|
||||
userInfoUrl: https://authentik.example.com/application/o/userinfo/
|
||||
redirectUrl: http://localhost:8080/callback
|
||||
redirectPath: ""
|
||||
cookiePath: ""
|
||||
scopes:
|
||||
- email
|
||||
- openid
|
||||
state: randomStateString
|
||||
jwtSecret: your-strong-jwt-secret | It's optional
|
||||
@@ -13,6 +13,7 @@ gateway:
|
||||
sslKeyFile: ''#key.pem
|
||||
# Proxy rate limit, it's In-Memory IP based
|
||||
rateLimit: 0
|
||||
logLevel: info # debug, trace
|
||||
accessLog: "/dev/Stdout"
|
||||
errorLog: "/dev/stderr"
|
||||
## Enable, disable routes health check
|
||||
@@ -44,10 +45,10 @@ gateway:
|
||||
##### Define routes
|
||||
routes:
|
||||
# Example of a route | 1
|
||||
- name: Public # Name is optional
|
||||
- path: /
|
||||
name: Public # Name is optional
|
||||
# host Domain/host based request routing
|
||||
host: "" # Host is optional
|
||||
path: /public
|
||||
hosts: [] # Hosts are optional
|
||||
## Rewrite a request path
|
||||
# e.g rewrite: /store to /
|
||||
rewrite: /
|
||||
@@ -58,8 +59,6 @@ gateway:
|
||||
# [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, route will override global cors origins
|
||||
@@ -79,17 +78,27 @@ gateway:
|
||||
middlewares:
|
||||
- api-forbidden-paths
|
||||
# Example of a route | 2
|
||||
- name: Basic auth
|
||||
path: /protected
|
||||
- path: /protected
|
||||
name: Basic auth
|
||||
rewrite: /
|
||||
destination: https://example.com
|
||||
methods: []
|
||||
destination: ''
|
||||
backends:
|
||||
- https://example.com
|
||||
- https://example2.com
|
||||
methods:
|
||||
- GET
|
||||
# Route healthcheck
|
||||
healthCheck:
|
||||
path: /health/live
|
||||
interval: 30
|
||||
timeout: 10
|
||||
healthyStatuses:
|
||||
- 200
|
||||
- 404
|
||||
cors: {}
|
||||
middlewares:
|
||||
- api-forbidden-paths
|
||||
- basic-auth
|
||||
|
||||
#Defines proxy middlewares
|
||||
# middleware name must be unique
|
||||
middlewares:
|
||||
|
||||
@@ -32,7 +32,7 @@ func TestMiddleware(t *testing.T) {
|
||||
middlewares := []Middleware{
|
||||
{
|
||||
Name: "basic-auth",
|
||||
Type: "basic",
|
||||
Type: BasicAuth,
|
||||
Paths: []string{"/", "/admin"},
|
||||
Rule: BasicRuleMiddleware{
|
||||
Username: "goma",
|
||||
@@ -41,7 +41,7 @@ func TestMiddleware(t *testing.T) {
|
||||
},
|
||||
{
|
||||
Name: "forbidden path access",
|
||||
Type: "access",
|
||||
Type: AccessMiddleware,
|
||||
Paths: []string{"/", "/admin"},
|
||||
Rule: BasicRuleMiddleware{
|
||||
Username: "goma",
|
||||
@@ -51,7 +51,7 @@ func TestMiddleware(t *testing.T) {
|
||||
|
||||
{
|
||||
Name: "jwt",
|
||||
Type: "jwt",
|
||||
Type: JWTAuth,
|
||||
Paths: []string{"/", "/admin"},
|
||||
Rule: JWTRuleMiddleware{
|
||||
URL: "https://www.googleapis.com/auth/userinfo.email",
|
||||
@@ -59,6 +59,35 @@ func TestMiddleware(t *testing.T) {
|
||||
Params: map[string]string{},
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "oauth-google",
|
||||
Type: OAuth,
|
||||
Paths: []string{
|
||||
"/protected",
|
||||
"/example-of-oauth",
|
||||
},
|
||||
Rule: OauthRulerMiddleware{
|
||||
ClientID: "xxx",
|
||||
ClientSecret: "xxx",
|
||||
Provider: "google",
|
||||
JWTSecret: "your-strong-jwt-secret | It's optional",
|
||||
RedirectURL: "http://localhost:8080/callback",
|
||||
Scopes: []string{"https://www.googleapis.com/auth/userinfo.email",
|
||||
"https://www.googleapis.com/auth/userinfo.profile"},
|
||||
Endpoint: OauthEndpoint{},
|
||||
State: "randomStateString",
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "api-forbidden-paths",
|
||||
Type: AccessMiddleware,
|
||||
Paths: []string{
|
||||
"/swagger-ui/*",
|
||||
"/v2/swagger-ui/*",
|
||||
"/api-docs/*",
|
||||
"/actuator/*",
|
||||
},
|
||||
},
|
||||
}
|
||||
yamlData, err := yaml.Marshal(&middlewares)
|
||||
if err != nil {
|
||||
@@ -74,28 +103,43 @@ func TestMiddleware(t *testing.T) {
|
||||
func TestReadMiddleware(t *testing.T) {
|
||||
TestMiddleware(t)
|
||||
middlewares := getMiddlewares(t)
|
||||
middleware, err := getMiddleware(rules, middlewares)
|
||||
m, err := getMiddleware(rules, middlewares)
|
||||
if err != nil {
|
||||
t.Fatalf("Error searching middleware %s", err.Error())
|
||||
}
|
||||
switch middleware.Type {
|
||||
case "basic":
|
||||
log.Println("Basic auth")
|
||||
basicAuth, err := getBasicAuthMiddleware(middleware.Rule)
|
||||
if err != nil {
|
||||
log.Fatalln("error:", err)
|
||||
}
|
||||
log.Printf("Username: %s and password: %s\n", basicAuth.Username, basicAuth.Password)
|
||||
case "jwt":
|
||||
log.Println("JWT auth")
|
||||
jwt, err := getJWTMiddleware(middleware.Rule)
|
||||
if err != nil {
|
||||
log.Fatalln("error:", err)
|
||||
}
|
||||
log.Printf("JWT authentification URL is %s\n", jwt.URL)
|
||||
default:
|
||||
t.Errorf("Unknown middleware type %s", middleware.Type)
|
||||
log.Printf("Middleware: %v\n", m)
|
||||
|
||||
for _, middleware := range middlewares {
|
||||
|
||||
switch middleware.Type {
|
||||
case BasicAuth:
|
||||
log.Println("Basic auth")
|
||||
basicAuth, err := getBasicAuthMiddleware(middleware.Rule)
|
||||
if err != nil {
|
||||
log.Fatalln("error:", err)
|
||||
}
|
||||
log.Printf("Username: %s and password: %s\n", basicAuth.Username, basicAuth.Password)
|
||||
case JWTAuth:
|
||||
log.Println("JWT auth")
|
||||
jwt, err := getJWTMiddleware(middleware.Rule)
|
||||
if err != nil {
|
||||
log.Fatalln("error:", err)
|
||||
}
|
||||
log.Printf("JWT authentification URL is %s\n", jwt.URL)
|
||||
case OAuth:
|
||||
log.Println("OAuth auth")
|
||||
oauth, err := oAuthMiddleware(middleware.Rule)
|
||||
if err != nil {
|
||||
log.Fatalln("error:", err)
|
||||
}
|
||||
log.Printf("OAuth authentification: provider %s\n", oauth.Provider)
|
||||
case AccessMiddleware:
|
||||
log.Println("Access middleware")
|
||||
log.Printf("Access middleware: paths: [%s]\n", middleware.Paths)
|
||||
default:
|
||||
t.Errorf("Unknown middleware type %s", middleware.Type)
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
package pkg
|
||||
|
||||
import (
|
||||
"context"
|
||||
"log"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"os"
|
||||
@@ -19,6 +21,16 @@ func TestInit(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestCheckConfig(t *testing.T) {
|
||||
TestInit(t)
|
||||
initConfig(configFile)
|
||||
err := CheckConfig(configFile)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
log.Println("Goma Gateway configuration file checked successfully")
|
||||
}
|
||||
|
||||
func TestStart(t *testing.T) {
|
||||
TestInit(t)
|
||||
initConfig(configFile)
|
||||
@@ -28,7 +40,8 @@ func TestStart(t *testing.T) {
|
||||
t.Error(err)
|
||||
}
|
||||
route := gatewayServer.Initialize()
|
||||
route.HandleFunc("/", func(rw http.ResponseWriter, r *http.Request) {
|
||||
|
||||
route.HandleFunc("/test", func(rw http.ResponseWriter, r *http.Request) {
|
||||
_, err := rw.Write([]byte("Hello Goma Proxy"))
|
||||
if err != nil {
|
||||
t.Fatalf("Failed writing HTTP response: %v", err)
|
||||
@@ -43,10 +56,19 @@ func TestStart(t *testing.T) {
|
||||
t.Fatalf("expected a status code of 200, got %v", resp.StatusCode)
|
||||
}
|
||||
}
|
||||
ctx := context.Background()
|
||||
go func() {
|
||||
err = gatewayServer.Start(ctx)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
return
|
||||
}
|
||||
}()
|
||||
|
||||
t.Run("httpServer", func(t *testing.T) {
|
||||
s := httptest.NewServer(route)
|
||||
defer s.Close()
|
||||
assertResponseBody(t, s, "Hello Goma Proxy")
|
||||
})
|
||||
|
||||
ctx.Done()
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user