|
|
|
@ -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
|
|
|
|
|
}
|
|
|
|
|