chore: add route gateway verification
This commit is contained in:
@@ -11,7 +11,7 @@
|
|||||||
```
|
```
|
||||||
Goma Gateway is a lightweight API Gateway and Reverse Proxy.
|
Goma Gateway is a lightweight API Gateway and Reverse Proxy.
|
||||||
|
|
||||||
[](https://github.com/jkaninda/goma/actions/workflows/release.yml)
|
[](https://github.com/jkaninda/goma-gateway/actions/workflows/release.yml)
|
||||||
[](https://goreportcard.com/report/github.com/jkaninda/goma-gateway)
|
[](https://goreportcard.com/report/github.com/jkaninda/goma-gateway)
|
||||||
[](https://pkg.go.dev/github.com/jkaninda/goma-gateway)
|
[](https://pkg.go.dev/github.com/jkaninda/goma-gateway)
|
||||||

|

|
||||||
@@ -65,8 +65,13 @@ docker run --rm --name goma-gateway \
|
|||||||
```
|
```
|
||||||
### 4. Healthcheck
|
### 4. Healthcheck
|
||||||
|
|
||||||
|
- Goma Gateway readiness: `/readyz`
|
||||||
|
- Routes health check: `/healthz`
|
||||||
|
|
||||||
[http://localhost/healthz](http://localhost/healthz)
|
[http://localhost/healthz](http://localhost/healthz)
|
||||||
|
|
||||||
|
[http://localhost/readyz](http://localhost/readyz)
|
||||||
|
|
||||||
> Healthcheck response body
|
> Healthcheck response body
|
||||||
|
|
||||||
```json
|
```json
|
||||||
|
|||||||
@@ -179,7 +179,7 @@ func (GatewayServer) Config(configFile string) (*GatewayServer, error) {
|
|||||||
c := &GatewayConfig{}
|
c := &GatewayConfig{}
|
||||||
err = yaml.Unmarshal(buf, c)
|
err = yaml.Unmarshal(buf, c)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("in file %q: %w", configFile, err)
|
return nil, fmt.Errorf("error parsing yaml %q: %w", configFile, err)
|
||||||
}
|
}
|
||||||
return &GatewayServer{
|
return &GatewayServer{
|
||||||
ctx: nil,
|
ctx: nil,
|
||||||
@@ -338,11 +338,15 @@ func ToJWTRuler(input interface{}) (JWTRuler, error) {
|
|||||||
var bytes []byte
|
var bytes []byte
|
||||||
bytes, err := yaml.Marshal(input)
|
bytes, err := yaml.Marshal(input)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return JWTRuler{}, fmt.Errorf("error marshalling yaml: %v", err)
|
return JWTRuler{}, fmt.Errorf("error parsing yaml: %v", err)
|
||||||
}
|
}
|
||||||
err = yaml.Unmarshal(bytes, jWTRuler)
|
err = yaml.Unmarshal(bytes, jWTRuler)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return JWTRuler{}, fmt.Errorf("error unmarshalling yaml: %v", err)
|
return JWTRuler{}, fmt.Errorf("error parsing yaml: %v", err)
|
||||||
|
}
|
||||||
|
if jWTRuler.URL == "" {
|
||||||
|
return JWTRuler{}, fmt.Errorf("error parsing yaml: empty url in jwt auth middleware")
|
||||||
|
|
||||||
}
|
}
|
||||||
return *jWTRuler, nil
|
return *jWTRuler, nil
|
||||||
}
|
}
|
||||||
@@ -352,11 +356,15 @@ func ToBasicAuth(input interface{}) (BasicRule, error) {
|
|||||||
var bytes []byte
|
var bytes []byte
|
||||||
bytes, err := yaml.Marshal(input)
|
bytes, err := yaml.Marshal(input)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return BasicRule{}, fmt.Errorf("error marshalling yaml: %v", err)
|
return BasicRule{}, fmt.Errorf("error parsing yaml: %v", err)
|
||||||
}
|
}
|
||||||
err = yaml.Unmarshal(bytes, basicAuth)
|
err = yaml.Unmarshal(bytes, basicAuth)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return BasicRule{}, fmt.Errorf("error unmarshalling yaml: %v", err)
|
return BasicRule{}, fmt.Errorf("error parsing yaml: %v", err)
|
||||||
|
}
|
||||||
|
if basicAuth.Username == "" || basicAuth.Password == "" {
|
||||||
|
return BasicRule{}, fmt.Errorf("error parsing yaml: empty username/password in basic auth middleware")
|
||||||
|
|
||||||
}
|
}
|
||||||
return *basicAuth, nil
|
return *basicAuth, nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -105,3 +105,15 @@ func (heathRoute HealthCheckRoute) HealthCheckHandler(w http.ResponseWriter, r *
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
func (heathRoute HealthCheckRoute) HealthReadyHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
|
response := HealthCheckRouteResponse{
|
||||||
|
Status: "healthy",
|
||||||
|
Error: "",
|
||||||
|
}
|
||||||
|
w.Header().Set("Content-Type", "application/json")
|
||||||
|
w.WriteHeader(http.StatusOK)
|
||||||
|
err := json.NewEncoder(w).Encode(response)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -20,5 +20,5 @@ func Intro() {
|
|||||||
nameFigure.Print()
|
nameFigure.Print()
|
||||||
fmt.Printf("Version: %s\n", util.FullVersion())
|
fmt.Printf("Version: %s\n", util.FullVersion())
|
||||||
fmt.Println("Copyright (c) 2024 Jonas Kaninda")
|
fmt.Println("Copyright (c) 2024 Jonas Kaninda")
|
||||||
fmt.Println("Starting Goma server...")
|
fmt.Println("Starting Goma Gateway server...")
|
||||||
}
|
}
|
||||||
|
|||||||
142
pkg/route.go
142
pkg/route.go
@@ -34,8 +34,8 @@ func (gatewayServer GatewayServer) Initialize() *mux.Router {
|
|||||||
Routes: gateway.Routes,
|
Routes: gateway.Routes,
|
||||||
}
|
}
|
||||||
// Define the health check route
|
// Define the health check route
|
||||||
r.HandleFunc("/health", heath.HealthCheckHandler).Methods("GET")
|
|
||||||
r.HandleFunc("/healthz", heath.HealthCheckHandler).Methods("GET")
|
r.HandleFunc("/healthz", heath.HealthCheckHandler).Methods("GET")
|
||||||
|
r.HandleFunc("/readyz", heath.HealthReadyHandler).Methods("GET")
|
||||||
// Apply global Cors middlewares
|
// Apply global Cors middlewares
|
||||||
r.Use(CORSHandler(gateway.Cors)) // Apply CORS middleware
|
r.Use(CORSHandler(gateway.Cors)) // Apply CORS middleware
|
||||||
if gateway.RateLimiter != 0 {
|
if gateway.RateLimiter != 0 {
|
||||||
@@ -45,15 +45,76 @@ func (gatewayServer GatewayServer) Initialize() *mux.Router {
|
|||||||
r.Use(limiter.RateLimitMiddleware())
|
r.Use(limiter.RateLimitMiddleware())
|
||||||
}
|
}
|
||||||
for _, route := range gateway.Routes {
|
for _, route := range gateway.Routes {
|
||||||
blM := middleware.BlockListMiddleware{
|
if route.Path != "" {
|
||||||
Path: route.Path,
|
blM := middleware.BlockListMiddleware{
|
||||||
List: route.Blocklist,
|
Path: route.Path,
|
||||||
}
|
List: route.Blocklist,
|
||||||
// Add block access middleware to all route, if defined
|
}
|
||||||
r.Use(blM.BlocklistMiddleware)
|
// Add block access middleware to all route, if defined
|
||||||
//if route.Middlewares != nil {
|
r.Use(blM.BlocklistMiddleware)
|
||||||
for _, mid := range route.Middlewares {
|
// Apply route middleware
|
||||||
secureRouter := r.PathPrefix(util.ParseURLPath(route.Path + mid.Path)).Subrouter()
|
for _, mid := range route.Middlewares {
|
||||||
|
if mid.Path != "" {
|
||||||
|
secureRouter := r.PathPrefix(util.ParseURLPath(route.Path + mid.Path)).Subrouter()
|
||||||
|
proxyRoute := ProxyRoute{
|
||||||
|
path: route.Path,
|
||||||
|
rewrite: route.Rewrite,
|
||||||
|
destination: route.Destination,
|
||||||
|
disableXForward: route.DisableHeaderXForward,
|
||||||
|
cors: route.Cors,
|
||||||
|
}
|
||||||
|
rMiddleware, err := searchMiddleware(mid.Rules, middlewares)
|
||||||
|
if err != nil {
|
||||||
|
logger.Error("Middleware name not found")
|
||||||
|
} else {
|
||||||
|
//Check Authentication middleware
|
||||||
|
switch rMiddleware.Type {
|
||||||
|
case "basic":
|
||||||
|
basicAuth, err := ToBasicAuth(rMiddleware.Rule)
|
||||||
|
if err != nil {
|
||||||
|
logger.Error("Error: %s", err.Error())
|
||||||
|
} else {
|
||||||
|
amw := middleware.AuthBasic{
|
||||||
|
Username: basicAuth.Username,
|
||||||
|
Password: basicAuth.Password,
|
||||||
|
Headers: nil,
|
||||||
|
Params: nil,
|
||||||
|
}
|
||||||
|
// Apply JWT authentication middleware
|
||||||
|
secureRouter.Use(amw.AuthMiddleware)
|
||||||
|
secureRouter.Use(CORSHandler(route.Cors))
|
||||||
|
secureRouter.PathPrefix("/").Handler(proxyRoute.ProxyHandler()) // Proxy handler
|
||||||
|
secureRouter.PathPrefix("").Handler(proxyRoute.ProxyHandler()) // Proxy handler
|
||||||
|
}
|
||||||
|
case "jwt":
|
||||||
|
jwt, err := ToJWTRuler(rMiddleware.Rule)
|
||||||
|
if err != nil {
|
||||||
|
logger.Error("Error: %s", err.Error())
|
||||||
|
} else {
|
||||||
|
amw := middleware.AuthJWT{
|
||||||
|
AuthURL: jwt.URL,
|
||||||
|
RequiredHeaders: jwt.RequiredHeaders,
|
||||||
|
Headers: jwt.Headers,
|
||||||
|
Params: jwt.Params,
|
||||||
|
}
|
||||||
|
// Apply JWT authentication middleware
|
||||||
|
secureRouter.Use(amw.AuthMiddleware)
|
||||||
|
secureRouter.Use(CORSHandler(route.Cors))
|
||||||
|
secureRouter.PathPrefix("/").Handler(proxyRoute.ProxyHandler()) // Proxy handler
|
||||||
|
secureRouter.PathPrefix("").Handler(proxyRoute.ProxyHandler()) // Proxy handler
|
||||||
|
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
logger.Error("Unknown middleware type %s", rMiddleware.Type)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
logger.Error("Error, middleware path is empty")
|
||||||
|
logger.Error("Middleware ignored")
|
||||||
|
}
|
||||||
|
}
|
||||||
proxyRoute := ProxyRoute{
|
proxyRoute := ProxyRoute{
|
||||||
path: route.Path,
|
path: route.Path,
|
||||||
rewrite: route.Rewrite,
|
rewrite: route.Rewrite,
|
||||||
@@ -61,63 +122,14 @@ func (gatewayServer GatewayServer) Initialize() *mux.Router {
|
|||||||
disableXForward: route.DisableHeaderXForward,
|
disableXForward: route.DisableHeaderXForward,
|
||||||
cors: route.Cors,
|
cors: route.Cors,
|
||||||
}
|
}
|
||||||
rMiddleware, err := searchMiddleware(mid.Rules, middlewares)
|
|
||||||
if err != nil {
|
|
||||||
logger.Error("Middleware name not found")
|
|
||||||
} else {
|
|
||||||
//Check Authentication middleware
|
|
||||||
switch rMiddleware.Type {
|
|
||||||
case "basic":
|
|
||||||
basicAuth, err := ToBasicAuth(rMiddleware.Rule)
|
|
||||||
if err != nil {
|
|
||||||
|
|
||||||
logger.Error("Error: %s", err.Error())
|
router := r.PathPrefix(route.Path).Subrouter()
|
||||||
} else {
|
router.Use(CORSHandler(route.Cors))
|
||||||
amw := middleware.AuthBasic{
|
router.PathPrefix("/").Handler(proxyRoute.ProxyHandler())
|
||||||
Username: basicAuth.Username,
|
} else {
|
||||||
Password: basicAuth.Password,
|
logger.Error("Error, path is empty in route %s", route.Name)
|
||||||
Headers: nil,
|
logger.Info("Route path ignored: %s", route.Path)
|
||||||
Params: nil,
|
|
||||||
}
|
|
||||||
// Apply JWT authentication middleware
|
|
||||||
secureRouter.Use(amw.AuthMiddleware)
|
|
||||||
}
|
|
||||||
case "jwt":
|
|
||||||
jwt, err := ToJWTRuler(rMiddleware.Rule)
|
|
||||||
if err != nil {
|
|
||||||
|
|
||||||
} else {
|
|
||||||
amw := middleware.AuthJWT{
|
|
||||||
AuthURL: jwt.URL,
|
|
||||||
RequiredHeaders: jwt.RequiredHeaders,
|
|
||||||
Headers: jwt.Headers,
|
|
||||||
Params: jwt.Params,
|
|
||||||
}
|
|
||||||
// Apply JWT authentication middleware
|
|
||||||
secureRouter.Use(amw.AuthMiddleware)
|
|
||||||
|
|
||||||
}
|
|
||||||
default:
|
|
||||||
logger.Error("Unknown middleware type %s", rMiddleware.Type)
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
secureRouter.Use(CORSHandler(route.Cors))
|
|
||||||
secureRouter.PathPrefix("/").Handler(proxyRoute.ProxyHandler()) // Proxy handler
|
|
||||||
secureRouter.PathPrefix("").Handler(proxyRoute.ProxyHandler()) // Proxy handler
|
|
||||||
}
|
}
|
||||||
proxyRoute := ProxyRoute{
|
|
||||||
path: route.Path,
|
|
||||||
rewrite: route.Rewrite,
|
|
||||||
destination: route.Destination,
|
|
||||||
disableXForward: route.DisableHeaderXForward,
|
|
||||||
cors: route.Cors,
|
|
||||||
}
|
|
||||||
|
|
||||||
router := r.PathPrefix(route.Path).Subrouter()
|
|
||||||
router.Use(CORSHandler(route.Cors))
|
|
||||||
router.PathPrefix("/").Handler(proxyRoute.ProxyHandler())
|
|
||||||
}
|
}
|
||||||
return r
|
return r
|
||||||
|
|
||||||
|
|||||||
@@ -66,3 +66,45 @@ func (gatewayServer GatewayServer) Start(ctx context.Context) error {
|
|||||||
return nil
|
return nil
|
||||||
|
|
||||||
}
|
}
|
||||||
|
func waitForReady(
|
||||||
|
ctx context.Context,
|
||||||
|
timeout time.Duration,
|
||||||
|
endpoint string,
|
||||||
|
) error {
|
||||||
|
client := http.Client{}
|
||||||
|
startTime := time.Now()
|
||||||
|
for {
|
||||||
|
req, err := http.NewRequestWithContext(
|
||||||
|
ctx,
|
||||||
|
http.MethodGet,
|
||||||
|
endpoint,
|
||||||
|
nil,
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to create request: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
resp, err := client.Do(req)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Printf("Error making request: %s\n", err.Error())
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if resp.StatusCode == http.StatusOK {
|
||||||
|
fmt.Println("Endpoint is ready!")
|
||||||
|
resp.Body.Close()
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
resp.Body.Close()
|
||||||
|
|
||||||
|
select {
|
||||||
|
case <-ctx.Done():
|
||||||
|
return ctx.Err()
|
||||||
|
default:
|
||||||
|
if time.Since(startTime) >= timeout {
|
||||||
|
return fmt.Errorf("timeout reached while waiting for endpoint")
|
||||||
|
}
|
||||||
|
// wait a little while between checks
|
||||||
|
time.Sleep(250 * time.Millisecond)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user