mirror of
https://github.com/tailscale/tailscale.git
synced 2024-11-29 04:55:31 +00:00
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:
parent
b9611461e5
commit
2409661a0d
@ -82,9 +82,9 @@ type Direct struct {
|
|||||||
|
|
||||||
dialPlan ControlDialPlanner // can be nil
|
dialPlan ControlDialPlanner // can be nil
|
||||||
|
|
||||||
mu sync.Mutex // mutex guards the following fields
|
mu sync.Mutex // mutex guards the following fields
|
||||||
serverKey key.MachinePublic // original ("legacy") nacl crypto_box-based public key
|
serverLegacyKey key.MachinePublic // original ("legacy") nacl crypto_box-based public key; only used for signRegisterRequest on Windows now
|
||||||
serverNoiseKey key.MachinePublic
|
serverNoiseKey key.MachinePublic
|
||||||
|
|
||||||
sfGroup singleflight.Group[struct{}, *NoiseClient] // protects noiseClient creation.
|
sfGroup singleflight.Group[struct{}, *NoiseClient] // protects noiseClient creation.
|
||||||
noiseClient *NoiseClient
|
noiseClient *NoiseClient
|
||||||
@ -436,12 +436,6 @@ type loginOpt struct {
|
|||||||
OldNodeKeySignature tkatype.MarshaledSignature
|
OldNodeKeySignature tkatype.MarshaledSignature
|
||||||
}
|
}
|
||||||
|
|
||||||
// httpClient provides a common interface for the noiseClient and
|
|
||||||
// the NaCl box http.Client.
|
|
||||||
type httpClient interface {
|
|
||||||
Do(req *http.Request) (*http.Response, error)
|
|
||||||
}
|
|
||||||
|
|
||||||
// hostInfoLocked returns a Clone of c.hostinfo and c.netinfo.
|
// hostInfoLocked returns a Clone of c.hostinfo and c.netinfo.
|
||||||
// It must only be called with c.mu held.
|
// It must only be called with c.mu held.
|
||||||
func (c *Direct) hostInfoLocked() *tailcfg.Hostinfo {
|
func (c *Direct) hostInfoLocked() *tailcfg.Hostinfo {
|
||||||
@ -454,7 +448,7 @@ func (c *Direct) doLogin(ctx context.Context, opt loginOpt) (mustRegen bool, new
|
|||||||
c.mu.Lock()
|
c.mu.Lock()
|
||||||
persist := c.persist.AsStruct()
|
persist := c.persist.AsStruct()
|
||||||
tryingNewKey := c.tryingNewKey
|
tryingNewKey := c.tryingNewKey
|
||||||
serverKey := c.serverKey
|
serverKey := c.serverLegacyKey
|
||||||
serverNoiseKey := c.serverNoiseKey
|
serverNoiseKey := c.serverNoiseKey
|
||||||
authKey, isWrapped, wrappedSig, wrappedKey := decodeWrappedAuthkey(c.authKey, c.logf)
|
authKey, isWrapped, wrappedSig, wrappedKey := decodeWrappedAuthkey(c.authKey, c.logf)
|
||||||
hi := c.hostInfoLocked()
|
hi := c.hostInfoLocked()
|
||||||
@ -494,20 +488,22 @@ func (c *Direct) doLogin(ctx context.Context, opt loginOpt) (mustRegen bool, new
|
|||||||
c.logf("control server key from %s: ts2021=%s, legacy=%v", c.serverURL, keys.PublicKey.ShortString(), keys.LegacyPublicKey.ShortString())
|
c.logf("control server key from %s: ts2021=%s, legacy=%v", c.serverURL, keys.PublicKey.ShortString(), keys.LegacyPublicKey.ShortString())
|
||||||
|
|
||||||
c.mu.Lock()
|
c.mu.Lock()
|
||||||
c.serverKey = keys.LegacyPublicKey
|
c.serverLegacyKey = keys.LegacyPublicKey
|
||||||
c.serverNoiseKey = keys.PublicKey
|
c.serverNoiseKey = keys.PublicKey
|
||||||
c.mu.Unlock()
|
c.mu.Unlock()
|
||||||
serverKey = keys.LegacyPublicKey
|
serverKey = keys.LegacyPublicKey
|
||||||
serverNoiseKey = keys.PublicKey
|
serverNoiseKey = keys.PublicKey
|
||||||
|
|
||||||
// For servers supporting the Noise transport,
|
// Proactively shut down our TLS TCP connection.
|
||||||
// proactively shut down our TLS TCP connection.
|
|
||||||
// We're not going to need it and it's nicer to the
|
// We're not going to need it and it's nicer to the
|
||||||
// server.
|
// server.
|
||||||
if !serverNoiseKey.IsZero() {
|
c.httpc.CloseIdleConnections()
|
||||||
c.httpc.CloseIdleConnections()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if serverNoiseKey.IsZero() {
|
||||||
|
return false, "", nil, errors.New("control server is too old; no noise key")
|
||||||
|
}
|
||||||
|
|
||||||
var oldNodeKey key.NodePublic
|
var oldNodeKey key.NodePublic
|
||||||
switch {
|
switch {
|
||||||
case opt.Logout:
|
case opt.Logout:
|
||||||
@ -594,7 +590,7 @@ func (c *Direct) doLogin(ctx context.Context, opt loginOpt) (mustRegen bool, new
|
|||||||
request.Auth.Provider = persist.Provider
|
request.Auth.Provider = persist.Provider
|
||||||
request.Auth.LoginName = persist.UserProfile.LoginName
|
request.Auth.LoginName = persist.UserProfile.LoginName
|
||||||
request.Auth.AuthKey = authKey
|
request.Auth.AuthKey = authKey
|
||||||
err = signRegisterRequest(&request, c.serverURL, c.serverKey, machinePrivKey.Public())
|
err = signRegisterRequest(&request, c.serverURL, c.serverLegacyKey, machinePrivKey.Public())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
// If signing failed, clear all related fields
|
// If signing failed, clear all related fields
|
||||||
request.SignatureType = tailcfg.SignatureNone
|
request.SignatureType = tailcfg.SignatureNone
|
||||||
@ -614,21 +610,16 @@ func (c *Direct) doLogin(ctx context.Context, opt loginOpt) (mustRegen bool, new
|
|||||||
}
|
}
|
||||||
|
|
||||||
// URL and httpc are protocol specific.
|
// URL and httpc are protocol specific.
|
||||||
var url string
|
|
||||||
var httpc httpClient
|
request.Version = tailcfg.CurrentCapabilityVersion
|
||||||
if serverNoiseKey.IsZero() {
|
httpc, err := c.getNoiseClient()
|
||||||
httpc = c.httpc
|
if err != nil {
|
||||||
url = fmt.Sprintf("%s/machine/%s", c.serverURL, machinePrivKey.Public().UntypedHexString())
|
return regen, opt.URL, nil, fmt.Errorf("getNoiseClient: %w", err)
|
||||||
} else {
|
|
||||||
request.Version = tailcfg.CurrentCapabilityVersion
|
|
||||||
httpc, err = c.getNoiseClient()
|
|
||||||
if err != nil {
|
|
||||||
return regen, opt.URL, nil, fmt.Errorf("getNoiseClient: %w", err)
|
|
||||||
}
|
|
||||||
url = fmt.Sprintf("%s/machine/register", c.serverURL)
|
|
||||||
url = strings.Replace(url, "http:", "https:", 1)
|
|
||||||
}
|
}
|
||||||
bodyData, err := encode(request, serverKey, serverNoiseKey, machinePrivKey)
|
url := fmt.Sprintf("%s/machine/register", c.serverURL)
|
||||||
|
url = strings.Replace(url, "http:", "https:", 1)
|
||||||
|
|
||||||
|
bodyData, err := encode(request)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return regen, opt.URL, nil, err
|
return regen, opt.URL, nil, err
|
||||||
}
|
}
|
||||||
@ -650,7 +641,7 @@ func (c *Direct) doLogin(ctx context.Context, opt loginOpt) (mustRegen bool, new
|
|||||||
res.StatusCode, strings.TrimSpace(string(msg)))
|
res.StatusCode, strings.TrimSpace(string(msg)))
|
||||||
}
|
}
|
||||||
resp := tailcfg.RegisterResponse{}
|
resp := tailcfg.RegisterResponse{}
|
||||||
if err := decode(res, &resp, serverKey, serverNoiseKey, machinePrivKey); err != nil {
|
if err := decode(res, &resp); err != nil {
|
||||||
c.logf("error decoding RegisterResponse with server key %s and machine key %s: %v", serverKey, machinePrivKey.Public(), err)
|
c.logf("error decoding RegisterResponse with server key %s and machine key %s: %v", serverKey, machinePrivKey.Public(), err)
|
||||||
return regen, opt.URL, nil, fmt.Errorf("register request: %v", err)
|
return regen, opt.URL, nil, fmt.Errorf("register request: %v", err)
|
||||||
}
|
}
|
||||||
@ -844,7 +835,6 @@ func (c *Direct) sendMapRequest(ctx context.Context, isStreaming bool, nu Netmap
|
|||||||
c.mu.Lock()
|
c.mu.Lock()
|
||||||
persist := c.persist
|
persist := c.persist
|
||||||
serverURL := c.serverURL
|
serverURL := c.serverURL
|
||||||
serverKey := c.serverKey
|
|
||||||
serverNoiseKey := c.serverNoiseKey
|
serverNoiseKey := c.serverNoiseKey
|
||||||
hi := c.hostInfoLocked()
|
hi := c.hostInfoLocked()
|
||||||
backendLogID := hi.BackendLogID
|
backendLogID := hi.BackendLogID
|
||||||
@ -858,6 +848,10 @@ func (c *Direct) sendMapRequest(ctx context.Context, isStreaming bool, nu Netmap
|
|||||||
}
|
}
|
||||||
c.mu.Unlock()
|
c.mu.Unlock()
|
||||||
|
|
||||||
|
if serverNoiseKey.IsZero() {
|
||||||
|
return errors.New("control server is too old; no noise key")
|
||||||
|
}
|
||||||
|
|
||||||
machinePrivKey, err := c.getMachinePrivKey()
|
machinePrivKey, err := c.getMachinePrivKey()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("getMachinePrivKey: %w", err)
|
return fmt.Errorf("getMachinePrivKey: %w", err)
|
||||||
@ -914,7 +908,7 @@ func (c *Direct) sendMapRequest(ctx context.Context, isStreaming bool, nu Netmap
|
|||||||
}
|
}
|
||||||
request.Compress = "zstd"
|
request.Compress = "zstd"
|
||||||
|
|
||||||
bodyData, err := encode(request, serverKey, serverNoiseKey, machinePrivKey)
|
bodyData, err := encode(request)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
vlogf("netmap: encode: %v", err)
|
vlogf("netmap: encode: %v", err)
|
||||||
return err
|
return err
|
||||||
@ -926,20 +920,12 @@ func (c *Direct) sendMapRequest(ctx context.Context, isStreaming bool, nu Netmap
|
|||||||
machinePubKey := machinePrivKey.Public()
|
machinePubKey := machinePrivKey.Public()
|
||||||
t0 := c.clock.Now()
|
t0 := c.clock.Now()
|
||||||
|
|
||||||
// Url and httpc are protocol specific.
|
httpc, err := c.getNoiseClient()
|
||||||
var url string
|
if err != nil {
|
||||||
var httpc httpClient
|
return fmt.Errorf("getNoiseClient: %w", err)
|
||||||
if serverNoiseKey.IsZero() {
|
|
||||||
httpc = c.httpc
|
|
||||||
url = fmt.Sprintf("%s/machine/%s/map", serverURL, machinePubKey.UntypedHexString())
|
|
||||||
} else {
|
|
||||||
httpc, err = c.getNoiseClient()
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("getNoiseClient: %w", err)
|
|
||||||
}
|
|
||||||
url = fmt.Sprintf("%s/machine/map", serverURL)
|
|
||||||
url = strings.Replace(url, "http:", "https:", 1)
|
|
||||||
}
|
}
|
||||||
|
url := fmt.Sprintf("%s/machine/map", serverURL)
|
||||||
|
url = strings.Replace(url, "http:", "https:", 1)
|
||||||
|
|
||||||
// Create a watchdog timer that breaks the connection if we don't receive a
|
// Create a watchdog timer that breaks the connection if we don't receive a
|
||||||
// MapResponse from the network at least once every two minutes. The
|
// MapResponse from the network at least once every two minutes. The
|
||||||
@ -1047,7 +1033,7 @@ func (c *Direct) sendMapRequest(ctx context.Context, isStreaming bool, nu Netmap
|
|||||||
vlogf("netmap: read body after %v", time.Since(t0).Round(time.Millisecond))
|
vlogf("netmap: read body after %v", time.Since(t0).Round(time.Millisecond))
|
||||||
|
|
||||||
var resp tailcfg.MapResponse
|
var resp tailcfg.MapResponse
|
||||||
if err := c.decodeMsg(msg, &resp, machinePrivKey); err != nil {
|
if err := c.decodeMsg(msg, &resp); err != nil {
|
||||||
vlogf("netmap: decode error: %v", err)
|
vlogf("netmap: decode error: %v", err)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@ -1164,9 +1150,8 @@ func initDisplayNames(selfNode tailcfg.NodeView, resp *tailcfg.MapResponse) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// decode JSON decodes the res.Body into v. If serverNoiseKey is not specified,
|
// decode JSON decodes the res.Body into v.
|
||||||
// it uses the serverKey and mkey to decode the message from the NaCl-crypto-box.
|
func decode(res *http.Response, v any) error {
|
||||||
func decode(res *http.Response, v any, serverKey, serverNoiseKey key.MachinePublic, mkey key.MachinePrivate) error {
|
|
||||||
defer res.Body.Close()
|
defer res.Body.Close()
|
||||||
msg, err := io.ReadAll(io.LimitReader(res.Body, 1<<20))
|
msg, err := io.ReadAll(io.LimitReader(res.Body, 1<<20))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -1175,10 +1160,7 @@ func decode(res *http.Response, v any, serverKey, serverNoiseKey key.MachinePubl
|
|||||||
if res.StatusCode != 200 {
|
if res.StatusCode != 200 {
|
||||||
return fmt.Errorf("%d: %v", res.StatusCode, string(msg))
|
return fmt.Errorf("%d: %v", res.StatusCode, string(msg))
|
||||||
}
|
}
|
||||||
if !serverNoiseKey.IsZero() {
|
return json.Unmarshal(msg, v)
|
||||||
return json.Unmarshal(msg, v)
|
|
||||||
}
|
|
||||||
return decodeMsg(msg, v, serverKey, mkey)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
var (
|
var (
|
||||||
@ -1189,25 +1171,8 @@ func decode(res *http.Response, v any, serverKey, serverNoiseKey key.MachinePubl
|
|||||||
var jsonEscapedZero = []byte(`\u0000`)
|
var jsonEscapedZero = []byte(`\u0000`)
|
||||||
|
|
||||||
// decodeMsg is responsible for uncompressing msg and unmarshaling into v.
|
// decodeMsg is responsible for uncompressing msg and unmarshaling into v.
|
||||||
// If c.serverNoiseKey is not specified, it uses the c.serverKey and mkey
|
func (c *Direct) decodeMsg(compressedMsg []byte, v any) error {
|
||||||
// to first the decrypt msg from the NaCl-crypto-box.
|
b, err := zstdframe.AppendDecode(nil, compressedMsg)
|
||||||
func (c *Direct) decodeMsg(msg []byte, v any, mkey key.MachinePrivate) error {
|
|
||||||
c.mu.Lock()
|
|
||||||
serverKey := c.serverKey
|
|
||||||
serverNoiseKey := c.serverNoiseKey
|
|
||||||
c.mu.Unlock()
|
|
||||||
|
|
||||||
var decrypted []byte
|
|
||||||
if serverNoiseKey.IsZero() {
|
|
||||||
var ok bool
|
|
||||||
decrypted, ok = mkey.OpenFrom(serverKey, msg)
|
|
||||||
if !ok {
|
|
||||||
return errors.New("cannot decrypt response")
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
decrypted = msg
|
|
||||||
}
|
|
||||||
b, err := zstdframe.AppendDecode(nil, decrypted)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@ -1224,26 +1189,11 @@ func (c *Direct) decodeMsg(msg []byte, v any, mkey key.MachinePrivate) error {
|
|||||||
return fmt.Errorf("response: %v", err)
|
return fmt.Errorf("response: %v", err)
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func decodeMsg(msg []byte, v any, serverKey key.MachinePublic, machinePrivKey key.MachinePrivate) error {
|
// encode JSON encodes v as JSON, logging tailcfg.MapRequest values if
|
||||||
decrypted, ok := machinePrivKey.OpenFrom(serverKey, msg)
|
// debugMap is set.
|
||||||
if !ok {
|
func encode(v any) ([]byte, error) {
|
||||||
return errors.New("cannot decrypt response")
|
|
||||||
}
|
|
||||||
if bytes.Contains(decrypted, jsonEscapedZero) {
|
|
||||||
log.Printf("[unexpected] zero byte in controlclient decodeMsg into %T: %q", v, decrypted)
|
|
||||||
}
|
|
||||||
if err := json.Unmarshal(decrypted, v); err != nil {
|
|
||||||
return fmt.Errorf("response: %v", err)
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// encode JSON encodes v. If serverNoiseKey is not specified, it uses the serverKey and mkey to
|
|
||||||
// seal the message into a NaCl-crypto-box.
|
|
||||||
func encode(v any, serverKey, serverNoiseKey key.MachinePublic, mkey key.MachinePrivate) ([]byte, error) {
|
|
||||||
b, err := json.Marshal(v)
|
b, err := json.Marshal(v)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
@ -1253,10 +1203,7 @@ func encode(v any, serverKey, serverNoiseKey key.MachinePublic, mkey key.Machine
|
|||||||
log.Printf("MapRequest: %s", b)
|
log.Printf("MapRequest: %s", b)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if !serverNoiseKey.IsZero() {
|
return b, nil
|
||||||
return b, nil
|
|
||||||
}
|
|
||||||
return mkey.SealTo(serverKey, b), nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func loadServerPubKeys(ctx context.Context, httpc *http.Client, serverURL string) (*tailcfg.OverTLSPublicKeyResponse, error) {
|
func loadServerPubKeys(ctx context.Context, httpc *http.Client, serverURL string) (*tailcfg.OverTLSPublicKeyResponse, error) {
|
||||||
@ -1353,7 +1300,7 @@ func (c *Direct) isUniquePingRequest(pr *tailcfg.PingRequest) bool {
|
|||||||
|
|
||||||
func (c *Direct) answerPing(pr *tailcfg.PingRequest) {
|
func (c *Direct) answerPing(pr *tailcfg.PingRequest) {
|
||||||
httpc := c.httpc
|
httpc := c.httpc
|
||||||
useNoise := pr.URLIsNoise || pr.Types == "c2n" && c.noiseConfigured()
|
useNoise := pr.URLIsNoise || pr.Types == "c2n"
|
||||||
if useNoise {
|
if useNoise {
|
||||||
nc, err := c.getNoiseClient()
|
nc, err := c.getNoiseClient()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -1554,14 +1501,6 @@ func (c *Direct) setDNSNoise(ctx context.Context, req *tailcfg.SetDNSRequest) er
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// noiseConfigured reports whether the client can communicate with Control
|
|
||||||
// over Noise.
|
|
||||||
func (c *Direct) noiseConfigured() bool {
|
|
||||||
c.mu.Lock()
|
|
||||||
defer c.mu.Unlock()
|
|
||||||
return !c.serverNoiseKey.IsZero()
|
|
||||||
}
|
|
||||||
|
|
||||||
// SetDNS sends the SetDNSRequest request to the control plane server,
|
// SetDNS sends the SetDNSRequest request to the control plane server,
|
||||||
// requesting a DNS record be created or updated.
|
// requesting a DNS record be created or updated.
|
||||||
func (c *Direct) SetDNS(ctx context.Context, req *tailcfg.SetDNSRequest) (err error) {
|
func (c *Direct) SetDNS(ctx context.Context, req *tailcfg.SetDNSRequest) (err error) {
|
||||||
@ -1571,53 +1510,7 @@ func (c *Direct) SetDNS(ctx context.Context, req *tailcfg.SetDNSRequest) (err er
|
|||||||
metricSetDNSError.Add(1)
|
metricSetDNSError.Add(1)
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
if c.noiseConfigured() {
|
return c.setDNSNoise(ctx, req)
|
||||||
return c.setDNSNoise(ctx, req)
|
|
||||||
}
|
|
||||||
c.mu.Lock()
|
|
||||||
serverKey := c.serverKey
|
|
||||||
c.mu.Unlock()
|
|
||||||
|
|
||||||
if serverKey.IsZero() {
|
|
||||||
return errors.New("zero serverKey")
|
|
||||||
}
|
|
||||||
machinePrivKey, err := c.getMachinePrivKey()
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("getMachinePrivKey: %w", err)
|
|
||||||
}
|
|
||||||
if machinePrivKey.IsZero() {
|
|
||||||
return errors.New("getMachinePrivKey returned zero key")
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO(maisem): dedupe this codepath from SetDNSNoise.
|
|
||||||
var serverNoiseKey key.MachinePublic
|
|
||||||
bodyData, err := encode(req, serverKey, serverNoiseKey, machinePrivKey)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
body := bytes.NewReader(bodyData)
|
|
||||||
|
|
||||||
u := fmt.Sprintf("%s/machine/%s/set-dns", c.serverURL, machinePrivKey.Public().UntypedHexString())
|
|
||||||
hreq, err := http.NewRequestWithContext(ctx, "POST", u, body)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
res, err := c.httpc.Do(hreq)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
defer res.Body.Close()
|
|
||||||
if res.StatusCode != 200 {
|
|
||||||
msg, _ := io.ReadAll(res.Body)
|
|
||||||
return fmt.Errorf("set-dns response: %v, %.200s", res.Status, strings.TrimSpace(string(msg)))
|
|
||||||
}
|
|
||||||
var setDNSRes tailcfg.SetDNSResponse
|
|
||||||
if err := decode(res, &setDNSRes, serverKey, serverNoiseKey, machinePrivKey); err != nil {
|
|
||||||
c.logf("error decoding SetDNSResponse with server key %s and machine key %s: %v", serverKey, machinePrivKey.Public(), err)
|
|
||||||
return fmt.Errorf("set-dns-response: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Direct) DoNoiseRequest(req *http.Request) (*http.Response, error) {
|
func (c *Direct) DoNoiseRequest(req *http.Request) (*http.Response, error) {
|
||||||
|
@ -562,16 +562,11 @@ func TestAddPingRequest(t *testing.T) {
|
|||||||
func TestC2NPingRequest(t *testing.T) {
|
func TestC2NPingRequest(t *testing.T) {
|
||||||
tstest.Shard(t)
|
tstest.Shard(t)
|
||||||
tstest.Parallel(t)
|
tstest.Parallel(t)
|
||||||
env := newTestEnv(t)
|
|
||||||
n1 := newTestNode(t, env)
|
|
||||||
n1.StartDaemon()
|
|
||||||
|
|
||||||
n1.AwaitListening()
|
env := newTestEnv(t)
|
||||||
n1.MustUp()
|
|
||||||
n1.AwaitRunning()
|
|
||||||
|
|
||||||
gotPing := make(chan bool, 1)
|
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" {
|
if r.Method != "POST" {
|
||||||
t.Errorf("unexpected ping method %q", r.Method)
|
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)
|
t.Errorf("body error\n got: %q\nwant: %q", got, want)
|
||||||
}
|
}
|
||||||
gotPing <- true
|
gotPing <- true
|
||||||
}))
|
})
|
||||||
defer waitPing.Close()
|
|
||||||
|
n1 := newTestNode(t, env)
|
||||||
|
n1.StartDaemon()
|
||||||
|
|
||||||
|
n1.AwaitListening()
|
||||||
|
n1.MustUp()
|
||||||
|
n1.AwaitRunning()
|
||||||
|
|
||||||
nodes := env.Control.AllNodes()
|
nodes := env.Control.AllNodes()
|
||||||
if len(nodes) != 1 {
|
if len(nodes) != 1 {
|
||||||
@ -604,7 +605,7 @@ func TestC2NPingRequest(t *testing.T) {
|
|||||||
cancel()
|
cancel()
|
||||||
|
|
||||||
pr := &tailcfg.PingRequest{
|
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,
|
Log: true,
|
||||||
Types: "c2n",
|
Types: "c2n",
|
||||||
Payload: []byte("POST /echo HTTP/1.0\r\nContent-Length: 3\r\n\r\nabc"),
|
Payload: []byte("POST /echo HTTP/1.0\r\nContent-Length: 3\r\n\r\nabc"),
|
||||||
|
@ -24,7 +24,8 @@
|
|||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"go4.org/mem"
|
"golang.org/x/net/http2"
|
||||||
|
"tailscale.com/control/controlhttp"
|
||||||
"tailscale.com/net/netaddr"
|
"tailscale.com/net/netaddr"
|
||||||
"tailscale.com/net/tsaddr"
|
"tailscale.com/net/tsaddr"
|
||||||
"tailscale.com/tailcfg"
|
"tailscale.com/tailcfg"
|
||||||
@ -50,6 +51,7 @@ type Server struct {
|
|||||||
Verbose bool
|
Verbose bool
|
||||||
DNSConfig *tailcfg.DNSConfig // nil means no DNS config
|
DNSConfig *tailcfg.DNSConfig // nil means no DNS config
|
||||||
MagicDNSDomain string
|
MagicDNSDomain string
|
||||||
|
HandleC2N http.Handler // if non-nil, used for /some-c2n-path/ in tests
|
||||||
|
|
||||||
// ExplicitBaseURL or HTTPTestServer must be set.
|
// ExplicitBaseURL or HTTPTestServer must be set.
|
||||||
ExplicitBaseURL string // e.g. "http://127.0.0.1:1234" with no trailing URL
|
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]
|
suppressAutoMapResponses set.Set[key.NodePublic]
|
||||||
|
|
||||||
noisePubKey key.MachinePublic
|
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
|
nodes map[key.NodePublic]*tailcfg.Node
|
||||||
users map[key.NodePublic]*tailcfg.User
|
users map[key.NodePublic]*tailcfg.User
|
||||||
@ -253,6 +255,10 @@ func (s *Server) initMux() {
|
|||||||
})
|
})
|
||||||
s.mux.HandleFunc("/key", s.serveKey)
|
s.mux.HandleFunc("/key", s.serveKey)
|
||||||
s.mux.HandleFunc("/machine/", s.serveMachine)
|
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) {
|
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()))
|
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) {
|
func (s *Server) publicKeys() (noiseKey, pubKey key.MachinePublic) {
|
||||||
s.mu.Lock()
|
s.mu.Lock()
|
||||||
defer s.mu.Unlock()
|
defer s.mu.Unlock()
|
||||||
@ -273,63 +309,50 @@ func (s *Server) publicKeys() (noiseKey, pubKey key.MachinePublic) {
|
|||||||
return s.noisePubKey, s.pubKey
|
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() {
|
func (s *Server) ensureKeyPairLocked() {
|
||||||
if !s.pubKey.IsZero() {
|
if !s.pubKey.IsZero() {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
s.noisePrivKey = key.NewControl()
|
s.noisePrivKey = key.NewMachine()
|
||||||
s.noisePubKey = s.noisePrivKey.Public()
|
s.noisePubKey = s.noisePrivKey.Public()
|
||||||
s.privKey = key.NewControl()
|
s.privKey = key.NewControl()
|
||||||
s.pubKey = s.privKey.Public()
|
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) {
|
||||||
_, legacyKey := s.publicKeys()
|
noiseKey, legacyKey := s.publicKeys()
|
||||||
if r.FormValue("v") == "" {
|
if r.FormValue("v") == "" {
|
||||||
w.Header().Set("Content-Type", "text/plain")
|
w.Header().Set("Content-Type", "text/plain")
|
||||||
io.WriteString(w, legacyKey.UntypedHexString())
|
io.WriteString(w, legacyKey.UntypedHexString())
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
w.Header().Set("Content-Type", "application/json")
|
w.Header().Set("Content-Type", "application/json")
|
||||||
// TODO(maisem/bradfitz): support noise protocol here.
|
|
||||||
json.NewEncoder(w).Encode(&tailcfg.OverTLSPublicKeyResponse{
|
json.NewEncoder(w).Encode(&tailcfg.OverTLSPublicKeyResponse{
|
||||||
LegacyPublicKey: legacyKey,
|
LegacyPublicKey: legacyKey,
|
||||||
// PublicKey: noiseKey,
|
PublicKey: noiseKey,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Server) serveMachine(w http.ResponseWriter, r *http.Request) {
|
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" {
|
if r.Method != "POST" {
|
||||||
http.Error(w, "POST required", 400)
|
http.Error(w, "POST required", 400)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
ctx := r.Context()
|
||||||
|
|
||||||
switch rem {
|
mkey, ok := ctx.Value(peerMachinePublicContextKey{}).(key.MachinePublic)
|
||||||
case "":
|
if !ok {
|
||||||
s.serveRegister(w, r, mkey)
|
panic("no peer machine public key in context")
|
||||||
case "/map":
|
}
|
||||||
|
|
||||||
|
switch r.URL.Path {
|
||||||
|
case "/machine/map":
|
||||||
s.serveMap(w, r, mkey)
|
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:
|
default:
|
||||||
s.serveUnhandled(w, r)
|
s.serveUnhandled(w, r)
|
||||||
}
|
}
|
||||||
@ -549,7 +572,7 @@ func (s *Server) serveRegister(w http.ResponseWriter, r *http.Request, mkey key.
|
|||||||
}
|
}
|
||||||
|
|
||||||
var req tailcfg.RegisterRequest
|
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))
|
go panic(fmt.Sprintf("serveRegister: decode: %v", err))
|
||||||
}
|
}
|
||||||
if req.Version == 0 {
|
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)
|
log.Printf("Got %T: %s", req, j)
|
||||||
}
|
}
|
||||||
if s.RequireAuthKey != "" && req.Auth.AuthKey != s.RequireAuthKey {
|
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",
|
Error: "invalid authkey",
|
||||||
}))
|
}))
|
||||||
w.WriteHeader(200)
|
w.WriteHeader(200)
|
||||||
@ -637,7 +660,7 @@ func (s *Server) serveRegister(w http.ResponseWriter, r *http.Request, mkey key.
|
|||||||
authURL = s.BaseURL() + authPath
|
authURL = s.BaseURL() + authPath
|
||||||
}
|
}
|
||||||
|
|
||||||
res, err := s.encode(mkey, false, tailcfg.RegisterResponse{
|
res, err := s.encode(false, tailcfg.RegisterResponse{
|
||||||
User: *user,
|
User: *user,
|
||||||
Login: *login,
|
Login: *login,
|
||||||
NodeKeyExpired: allExpired,
|
NodeKeyExpired: allExpired,
|
||||||
@ -729,7 +752,7 @@ func (s *Server) serveMap(w http.ResponseWriter, r *http.Request, mkey key.Machi
|
|||||||
r.Body.Close()
|
r.Body.Close()
|
||||||
|
|
||||||
req := new(tailcfg.MapRequest)
|
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))
|
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 {
|
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 {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@ -1034,19 +1057,14 @@ func (s *Server) sendMapMsg(w http.ResponseWriter, mkey key.MachinePublic, compr
|
|||||||
return nil
|
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 {
|
if len(msg) == msgLimit {
|
||||||
return errors.New("encrypted message too long")
|
return errors.New("encrypted message too long")
|
||||||
}
|
}
|
||||||
|
return json.Unmarshal(msg, v)
|
||||||
decrypted, ok := s.privateKey().OpenFrom(mkey, msg)
|
|
||||||
if !ok {
|
|
||||||
return errors.New("can't decrypt request")
|
|
||||||
}
|
|
||||||
return json.Unmarshal(decrypted, 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
|
var isBytes bool
|
||||||
if b, isBytes = v.([]byte); !isBytes {
|
if b, isBytes = v.([]byte); !isBytes {
|
||||||
b, err = json.Marshal(v)
|
b, err = json.Marshal(v)
|
||||||
@ -1057,7 +1075,7 @@ func (s *Server) encode(mkey key.MachinePublic, compress bool, v any) (b []byte,
|
|||||||
if compress {
|
if compress {
|
||||||
b = zstdframe.AppendEncode(nil, b, zstdframe.FastestCompression)
|
b = zstdframe.AppendEncode(nil, b, zstdframe.FastestCompression)
|
||||||
}
|
}
|
||||||
return s.privateKey().SealTo(mkey, b), nil
|
return b, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// filterInvalidIPv6Endpoints removes invalid IPv6 endpoints from eps,
|
// filterInvalidIPv6Endpoints removes invalid IPv6 endpoints from eps,
|
||||||
|
Loading…
Reference in New Issue
Block a user