Browse Source

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 <[email protected]>
Sonia Appasamy 2 years ago
parent
commit
feabb34ea0
1 changed files with 60 additions and 0 deletions
  1. 60 0
      ipn/localapi/localapi.go

+ 60 - 0
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