Something went wrong. Try again.
forked from tailscale/tailscale
Something went wrong. Try again.
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414// Copyright (c) Tailscale Inc & contributors// SPDX-License-Identifier: BSD-3-Clause
//go:build !ts_omit_tailnetlock
package localapi
import ( "encoding/json" "io" "net/http" "strconv"
"tailscale.com/tka" "tailscale.com/types/key" "tailscale.com/types/tkatype" "tailscale.com/util/httpm")
func init() { Register("tka/affected-sigs", (*Handler).serveTKAAffectedSigs) Register("tka/cosign-recovery-aum", (*Handler).serveTKACosignRecoveryAUM) Register("tka/disable", (*Handler).serveTKADisable) Register("tka/force-local-disable", (*Handler).serveTKALocalDisable) Register("tka/generate-recovery-aum", (*Handler).serveTKAGenerateRecoveryAUM) Register("tka/init", (*Handler).serveTKAInit) Register("tka/log", (*Handler).serveTKALog) Register("tka/modify", (*Handler).serveTKAModify) Register("tka/sign", (*Handler).serveTKASign) Register("tka/status", (*Handler).serveTKAStatus) Register("tka/submit-recovery-aum", (*Handler).serveTKASubmitRecoveryAUM) Register("tka/verify-deeplink", (*Handler).serveTKAVerifySigningDeeplink) Register("tka/wrap-preauth-key", (*Handler).serveTKAWrapPreauthKey)}
func (h *Handler) serveTKAStatus(w http.ResponseWriter, r *http.Request) { if !h.PermitRead { http.Error(w, "lock status access denied", http.StatusForbidden) return } if r.Method != httpm.GET { http.Error(w, "use GET", http.StatusMethodNotAllowed) return }
j, err := json.MarshalIndent(h.b.TailnetLockStatus(), "", "\t") if err != nil { http.Error(w, "JSON encoding error", http.StatusInternalServerError) return } w.Header().Set("Content-Type", "application/json") w.Write(j)}
func (h *Handler) serveTKASign(w http.ResponseWriter, r *http.Request) { if !h.PermitWrite { http.Error(w, "lock sign access denied", http.StatusForbidden) return } if r.Method != httpm.POST { http.Error(w, "use POST", http.StatusMethodNotAllowed) return }
type signRequest struct { NodeKey key.NodePublic RotationPublic []byte } var req signRequest if err := json.NewDecoder(r.Body).Decode(&req); err != nil { http.Error(w, "invalid JSON body", http.StatusBadRequest) return }
if err := h.b.TailnetLockSign(req.NodeKey, req.RotationPublic); err != nil { http.Error(w, "signing failed: "+err.Error(), http.StatusInternalServerError) return }
w.WriteHeader(http.StatusOK)}
func (h *Handler) serveTKAInit(w http.ResponseWriter, r *http.Request) { if !h.PermitWrite { http.Error(w, "lock init access denied", http.StatusForbidden) return } if r.Method != httpm.POST { http.Error(w, "use POST", http.StatusMethodNotAllowed) return }
type initRequest struct { Keys []tka.Key DisablementValues [][]byte SupportDisablement []byte } var req initRequest if err := json.NewDecoder(r.Body).Decode(&req); err != nil { http.Error(w, "invalid JSON body", http.StatusBadRequest) return }
if !h.b.TailnetLockAllowed() { http.Error(w, "Tailnet Lock is not supported on your pricing plan", http.StatusForbidden) return }
if err := h.b.TailnetLockInit(req.Keys, req.DisablementValues, req.SupportDisablement); err != nil { http.Error(w, "initialization failed: "+err.Error(), http.StatusInternalServerError) return }
j, err := json.MarshalIndent(h.b.TailnetLockStatus(), "", "\t") if err != nil { http.Error(w, "JSON encoding error", http.StatusInternalServerError) return } w.Header().Set("Content-Type", "application/json") w.Write(j)}
func (h *Handler) serveTKAModify(w http.ResponseWriter, r *http.Request) { if !h.PermitWrite { http.Error(w, "tailnet-lock modify access denied", http.StatusForbidden) return } if r.Method != httpm.POST { http.Error(w, "use POST", http.StatusMethodNotAllowed) return }
type modifyRequest struct { AddKeys []tka.Key RemoveKeys []tka.Key } var req modifyRequest if err := json.NewDecoder(r.Body).Decode(&req); err != nil { http.Error(w, "invalid JSON body", http.StatusBadRequest) return }
if err := h.b.TailnetLockModify(req.AddKeys, req.RemoveKeys); err != nil { http.Error(w, "tailnet-lock modify failed: "+err.Error(), http.StatusInternalServerError) return } w.WriteHeader(204)}
func (h *Handler) serveTKAWrapPreauthKey(w http.ResponseWriter, r *http.Request) { if !h.PermitWrite { http.Error(w, "tailnet-lock modify access denied", http.StatusForbidden) return } if r.Method != httpm.POST { http.Error(w, "use POST", http.StatusMethodNotAllowed) return }
type wrapRequest struct { TSKey string TKAKey string // key.TLPrivate.MarshalText } var req wrapRequest if err := json.NewDecoder(http.MaxBytesReader(w, r.Body, 12*1024)).Decode(&req); err != nil { http.Error(w, "invalid JSON body", http.StatusBadRequest) return } var priv key.TLPrivate if err := priv.UnmarshalText([]byte(req.TKAKey)); err != nil { http.Error(w, "invalid JSON body", http.StatusBadRequest) return }
wrappedKey, err := h.b.TailnetLockWrapPreauthKey(req.TSKey, priv) if err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) return } w.WriteHeader(http.StatusOK) w.Write([]byte(wrappedKey))}
func (h *Handler) serveTKAVerifySigningDeeplink(w http.ResponseWriter, r *http.Request) { if !h.PermitRead { http.Error(w, "signing deeplink verification access denied", http.StatusForbidden) return } if r.Method != httpm.POST { http.Error(w, "use POST", http.StatusMethodNotAllowed) return }
type verifyRequest struct { URL string } var req verifyRequest if err := json.NewDecoder(r.Body).Decode(&req); err != nil { http.Error(w, "invalid JSON for verifyRequest body", http.StatusBadRequest) return }
res := h.b.TailnetLockVerifySigningDeeplink(req.URL) j, err := json.MarshalIndent(res, "", "\t") if err != nil { http.Error(w, "JSON encoding error", http.StatusInternalServerError) return } w.Header().Set("Content-Type", "application/json") w.Write(j)}
func (h *Handler) serveTKADisable(w http.ResponseWriter, r *http.Request) { if !h.PermitWrite { http.Error(w, "tailnet-lock modify access denied", http.StatusForbidden) return } if r.Method != httpm.POST { http.Error(w, "use POST", http.StatusMethodNotAllowed) return }
body := io.LimitReader(r.Body, 1024*1024) secret, err := io.ReadAll(body) if err != nil { http.Error(w, "reading secret", http.StatusBadRequest) return }
if err := h.b.TailnetLockDisable(secret); err != nil { http.Error(w, "tailnet-lock disable failed: "+err.Error(), http.StatusBadRequest) return } w.WriteHeader(http.StatusOK)}
func (h *Handler) serveTKALocalDisable(w http.ResponseWriter, r *http.Request) { if !h.PermitWrite { http.Error(w, "tailnet-lock modify access denied", http.StatusForbidden) return } if r.Method != httpm.POST { http.Error(w, "use POST", http.StatusMethodNotAllowed) return }
// Require a JSON stanza for the body as an additional CSRF protection. var req struct{} if err := json.NewDecoder(r.Body).Decode(&req); err != nil { http.Error(w, "invalid JSON body", http.StatusBadRequest) return }
if err := h.b.TailnetLockForceLocalDisable(); err != nil { http.Error(w, "tailnet-lock local disable failed: "+err.Error(), http.StatusBadRequest) return } w.WriteHeader(http.StatusOK)}
func (h *Handler) serveTKALog(w http.ResponseWriter, r *http.Request) { if r.Method != httpm.GET { http.Error(w, "use GET", http.StatusMethodNotAllowed) return }
limit := 50 if limitStr := r.FormValue("limit"); limitStr != "" { lm, err := strconv.Atoi(limitStr) if err != nil { http.Error(w, "parsing 'limit' parameter: "+err.Error(), http.StatusBadRequest) return } limit = int(lm) }
updates, err := h.b.TailnetLockLog(limit) if err != nil { http.Error(w, "reading log failed: "+err.Error(), http.StatusInternalServerError) return }
j, err := json.MarshalIndent(updates, "", "\t") if err != nil { http.Error(w, "JSON encoding error", http.StatusInternalServerError) return } w.Header().Set("Content-Type", "application/json") w.Write(j)}
func (h *Handler) serveTKAAffectedSigs(w http.ResponseWriter, r *http.Request) { if r.Method != httpm.POST { http.Error(w, "use POST", http.StatusMethodNotAllowed) return } keyID, err := io.ReadAll(http.MaxBytesReader(w, r.Body, 2048)) if err != nil { http.Error(w, "reading body", http.StatusBadRequest) return }
sigs, err := h.b.TailnetLockAffectedSigs(keyID) if err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) return }
j, err := json.MarshalIndent(sigs, "", "\t") if err != nil { http.Error(w, "JSON encoding error", http.StatusInternalServerError) return } w.Header().Set("Content-Type", "application/json") w.Write(j)}
func (h *Handler) serveTKAGenerateRecoveryAUM(w http.ResponseWriter, r *http.Request) { if !h.PermitWrite { http.Error(w, "access denied", http.StatusForbidden) return } if r.Method != httpm.POST { http.Error(w, "use POST", http.StatusMethodNotAllowed) return }
type verifyRequest struct { Keys []tkatype.KeyID ForkFrom string } var req verifyRequest if err := json.NewDecoder(r.Body).Decode(&req); err != nil { http.Error(w, "invalid JSON for verifyRequest body", http.StatusBadRequest) return }
var forkFrom tka.AUMHash if req.ForkFrom != "" { if err := forkFrom.UnmarshalText([]byte(req.ForkFrom)); err != nil { http.Error(w, "decoding fork-from: "+err.Error(), http.StatusBadRequest) return } }
res, err := h.b.TailnetLockGenerateRecoveryAUM(req.Keys, forkFrom) if err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) return } w.Header().Set("Content-Type", "application/octet-stream") w.Write(res.Serialize())}
func (h *Handler) serveTKACosignRecoveryAUM(w http.ResponseWriter, r *http.Request) { if !h.PermitWrite { http.Error(w, "access denied", http.StatusForbidden) return } if r.Method != httpm.POST { http.Error(w, "use POST", http.StatusMethodNotAllowed) return }
body := io.LimitReader(r.Body, 1024*1024) aumBytes, err := io.ReadAll(body) if err != nil { http.Error(w, "reading AUM", http.StatusBadRequest) return } var aum tka.AUM if err := aum.Unserialize(aumBytes); err != nil { http.Error(w, "decoding AUM", http.StatusBadRequest) return }
res, err := h.b.TailnetLockCosignRecoveryAUM(&aum) if err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) return } w.Header().Set("Content-Type", "application/octet-stream") w.Write(res.Serialize())}
func (h *Handler) serveTKASubmitRecoveryAUM(w http.ResponseWriter, r *http.Request) { if !h.PermitWrite { http.Error(w, "access denied", http.StatusForbidden) return } if r.Method != httpm.POST { http.Error(w, "use POST", http.StatusMethodNotAllowed) return }
body := io.LimitReader(r.Body, 1024*1024) aumBytes, err := io.ReadAll(body) if err != nil { http.Error(w, "reading AUM", http.StatusBadRequest) return } var aum tka.AUM if err := aum.Unserialize(aumBytes); err != nil { http.Error(w, "decoding AUM", http.StatusBadRequest) return }
if err := h.b.TailnetLockSubmitRecoveryAUM(&aum); err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) return } w.WriteHeader(http.StatusOK)}