mirror of
https://github.com/tailscale/tailscale.git
synced 2025-08-13 14:30:35 +00:00
control/controlclient, ipn: store machine key separately from user prefs/persist
Updates #610 (fixes after some win/xcode changes in a separate repo)
This commit is contained in:
@@ -45,8 +45,19 @@ import (
|
||||
)
|
||||
|
||||
type Persist struct {
|
||||
_ structs.Incomparable
|
||||
PrivateMachineKey wgcfg.PrivateKey
|
||||
_ structs.Incomparable
|
||||
|
||||
// LegacyFrontendPrivateMachineKey is here temporarily
|
||||
// (starting 2020-09-28) during migration of Windows users'
|
||||
// machine keys from frontend storage to the backend. On the
|
||||
// first LocalBackend.Start call, the backend will initialize
|
||||
// the real (backend-owned) machine key from the frontend's
|
||||
// provided value (if non-zero), picking a new random one if
|
||||
// needed. This field should be considered read-only from GUI
|
||||
// frontends. The real value should not be written back in
|
||||
// this field, lest the frontend persist it to disk.
|
||||
LegacyFrontendPrivateMachineKey wgcfg.PrivateKey `json:"PrivateMachineKey"`
|
||||
|
||||
PrivateNodeKey wgcfg.PrivateKey
|
||||
OldPrivateNodeKey wgcfg.PrivateKey // needed to request key rotation
|
||||
Provider string
|
||||
@@ -61,7 +72,7 @@ func (p *Persist) Equals(p2 *Persist) bool {
|
||||
return false
|
||||
}
|
||||
|
||||
return p.PrivateMachineKey.Equal(p2.PrivateMachineKey) &&
|
||||
return p.LegacyFrontendPrivateMachineKey.Equal(p2.LegacyFrontendPrivateMachineKey) &&
|
||||
p.PrivateNodeKey.Equal(p2.PrivateNodeKey) &&
|
||||
p.OldPrivateNodeKey.Equal(p2.OldPrivateNodeKey) &&
|
||||
p.Provider == p2.Provider &&
|
||||
@@ -70,8 +81,8 @@ func (p *Persist) Equals(p2 *Persist) bool {
|
||||
|
||||
func (p *Persist) Pretty() string {
|
||||
var mk, ok, nk wgcfg.Key
|
||||
if !p.PrivateMachineKey.IsZero() {
|
||||
mk = p.PrivateMachineKey.Public()
|
||||
if !p.LegacyFrontendPrivateMachineKey.IsZero() {
|
||||
mk = p.LegacyFrontendPrivateMachineKey.Public()
|
||||
}
|
||||
if !p.OldPrivateNodeKey.IsZero() {
|
||||
ok = p.OldPrivateNodeKey.Public()
|
||||
@@ -79,7 +90,7 @@ func (p *Persist) Pretty() string {
|
||||
if !p.PrivateNodeKey.IsZero() {
|
||||
nk = p.PrivateNodeKey.Public()
|
||||
}
|
||||
return fmt.Sprintf("Persist{m=%v, o=%v, n=%v u=%#v}",
|
||||
return fmt.Sprintf("Persist{lm=%v, o=%v, n=%v u=%#v}",
|
||||
mk.ShortString(), ok.ShortString(), nk.ShortString(),
|
||||
p.LoginName)
|
||||
}
|
||||
@@ -94,6 +105,7 @@ type Direct struct {
|
||||
keepAlive bool
|
||||
logf logger.Logf
|
||||
discoPubKey tailcfg.DiscoKey
|
||||
machinePrivKey wgcfg.PrivateKey
|
||||
|
||||
mu sync.Mutex // mutex guards the following fields
|
||||
serverKey wgcfg.Key
|
||||
@@ -108,16 +120,17 @@ type Direct struct {
|
||||
}
|
||||
|
||||
type Options struct {
|
||||
Persist Persist // initial persistent data
|
||||
ServerURL string // URL of the tailcontrol server
|
||||
AuthKey string // optional node auth key for auto registration
|
||||
TimeNow func() time.Time // time.Now implementation used by Client
|
||||
Hostinfo *tailcfg.Hostinfo // non-nil passes ownership, nil means to use default using os.Hostname, etc
|
||||
DiscoPublicKey tailcfg.DiscoKey
|
||||
NewDecompressor func() (Decompressor, error)
|
||||
KeepAlive bool
|
||||
Logf logger.Logf
|
||||
HTTPTestClient *http.Client // optional HTTP client to use (for tests only)
|
||||
Persist Persist // initial persistent data
|
||||
MachinePrivateKey wgcfg.PrivateKey // the machine key to use
|
||||
ServerURL string // URL of the tailcontrol server
|
||||
AuthKey string // optional node auth key for auto registration
|
||||
TimeNow func() time.Time // time.Now implementation used by Client
|
||||
Hostinfo *tailcfg.Hostinfo // non-nil passes ownership, nil means to use default using os.Hostname, etc
|
||||
DiscoPublicKey tailcfg.DiscoKey
|
||||
NewDecompressor func() (Decompressor, error)
|
||||
KeepAlive bool
|
||||
Logf logger.Logf
|
||||
HTTPTestClient *http.Client // optional HTTP client to use (for tests only)
|
||||
}
|
||||
|
||||
type Decompressor interface {
|
||||
@@ -130,6 +143,9 @@ func NewDirect(opts Options) (*Direct, error) {
|
||||
if opts.ServerURL == "" {
|
||||
return nil, errors.New("controlclient.New: no server URL specified")
|
||||
}
|
||||
if opts.MachinePrivateKey.IsZero() {
|
||||
return nil, errors.New("controlclient.New: no MachinePrivateKey specified")
|
||||
}
|
||||
opts.ServerURL = strings.TrimRight(opts.ServerURL, "/")
|
||||
serverURL, err := url.Parse(opts.ServerURL)
|
||||
if err != nil {
|
||||
@@ -158,6 +174,7 @@ func NewDirect(opts Options) (*Direct, error) {
|
||||
|
||||
c := &Direct{
|
||||
httpc: httpc,
|
||||
machinePrivKey: opts.MachinePrivateKey,
|
||||
serverURL: opts.ServerURL,
|
||||
timeNow: opts.TimeNow,
|
||||
logf: opts.Logf,
|
||||
@@ -251,14 +268,12 @@ func (c *Direct) TryLogout(ctx context.Context) error {
|
||||
// immediately invalidated.
|
||||
//if !c.persist.PrivateNodeKey.IsZero() {
|
||||
//}
|
||||
c.persist = Persist{
|
||||
PrivateMachineKey: c.persist.PrivateMachineKey,
|
||||
}
|
||||
c.persist = Persist{}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *Direct) TryLogin(ctx context.Context, t *oauth2.Token, flags LoginFlags) (url string, err error) {
|
||||
c.logf("direct.TryLogin(%v, %v)", t != nil, flags)
|
||||
c.logf("direct.TryLogin(token=%v, flags=%v)", t != nil, flags)
|
||||
return c.doLoginOrRegen(ctx, t, flags, false, "")
|
||||
}
|
||||
|
||||
@@ -289,13 +304,8 @@ func (c *Direct) doLogin(ctx context.Context, t *oauth2.Token, flags LoginFlags,
|
||||
expired := c.expiry != nil && !c.expiry.IsZero() && c.expiry.Before(c.timeNow())
|
||||
c.mu.Unlock()
|
||||
|
||||
if persist.PrivateMachineKey.IsZero() {
|
||||
c.logf("Generating a new machinekey.")
|
||||
mkey, err := wgcfg.NewPrivateKey()
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
persist.PrivateMachineKey = mkey
|
||||
if c.machinePrivKey.IsZero() {
|
||||
return false, "", errors.New("controlclient.Direct requires a machine private key")
|
||||
}
|
||||
|
||||
if expired {
|
||||
@@ -360,13 +370,13 @@ func (c *Direct) doLogin(ctx context.Context, t *oauth2.Token, flags LoginFlags,
|
||||
request.Auth.Provider = persist.Provider
|
||||
request.Auth.LoginName = persist.LoginName
|
||||
request.Auth.AuthKey = authKey
|
||||
bodyData, err := encode(request, &serverKey, &persist.PrivateMachineKey)
|
||||
bodyData, err := encode(request, &serverKey, &c.machinePrivKey)
|
||||
if err != nil {
|
||||
return regen, url, err
|
||||
}
|
||||
body := bytes.NewReader(bodyData)
|
||||
|
||||
u := fmt.Sprintf("%s/machine/%s", c.serverURL, persist.PrivateMachineKey.Public().HexString())
|
||||
u := fmt.Sprintf("%s/machine/%s", c.serverURL, c.machinePrivKey.Public().HexString())
|
||||
req, err := http.NewRequest("POST", u, body)
|
||||
if err != nil {
|
||||
return regen, url, err
|
||||
@@ -377,11 +387,14 @@ func (c *Direct) doLogin(ctx context.Context, t *oauth2.Token, flags LoginFlags,
|
||||
if err != nil {
|
||||
return regen, url, fmt.Errorf("register request: %v", err)
|
||||
}
|
||||
c.logf("RegisterReq: returned.")
|
||||
resp := tailcfg.RegisterResponse{}
|
||||
if err := decode(res, &resp, &serverKey, &persist.PrivateMachineKey); err != nil {
|
||||
if err := decode(res, &resp, &serverKey, &c.machinePrivKey); err != nil {
|
||||
c.logf("error decoding RegisterReq: %v", err)
|
||||
return regen, url, fmt.Errorf("register request: %v", err)
|
||||
}
|
||||
// Log without PII:
|
||||
c.logf("RegisterReq: got response; nodeKeyExpired=%v, machineAuthorized=%v; authURL=%v",
|
||||
resp.NodeKeyExpired, resp.MachineAuthorized, resp.AuthURL != "")
|
||||
|
||||
if resp.NodeKeyExpired {
|
||||
if regen {
|
||||
@@ -507,14 +520,15 @@ func (c *Direct) PollNetMap(ctx context.Context, maxPolls int, cb func(*NetworkM
|
||||
request.Compress = "zstd"
|
||||
}
|
||||
|
||||
bodyData, err := encode(request, &serverKey, &persist.PrivateMachineKey)
|
||||
bodyData, err := encode(request, &serverKey, &c.machinePrivKey)
|
||||
if err != nil {
|
||||
vlogf("netmap: encode: %v", err)
|
||||
return err
|
||||
}
|
||||
|
||||
machinePubKey := tailcfg.MachineKey(c.machinePrivKey.Public())
|
||||
t0 := time.Now()
|
||||
u := fmt.Sprintf("%s/machine/%s/map", serverURL, persist.PrivateMachineKey.Public().HexString())
|
||||
u := fmt.Sprintf("%s/machine/%s/map", serverURL, machinePubKey.HexString())
|
||||
req, err := http.NewRequest("POST", u, bytes.NewReader(bodyData))
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -648,6 +662,7 @@ func (c *Direct) PollNetMap(ctx context.Context, maxPolls int, cb func(*NetworkM
|
||||
nm := &NetworkMap{
|
||||
NodeKey: tailcfg.NodeKey(persist.PrivateNodeKey.Public()),
|
||||
PrivateKey: persist.PrivateNodeKey,
|
||||
MachineKey: machinePubKey,
|
||||
Expiry: resp.Node.KeyExpiry,
|
||||
Name: resp.Node.Name,
|
||||
Addresses: resp.Node.Addresses,
|
||||
@@ -719,11 +734,10 @@ var dumpMapResponse, _ = strconv.ParseBool(os.Getenv("TS_DEBUG_MAPRESPONSE"))
|
||||
|
||||
func (c *Direct) decodeMsg(msg []byte, v interface{}) error {
|
||||
c.mu.Lock()
|
||||
mkey := c.persist.PrivateMachineKey
|
||||
serverKey := c.serverKey
|
||||
c.mu.Unlock()
|
||||
|
||||
decrypted, err := decryptMsg(msg, &serverKey, &mkey)
|
||||
decrypted, err := decryptMsg(msg, &serverKey, &c.machinePrivKey)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
Reference in New Issue
Block a user