Documentation
¶
Overview ¶
Package ipfilter refuses requests by client address with 403 ip_not_allowed (ADR-0085):
allow, err := ipfilter.ParsePrefixes(os.Getenv("OFFICE_RANGES"))
onlyOffice, err := ipfilter.New(allow, nil)
handler := httpx.Chain(mux, httpx.TrustedProxies(proxies), onlyOffice)
gorbital.dev/gorbital/opshttp puts every /ops/ route behind it when OPS_ALLOWED_IPS is set.
It is a package of its own, not part of httpx, because apps generated by orb v0.1 record every problem code of the gorbital.dev packages they link in api/surface.json: a new code in httpx would fail their tests (docs/guides/stability.md).
Stability: experimental until v0.2.0 (ADR-0015, ADR-0081).
Index ¶
Examples ¶
Constants ¶
This section is empty.
Variables ¶
var ErrDenyAll = errors.New("ipfilter: a deny range can't cover every address; list the allowed ranges instead")
ErrDenyAll reports a deny range covering every IPv4 or IPv6 address, which would refuse every request of that family.
Functions ¶
func New ¶
func New(allow, deny []netip.Prefix) (httpx.Middleware, error)
New returns middleware that refuses requests by client address with a 403 problem, code "ip_not_allowed". The address is the request's RemoteAddr, so behind a proxy install httpx.TrustedProxies first. A request is refused when its address is in a deny range, or when allow isn't empty and its address is in no allow range: deny wins, and an empty allow list allows every address not denied. A RemoteAddr that isn't an IP address is refused. IPv4-mapped IPv6 addresses are compared as IPv4, and IPv6 zones are ignored.
It returns an error for an invalid range, a deny range covering every address of a family (ErrDenyAll; list allowed ranges instead), or an allow range entirely inside a deny range, which could never match. With both lists empty the middleware changes nothing.
Example ¶
package main
import (
"encoding/json"
"fmt"
"net/http"
"net/http/httptest"
"gorbital.dev/httpx"
"gorbital.dev/httpx/ipfilter"
)
func main() {
allow, err := ipfilter.ParsePrefixes("10.0.0.0/8, 2001:db8::/32")
if err != nil {
panic(err)
}
onlyOffice, err := ipfilter.New(allow, nil)
if err != nil {
panic(err)
}
ops := httpx.Chain(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
_, _ = w.Write([]byte("ok"))
}), onlyOffice)
for _, addr := range []string{"10.1.2.3:52000", "203.0.113.9:52000"} {
req := httptest.NewRequest(http.MethodGet, "/ops/system", nil)
req.RemoteAddr = addr
rec := httptest.NewRecorder()
ops.ServeHTTP(rec, req)
fmt.Println(addr, rec.Code, rec.Body.String() == "ok" || problemCode(rec.Body.Bytes()) == "ip_not_allowed")
}
}
// problemCode returns the code of a problem response body.
func problemCode(body []byte) string {
var p httpx.Problem
_ = json.Unmarshal(body, &p)
return p.Code
}
Output: 10.1.2.3:52000 200 true 203.0.113.9:52000 403 true
Example (BehindAProxy) ¶
package main
import (
"fmt"
"net/http"
"net/http/httptest"
"gorbital.dev/httpx"
"gorbital.dev/httpx/ipfilter"
)
func main() {
// Behind a load balancer, resolve the client address first.
proxies, _ := httpx.ParseTrustedProxies("10.0.0.0/8")
deny, _ := ipfilter.ParsePrefixes("198.51.100.0/24")
filter, err := ipfilter.New(nil, deny)
if err != nil {
panic(err)
}
h := httpx.Chain(http.NotFoundHandler(), httpx.TrustedProxies(proxies), filter)
req := httptest.NewRequest(http.MethodGet, "/", nil)
req.RemoteAddr = "10.0.0.2:443"
req.Header.Set("X-Forwarded-For", "198.51.100.7")
rec := httptest.NewRecorder()
h.ServeHTTP(rec, req)
fmt.Println(rec.Code)
}
Output: 403
func ParsePrefixes ¶
ParsePrefixes reads a comma-separated list of CIDR ranges or single addresses, such as "10.0.0.0/8, 2001:db8::/32, 192.0.2.10", for New. Ranges are masked ("10.0.0.5/8" is 10.0.0.0/8), and IPv4 addresses written in IPv6 form ("::ffff:10.0.0.5") become IPv4, as client addresses are compared. An empty list returns nil.
Example ¶
package main
import (
"fmt"
"gorbital.dev/httpx/ipfilter"
)
func main() {
p, err := ipfilter.ParsePrefixes("10.0.0.5/8, 192.0.2.10, ::ffff:198.51.100.1")
fmt.Println(p, err)
_, err = ipfilter.ParsePrefixes("office-vpn")
fmt.Println(err)
}
Output: [10.0.0.0/8 192.0.2.10/32 198.51.100.1/32] <nil> ipfilter: "office-vpn" is not a CIDR range or IP address
Types ¶
This section is empty.