tailcfg,ipn/localapi,client/tailscale: add QueryFeature endpoint

Updates tailscale/corp#10577

Signed-off-by: Sonia Appasamy <sonia@tailscale.com>
This commit is contained in:
Sonia Appasamy
2023-08-02 10:56:18 -04:00
committed by Sonia Appasamy
parent ab7749aed7
commit 301e59f398
5 changed files with 128 additions and 0 deletions

View File

@@ -113,6 +113,7 @@ var handler = map[string]localAPIHandler{
"upload-client-metrics": (*Handler).serveUploadClientMetrics,
"watch-ipn-bus": (*Handler).serveWatchIPNBus,
"whois": (*Handler).serveWhoIs,
"query-feature": (*Handler).serveQueryFeature,
}
func randHex(n int) string {
@@ -1932,6 +1933,66 @@ func (h *Handler) serveProfiles(w http.ResponseWriter, r *http.Request) {
}
}
// serveQueryFeature makes a request to the "/machine/feature/query"
// Noise endpoint to get instructions on how to enable a feature, such as
// Funnel, for the node's tailnet.
//
// This request itself does not directly enable the feature on behalf of
// the node, but rather returns information that can be presented to the
// acting user about where/how to enable the feature. If relevant, this
// includes a control URL the user can visit to explicitly consent to
// using the feature.
//
// See tailcfg.QueryFeatureResponse for full response structure.
func (h *Handler) serveQueryFeature(w http.ResponseWriter, r *http.Request) {
feature := r.FormValue("feature")
switch {
case !h.PermitRead:
http.Error(w, "access denied", http.StatusForbidden)
return
case r.Method != httpm.POST:
http.Error(w, "use POST", http.StatusMethodNotAllowed)
return
case feature == "":
http.Error(w, "missing feature", http.StatusInternalServerError)
return
}
nm := h.b.NetMap()
if nm == nil {
http.Error(w, "no netmap", http.StatusServiceUnavailable)
return
}
b, err := json.Marshal(&tailcfg.QueryFeatureRequest{
NodeKey: nm.NodeKey,
Feature: feature,
})
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
req, err := http.NewRequestWithContext(r.Context(),
"POST", "https://unused/machine/feature/query", bytes.NewReader(b))
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()
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(resp.StatusCode)
if _, err := io.Copy(w, resp.Body); err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
}
func defBool(a string, def bool) bool {
if a == "" {
return def