@@ -20,9 +20,12 @@ type Config struct {
2020 Enabled bool // Enable this plugin?
2121 DatabaseFilePath string // Path to ip2location database file
2222 AllowedCountries []string // Whitelist of countries to allow (ISO 3166-1 alpha-2)
23+ BlockedCountries []string // Blocklist of countries to be blocked (ISO 3166-1 alpha-2)
24+ DefaultAllow bool // If source matches neither blocklist nor whitelist, should it be allowed through?
2325 AllowPrivate bool // Allow requests from private / internal networks?
2426 DisallowedStatusCode int // HTTP status code to return for disallowed requests
2527 AllowedIPBlocks []string // List of whitelist CIDR
28+ BlockedIPBlocks []string // List of blocklisted CIDRs
2629}
2730
2831// CreateConfig creates the default plugin configuration.
@@ -36,16 +39,20 @@ type Plugin struct {
3639 db * ip2location.DB
3740 enabled bool
3841 allowedCountries []string
42+ blockedCountries []string
43+ defaultAllow bool
3944 allowPrivate bool
4045 disallowedStatusCode int
4146 allowedIPBlocks []* net.IPNet
47+ blockedIPBlocks []* net.IPNet
4248}
4349
4450// New creates a new plugin instance.
4551func New (_ context.Context , next http.Handler , cfg * Config , name string ) (http.Handler , error ) {
4652 if next == nil {
4753 return nil , fmt .Errorf ("%s: no next handler provided" , name )
4854 }
55+
4956 if cfg == nil {
5057 return nil , fmt .Errorf ("%s: no config provided" , name )
5158 }
@@ -73,7 +80,12 @@ func New(_ context.Context, next http.Handler, cfg *Config, name string) (http.H
7380 return nil , fmt .Errorf ("%s: failed to open database: %w" , name , err )
7481 }
7582
76- allowedIPBlocks , err := initAllowedIPBlocks (cfg .AllowedIPBlocks )
83+ allowedIPBlocks , err := initIPBlocks (cfg .AllowedIPBlocks )
84+ if err != nil {
85+ return nil , fmt .Errorf ("%s: failed loading allowed CIDR blocks: %w" , name , err )
86+ }
87+
88+ blockedIPBlocks , err := initIPBlocks (cfg .BlockedIPBlocks )
7789 if err != nil {
7890 return nil , fmt .Errorf ("%s: failed loading allowed CIDR blocks: %w" , name , err )
7991 }
@@ -84,9 +96,12 @@ func New(_ context.Context, next http.Handler, cfg *Config, name string) (http.H
8496 db : db ,
8597 enabled : cfg .Enabled ,
8698 allowedCountries : cfg .AllowedCountries ,
99+ blockedCountries : cfg .BlockedCountries ,
100+ defaultAllow : cfg .DefaultAllow ,
87101 allowPrivate : cfg .AllowPrivate ,
88102 disallowedStatusCode : cfg .DisallowedStatusCode ,
89103 allowedIPBlocks : allowedIPBlocks ,
104+ blockedIPBlocks : blockedIPBlocks ,
90105 }, nil
91106}
92107
@@ -146,37 +161,92 @@ func (p Plugin) GetRemoteIPs(req *http.Request) []string {
146161}
147162
148163// CheckAllowed checks whether a given IP address is allowed according to the configured allowed countries.
149- func (p Plugin ) CheckAllowed (ip string ) (bool , string , error ) {
150- country , err := p .Lookup (ip )
164+ func (p Plugin ) CheckAllowed (ip string ) (allow bool , country string , err error ) {
165+ var allowedCountry , allowedIP , blockedCountry , blockedIP bool
166+ var allowedNetworkLength , blockedNetworkLength int
167+
168+ country , err = p .Lookup (ip )
151169 if err != nil {
152- return false , "" , fmt .Errorf ("lookup of %s failed: %w" , ip , err )
170+ return false , ip , fmt .Errorf ("lookup of %s failed: %w" , ip , err )
153171 }
154172
155- if country == "-" { // Private address
156- if p .allowPrivate {
157- return true , ip , nil
173+ if country == "-" {
174+ return p .allowPrivate , country , nil
175+ }
176+
177+ if country != "-" {
178+ for _ , item := range p .blockedCountries {
179+ if item == country {
180+ blockedCountry = true
181+
182+ break
183+ }
158184 }
159185
160- return false , ip , nil
186+ for _ , item := range p .allowedCountries {
187+ if item == country {
188+ allowedCountry = true
189+ }
190+ }
191+ }
192+
193+ blocked , blockedNetworkLength , err := p .isBlockedIPBlocks (ip )
194+ if err != nil {
195+ return false , ip , fmt .Errorf ("failed to check if IP %q is blocked by IP block: %w" , ip , err )
196+ }
197+
198+ if blocked {
199+ blockedIP = true
161200 }
162201
163- var allowed bool
164202 for _ , allowedCountry := range p .allowedCountries {
165203 if allowedCountry == country {
166- return true , country , nil
204+ return true , ip , nil
167205 }
168206 }
169207
170- allowed , err = p .isAllowedIPBlocks (ip )
208+ allowed , allowedNetBits , err : = p .isAllowedIPBlocks (ip )
171209 if err != nil {
172- return false , "" , fmt .Errorf ("checking if %s is part of an allowed range failed : %w" , ip , err )
210+ return false , ip , fmt .Errorf ("failed to check if IP %q is allowed by IP block : %w" , ip , err )
173211 }
174212
175- if ! allowed {
213+ if allowed {
214+ allowedIP = true
215+ allowedNetworkLength = allowedNetBits
216+ }
217+
218+ // Handle final values
219+ //
220+ // NB: discrete IPs have higher priority than countries: more specific to less specific.
221+
222+ // NB: whichever matched prefix is longer has higher priority: more specific to less specific.
223+ if allowedNetworkLength < blockedNetworkLength {
224+ if blockedIP {
225+ return false , country , nil
226+ }
227+
228+ if allowedIP {
229+ return true , country , nil
230+ }
231+ } else {
232+ if allowedIP {
233+ return true , country , nil
234+ }
235+
236+ if blockedIP {
237+ return false , country , nil
238+ }
239+ }
240+
241+ if allowedCountry {
242+ return true , country , nil
243+ }
244+
245+ if blockedCountry {
176246 return false , country , nil
177247 }
178248
179- return true , country , nil
249+ return p . defaultAllow , country , nil
180250}
181251
182252// Lookup queries the ip2location database for a given IP address.
@@ -195,34 +265,46 @@ func (p Plugin) Lookup(ip string) (string, error) {
195265}
196266
197267// Create IP Networks using CIDR block array
198- func initAllowedIPBlocks ( allowedIPBlocks []string ) ([]* net.IPNet , error ) {
268+ func initIPBlocks ( ipBlocks []string ) ([]* net.IPNet , error ) {
199269
200- var allowedIPBlocksNet []* net.IPNet
270+ var ipBlocksNet []* net.IPNet
201271
202- for _ , cidr := range allowedIPBlocks {
272+ for _ , cidr := range ipBlocks {
203273 _ , block , err := net .ParseCIDR (cidr )
204274 if err != nil {
205275 return nil , fmt .Errorf ("parse error on %q: %v" , cidr , err )
206276 }
207- allowedIPBlocksNet = append (allowedIPBlocksNet , block )
277+ ipBlocksNet = append (ipBlocksNet , block )
208278 }
209279
210- return allowedIPBlocksNet , nil
280+ return ipBlocksNet , nil
211281}
212282
213- // isAllowedIPBlocks check if an IP is allowed base on the allowed CIDR blocks
214- func (p Plugin ) isAllowedIPBlocks (ip string ) (bool , error ) {
215- var ipAddress net.IP = net .ParseIP (ip )
283+ // isAllowedIPBlocks checks if an IP is allowed base on the allowed CIDR blocks
284+ func (p Plugin ) isAllowedIPBlocks (ip string ) (bool , int , error ) {
285+ return p .isInIPBlocks (ip , p .allowedIPBlocks )
286+ }
287+
288+ // isBlockedIPBlocks checks if an IP is allowed base on the blocked CIDR blocks
289+ func (p Plugin ) isBlockedIPBlocks (ip string ) (bool , int , error ) {
290+ return p .isInIPBlocks (ip , p .blockedIPBlocks )
291+ }
292+
293+ // isInIPBlocks indicates whether the given IP exists in any of the IP subnets contained within ipBlocks.
294+ func (p Plugin ) isInIPBlocks (ip string , ipBlocks []* net.IPNet ) (bool , int , error ) {
295+ ipAddress := net .ParseIP (ip )
216296
217297 if ipAddress == nil {
218- return false , fmt .Errorf ("unable parse IP address from address [%s]" , ip )
298+ return false , 0 , fmt .Errorf ("unable parse IP address from address [%s]" , ip )
219299 }
220300
221- for _ , block := range p . allowedIPBlocks {
301+ for _ , block := range ipBlocks {
222302 if block .Contains (ipAddress ) {
223- return true , nil
303+ ones , _ := block .Mask .Size ()
304+
305+ return true , ones , nil
224306 }
225307 }
226308
227- return false , nil
309+ return false , 0 , nil
228310}
0 commit comments