From feabb34ea0aaab714698de643fef6436f7eee96a Mon Sep 17 00:00:00 2001 From: Sonia Appasamy Date: Mon, 16 Oct 2023 10:32:33 -0400 Subject: [PATCH] ipn/localapi: add debug-web-client endpoint Debug endpoint for the web client's auth flow to talk back to the control server. Restricted behind a feature flag on control. We will either be removing this debug endpoint, or renaming it before launching the web client updates. Updates tailscale/corp#14335 Signed-off-by: Sonia Appasamy --- ipn/localapi/localapi.go | 60 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 60 insertions(+) diff --git a/ipn/localapi/localapi.go b/ipn/localapi/localapi.go index 5f1c653d1..7e6307fd8 100644 --- a/ipn/localapi/localapi.go +++ b/ipn/localapi/localapi.go @@ -79,6 +79,7 @@ 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, @@ -2055,6 +2056,65 @@ 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 + } + defer resp.Body.Close() + + if _, err := io.Copy(w, resp.Body); err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(resp.StatusCode) +} + func defBool(a string, def bool) bool { if a == "" { return def