mirror of
https://github.com/tailscale/tailscale.git
synced 2024-11-29 13:05:46 +00:00
ipn/ipnlocal: add some validation to PeerAPI
Updates tailscale/corp#7948 Signed-off-by: Maisem Ali <maisem@tailscale.com>
This commit is contained in:
parent
1f4669a380
commit
1e78fc462c
@ -32,6 +32,7 @@
|
|||||||
"unicode/utf8"
|
"unicode/utf8"
|
||||||
|
|
||||||
"github.com/kortschak/wol"
|
"github.com/kortschak/wol"
|
||||||
|
"golang.org/x/exp/slices"
|
||||||
"golang.org/x/net/dns/dnsmessage"
|
"golang.org/x/net/dns/dnsmessage"
|
||||||
"tailscale.com/client/tailscale/apitype"
|
"tailscale.com/client/tailscale/apitype"
|
||||||
"tailscale.com/envknob"
|
"tailscale.com/envknob"
|
||||||
@ -542,7 +543,37 @@ func (h *peerAPIHandler) logf(format string, a ...any) {
|
|||||||
h.ps.b.logf("peerapi: "+format, a...)
|
h.ps.b.logf("peerapi: "+format, a...)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (h *peerAPIHandler) validateHost(r *http.Request) error {
|
||||||
|
if r.Host == "peer" {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
ap, err := netip.ParseAddrPort(r.Host)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
hostIPPfx := netip.PrefixFrom(ap.Addr(), ap.Addr().BitLen())
|
||||||
|
if !slices.Contains(h.ps.selfNode.Addresses, hostIPPfx) {
|
||||||
|
return fmt.Errorf("%v not found in self addresses", hostIPPfx)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *peerAPIHandler) validatePeerAPIRequest(r *http.Request) error {
|
||||||
|
if r.Referer() != "" {
|
||||||
|
return errors.New("unexpected Referer")
|
||||||
|
}
|
||||||
|
if r.Header.Get("Origin") != "" {
|
||||||
|
return errors.New("unexpected Origin")
|
||||||
|
}
|
||||||
|
return h.validateHost(r)
|
||||||
|
}
|
||||||
|
|
||||||
func (h *peerAPIHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
func (h *peerAPIHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||||
|
if err := h.validatePeerAPIRequest(r); err != nil {
|
||||||
|
h.logf("invalid request from %v: %v", h.remoteAddr, err)
|
||||||
|
http.Error(w, "invalid peerapi request", http.StatusForbidden)
|
||||||
|
return
|
||||||
|
}
|
||||||
if strings.HasPrefix(r.URL.Path, "/v0/put/") {
|
if strings.HasPrefix(r.URL.Path, "/v0/put/") {
|
||||||
h.handlePeerPut(w, r)
|
h.handlePeerPut(w, r)
|
||||||
return
|
return
|
||||||
|
@ -108,6 +108,7 @@ func hexAll(v string) string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestHandlePeerAPI(t *testing.T) {
|
func TestHandlePeerAPI(t *testing.T) {
|
||||||
|
const nodeFQDN = "self-node.tail-scale.ts.net."
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
name string
|
name string
|
||||||
isSelf bool // the peer sending the request is owned by us
|
isSelf bool // the peer sending the request is owned by us
|
||||||
@ -402,6 +403,30 @@ func TestHandlePeerAPI(t *testing.T) {
|
|||||||
bodyContains("bad filename"),
|
bodyContains("bad filename"),
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name: "host-val/bad-ip",
|
||||||
|
isSelf: true,
|
||||||
|
req: httptest.NewRequest("GET", "http://12.23.45.66:1234/v0/env", nil),
|
||||||
|
checks: checks(
|
||||||
|
httpStatus(403),
|
||||||
|
),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "host-val/no-port",
|
||||||
|
isSelf: true,
|
||||||
|
req: httptest.NewRequest("GET", "http://100.100.100.101/v0/env", nil),
|
||||||
|
checks: checks(
|
||||||
|
httpStatus(403),
|
||||||
|
),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "host-val/peer",
|
||||||
|
isSelf: true,
|
||||||
|
req: httptest.NewRequest("GET", "http://peer/v0/env", nil),
|
||||||
|
checks: checks(
|
||||||
|
httpStatus(200),
|
||||||
|
),
|
||||||
|
},
|
||||||
}
|
}
|
||||||
for _, tt := range tests {
|
for _, tt := range tests {
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
@ -417,6 +442,11 @@ func TestHandlePeerAPI(t *testing.T) {
|
|||||||
},
|
},
|
||||||
ps: &peerAPIServer{
|
ps: &peerAPIServer{
|
||||||
b: lb,
|
b: lb,
|
||||||
|
selfNode: &tailcfg.Node{
|
||||||
|
Addresses: []netip.Prefix{
|
||||||
|
netip.MustParsePrefix("100.100.100.101/32"),
|
||||||
|
},
|
||||||
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
var rootDir string
|
var rootDir string
|
||||||
@ -425,6 +455,9 @@ func TestHandlePeerAPI(t *testing.T) {
|
|||||||
e.ph.ps.rootDir = rootDir
|
e.ph.ps.rootDir = rootDir
|
||||||
}
|
}
|
||||||
e.rr = httptest.NewRecorder()
|
e.rr = httptest.NewRecorder()
|
||||||
|
if tt.req.Host == "example.com" {
|
||||||
|
tt.req.Host = "100.100.100.101:12345"
|
||||||
|
}
|
||||||
e.ph.ServeHTTP(e.rr, tt.req)
|
e.ph.ServeHTTP(e.rr, tt.req)
|
||||||
for _, f := range tt.checks {
|
for _, f := range tt.checks {
|
||||||
f(t, &e)
|
f(t, &e)
|
||||||
@ -455,6 +488,9 @@ func TestFileDeleteRace(t *testing.T) {
|
|||||||
logf: t.Logf,
|
logf: t.Logf,
|
||||||
capFileSharing: true,
|
capFileSharing: true,
|
||||||
},
|
},
|
||||||
|
selfNode: &tailcfg.Node{
|
||||||
|
Addresses: []netip.Prefix{netip.MustParsePrefix("100.100.100.101/32")},
|
||||||
|
},
|
||||||
rootDir: dir,
|
rootDir: dir,
|
||||||
}
|
}
|
||||||
ph := &peerAPIHandler{
|
ph := &peerAPIHandler{
|
||||||
@ -467,7 +503,7 @@ func TestFileDeleteRace(t *testing.T) {
|
|||||||
buf := make([]byte, 2<<20)
|
buf := make([]byte, 2<<20)
|
||||||
for i := 0; i < 30; i++ {
|
for i := 0; i < 30; i++ {
|
||||||
rr := httptest.NewRecorder()
|
rr := httptest.NewRecorder()
|
||||||
ph.ServeHTTP(rr, httptest.NewRequest("PUT", "/v0/put/foo.txt", bytes.NewReader(buf[:rand.Intn(len(buf))])))
|
ph.ServeHTTP(rr, httptest.NewRequest("PUT", "http://100.100.100.101:123/v0/put/foo.txt", bytes.NewReader(buf[:rand.Intn(len(buf))])))
|
||||||
if res := rr.Result(); res.StatusCode != 200 {
|
if res := rr.Result(); res.StatusCode != 200 {
|
||||||
t.Fatal(res.Status)
|
t.Fatal(res.Status)
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user