{client/web},{ipn/ipnlocal}: replace localapi debug-web-client endpoint
This change removes the existing debug-web-client localapi endpoint and replaces it with functions passed directly to the web.ServerOpts when constructing a web.ManageServerMode client. The debug-web-client endpoint previously handled making noise requests to the control server via the /machine/webclient/ endpoints. The noise requests must be made from tailscaled, which has the noise connection open. But, now that the full client is served from tailscaled, we no longer need to proxy this request over the localapi. Updates tailscale/corp#14335 Signed-off-by: Sonia Appasamy <sonia@tailscale.com>
This commit is contained in:
committed by
Sonia Appasamy
parent
4d196c12d9
commit
bd534b971a
@@ -7,8 +7,10 @@ package ipnlocal
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"net"
|
||||
"net/http"
|
||||
"net/netip"
|
||||
@@ -19,6 +21,7 @@ import (
|
||||
"tailscale.com/client/web"
|
||||
"tailscale.com/logtail/backoff"
|
||||
"tailscale.com/net/netutil"
|
||||
"tailscale.com/tailcfg"
|
||||
"tailscale.com/types/logger"
|
||||
"tailscale.com/util/mak"
|
||||
)
|
||||
@@ -67,6 +70,8 @@ func (b *LocalBackend) webClientGetOrInit() (s *web.Server, err error) {
|
||||
Mode: web.ManageServerMode,
|
||||
LocalClient: b.webClient.lc,
|
||||
Logf: b.logf,
|
||||
NewAuthURL: b.newWebClientAuthURL,
|
||||
WaitAuthURL: b.waitWebClientAuthURL,
|
||||
}); err != nil {
|
||||
return nil, fmt.Errorf("web.NewServer: %w", err)
|
||||
}
|
||||
@@ -144,3 +149,57 @@ func (b *LocalBackend) newWebClientListener(ctx context.Context, ap netip.AddrPo
|
||||
bo: backoff.NewBackoff("webclient-listener", logf, 30*time.Second),
|
||||
}
|
||||
}
|
||||
|
||||
// newWebClientAuthURL talks to the control server to create a new auth
|
||||
// URL that can be used to validate a browser session to manage this
|
||||
// tailscaled instance via the web client.
|
||||
func (b *LocalBackend) newWebClientAuthURL(ctx context.Context, src tailcfg.NodeID) (*tailcfg.WebClientAuthResponse, error) {
|
||||
return b.doWebClientNoiseRequest(ctx, "", src)
|
||||
}
|
||||
|
||||
// waitWebClientAuthURL connects to the control server and blocks
|
||||
// until the associated auth URL has been completed by its user,
|
||||
// or until ctx is canceled.
|
||||
func (b *LocalBackend) waitWebClientAuthURL(ctx context.Context, id string, src tailcfg.NodeID) (*tailcfg.WebClientAuthResponse, error) {
|
||||
return b.doWebClientNoiseRequest(ctx, id, src)
|
||||
}
|
||||
|
||||
// doWebClientNoiseRequest handles making the "/machine/webclient"
|
||||
// noise requests to the control server for web client user auth.
|
||||
//
|
||||
// It either creates a new control auth URL or waits for an existing
|
||||
// one to be completed, based on the presence or absence of the
|
||||
// provided id value.
|
||||
func (b *LocalBackend) doWebClientNoiseRequest(ctx context.Context, id string, src tailcfg.NodeID) (*tailcfg.WebClientAuthResponse, error) {
|
||||
nm := b.NetMap()
|
||||
if nm == nil || !nm.SelfNode.Valid() {
|
||||
return nil, errors.New("[unexpected] no self node")
|
||||
}
|
||||
dst := nm.SelfNode.ID()
|
||||
var noiseURL string
|
||||
if id != "" {
|
||||
noiseURL = fmt.Sprintf("https://unused/machine/webclient/wait/%d/to/%d/%s", src, dst, id)
|
||||
} else {
|
||||
noiseURL = fmt.Sprintf("https://unused/machine/webclient/init/%d/to/%d", src, dst)
|
||||
}
|
||||
|
||||
req, err := http.NewRequestWithContext(ctx, "POST", noiseURL, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
resp, err := b.DoNoiseRequest(req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
body, _ := io.ReadAll(resp.Body)
|
||||
resp.Body.Close()
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
return nil, fmt.Errorf("failed request: %s", body)
|
||||
}
|
||||
var authResp *tailcfg.WebClientAuthResponse
|
||||
if err := json.Unmarshal(body, &authResp); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return authResp, nil
|
||||
}
|
||||
|
||||
@@ -86,7 +86,6 @@ var handler = map[string]localAPIHandler{
|
||||
"debug-peer-endpoint-changes": (*Handler).serveDebugPeerEndpointChanges,
|
||||
"debug-capture": (*Handler).serveDebugCapture,
|
||||
"debug-log": (*Handler).serveDebugLog,
|
||||
"debug-web-client": (*Handler).serveDebugWebClient,
|
||||
"derpmap": (*Handler).serveDERPMap,
|
||||
"dev-set-state-store": (*Handler).serveDevSetStateStore,
|
||||
"set-push-device-token": (*Handler).serveSetPushDeviceToken,
|
||||
@@ -2300,65 +2299,6 @@ func (h *Handler) serveQueryFeature(w http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
}
|
||||
|
||||
// serveDebugWebClient is for use by the web client to communicate with
|
||||
// the control server for browser auth sessions.
|
||||
//
|
||||
// This is an unsupported localapi endpoint and restricted to flagged
|
||||
// domains on the control side. TODO(tailscale/#14335): Rename this handler.
|
||||
func (h *Handler) serveDebugWebClient(w http.ResponseWriter, r *http.Request) {
|
||||
if !h.PermitWrite {
|
||||
http.Error(w, "access denied", http.StatusForbidden)
|
||||
return
|
||||
}
|
||||
if r.Method != "POST" {
|
||||
http.Error(w, "POST required", http.StatusMethodNotAllowed)
|
||||
return
|
||||
}
|
||||
|
||||
type reqData struct {
|
||||
ID string
|
||||
Src tailcfg.NodeID
|
||||
}
|
||||
var data reqData
|
||||
if err := json.NewDecoder(r.Body).Decode(&data); err != nil {
|
||||
http.Error(w, "invalid JSON body", 400)
|
||||
return
|
||||
}
|
||||
nm := h.b.NetMap()
|
||||
if nm == nil || !nm.SelfNode.Valid() {
|
||||
http.Error(w, "[unexpected] no self node", 400)
|
||||
return
|
||||
}
|
||||
dst := nm.SelfNode.ID()
|
||||
|
||||
var noiseURL string
|
||||
if data.ID != "" {
|
||||
noiseURL = fmt.Sprintf("https://unused/machine/webclient/wait/%d/to/%d/%s", data.Src, dst, data.ID)
|
||||
} else {
|
||||
noiseURL = fmt.Sprintf("https://unused/machine/webclient/init/%d/to/%d", data.Src, dst)
|
||||
}
|
||||
|
||||
req, err := http.NewRequestWithContext(r.Context(), "POST", noiseURL, nil)
|
||||
if err != nil {
|
||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
resp, err := h.b.DoNoiseRequest(req)
|
||||
if err != nil {
|
||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
body, _ := io.ReadAll(resp.Body)
|
||||
resp.Body.Close()
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
http.Error(w, string(body), resp.StatusCode)
|
||||
return
|
||||
}
|
||||
w.Write(body)
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
}
|
||||
|
||||
func defBool(a string, def bool) bool {
|
||||
if a == "" {
|
||||
return def
|
||||
|
||||
Reference in New Issue
Block a user