I'm writing a small web service in GO using just the GO http package. I want to restrict access to the web service to clients on the local subnets (127.0.0.1, 10.0.0.0/8, 172.16.0.0/12, 192.168.0.0/16).
I tried using a subnet mask as addr argument to ListenAndServe but it exits with a "no such host" error.
EDIT:
This is the solution I came up with the help of @RickA and @Dewy Broto.
func JustLocal(handler http.Handler) http.Handler {
var local_subnets []*net.IPNet
local_subnet_s := []string{"127.0.0.1/31", "10.0.0.0/8", "172.16.0.0/12", "192.168.0.0/16"}
for _,net_s := range local_subnet_s {
_, n, _ := net.ParseCIDR(net_s)
local_subnets = append(local_subnets, n)
}
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
fmt.Println(r.RemoteAddr)
remote_ip := net.ParseIP(strings.Split(r.RemoteAddr, ":")[0])
fmt.Println(remote_ip)
local := false
for _, local_subnet := range local_subnets {
fmt.Println(local_subnet, remote_ip)
if local_subnet.Contains(remote_ip) {
local = true
break
}
}
if !local {
http.Error(w, "go away", 403)
return
}
handler.ServeHTTP(w, r)
return
})
}
It's a bit raw around the edges but it works as far as I could tell. Thanks for all the help!
Write a handler that checks to see if the client's IP address is allowed before delegating to another handler:
type authorizationHandler struct {
nets []*net.IPNet
h handler
}
func (ah *authorizationHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
ok := false
ip := net.ParseIP(r.RemoteAddr)
for _, n := range ah.nets {
if n.Contains(ip) {
ok := true
break
}
}
if !ok {
http.Error(w, "go away", 403)
return
}
ah.h(w, r)
}
Use this handler to wrap your the root handler:
err := ListenAndServe(addr,
&authorizationHandler{nets: allowedNets, h:rootHandler))
Note: I typed in the above code without compiling or testing it. Some tweaks are probably required around parsing and testing the remote address.
The address in ListenAndServe
is the address your server binds to on the machine where it is running. It is not some sort of ip whitelist thingy. This allows you to specify on what (network)interface you want to bind the server.
So if this machine has a ethernet card (ifconfig
) that has the ipadress 10.0.100.100
you can bind ListenAndServe
to 10.0.100.100:8123
. The ip-less version :8123
binds the server to all available interfaces. eg. [::]:8123
(netstat -l
), and is thus reachable from outside.
To get what you want you have a couple of options:
ListenAndServe
to the ip of that subnet on your machine. Thus it should only be routable from within that subnet.request.URL
and test that against your whitelist)You can listen on multiple ports / hosts, you have to call ListenAndServe
multiple times.
func main() {
addrs := []string{"127.0.0.1:8080", "10.0.0.1:8080", "172.16.0.1:8080", "192.168.0.1:8080", "192.168.1.1:8080"}
errs := make(chan error)
for _, addr := range addrs {
go func(addr string) {
errs <- http.ListenAndServe(addr, nil) //or replace the nil with your mux
}(addr)
}
i := len(addrs)
for err := range errs {
fmt.Println(err)
if i = i - 1; i == 0 {
break
}
}
}
Or you could use nging and just make your go server listen on 127.0.0.1
.