client/web: add csrf protection to web client api

Adds csrf protection and hooks up an initial POST request from
the React web client.

Updates tailscale/corp#13775

Signed-off-by: Sonia Appasamy <sonia@tailscale.com>
This commit is contained in:
Sonia Appasamy
2023-08-16 18:52:31 -04:00
committed by Sonia Appasamy
parent 77ff705545
commit 077bbb8403
11 changed files with 245 additions and 47 deletions

View File

@@ -7,6 +7,7 @@ package web
import (
"bytes"
"context"
"crypto/rand"
"crypto/tls"
_ "embed"
"encoding/json"
@@ -23,6 +24,7 @@ import (
"os/exec"
"strings"
"github.com/gorilla/csrf"
"tailscale.com/client/tailscale"
"tailscale.com/envknob"
"tailscale.com/ipn"
@@ -31,7 +33,6 @@ import (
"tailscale.com/net/netutil"
"tailscale.com/tailcfg"
"tailscale.com/util/groupmember"
"tailscale.com/util/httpm"
"tailscale.com/version/distro"
)
@@ -52,6 +53,8 @@ type Server struct {
devMode bool
devProxy *httputil.ReverseProxy // only filled when devMode is on
apiHandler http.Handler // csrf-protected api handler
}
// NewServer constructs a new Tailscale web client server.
@@ -70,6 +73,11 @@ func NewServer(devMode bool, lc *tailscale.LocalClient) (s *Server, cleanup func
if s.devMode {
cleanup = s.startDevServer()
s.addProxyToDevServer()
// Create new handler for "/api" requests.
// And protect with gorilla csrf.
csrfProtect := csrf.Protect(csrfKey())
s.apiHandler = csrfProtect(&api{s: s})
}
return s, cleanup
}
@@ -271,19 +279,9 @@ req.send(null);
// ServeHTTP processes all requests for the Tailscale web client.
func (s *Server) ServeHTTP(w http.ResponseWriter, r *http.Request) {
if s.devMode {
if r.URL.Path == "/api/data" {
user, err := authorize(w, r)
if err != nil {
return
}
switch r.Method {
case httpm.GET:
s.serveGetNodeDataJSON(w, r, user)
case httpm.POST:
s.servePostNodeUpdate(w, r)
default:
http.Error(w, "method not allowed", http.StatusMethodNotAllowed)
}
if strings.HasPrefix(r.URL.Path, "/api/") {
// Pass through to other handlers via CSRF protection.
s.apiHandler.ServeHTTP(w, r)
return
}
// When in dev mode, proxy to the Vite dev server.
@@ -527,3 +525,14 @@ func (s *Server) tailscaleUp(ctx context.Context, st *ipnstate.Status, postData
}
}
}
// csrfKey creates a new random csrf token.
// If an error surfaces during key creation,
// the error is logged and the active process terminated.
func csrfKey() []byte {
key := make([]byte, 32)
if _, err := rand.Read(key); err != nil {
log.Fatal("error generating CSRF key: %w", err)
}
return key
}