Skip to content

Commit

Permalink
#minor: add allowlist and statuscode configuration options
Browse files Browse the repository at this point in the history
  • Loading branch information
circa10a committed Nov 28, 2021
1 parent 7995d75 commit 64470ff
Show file tree
Hide file tree
Showing 6 changed files with 87 additions and 31 deletions.
12 changes: 9 additions & 3 deletions Caddyfile
Original file line number Diff line number Diff line change
Expand Up @@ -7,15 +7,15 @@ localhost:80

route /* {
geofence {
# Cache ip addresses and if they are within proximity or not
# cache_ttl is the duration to store ip addresses and if they are within proximity or not to increase performance
# Cache for 7 days, valid time units are "ms", "s", "m", "h"
# Not specifying a TTL sets no expiration on cached items and will live until restart
cache_ttl 168h

# freegeoip.app API token, this example reads from an environment variable
freegeoip_api_token {$FREEGEOIP_API_TOKEN}

# Sensitivity/Proximity
# sensitivity (proximity)
# 0 - 111 km
# 1 - 11.1 km
# 2 - 1.11 km
Expand All @@ -24,11 +24,17 @@ route /* {
# 5 1.11 meters
sensitivity 3

# AllowPrivateIPAddresses is a boolean for whether or not to allow private ip ranges
# allow_private_ip_addresses is a boolean for whether or not to allow private ip ranges
# such as 192.X, 172.X, 10.X, [::1] (localhost)
# false by default
# Some cellular networks doing NATing with 172.X addresses, in which case, you may not want to allow
allow_private_ip_addresses true

# allowlist is a list of IP addresses that will not be checked for proximity and will be allowed to access the server
allowlist 206.189.205.251 206.189.205.252

# status_code is the HTTP response code that is returned if IP address is not within proximity. Default is 403
status_code 403
}
}

Expand Down
12 changes: 9 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -50,15 +50,15 @@ localhost:80
route /* {
geofence {
# Cache ip addresses and if they are within proximity or not
# cache_ttl is the duration to store ip addresses and if they are within proximity or not to increase performance
# Cache for 7 days, valid time units are "ms", "s", "m", "h"
# Not specifying a TTL sets no expiration on cached items and will live until restart
cache_ttl 168h
# freegeoip.app API token, this example reads from an environment variable
freegeoip_api_token {$FREEGEOIP_API_TOKEN}
# Sensitivity/Proximity
# sensitivity (proximity)
# 0 - 111 km
# 1 - 11.1 km
# 2 - 1.11 km
Expand All @@ -67,11 +67,17 @@ route /* {
# 5 1.11 meters
sensitivity 3
# AllowPrivateIPAddresses is a boolean for whether or not to allow private ip ranges
# allow_private_ip_addresses is a boolean for whether or not to allow private ip ranges
# such as 192.X, 172.X, 10.X, [::1] (localhost)
# false by default
# Some cellular networks doing NATing with 172.X addresses, in which case, you may not want to allow
allow_private_ip_addresses true
# allowlist is a list of IP addresses that will not be checked for proximity and will be allowed to access the server
allowlist 206.189.205.251 206.189.205.252
# status_code is the HTTP response code that is returned if IP address is not within proximity. Default is 403
status_code 403
}
}
Expand Down
49 changes: 30 additions & 19 deletions caddy_geofence.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ import (
"fmt"
"net"
"net/http"
"strings"
"time"

"github.com/caddyserver/caddy/v2"
Expand All @@ -20,6 +19,8 @@ const (
defaultCacheTTL = -1
// 100m
defaultSensitivity = 3
// 403
defaultStatusCode = http.StatusForbidden
// Logger namespace string
loggerNamespace = "geofence"
)
Expand All @@ -31,22 +32,26 @@ type CaddyGeofence struct {
// freegeoip_api_token is REQUIRED and is an API token from freegeoip.app
// Free tier includes 15000 requests per hour
FreeGeoIPAPIToken string `json:"freegeoip_api_token,omitempty"`
// RemoteIP is the IP address to geofence against
// remote_ip is the IP address to geofence against
// Not specifying this field results in geofencing the public address of the machine caddy is running on
RemoteIP string `json:"remote_ip,omitempty"`
// CacheTTL is string parameter for caching ip addresses with their allowed/not allowed state
// allowlist is a list of IP addresses that will not be checked for proximity and will be allowed to access the server
Allowlist []string `json:"allowlist,omitempty"`
// status_code is the HTTP response code that is returned if IP address is not within proximity. Default is 403
StatusCode int `json:"status_code,omitempty"`
// cache_ttl is string parameter for caching ip addresses with their allowed/not allowed state
// Not specifying a TTL sets no expiration on cached items and will live until restart
// Valid time units are "ms", "s", "m", "h"
CacheTTL time.Duration `json:"cache_ttl,omitempty"`
// Sensitivity is a 0-5 scale of the geofence proximity
// sensitivity is a 0-5 scale of the geofence proximity
// 0 - 111 km
// 1 - 11.1 km
// 2 - 1.11 km
// 3 111 meters
// 4 11.1 meters
// 5 1.11 meters
Sensitivity int `json:"sensitivity,omitempty"`
// AllowPrivateIPAddresses is a boolean for whether or not to allow private ip ranges
// allow_private_ip_addresses is a boolean for whether or not to allow private ip ranges
// such as 192.X, 172.X, 10.X, [::1] (localhost)
// false by default
// Some cellular networks doing NATing with 172.X addresses, in which case, you may not want to allow
Expand All @@ -66,14 +71,6 @@ func (CaddyGeofence) CaddyModule() caddy.ModuleInfo {
}
}

// isPrivateAddress checks if remote address is from known private ip space
func isPrivateAddress(addr string) bool {
return strings.HasPrefix(addr, "192.") ||
strings.HasPrefix(addr, "172.") ||
strings.HasPrefix(addr, "10.") ||
strings.HasPrefix(addr, "::1")
}

// Provision implements caddy.Provisioner.
func (cg *CaddyGeofence) Provision(ctx caddy.Context) error {
// Instantiate logger
Expand All @@ -84,7 +81,7 @@ func (cg *CaddyGeofence) Provision(ctx caddy.Context) error {
return fmt.Errorf("freegeoip_api_token: freegeoip API token not set")
}

// Set cache to never expire if not specified
// Set cache to never expire if not set
if cg.CacheTTL == 0 {
cg.CacheTTL = defaultCacheTTL
}
Expand All @@ -94,6 +91,11 @@ func (cg *CaddyGeofence) Provision(ctx caddy.Context) error {
cg.Sensitivity = defaultSensitivity
}

// Set default status code if not set (403)
if cg.StatusCode == 0 {
cg.StatusCode = defaultStatusCode
}

// Setup client
geofenceClient, err := geofence.New(&geofence.Config{
IPAddress: cg.RemoteIP,
Expand All @@ -116,25 +118,34 @@ func (cg CaddyGeofence) Validate() error {

// ServeHTTP implements caddyhttp.MiddlewareHandler.
func (cg CaddyGeofence) ServeHTTP(w http.ResponseWriter, r *http.Request, next caddyhttp.Handler) error {
// Get host address, can contain a port so we make sure strip that off
// Get host address, can contain a port so we make sure we strip that off
remoteAddr, _, err := net.SplitHostPort(r.RemoteAddr)
if err != nil {
return err
}

// Debug private addresses
// Check if ip address is in allowlist
inAllowlist := strInSlice(remoteAddr, cg.Allowlist)

// Debug private address/allowlist rules
cg.logger.Debug(loggerNamespace,
zap.String("remote_addr", remoteAddr),
zap.Bool("is_private_address", isPrivateAddress(remoteAddr)),
zap.Bool("is_private_address_allowed", cg.AllowPrivateIPAddresses),
zap.Bool("is_in_allowlist", inAllowlist),
)

// If known private ip and config says to allow
// If ip address is in allowlist, continue
if inAllowlist {
return next.ServeHTTP(w, r)
}

// If known private ip address and config says to allow private ip addresses
if isPrivateAddress(remoteAddr) && cg.AllowPrivateIPAddresses {
return next.ServeHTTP(w, r)
}

// Check if address is nearby
// Check if ip address is nearby
isAddressNear, err := cg.GeofenceClient.IsIPAddressNear(remoteAddr)
if err != nil {
return err
Expand All @@ -148,7 +159,7 @@ func (cg CaddyGeofence) ServeHTTP(w http.ResponseWriter, r *http.Request, next c

// If remote address is not nearby, reject the request
if !isAddressNear {
return caddyhttp.Error(http.StatusForbidden, nil)
return caddyhttp.Error(cg.StatusCode, nil)
}

return next.ServeHTTP(w, r)
Expand Down
18 changes: 16 additions & 2 deletions caddyfile.go
Original file line number Diff line number Diff line change
Expand Up @@ -35,14 +35,28 @@ func (cg *CaddyGeofence) UnmarshalCaddyfile(d *caddyfile.Dispenser) error {
return d.ArgErr()
}
cg.FreeGeoIPAPIToken = d.Val()
case "remote_IP":
case "remote_ip":
if !d.NextArg() {
return d.ArgErr()
}
if net.ParseIP(d.Val()) == nil {
return fmt.Errorf("remote_ip: invalid IPv4 address provided")
return fmt.Errorf("remote_ip: invalid IP address provided")
}
cg.RemoteIP = d.Val()
case "allowlist":
cg.Allowlist = d.RemainingArgs()
if len(cg.Allowlist) == 0 {
return d.ArgErr()
}
case "status_code":
if !d.NextArg() {
return d.ArgErr()
}
statusCode, err := strconv.Atoi(d.Val())
if err != nil {
return err
}
cg.StatusCode = statusCode
case "sensitivity":
if !d.NextArg() {
return d.ArgErr()
Expand Down
6 changes: 2 additions & 4 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -190,10 +190,6 @@ github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e h1:fY5BOSpyZCqRo5O
github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=
github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1 h1:q763qf9huN11kDQavWsoZXJNW3xEE4JJyHa5Q25/sd8=
github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
github.com/circa10a/go-geofence v0.2.2 h1:5oQ2X++fMsB1yCsesDI2ThmvGJjZSeqO9GiYOdE6qvU=
github.com/circa10a/go-geofence v0.2.2/go.mod h1:ngFR0sZiPl8QbbIm6TNCnSVppyyvP8SNbbXQ3sAJALw=
github.com/circa10a/go-geofence v0.3.0 h1:s0I9BSMUJyLymolWREK9Iu/Vek+ecGEPp86gLuiNYFg=
github.com/circa10a/go-geofence v0.3.0/go.mod h1:0DFACqglBg6J36ksSzUpCdyBQlxEavqEbw3Yz/Otdic=
github.com/circa10a/go-geofence v0.3.1 h1:UvKkNBFe5uqaUdDt4jK1uPLtVXaJMdN5l2BA+jwS+aY=
github.com/circa10a/go-geofence v0.3.1/go.mod h1:0DFACqglBg6J36ksSzUpCdyBQlxEavqEbw3Yz/Otdic=
github.com/clbanning/x2j v0.0.0-20191024224557-825249438eec/go.mod h1:jMjuTZXRI4dUb/I5gc9Hdhagfvm9+RyrPryS/auMzxE=
Expand Down Expand Up @@ -511,6 +507,8 @@ github.com/imdario/mergo v0.3.12/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH
github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8=
github.com/influxdata/influxdb1-client v0.0.0-20191209144304-8bf82d3c094d/go.mod h1:qj24IKcXYK6Iy9ceXlo3Tc+vtHo9lIhSX5JddghvEPo=
github.com/jarcoal/httpmock v1.0.5/go.mod h1:ATjnClrvW/3tijVmpL/va5Z3aAyGvqU3gCT8nX0Txik=
github.com/jarcoal/httpmock v1.0.8 h1:8kI16SoO6LQKgPE7PvQuV+YuD/inwHd7fOOe2zMbo4k=
github.com/jarcoal/httpmock v1.0.8/go.mod h1:ATjnClrvW/3tijVmpL/va5Z3aAyGvqU3gCT8nX0Txik=
github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99/go.mod h1:1lJo3i6rXxKeerYnT8Nvf0QmHCRC1n8sfWVwXF2Frvo=
github.com/jellevandenhooff/dkim v0.0.0-20150330215556-f50fe3d243e1/go.mod h1:E0B/fFc00Y+Rasa88328GlI/XbtyysCtTHZS8h7IrBU=
github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI=
Expand Down
21 changes: 21 additions & 0 deletions utils.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package caddygeofence

import "strings"

// isPrivateAddress checks if remote address is from known private ip space
func isPrivateAddress(addr string) bool {
return strings.HasPrefix(addr, "192.") ||
strings.HasPrefix(addr, "172.") ||
strings.HasPrefix(addr, "10.") ||
strings.HasPrefix(addr, "::1")
}

// strInSlice returns true if string is in slice
func strInSlice(str string, list []string) bool {
for _, item := range list {
if str == item {
return true
}
}
return false
}

0 comments on commit 64470ff

Please sign in to comment.