control/controlclient: delete old naclbox code, require ts2021 Noise

Updates #11585
Updates tailscale/corp#18882

Change-Id: I90e2e4a211c58d429e2b128604614dde18986442
Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
This commit is contained in:
Brad Fitzpatrick
2024-04-01 21:23:27 -07:00
committed by Brad Fitzpatrick
parent b9611461e5
commit 2409661a0d
3 changed files with 119 additions and 207 deletions

View File

@@ -562,16 +562,11 @@ func TestAddPingRequest(t *testing.T) {
func TestC2NPingRequest(t *testing.T) {
tstest.Shard(t)
tstest.Parallel(t)
env := newTestEnv(t)
n1 := newTestNode(t, env)
n1.StartDaemon()
n1.AwaitListening()
n1.MustUp()
n1.AwaitRunning()
env := newTestEnv(t)
gotPing := make(chan bool, 1)
waitPing := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
env.Control.HandleC2N = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if r.Method != "POST" {
t.Errorf("unexpected ping method %q", r.Method)
}
@@ -584,8 +579,14 @@ func TestC2NPingRequest(t *testing.T) {
t.Errorf("body error\n got: %q\nwant: %q", got, want)
}
gotPing <- true
}))
defer waitPing.Close()
})
n1 := newTestNode(t, env)
n1.StartDaemon()
n1.AwaitListening()
n1.MustUp()
n1.AwaitRunning()
nodes := env.Control.AllNodes()
if len(nodes) != 1 {
@@ -604,7 +605,7 @@ func TestC2NPingRequest(t *testing.T) {
cancel()
pr := &tailcfg.PingRequest{
URL: fmt.Sprintf("%s/ping-%d", waitPing.URL, try),
URL: fmt.Sprintf("https://unused/some-c2n-path/ping-%d", try),
Log: true,
Types: "c2n",
Payload: []byte("POST /echo HTTP/1.0\r\nContent-Length: 3\r\n\r\nabc"),

View File

@@ -24,7 +24,8 @@ import (
"sync"
"time"
"go4.org/mem"
"golang.org/x/net/http2"
"tailscale.com/control/controlhttp"
"tailscale.com/net/netaddr"
"tailscale.com/net/tsaddr"
"tailscale.com/tailcfg"
@@ -50,6 +51,7 @@ type Server struct {
Verbose bool
DNSConfig *tailcfg.DNSConfig // nil means no DNS config
MagicDNSDomain string
HandleC2N http.Handler // if non-nil, used for /some-c2n-path/ in tests
// ExplicitBaseURL or HTTPTestServer must be set.
ExplicitBaseURL string // e.g. "http://127.0.0.1:1234" with no trailing URL
@@ -82,7 +84,7 @@ type Server struct {
suppressAutoMapResponses set.Set[key.NodePublic]
noisePubKey key.MachinePublic
noisePrivKey key.ControlPrivate // not strictly needed vs. MachinePrivate, but handy to test type interactions.
noisePrivKey key.MachinePrivate
nodes map[key.NodePublic]*tailcfg.Node
users map[key.NodePublic]*tailcfg.User
@@ -253,6 +255,10 @@ func (s *Server) initMux() {
})
s.mux.HandleFunc("/key", s.serveKey)
s.mux.HandleFunc("/machine/", s.serveMachine)
s.mux.HandleFunc("/ts2021", s.serveNoiseUpgrade)
if s.HandleC2N != nil {
s.mux.Handle("/some-c2n-path/", s.HandleC2N)
}
}
func (s *Server) ServeHTTP(w http.ResponseWriter, r *http.Request) {
@@ -266,6 +272,36 @@ func (s *Server) serveUnhandled(w http.ResponseWriter, r *http.Request) {
go panic(fmt.Sprintf("testcontrol.Server received unhandled request: %s", got.Bytes()))
}
type peerMachinePublicContextKey struct{}
func (s *Server) serveNoiseUpgrade(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
if r.Method != "POST" {
http.Error(w, "POST required", 400)
return
}
s.mu.Lock()
noisePrivate := s.noisePrivKey
s.mu.Unlock()
cc, err := controlhttp.AcceptHTTP(ctx, w, r, noisePrivate, nil)
if err != nil {
log.Printf("AcceptHTTP: %v", err)
return
}
defer cc.Close()
var h2srv http2.Server
peerPub := cc.Peer()
h2srv.ServeConn(cc, &http2.ServeConnOpts{
Context: context.WithValue(ctx, peerMachinePublicContextKey{}, peerPub),
BaseConfig: &http.Server{
Handler: s.mux,
},
})
}
func (s *Server) publicKeys() (noiseKey, pubKey key.MachinePublic) {
s.mu.Lock()
defer s.mu.Unlock()
@@ -273,63 +309,50 @@ func (s *Server) publicKeys() (noiseKey, pubKey key.MachinePublic) {
return s.noisePubKey, s.pubKey
}
func (s *Server) privateKey() key.ControlPrivate {
s.mu.Lock()
defer s.mu.Unlock()
s.ensureKeyPairLocked()
return s.privKey
}
func (s *Server) ensureKeyPairLocked() {
if !s.pubKey.IsZero() {
return
}
s.noisePrivKey = key.NewControl()
s.noisePrivKey = key.NewMachine()
s.noisePubKey = s.noisePrivKey.Public()
s.privKey = key.NewControl()
s.pubKey = s.privKey.Public()
}
func (s *Server) serveKey(w http.ResponseWriter, r *http.Request) {
_, legacyKey := s.publicKeys()
noiseKey, legacyKey := s.publicKeys()
if r.FormValue("v") == "" {
w.Header().Set("Content-Type", "text/plain")
io.WriteString(w, legacyKey.UntypedHexString())
return
}
w.Header().Set("Content-Type", "application/json")
// TODO(maisem/bradfitz): support noise protocol here.
json.NewEncoder(w).Encode(&tailcfg.OverTLSPublicKeyResponse{
LegacyPublicKey: legacyKey,
// PublicKey: noiseKey,
PublicKey: noiseKey,
})
}
func (s *Server) serveMachine(w http.ResponseWriter, r *http.Request) {
mkeyStr := strings.TrimPrefix(r.URL.Path, "/machine/")
rem := ""
if i := strings.IndexByte(mkeyStr, '/'); i != -1 {
rem = mkeyStr[i:]
mkeyStr = mkeyStr[:i]
}
// TODO(maisem/bradfitz): support noise protocol here.
mkey, err := key.ParseMachinePublicUntyped(mem.S(mkeyStr))
if err != nil {
http.Error(w, "bad machine key hex", 400)
return
}
if r.Method != "POST" {
http.Error(w, "POST required", 400)
return
}
ctx := r.Context()
switch rem {
case "":
s.serveRegister(w, r, mkey)
case "/map":
mkey, ok := ctx.Value(peerMachinePublicContextKey{}).(key.MachinePublic)
if !ok {
panic("no peer machine public key in context")
}
switch r.URL.Path {
case "/machine/map":
s.serveMap(w, r, mkey)
case "/machine/register":
s.serveRegister(w, r, mkey)
case "/machine/update-health":
io.Copy(io.Discard, r.Body)
w.WriteHeader(http.StatusNoContent)
default:
s.serveUnhandled(w, r)
}
@@ -549,7 +572,7 @@ func (s *Server) serveRegister(w http.ResponseWriter, r *http.Request, mkey key.
}
var req tailcfg.RegisterRequest
if err := s.decode(mkey, msg, &req); err != nil {
if err := s.decode(msg, &req); err != nil {
go panic(fmt.Sprintf("serveRegister: decode: %v", err))
}
if req.Version == 0 {
@@ -563,7 +586,7 @@ func (s *Server) serveRegister(w http.ResponseWriter, r *http.Request, mkey key.
log.Printf("Got %T: %s", req, j)
}
if s.RequireAuthKey != "" && req.Auth.AuthKey != s.RequireAuthKey {
res := must.Get(s.encode(mkey, false, tailcfg.RegisterResponse{
res := must.Get(s.encode(false, tailcfg.RegisterResponse{
Error: "invalid authkey",
}))
w.WriteHeader(200)
@@ -637,7 +660,7 @@ func (s *Server) serveRegister(w http.ResponseWriter, r *http.Request, mkey key.
authURL = s.BaseURL() + authPath
}
res, err := s.encode(mkey, false, tailcfg.RegisterResponse{
res, err := s.encode(false, tailcfg.RegisterResponse{
User: *user,
Login: *login,
NodeKeyExpired: allExpired,
@@ -729,7 +752,7 @@ func (s *Server) serveMap(w http.ResponseWriter, r *http.Request, mkey key.Machi
r.Body.Close()
req := new(tailcfg.MapRequest)
if err := s.decode(mkey, msg, req); err != nil {
if err := s.decode(msg, req); err != nil {
go panic(fmt.Sprintf("bad map request: %v", err))
}
@@ -1011,7 +1034,7 @@ func (s *Server) takeRawMapMessage(nk key.NodePublic) (mapResJSON []byte, ok boo
}
func (s *Server) sendMapMsg(w http.ResponseWriter, mkey key.MachinePublic, compress bool, msg any) error {
resBytes, err := s.encode(mkey, compress, msg)
resBytes, err := s.encode(compress, msg)
if err != nil {
return err
}
@@ -1034,19 +1057,14 @@ func (s *Server) sendMapMsg(w http.ResponseWriter, mkey key.MachinePublic, compr
return nil
}
func (s *Server) decode(mkey key.MachinePublic, msg []byte, v any) error {
func (s *Server) decode(msg []byte, v any) error {
if len(msg) == msgLimit {
return errors.New("encrypted message too long")
}
decrypted, ok := s.privateKey().OpenFrom(mkey, msg)
if !ok {
return errors.New("can't decrypt request")
}
return json.Unmarshal(decrypted, v)
return json.Unmarshal(msg, v)
}
func (s *Server) encode(mkey key.MachinePublic, compress bool, v any) (b []byte, err error) {
func (s *Server) encode(compress bool, v any) (b []byte, err error) {
var isBytes bool
if b, isBytes = v.([]byte); !isBytes {
b, err = json.Marshal(v)
@@ -1057,7 +1075,7 @@ func (s *Server) encode(mkey key.MachinePublic, compress bool, v any) (b []byte,
if compress {
b = zstdframe.AppendEncode(nil, b, zstdframe.FastestCompression)
}
return s.privateKey().SealTo(mkey, b), nil
return b, nil
}
// filterInvalidIPv6Endpoints removes invalid IPv6 endpoints from eps,