ipn/localapi: log calls to localapi (#17880)

Updates tailscale/corp#34238

Signed-off-by: James Sanderson <jsanderson@tailscale.com>
pull/17961/head
James 'zofrex' Sanderson 2 weeks ago committed by GitHub
parent a2e9dfacde
commit 9048ea25db
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194

@ -264,7 +264,8 @@ func (h *Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
return return
} }
} }
if fn, ok := handlerForPath(r.URL.Path); ok { if fn, route, ok := handlerForPath(r.URL.Path); ok {
h.logRequest(r.Method, route)
fn(h, w, r) fn(h, w, r)
} else { } else {
http.NotFound(w, r) http.NotFound(w, r)
@ -300,9 +301,9 @@ func (h *Handler) validHost(hostname string) bool {
// handlerForPath returns the LocalAPI handler for the provided Request.URI.Path. // handlerForPath returns the LocalAPI handler for the provided Request.URI.Path.
// (the path doesn't include any query parameters) // (the path doesn't include any query parameters)
func handlerForPath(urlPath string) (h LocalAPIHandler, ok bool) { func handlerForPath(urlPath string) (h LocalAPIHandler, route string, ok bool) {
if urlPath == "/" { if urlPath == "/" {
return (*Handler).serveLocalAPIRoot, true return (*Handler).serveLocalAPIRoot, "/", true
} }
suff, ok := strings.CutPrefix(urlPath, "/localapi/v0/") suff, ok := strings.CutPrefix(urlPath, "/localapi/v0/")
if !ok { if !ok {
@ -310,22 +311,31 @@ func handlerForPath(urlPath string) (h LocalAPIHandler, ok bool) {
// to people that they're not necessarily stable APIs. In practice we'll // to people that they're not necessarily stable APIs. In practice we'll
// probably need to keep them pretty stable anyway, but for now treat // probably need to keep them pretty stable anyway, but for now treat
// them as an internal implementation detail. // them as an internal implementation detail.
return nil, false return nil, "", false
} }
if fn, ok := handler[suff]; ok { if fn, ok := handler[suff]; ok {
// Here we match exact handler suffixes like "status" or ones with a // Here we match exact handler suffixes like "status" or ones with a
// slash already in their name, like "tka/status". // slash already in their name, like "tka/status".
return fn, true return fn, "/localapi/v0/" + suff, true
} }
// Otherwise, it might be a prefix match like "files/*" which we look up // Otherwise, it might be a prefix match like "files/*" which we look up
// by the prefix including first trailing slash. // by the prefix including first trailing slash.
if i := strings.IndexByte(suff, '/'); i != -1 { if i := strings.IndexByte(suff, '/'); i != -1 {
suff = suff[:i+1] suff = suff[:i+1]
if fn, ok := handler[suff]; ok { if fn, ok := handler[suff]; ok {
return fn, true return fn, "/localapi/v0/" + suff, true
} }
} }
return nil, false return nil, "", false
}
func (h *Handler) logRequest(method, route string) {
switch method {
case httpm.GET, httpm.HEAD, httpm.OPTIONS:
// don't log safe methods
default:
h.Logf("localapi: [%s] %s", method, route)
}
} }
func (*Handler) serveLocalAPIRoot(w http.ResponseWriter, r *http.Request) { func (*Handler) serveLocalAPIRoot(w http.ResponseWriter, r *http.Request) {

@ -40,6 +40,19 @@ import (
"tailscale.com/wgengine" "tailscale.com/wgengine"
) )
func handlerForTest(t testing.TB, h *Handler) *Handler {
if h.Actor == nil {
h.Actor = &ipnauth.TestActor{}
}
if h.b == nil {
h.b = &ipnlocal.LocalBackend{}
}
if h.logf == nil {
h.logf = logger.TestLogger(t)
}
return h
}
func TestValidHost(t *testing.T) { func TestValidHost(t *testing.T) {
tests := []struct { tests := []struct {
host string host string
@ -57,7 +70,7 @@ func TestValidHost(t *testing.T) {
for _, test := range tests { for _, test := range tests {
t.Run(test.host, func(t *testing.T) { t.Run(test.host, func(t *testing.T) {
h := &Handler{} h := handlerForTest(t, &Handler{})
if got := h.validHost(test.host); got != test.valid { if got := h.validHost(test.host); got != test.valid {
t.Errorf("validHost(%q)=%v, want %v", test.host, got, test.valid) t.Errorf("validHost(%q)=%v, want %v", test.host, got, test.valid)
} }
@ -68,10 +81,9 @@ func TestValidHost(t *testing.T) {
func TestSetPushDeviceToken(t *testing.T) { func TestSetPushDeviceToken(t *testing.T) {
tstest.Replace(t, &validLocalHostForTesting, true) tstest.Replace(t, &validLocalHostForTesting, true)
h := &Handler{ h := handlerForTest(t, &Handler{
PermitWrite: true, PermitWrite: true,
b: &ipnlocal.LocalBackend{}, })
}
s := httptest.NewServer(h) s := httptest.NewServer(h)
defer s.Close() defer s.Close()
c := s.Client() c := s.Client()
@ -125,9 +137,9 @@ func (b whoIsBackend) PeerCaps(ip netip.Addr) tailcfg.PeerCapMap {
// //
// And https://github.com/tailscale/tailscale/issues/12465 // And https://github.com/tailscale/tailscale/issues/12465
func TestWhoIsArgTypes(t *testing.T) { func TestWhoIsArgTypes(t *testing.T) {
h := &Handler{ h := handlerForTest(t, &Handler{
PermitRead: true, PermitRead: true,
} })
match := func() (n tailcfg.NodeView, u tailcfg.UserProfile, ok bool) { match := func() (n tailcfg.NodeView, u tailcfg.UserProfile, ok bool) {
return (&tailcfg.Node{ return (&tailcfg.Node{
@ -190,7 +202,10 @@ func TestWhoIsArgTypes(t *testing.T) {
func TestShouldDenyServeConfigForGOOSAndUserContext(t *testing.T) { func TestShouldDenyServeConfigForGOOSAndUserContext(t *testing.T) {
newHandler := func(connIsLocalAdmin bool) *Handler { newHandler := func(connIsLocalAdmin bool) *Handler {
return &Handler{Actor: &ipnauth.TestActor{LocalAdmin: connIsLocalAdmin}, b: newTestLocalBackend(t)} return handlerForTest(t, &Handler{
Actor: &ipnauth.TestActor{LocalAdmin: connIsLocalAdmin},
b: newTestLocalBackend(t),
})
} }
tests := []struct { tests := []struct {
name string name string
@ -298,11 +313,11 @@ func TestServeWatchIPNBus(t *testing.T) {
for _, tt := range tests { for _, tt := range tests {
t.Run(tt.desc, func(t *testing.T) { t.Run(tt.desc, func(t *testing.T) {
h := &Handler{ h := handlerForTest(t, &Handler{
PermitRead: tt.permitRead, PermitRead: tt.permitRead,
PermitWrite: tt.permitWrite, PermitWrite: tt.permitWrite,
b: newTestLocalBackend(t), b: newTestLocalBackend(t),
} })
s := httptest.NewServer(h) s := httptest.NewServer(h)
defer s.Close() defer s.Close()
c := s.Client() c := s.Client()

Loading…
Cancel
Save