ipn/ipnlocal: add some peerapi tests

Updates tailscale/corp#1594

Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
pull/1744/head
Brad Fitzpatrick 4 years ago
parent f72a120016
commit 0d1550898e

@ -290,7 +290,6 @@ func (pln *peerAPIListener) serve() {
remoteAddr: ipp, remoteAddr: ipp,
peerNode: peerNode, peerNode: peerNode,
peerUser: peerUser, peerUser: peerUser,
lb: pln.lb,
} }
httpServer := &http.Server{ httpServer := &http.Server{
Handler: h, Handler: h,
@ -324,7 +323,6 @@ type peerAPIHandler struct {
isSelf bool // whether peerNode is owned by same user as this node isSelf bool // whether peerNode is owned by same user as this node
peerNode *tailcfg.Node // peerNode is who's making the request peerNode *tailcfg.Node // peerNode is who's making the request
peerUser tailcfg.UserProfile // profile of peerNode peerUser tailcfg.UserProfile // profile of peerNode
lb *LocalBackend
} }
func (h *peerAPIHandler) logf(format string, a ...interface{}) { func (h *peerAPIHandler) logf(format string, a ...interface{}) {
@ -333,7 +331,7 @@ func (h *peerAPIHandler) logf(format string, a ...interface{}) {
func (h *peerAPIHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { func (h *peerAPIHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
if strings.HasPrefix(r.URL.Path, "/v0/put/") { if strings.HasPrefix(r.URL.Path, "/v0/put/") {
h.put(w, r) h.handlePeerPut(w, r)
return return
} }
who := h.peerUser.DisplayName who := h.peerUser.DisplayName
@ -405,7 +403,7 @@ func (f *incomingFile) PartialFile() ipn.PartialFile {
} }
} }
func (h *peerAPIHandler) put(w http.ResponseWriter, r *http.Request) { func (h *peerAPIHandler) handlePeerPut(w http.ResponseWriter, r *http.Request) {
if !h.isSelf { if !h.isSelf {
http.Error(w, "not owner", http.StatusForbidden) http.Error(w, "not owner", http.StatusForbidden)
return return

@ -0,0 +1,153 @@
// Copyright (c) 2021 Tailscale Inc & AUTHORS All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package ipnlocal
import (
"bytes"
"fmt"
"net/http"
"net/http/httptest"
"os"
"path/filepath"
"strings"
"testing"
"tailscale.com/tailcfg"
"tailscale.com/types/netmap"
)
type peerAPITestEnv struct {
ph *peerAPIHandler
rr *httptest.ResponseRecorder
logBuf bytes.Buffer
}
func (e *peerAPITestEnv) logf(format string, a ...interface{}) {
fmt.Fprintf(&e.logBuf, format, a...)
}
type check func(*testing.T, *peerAPITestEnv)
func checks(vv ...check) []check { return vv }
func httpStatus(wantStatus int) check {
return func(t *testing.T, e *peerAPITestEnv) {
if res := e.rr.Result(); res.StatusCode != wantStatus {
t.Errorf("HTTP response code = %v; want %v", res.Status, wantStatus)
}
}
}
func bodyContains(sub string) check {
return func(t *testing.T, e *peerAPITestEnv) {
if body := e.rr.Body.String(); !strings.Contains(body, sub) {
t.Errorf("HTTP response body does not contain %q; got: %s", sub, body)
}
}
}
func fileHasSize(name string, size int64) check {
return func(t *testing.T, e *peerAPITestEnv) {
root := e.ph.ps.rootDir
if root == "" {
t.Errorf("no rootdir; can't check whether %q has size %v", name, size)
return
}
path := filepath.Join(root, name)
if fi, err := os.Stat(path); err != nil {
t.Errorf("fileHasSize(%q, %v): %v", name, size, err)
} else if fi.Size() != size {
t.Errorf("file %q has size %v; want %v", name, fi.Size(), size)
}
}
}
func TestHandlePeerPut(t *testing.T) {
tests := []struct {
name string
isSelf bool // the peer sending the request is owned by us
capSharing bool // self node has file sharing capabilty
omitRoot bool // don't configure
req *http.Request
checks []check
}{
{
name: "reject_non_owner_put",
isSelf: false,
capSharing: true,
req: httptest.NewRequest("PUT", "/v0/put/foo", nil),
checks: checks(
httpStatus(http.StatusForbidden),
bodyContains("not owner"),
),
},
{
name: "owner_without_cap",
isSelf: true,
capSharing: false,
req: httptest.NewRequest("PUT", "/v0/put/foo", nil),
checks: checks(
httpStatus(http.StatusForbidden),
bodyContains("file sharing not enabled by Tailscale admin"),
),
},
{
name: "owner_with_cap_no_rootdir",
omitRoot: true,
isSelf: true,
capSharing: true,
req: httptest.NewRequest("PUT", "/v0/put/foo", nil),
checks: checks(
httpStatus(http.StatusInternalServerError),
bodyContains("no rootdir"),
),
},
{
name: "owner_with_cap",
isSelf: true,
capSharing: true,
req: httptest.NewRequest("PUT", "/v0/put/foo", nil),
checks: checks(
httpStatus(200),
bodyContains("{}"),
fileHasSize("foo", 0),
),
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
var caps []string
if tt.capSharing {
caps = append(caps, tailcfg.CapabilityFileSharing)
}
var e peerAPITestEnv
lb := &LocalBackend{
netMap: &netmap.NetworkMap{
SelfNode: &tailcfg.Node{
Capabilities: caps,
},
},
logf: e.logf,
}
e.ph = &peerAPIHandler{
isSelf: tt.isSelf,
peerNode: &tailcfg.Node{
ComputedName: "some-peer-name",
},
ps: &peerAPIServer{
b: lb,
},
}
if !tt.omitRoot {
e.ph.ps.rootDir = t.TempDir()
}
e.rr = httptest.NewRecorder()
e.ph.ServeHTTP(e.rr, tt.req)
for _, f := range tt.checks {
f(t, &e)
}
})
}
}
Loading…
Cancel
Save