Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion docker/dev-geoblock/docker-compose.yml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
services:
traefik:
image: traefik:v3.6.4
image: traefik:v3.7.6

volumes:
- /var/run/docker.sock:/var/run/docker.sock
Expand Down
115 changes: 78 additions & 37 deletions geoblock.go
Original file line number Diff line number Diff line change
Expand Up @@ -104,73 +104,114 @@ type GeoBlock struct {
// New created a new GeoBlock plugin.
func New(ctx context.Context, next http.Handler, config *Config, name string) (http.Handler, error) {
gob.Register(ipEntry{})
infoLogger := buildLogger()

infoLogger := log.New(io.Discard, "INFO: GeoBlock: ", log.Ldate|log.Ltime)
if err := validateConfig(config); err != nil {
return nil, err
}

if err := applyDefaults(config); err != nil {
return nil, err
}

allowedIPAddresses, allowedIPRanges := parseAllowedIPAddresses(config.AllowedIPAddresses, infoLogger)

excludedPathRegexps, err := compileExcludedPathPatterns(config.ExcludedPathPatterns)
if err != nil {
return nil, err
}

infoLogger.SetOutput(os.Stdout)
if !config.SilentStartUp {
infoLogger.Printf("%s: Starting middleware", name)
printConfiguration(name, config, infoLogger)
}

logFile, err := buildLogTarget(ctx, config, infoLogger, name)
if err != nil {
return nil, err
}

cache, ipDB, err := buildCache(config, infoLogger, name)
if err != nil {
return nil, err
}

return buildGeoBlock(
next, config, name, infoLogger, logFile, cache, ipDB,
allowedIPAddresses, allowedIPRanges, excludedPathRegexps,
), nil
}

// check geolocation API uri
func validateConfig(config *Config) error {
if len(config.API) == 0 || !strings.Contains(config.API, "{ip}") {
return nil, fmt.Errorf("no api uri given")
return fmt.Errorf("no api uri given")
}

// check if at least one allowed country is provided
if len(config.Countries) == 0 {
return nil, fmt.Errorf("no allowed country code provided")
return fmt.Errorf("no allowed country code provided")
}

// set default API timeout if non is given
return nil
}

func applyDefaults(config *Config) error {
if config.APITimeoutMs == 0 {
config.APITimeoutMs = 750
}

// set default HTTP status code for denied requests if non other is supplied
deniedRequestHTTPStatusCode, err := getHTTPStatusCodeDeniedRequest(config.HTTPStatusCodeDeniedRequest)
if err != nil {
return nil, err
return err
}
config.HTTPStatusCodeDeniedRequest = deniedRequestHTTPStatusCode

// build allowed IP and IP ranges lists
allowedIPAddresses, allowedIPRanges := parseAllowedIPAddresses(config.AllowedIPAddresses, infoLogger)

// compile excluded path regex patterns
excludedPathRegexps, err := compileExcludedPathPatterns(config.ExcludedPathPatterns)
if err != nil {
return nil, err
}
return nil
}

infoLogger.SetOutput(os.Stdout)
func buildLogger() *log.Logger {
return log.New(io.Discard, "INFO: GeoBlock: ", log.Ldate|log.Ltime)
}

// output configuration of the middleware instance
if !config.SilentStartUp {
infoLogger.Printf("%s: Starting middleware", name)
printConfiguration(name, config, infoLogger)
func buildLogTarget(ctx context.Context, config *Config, logger *log.Logger, name string) (*os.File, error) {
if len(config.LogFilePath) == 0 {
return nil, nil
}

// create custom log target if needed
var logFile *os.File
if len(config.LogFilePath) > 0 {
logTarget, err := CreateCustomLogTarget(ctx, infoLogger, name, config.LogFilePath)
if err != nil {
infoLogger.Fatal(err)
}
logFile = logTarget
logTarget, err := CreateCustomLogTarget(ctx, logger, name, config.LogFilePath)
if err != nil {
return nil, err
}

// initialize local IP lookup cache
return logTarget, nil
}

func buildCache(config *Config, logger *log.Logger, name string) (*lru.LRUCache, *CachePersist, error) {
cacheOptions := Options{
CacheSize: config.CacheSize,
CachePath: config.IPDatabaseCachePath,
PersistInterval: defaultCacheWriteCycle,
Logger: infoLogger,
Logger: logger,
SilentStartUp: config.SilentStartUp,
Name: name,
}

// Share one cache + persistence worker per middleware (see GetOrInitCache).
cache, ipDB, err := GetOrInitCache(cacheOptions)
if err != nil {
infoLogger.Fatal(err)
}
return GetOrInitCache(cacheOptions)
}

func buildGeoBlock(
next http.Handler,
config *Config,
name string,
logger *log.Logger,
logFile *os.File,
cache *lru.LRUCache,
ipDB *CachePersist,
allowedIPAddresses []net.IP,
allowedIPRanges []*net.IPNet,
excludedPathRegexps []*regexp.Regexp,
) *GeoBlock {
return &GeoBlock{
next: next,
silentStartUp: config.SilentStartUp,
Expand Down Expand Up @@ -200,9 +241,9 @@ func New(ctx context.Context, next http.Handler, config *Config, name string) (h
redirectURLIfDenied: config.RedirectURLIfDenied,
excludedPathRegexps: excludedPathRegexps,
name: name,
infoLogger: infoLogger,
infoLogger: logger,
ipDatabasePersistence: ipDB, // may be nil => feature OFF
}, nil
}
}

func (a *GeoBlock) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
Expand Down
Loading