限制来自本地子网的客户端对GO Web服务的访问

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:

  • Put the machine in a private subnet that you trust and bind ListenAndServe to the ip of that subnet on your machine. Thus it should only be routable from within that subnet.
  • Use a webserver in front of this server, have it pass requests to it based on its ip acl (ex. nginx)
  • Implement your own ip(range) filtering in the go server code (eg. use 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.