control/controlclient: start fetching the server noise key

Updates #3488

Signed-off-by: Maisem Ali <maisem@tailscale.com>
pull/4092/head
Maisem Ali 2 years ago committed by Maisem Ali
parent d5f8f38ac6
commit 249758df90

@ -47,6 +47,7 @@ import (
"tailscale.com/types/opt" "tailscale.com/types/opt"
"tailscale.com/types/persist" "tailscale.com/types/persist"
"tailscale.com/util/clientmetric" "tailscale.com/util/clientmetric"
"tailscale.com/util/multierr"
"tailscale.com/util/systemd" "tailscale.com/util/systemd"
"tailscale.com/wgengine/monitor" "tailscale.com/wgengine/monitor"
) )
@ -68,8 +69,10 @@ type Direct struct {
skipIPForwardingCheck bool skipIPForwardingCheck bool
pinger Pinger pinger Pinger
mu sync.Mutex // mutex guards the following fields mu sync.Mutex // mutex guards the following fields
serverKey key.MachinePublic serverKey key.MachinePublic // original ("legacy") nacl crypto_box-based public key
serverNoiseKey key.MachinePublic
persist persist.Persist persist persist.Persist
authKey string authKey string
tryingNewKey key.NodePrivate tryingNewKey key.NodePrivate
@ -326,16 +329,17 @@ func (c *Direct) doLogin(ctx context.Context, opt loginOpt) (mustRegen bool, new
c.logf("doLogin(regen=%v, hasUrl=%v)", regen, opt.URL != "") c.logf("doLogin(regen=%v, hasUrl=%v)", regen, opt.URL != "")
if serverKey.IsZero() { if serverKey.IsZero() {
var err error keys, err := loadServerPubKeys(ctx, c.httpc, c.serverURL)
serverKey, err = loadServerKey(ctx, c.httpc, c.serverURL)
if err != nil { if err != nil {
return regen, opt.URL, err return regen, opt.URL, err
} }
c.logf("control server key %s from %s", serverKey.ShortString(), c.serverURL) c.logf("control server key %s from %s", serverKey.ShortString(), c.serverURL)
c.mu.Lock() c.mu.Lock()
c.serverKey = serverKey c.serverKey = keys.LegacyPublicKey
c.serverNoiseKey = keys.PublicKey
c.mu.Unlock() c.mu.Unlock()
serverKey = keys.LegacyPublicKey
} }
var oldNodeKey key.NodePublic var oldNodeKey key.NodePublic
@ -950,29 +954,39 @@ func encode(v interface{}, serverKey key.MachinePublic, mkey key.MachinePrivate)
return mkey.SealTo(serverKey, b), nil return mkey.SealTo(serverKey, b), nil
} }
func loadServerKey(ctx context.Context, httpc *http.Client, serverURL string) (key.MachinePublic, error) { func loadServerPubKeys(ctx context.Context, httpc *http.Client, serverURL string) (*tailcfg.OverTLSPublicKeyResponse, error) {
req, err := http.NewRequest("GET", serverURL+"/key", nil) keyURL := fmt.Sprintf("%v/key?v=%d", serverURL, tailcfg.CurrentCapabilityVersion)
req, err := http.NewRequestWithContext(ctx, "GET", keyURL, nil)
if err != nil { if err != nil {
return key.MachinePublic{}, fmt.Errorf("create control key request: %v", err) return nil, fmt.Errorf("create control key request: %v", err)
} }
req = req.WithContext(ctx)
res, err := httpc.Do(req) res, err := httpc.Do(req)
if err != nil { if err != nil {
return key.MachinePublic{}, fmt.Errorf("fetch control key: %v", err) return nil, fmt.Errorf("fetch control key: %v", err)
} }
defer res.Body.Close() defer res.Body.Close()
b, err := ioutil.ReadAll(io.LimitReader(res.Body, 1<<16)) b, err := ioutil.ReadAll(io.LimitReader(res.Body, 64<<10))
if err != nil { if err != nil {
return key.MachinePublic{}, fmt.Errorf("fetch control key response: %v", err) return nil, fmt.Errorf("fetch control key response: %v", err)
} }
if res.StatusCode != 200 { if res.StatusCode != 200 {
return key.MachinePublic{}, fmt.Errorf("fetch control key: %d: %s", res.StatusCode, string(b)) return nil, fmt.Errorf("fetch control key: %d", res.StatusCode)
} }
var out tailcfg.OverTLSPublicKeyResponse
jsonErr := json.Unmarshal(b, &out)
if jsonErr == nil {
return &out, nil
}
// Some old control servers might not be updated to send the new format.
// Accept the old pre-JSON format too.
out = tailcfg.OverTLSPublicKeyResponse{}
k, err := key.ParseMachinePublicUntyped(mem.B(b)) k, err := key.ParseMachinePublicUntyped(mem.B(b))
if err != nil { if err != nil {
return key.MachinePublic{}, fmt.Errorf("fetch control key: %v", err) return nil, multierr.New(jsonErr, err)
} }
return k, nil out.LegacyPublicKey = k
return &out, nil
} }
// Debug contains temporary internal-only debug knobs. // Debug contains temporary internal-only debug knobs.

@ -53,11 +53,15 @@ type Server struct {
initMuxOnce sync.Once initMuxOnce sync.Once
mux *http.ServeMux mux *http.ServeMux
mu sync.Mutex mu sync.Mutex
inServeMap int inServeMap int
cond *sync.Cond // lazily initialized by condLocked cond *sync.Cond // lazily initialized by condLocked
pubKey key.MachinePublic pubKey key.MachinePublic
privKey key.ControlPrivate // not strictly needed vs. MachinePrivate, but handy to test type interactions. privKey key.ControlPrivate // not strictly needed vs. MachinePrivate, but handy to test type interactions.
noisePubKey key.MachinePublic
noisePrivKey key.ControlPrivate // not strictly needed vs. MachinePrivate, but handy to test type interactions.
nodes map[key.NodePublic]*tailcfg.Node nodes map[key.NodePublic]*tailcfg.Node
users map[key.NodePublic]*tailcfg.User users map[key.NodePublic]*tailcfg.User
logins map[key.NodePublic]*tailcfg.Login logins map[key.NodePublic]*tailcfg.Login
@ -211,30 +215,42 @@ func (s *Server) serveUnhandled(w http.ResponseWriter, r *http.Request) {
go panic(fmt.Sprintf("testcontrol.Server received unhandled request: %s", got.Bytes())) go panic(fmt.Sprintf("testcontrol.Server received unhandled request: %s", got.Bytes()))
} }
func (s *Server) publicKey() key.MachinePublic { func (s *Server) publicKeys() (noiseKey, pubKey key.MachinePublic) {
pub, _ := s.keyPair() s.mu.Lock()
return pub defer s.mu.Unlock()
s.ensureKeyPairLocked()
return s.noisePubKey, s.pubKey
} }
func (s *Server) privateKey() key.ControlPrivate { func (s *Server) privateKey() key.ControlPrivate {
_, priv := s.keyPair()
return priv
}
func (s *Server) keyPair() (pub key.MachinePublic, priv key.ControlPrivate) {
s.mu.Lock() s.mu.Lock()
defer s.mu.Unlock() defer s.mu.Unlock()
if s.pubKey.IsZero() { s.ensureKeyPairLocked()
s.privKey = key.NewControl() return s.privKey
s.pubKey = s.privKey.Public() }
func (s *Server) ensureKeyPairLocked() {
if !s.pubKey.IsZero() {
return
} }
return s.pubKey, s.privKey s.noisePrivKey = key.NewControl()
s.noisePubKey = s.noisePrivKey.Public()
s.privKey = key.NewControl()
s.pubKey = s.privKey.Public()
} }
func (s *Server) serveKey(w http.ResponseWriter, r *http.Request) { func (s *Server) serveKey(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "text/plain") noiseKey, legacyKey := s.publicKeys()
w.WriteHeader(200) if r.FormValue("v") == "" {
io.WriteString(w, s.publicKey().UntypedHexString()) w.Header().Set("Content-Type", "text/plain")
io.WriteString(w, legacyKey.UntypedHexString())
return
}
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(&tailcfg.OverTLSPublicKeyResponse{
LegacyPublicKey: legacyKey,
PublicKey: noiseKey,
})
} }
func (s *Server) serveMachine(w http.ResponseWriter, r *http.Request) { func (s *Server) serveMachine(w http.ResponseWriter, r *http.Request) {
@ -245,6 +261,7 @@ func (s *Server) serveMachine(w http.ResponseWriter, r *http.Request) {
mkeyStr = mkeyStr[:i] mkeyStr = mkeyStr[:i]
} }
// TODO(maisem/bradfitz): support noise protocol here.
mkey, err := key.ParseMachinePublicUntyped(mem.S(mkeyStr)) mkey, err := key.ParseMachinePublicUntyped(mem.S(mkeyStr))
if err != nil { if err != nil {
http.Error(w, "bad machine key hex", 400) http.Error(w, "bad machine key hex", 400)

Loading…
Cancel
Save