Skip to content

Commit

Permalink
Release/0.8.4 (#118)
Browse files Browse the repository at this point in the history
* Bump up APIFW version

* Fix the DNS resolver issue in the GraphQL mode

* Update helm chart
  • Loading branch information
afr1ka authored Nov 12, 2024
1 parent cc2d0c1 commit e74db3b
Show file tree
Hide file tree
Showing 26 changed files with 2,441 additions and 1,173 deletions.
12 changes: 6 additions & 6 deletions .github/workflows/binaries.yml
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ jobs:
needs:
- draft-release
env:
X_GO_DISTRIBUTION: "https://go.dev/dl/go1.22.8.linux-amd64.tar.gz"
X_GO_DISTRIBUTION: "https://go.dev/dl/go1.22.9.linux-amd64.tar.gz"
strategy:
matrix:
include:
Expand Down Expand Up @@ -160,7 +160,7 @@ jobs:
needs:
- draft-release
env:
X_GO_VERSION: "1.22.8"
X_GO_VERSION: "1.22.9"
strategy:
matrix:
include:
Expand Down Expand Up @@ -267,19 +267,19 @@ jobs:
include:
- arch: armv6
distro: bullseye
go_distribution: https://go.dev/dl/go1.22.8.linux-armv6l.tar.gz
go_distribution: https://go.dev/dl/go1.22.9.linux-armv6l.tar.gz
artifact: armv6-libc
- arch: aarch64
distro: bullseye
go_distribution: https://go.dev/dl/go1.22.8.linux-arm64.tar.gz
go_distribution: https://go.dev/dl/go1.22.9.linux-arm64.tar.gz
artifact: arm64-libc
- arch: armv6
distro: alpine_latest
go_distribution: https://go.dev/dl/go1.22.8.linux-armv6l.tar.gz
go_distribution: https://go.dev/dl/go1.22.9.linux-armv6l.tar.gz
artifact: armv6-musl
- arch: aarch64
distro: alpine_latest
go_distribution: https://go.dev/dl/go1.22.8.linux-arm64.tar.gz
go_distribution: https://go.dev/dl/go1.22.9.linux-arm64.tar.gz
artifact: arm64-musl
steps:
- uses: actions/checkout@v2.1.0
Expand Down
2 changes: 1 addition & 1 deletion Makefile
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
VERSION := 0.8.3
VERSION := 0.8.4
NAMESPACE := github.com/wallarm/api-firewall

.DEFAULT_GOAL := build
Expand Down
282 changes: 282 additions & 0 deletions cmd/api-firewall/internal/handlers/api/run.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,282 @@
package api

import (
"fmt"
"net/url"
"os"
"os/signal"
"path"
"strings"
"sync"
"syscall"

"github.com/ardanlabs/conf"
"github.com/pkg/errors"
"github.com/savsgio/gotils/strconv"
"github.com/sirupsen/logrus"
"github.com/valyala/fasthttp"

"github.com/wallarm/api-firewall/internal/config"
"github.com/wallarm/api-firewall/internal/platform/allowiplist"
"github.com/wallarm/api-firewall/internal/platform/storage"
"github.com/wallarm/api-firewall/internal/version"
)

const (
livenessEndpoint = "/v1/liveness"
readinessEndpoint = "/v1/readiness"
)

func Run(logger *logrus.Logger) error {

// =========================================================================
// Configuration

var cfg config.APIMode
cfg.Version.SVN = version.Version
cfg.Version.Desc = version.ProjectName

if err := conf.Parse(os.Args[1:], version.Namespace, &cfg); err != nil {
switch err {
case conf.ErrHelpWanted:
usage, err := conf.Usage(version.Namespace, &cfg)
if err != nil {
return errors.Wrap(err, "generating config usage")
}
fmt.Println(usage)
return nil
case conf.ErrVersionWanted:
version, err := conf.VersionString(version.Namespace, &cfg)
if err != nil {
return errors.Wrap(err, "generating config version")
}
fmt.Println(version)
return nil
}
return errors.Wrap(err, "parsing config")
}

// =========================================================================
// Init Logger

if strings.EqualFold(cfg.LogFormat, "json") {
logger.SetFormatter(&logrus.JSONFormatter{})
}

switch strings.ToLower(cfg.LogLevel) {
case "trace":
logger.SetLevel(logrus.TraceLevel)
case "debug":
logger.SetLevel(logrus.DebugLevel)
case "error":
logger.SetLevel(logrus.ErrorLevel)
case "warning":
logger.SetLevel(logrus.WarnLevel)
case "info":
logger.SetLevel(logrus.InfoLevel)
default:
return errors.New("invalid log level")
}

logger.Infof("%s : Started : Application initializing : version %q", logPrefix, version.Version)
defer logger.Infof("%s: Completed", logPrefix)

out, err := conf.String(&cfg)
if err != nil {
return errors.Wrap(err, "generating config for output")
}
logger.Infof("%s: Configuration Loaded :\n%v\n", logPrefix, out)

// make a channel to listen for an interrupt or terminate signal from the OS
// use a buffered channel because the signal package requires it
shutdown := make(chan os.Signal, 1)
signal.Notify(shutdown, os.Interrupt, syscall.SIGTERM)

// DB Usage Lock
var dbLock sync.RWMutex

// make a channel to listen for errors coming from the listener. Use a
// buffered channel so the goroutine can exit if we don't collect this error.
serverErrors := make(chan error, 1)

// load spec from the database
specStorage, err := storage.NewOpenAPIDB(cfg.PathToSpecDB, cfg.DBVersion)
if err != nil {
logger.Errorf("%s: trying to load API Spec value from SQLLite Database : %v\n", logPrefix, err.Error())
}

if specStorage != nil {
logger.Debugf("OpenAPI specifications with the following IDs were found in the DB: %v", specStorage.SchemaIDs())
}

// =========================================================================
// Init ModSecurity Core

waf, err := config.LoadModSecurityConfiguration(logger, &cfg.ModSecurity)
if err != nil {
logger.Fatal(err)
return err
}

if waf != nil {
logger.Infof("%s: The ModSecurity configuration has been loaded successfully", logPrefix)
}

// Init Allow IP List

logger.Infof("%s: Initializing IP Whitelist Cache", logPrefix)

allowedIPCache, err := allowiplist.New(&cfg.AllowIP, logger)
if err != nil {
return errors.Wrap(err, "The allow IP list init error")
}

switch allowedIPCache {
case nil:
logger.Infof("%s: The allow IP list is not configured", logPrefix)
default:
logger.Infof("%s: Loaded %d Whitelisted IP's to the cache", logPrefix, allowedIPCache.ElementsNum)
}

// =========================================================================
// Init Handlers
requestHandlers := Handlers(&dbLock, &cfg, shutdown, logger, specStorage, allowedIPCache, waf)

// =========================================================================
// Start Health API Service

healthData := Health{
Logger: logger,
OpenAPIDB: specStorage,
}

// health service handler
healthHandler := func(ctx *fasthttp.RequestCtx) {
switch string(ctx.Path()) {
case livenessEndpoint:
if err := healthData.Liveness(ctx); err != nil {
healthData.Logger.Errorf("%s: liveness: %s", logPrefix, err.Error())
}
case readinessEndpoint:
if err := healthData.Readiness(ctx); err != nil {
healthData.Logger.Errorf("%s: readiness: %s", logPrefix, err.Error())
}
default:
ctx.Error("Unsupported path", fasthttp.StatusNotFound)
}
}

healthAPI := fasthttp.Server{
Handler: healthHandler,
ReadTimeout: cfg.ReadTimeout,
WriteTimeout: cfg.WriteTimeout,
Logger: logger,
NoDefaultServerHeader: true,
}

// Start the service listening for requests.
go func() {
logger.Infof("%s: Health API listening on %s", logPrefix, cfg.HealthAPIHost)
serverErrors <- healthAPI.ListenAndServe(cfg.HealthAPIHost)
}()

// =========================================================================
// Start API Service

logger.Infof("%s: Initializing API support", logPrefix)

apiHost, err := url.ParseRequestURI(cfg.APIHost)
if err != nil {
return errors.Wrap(err, "parsing API Host URL")
}

var isTLS bool

switch apiHost.Scheme {
case "http":
isTLS = false
case "https":
isTLS = true
}

api := fasthttp.Server{
Handler: requestHandlers,
ReadTimeout: cfg.ReadTimeout,
WriteTimeout: cfg.WriteTimeout,
ReadBufferSize: cfg.ReadBufferSize,
WriteBufferSize: cfg.WriteBufferSize,
MaxRequestBodySize: cfg.MaxRequestBodySize,
DisableKeepalive: cfg.DisableKeepalive,
MaxConnsPerIP: cfg.MaxConnsPerIP,
MaxRequestsPerConn: cfg.MaxRequestsPerConn,
ErrorHandler: func(ctx *fasthttp.RequestCtx, err error) {
logger.WithFields(logrus.Fields{
"error": err,
"host": strconv.B2S(ctx.Request.Header.Host()),
"path": strconv.B2S(ctx.Path()),
"method": strconv.B2S(ctx.Request.Header.Method()),
}).Error("request processing error")

ctx.Error("", fasthttp.StatusInternalServerError)
},
Logger: logger,
NoDefaultServerHeader: true,
}

// =========================================================================
// Init Regular Update Controller

updSpecErrors := make(chan error, 1)

updOpenAPISpec := NewHandlerUpdater(&dbLock, logger, specStorage, &cfg, &api, shutdown, &healthData, allowedIPCache, waf)

// disable updater if SpecificationUpdatePeriod == 0
if cfg.SpecificationUpdatePeriod.Seconds() > 0 {
go func() {
logger.Infof("%s: starting specification regular update process every %.0f seconds", logPrefix, cfg.SpecificationUpdatePeriod.Seconds())
updSpecErrors <- updOpenAPISpec.Start()
}()
}

// start the service listening for requests.
go func() {
logger.Infof("%s: API listening on %s", logPrefix, cfg.APIHost)
switch isTLS {
case false:
serverErrors <- api.ListenAndServe(apiHost.Host)
case true:
serverErrors <- api.ListenAndServeTLS(apiHost.Host, path.Join(cfg.TLS.CertsPath, cfg.TLS.CertFile),
path.Join(cfg.TLS.CertsPath, cfg.TLS.CertKey))
}
}()

// =========================================================================
// Shutdown

// blocking main and waiting for shutdown.
select {
case err := <-serverErrors:
return errors.Wrap(err, "server error")

case err := <-updSpecErrors:
return errors.Wrap(err, "regular updater error")

case sig := <-shutdown:
logger.Infof("%s: %v: Start shutdown", logPrefix, sig)

if cfg.SpecificationUpdatePeriod.Seconds() > 0 {
if err := updOpenAPISpec.Shutdown(); err != nil {
return errors.Wrap(err, "could not stop configuration updater gracefully")
}
}

// asking listener to shutdown and shed load.
if err := api.Shutdown(); err != nil {
return errors.Wrap(err, "could not stop server gracefully")
}
logger.Infof("%s: %v: Completed shutdown", logPrefix, sig)
}

return nil

}
Loading

0 comments on commit e74db3b

Please sign in to comment.