-
Notifications
You must be signed in to change notification settings - Fork 57
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
* Bump up APIFW version * Fix the DNS resolver issue in the GraphQL mode * Update helm chart
- Loading branch information
Showing
26 changed files
with
2,441 additions
and
1,173 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 | ||
|
||
} |
Oops, something went wrong.