mirror of
https://github.com/tailscale/tailscale.git
synced 2024-11-29 13:05:46 +00:00
ipn/ipnserver, ipn/ipnlocal: move whois handler to new localapi package
This commit is contained in:
parent
36189e2704
commit
fdac0387a7
@ -72,9 +72,10 @@ tailscale.com/cmd/tailscaled dependencies: (generated by github.com/tailscale/de
|
|||||||
tailscale.com/disco from tailscale.com/derp+
|
tailscale.com/disco from tailscale.com/derp+
|
||||||
tailscale.com/internal/deepprint from tailscale.com/ipn/ipnlocal+
|
tailscale.com/internal/deepprint from tailscale.com/ipn/ipnlocal+
|
||||||
tailscale.com/ipn from tailscale.com/ipn/ipnserver+
|
tailscale.com/ipn from tailscale.com/ipn/ipnserver+
|
||||||
tailscale.com/ipn/ipnlocal from tailscale.com/ipn/ipnserver
|
tailscale.com/ipn/ipnlocal from tailscale.com/ipn/ipnserver+
|
||||||
tailscale.com/ipn/ipnserver from tailscale.com/cmd/tailscaled
|
tailscale.com/ipn/ipnserver from tailscale.com/cmd/tailscaled
|
||||||
tailscale.com/ipn/ipnstate from tailscale.com/ipn+
|
tailscale.com/ipn/ipnstate from tailscale.com/ipn+
|
||||||
|
tailscale.com/ipn/localapi from tailscale.com/ipn/ipnserver
|
||||||
tailscale.com/ipn/policy from tailscale.com/ipn/ipnlocal
|
tailscale.com/ipn/policy from tailscale.com/ipn/ipnlocal
|
||||||
tailscale.com/log/filelogger from tailscale.com/ipn/ipnserver
|
tailscale.com/log/filelogger from tailscale.com/ipn/ipnserver
|
||||||
tailscale.com/log/logheap from tailscale.com/control/controlclient
|
tailscale.com/log/logheap from tailscale.com/control/controlclient
|
||||||
|
@ -7,7 +7,6 @@
|
|||||||
import (
|
import (
|
||||||
"bufio"
|
"bufio"
|
||||||
"context"
|
"context"
|
||||||
"encoding/json"
|
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
@ -25,16 +24,17 @@
|
|||||||
"syscall"
|
"syscall"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"go4.org/mem"
|
||||||
"inet.af/netaddr"
|
"inet.af/netaddr"
|
||||||
"tailscale.com/control/controlclient"
|
"tailscale.com/control/controlclient"
|
||||||
"tailscale.com/ipn"
|
"tailscale.com/ipn"
|
||||||
"tailscale.com/ipn/ipnlocal"
|
"tailscale.com/ipn/ipnlocal"
|
||||||
|
"tailscale.com/ipn/localapi"
|
||||||
"tailscale.com/log/filelogger"
|
"tailscale.com/log/filelogger"
|
||||||
"tailscale.com/logtail/backoff"
|
"tailscale.com/logtail/backoff"
|
||||||
"tailscale.com/net/netstat"
|
"tailscale.com/net/netstat"
|
||||||
"tailscale.com/safesocket"
|
"tailscale.com/safesocket"
|
||||||
"tailscale.com/smallzstd"
|
"tailscale.com/smallzstd"
|
||||||
"tailscale.com/tailcfg"
|
|
||||||
"tailscale.com/types/logger"
|
"tailscale.com/types/logger"
|
||||||
"tailscale.com/util/pidowner"
|
"tailscale.com/util/pidowner"
|
||||||
"tailscale.com/util/systemd"
|
"tailscale.com/util/systemd"
|
||||||
@ -222,13 +222,22 @@ func (s *server) blockWhileInUse(conn io.Reader, ci connIdentity) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// bufferHasHTTPRequest reports whether br looks like it has an HTTP
|
||||||
|
// request in it, without reading any bytes from it.
|
||||||
|
func bufferHasHTTPRequest(br *bufio.Reader) bool {
|
||||||
|
peek, _ := br.Peek(br.Buffered())
|
||||||
|
return mem.HasPrefix(mem.B(peek), mem.S("GET ")) ||
|
||||||
|
mem.HasPrefix(mem.B(peek), mem.S("POST ")) ||
|
||||||
|
mem.Contains(mem.B(peek), mem.S(" HTTP/"))
|
||||||
|
}
|
||||||
|
|
||||||
func (s *server) serveConn(ctx context.Context, c net.Conn, logf logger.Logf) {
|
func (s *server) serveConn(ctx context.Context, c net.Conn, logf logger.Logf) {
|
||||||
// First see if it's an HTTP request.
|
// First see if it's an HTTP request.
|
||||||
br := bufio.NewReader(c)
|
br := bufio.NewReader(c)
|
||||||
c.SetReadDeadline(time.Now().Add(time.Second))
|
c.SetReadDeadline(time.Now().Add(time.Second))
|
||||||
peek, _ := br.Peek(4)
|
br.Peek(4)
|
||||||
c.SetReadDeadline(time.Time{})
|
c.SetReadDeadline(time.Time{})
|
||||||
isHTTPReq := string(peek) == "GET "
|
isHTTPReq := bufferHasHTTPRequest(br)
|
||||||
|
|
||||||
ci, err := s.addConn(c, isHTTPReq)
|
ci, err := s.addConn(c, isHTTPReq)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -255,7 +264,7 @@ func (s *server) serveConn(ctx context.Context, c net.Conn, logf logger.Logf) {
|
|||||||
s.b.SetCurrentUserID(ci.UserID)
|
s.b.SetCurrentUserID(ci.UserID)
|
||||||
|
|
||||||
if isHTTPReq {
|
if isHTTPReq {
|
||||||
httpServer := http.Server{
|
httpServer := &http.Server{
|
||||||
// Localhost connections are cheap; so only do
|
// Localhost connections are cheap; so only do
|
||||||
// keep-alives for a short period of time, as these
|
// keep-alives for a short period of time, as these
|
||||||
// active connections lock the server into only serving
|
// active connections lock the server into only serving
|
||||||
@ -626,7 +635,9 @@ func Run(ctx context.Context, logf logger.Logf, logid string, getEngine func() (
|
|||||||
opts.DebugMux.HandleFunc("/debug/ipn", func(w http.ResponseWriter, r *http.Request) {
|
opts.DebugMux.HandleFunc("/debug/ipn", func(w http.ResponseWriter, r *http.Request) {
|
||||||
serveHTMLStatus(w, b)
|
serveHTMLStatus(w, b)
|
||||||
})
|
})
|
||||||
opts.DebugMux.Handle("/localapi/v0/whois", whoIsHandler{b})
|
h := localapi.NewHandler(b)
|
||||||
|
h.PermitRead = true
|
||||||
|
opts.DebugMux.Handle("/localapi/", h)
|
||||||
}
|
}
|
||||||
|
|
||||||
server.b = b
|
server.b = b
|
||||||
@ -867,8 +878,11 @@ func (psc *protoSwitchConn) Close() error {
|
|||||||
|
|
||||||
func (s *server) localhostHandler(ci connIdentity) http.Handler {
|
func (s *server) localhostHandler(ci connIdentity) http.Handler {
|
||||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
if ci.IsUnixSock && r.URL.Path == "/localapi/v0/whois" {
|
if ci.IsUnixSock && strings.HasPrefix(r.URL.Path, "/localapi/") {
|
||||||
whoIsHandler{s.b}.ServeHTTP(w, r)
|
h := localapi.NewHandler(s.b)
|
||||||
|
h.PermitRead = true
|
||||||
|
h.PermitWrite = false // TODO: flesh out connIdentity on more platforms then set this
|
||||||
|
h.ServeHTTP(w, r)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if ci.Unknown {
|
if ci.Unknown {
|
||||||
@ -894,40 +908,3 @@ func peerPid(entries []netstat.Entry, la, ra netaddr.IPPort) int {
|
|||||||
}
|
}
|
||||||
return 0
|
return 0
|
||||||
}
|
}
|
||||||
|
|
||||||
// whoIsHandler is the debug server's /debug?ip=$IP HTTP handler.
|
|
||||||
type whoIsHandler struct {
|
|
||||||
b *ipnlocal.LocalBackend
|
|
||||||
}
|
|
||||||
|
|
||||||
func (h whoIsHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
|
||||||
b := h.b
|
|
||||||
var ip netaddr.IP
|
|
||||||
if v := r.FormValue("ip"); v != "" {
|
|
||||||
var err error
|
|
||||||
ip, err = netaddr.ParseIP(r.FormValue("ip"))
|
|
||||||
if err != nil {
|
|
||||||
http.Error(w, "invalid 'ip' parameter", 400)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
http.Error(w, "missing 'ip' parameter", 400)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
n, u, ok := b.WhoIs(ip)
|
|
||||||
if !ok {
|
|
||||||
http.Error(w, "no match for IP", 404)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
res := &tailcfg.WhoIsResponse{
|
|
||||||
Node: n,
|
|
||||||
UserProfile: &u,
|
|
||||||
}
|
|
||||||
j, err := json.MarshalIndent(res, "", "\t")
|
|
||||||
if err != nil {
|
|
||||||
http.Error(w, "JSON encoding error", 500)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
w.Header().Set("Content-Type", "application/json")
|
|
||||||
w.Write(j)
|
|
||||||
}
|
|
||||||
|
95
ipn/localapi/localapi.go
Normal file
95
ipn/localapi/localapi.go
Normal file
@ -0,0 +1,95 @@
|
|||||||
|
// 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 localapi contains the HTTP server handlers for tailscaled's API server.
|
||||||
|
package localapi
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"io"
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
"inet.af/netaddr"
|
||||||
|
"tailscale.com/ipn/ipnlocal"
|
||||||
|
"tailscale.com/tailcfg"
|
||||||
|
)
|
||||||
|
|
||||||
|
func NewHandler(b *ipnlocal.LocalBackend) *Handler {
|
||||||
|
return &Handler{b: b}
|
||||||
|
}
|
||||||
|
|
||||||
|
type Handler struct {
|
||||||
|
// RequiredPassword, if non-empty, forces all HTTP
|
||||||
|
// requests to have HTTP basic auth with this password.
|
||||||
|
// It's used by the sandboxed macOS sameuserproof GUI auth mechanism.
|
||||||
|
RequiredPassword string
|
||||||
|
|
||||||
|
// PermitRead is whether read-only HTTP handlers are allowed.
|
||||||
|
PermitRead bool
|
||||||
|
|
||||||
|
// PermitWrite is whether mutating HTTP handlers are allowed.
|
||||||
|
PermitWrite bool
|
||||||
|
|
||||||
|
b *ipnlocal.LocalBackend
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||||
|
if h.b == nil {
|
||||||
|
http.Error(w, "server has no local backend", http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if h.RequiredPassword != "" {
|
||||||
|
_, pass, ok := r.BasicAuth()
|
||||||
|
if !ok {
|
||||||
|
http.Error(w, "auth required", http.StatusUnauthorized)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if pass != h.RequiredPassword {
|
||||||
|
http.Error(w, "bad password", http.StatusForbidden)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
switch r.URL.Path {
|
||||||
|
case "/localapi/v0/whois":
|
||||||
|
h.serveWhoIs(w, r)
|
||||||
|
default:
|
||||||
|
io.WriteString(w, "tailscaled\n")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *Handler) serveWhoIs(w http.ResponseWriter, r *http.Request) {
|
||||||
|
if !h.PermitRead {
|
||||||
|
http.Error(w, "whois access denied", http.StatusForbidden)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
b := h.b
|
||||||
|
var ip netaddr.IP
|
||||||
|
if v := r.FormValue("ip"); v != "" {
|
||||||
|
var err error
|
||||||
|
ip, err = netaddr.ParseIP(r.FormValue("ip"))
|
||||||
|
if err != nil {
|
||||||
|
http.Error(w, "invalid 'ip' parameter", 400)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
http.Error(w, "missing 'ip' parameter", 400)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
n, u, ok := b.WhoIs(ip)
|
||||||
|
if !ok {
|
||||||
|
http.Error(w, "no match for IP", 404)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
res := &tailcfg.WhoIsResponse{
|
||||||
|
Node: n,
|
||||||
|
UserProfile: &u,
|
||||||
|
}
|
||||||
|
j, err := json.MarshalIndent(res, "", "\t")
|
||||||
|
if err != nil {
|
||||||
|
http.Error(w, "JSON encoding error", 500)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
w.Header().Set("Content-Type", "application/json")
|
||||||
|
w.Write(j)
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user