HTTP with Go: IP blocklisting middleware
Implementing HTTP IP-based blocklisting with Golang.
Last updated on: 2024-12-09
Parse IP blocklist from config or other:
blockedIPNets := make([]*net.IPNet, 0, len(conf.IPBlocklist))
for _, v := range conf.IPBlocklist {
_, ipNet, err := net.ParseCIDR(v)
if err != nil {
return fmt.Errorf("parse CIDR: %w", err)
}
blockedIPNets = append(blockedIPNets, ipNet)
}
Define middleware type alias (optional):
type Middleware = func(http.Handler) http.Handler
Implement IP blocklist middleware:
func NewIPBlockMiddleware(trueIPHeader string, blocklist []*net.IPNet) Middleware {
return func(h http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
// Get request's IP address.
remoteAddr := r.RemoteAddr
if trueIPHeader != "" {
remoteAddr = r.Header.Get(trueIPHeader)
}
host, _, _ := net.SplitHostPort(remoteAddr)
if host == "" {
host = remoteAddr
}
ipAddr := net.ParseIP(host)
// Check if address is blocked.
if ipAddr != nil {
for _, blocked := range blocklist {
if blocked.Contains(ipAddr) {
http.Error(w, "Forbidden", http.StatusForbidden)
return
}
}
}
h.ServeHTTP(w, r)
})
}
}
Use the middleware:
httpHandler := NewIPBlockMiddleware("", []string{"1.2.3.4/32"})(httpHandler)
Notes
If you want the blocklist to be dynamic (for ex: when the source is a database), you can easily adapt the middleware to use a database query or local cache instead.
If performance is critical, you may look into more efficient tree-like data structures for checking if an IP address is within a given set.