mirror of
https://github.com/tailscale/tailscale.git
synced 2025-01-05 23:07:44 +00:00
tailcfg, localapi: plumb device token to server
Updates tailscale/corp#8940 Signed-off-by: David Crawshaw <crawshaw@tailscale.com>
This commit is contained in:
parent
38b32be926
commit
8cf2805cca
@ -32,3 +32,9 @@ type WaitingFile struct {
|
||||
Name string
|
||||
Size int64
|
||||
}
|
||||
|
||||
// SetPushDeviceTokenRequest is the body POSTed to the LocalAPI endpoint /set-device-token.
|
||||
type SetPushDeviceTokenRequest struct {
|
||||
// PushDeviceToken is the iOS/macOS APNs device token (and any future Android equivalent).
|
||||
PushDeviceToken string
|
||||
}
|
||||
|
@ -50,6 +50,7 @@ func New() *tailcfg.Hostinfo {
|
||||
GoVersion: runtime.Version(),
|
||||
Machine: condCall(unameMachine),
|
||||
DeviceModel: deviceModel(),
|
||||
PushDeviceToken: pushDeviceToken(),
|
||||
Cloud: string(cloudenv.Get()),
|
||||
NoLogsNoSupport: envknob.NoLogsNoSupport(),
|
||||
AllowsUpdate: envknob.AllowsRemoteUpdate(),
|
||||
@ -153,12 +154,16 @@ func GetEnvType() EnvType {
|
||||
}
|
||||
|
||||
var (
|
||||
deviceModelAtomic atomic.Value // of string
|
||||
osVersionAtomic atomic.Value // of string
|
||||
desktopAtomic atomic.Value // of opt.Bool
|
||||
packagingType atomic.Value // of string
|
||||
pushDeviceTokenAtomic atomic.Value // of string
|
||||
deviceModelAtomic atomic.Value // of string
|
||||
osVersionAtomic atomic.Value // of string
|
||||
desktopAtomic atomic.Value // of opt.Bool
|
||||
packagingType atomic.Value // of string
|
||||
)
|
||||
|
||||
// SetPushDeviceToken sets the device token for use in Hostinfo updates.
|
||||
func SetPushDeviceToken(token string) { pushDeviceTokenAtomic.Store(token) }
|
||||
|
||||
// SetDeviceModel sets the device model for use in Hostinfo updates.
|
||||
func SetDeviceModel(model string) { deviceModelAtomic.Store(model) }
|
||||
|
||||
@ -176,6 +181,11 @@ func deviceModel() string {
|
||||
return s
|
||||
}
|
||||
|
||||
func pushDeviceToken() string {
|
||||
s, _ := pushDeviceTokenAtomic.Load().(string)
|
||||
return s
|
||||
}
|
||||
|
||||
func desktop() (ret opt.Bool) {
|
||||
if runtime.GOOS != "linux" {
|
||||
return opt.Bool("")
|
||||
|
@ -1811,6 +1811,21 @@ func (b *LocalBackend) readPoller() {
|
||||
}
|
||||
}
|
||||
|
||||
// ResendHostinfoIfNeeded is called to recompute the Hostinfo and send
|
||||
// the new version to the control server.
|
||||
func (b *LocalBackend) ResendHostinfoIfNeeded() {
|
||||
hi := hostinfo.New()
|
||||
|
||||
b.mu.Lock()
|
||||
if b.hostinfo != nil {
|
||||
hi.Services = b.hostinfo.Services
|
||||
}
|
||||
b.hostinfo = hi
|
||||
b.mu.Unlock()
|
||||
|
||||
b.doSetHostinfoFilterServices(hi)
|
||||
}
|
||||
|
||||
// WatchNotifications subscribes to the ipn.Notify message bus notification
|
||||
// messages.
|
||||
//
|
||||
|
@ -70,6 +70,7 @@
|
||||
"debug-packet-filter-rules": (*Handler).serveDebugPacketFilterRules,
|
||||
"derpmap": (*Handler).serveDERPMap,
|
||||
"dev-set-state-store": (*Handler).serveDevSetStateStore,
|
||||
"set-push-device-token": (*Handler).serveSetPushDeviceToken,
|
||||
"dial": (*Handler).serveDial,
|
||||
"file-targets": (*Handler).serveFileTargets,
|
||||
"goroutines": (*Handler).serveGoroutines,
|
||||
@ -1205,6 +1206,25 @@ func (h *Handler) serveDial(w http.ResponseWriter, r *http.Request) {
|
||||
<-errc
|
||||
}
|
||||
|
||||
func (h *Handler) serveSetPushDeviceToken(w http.ResponseWriter, r *http.Request) {
|
||||
if !h.PermitWrite {
|
||||
http.Error(w, "set push device token access denied", http.StatusForbidden)
|
||||
return
|
||||
}
|
||||
if r.Method != "POST" {
|
||||
http.Error(w, "unsupported method", http.StatusMethodNotAllowed)
|
||||
return
|
||||
}
|
||||
var params apitype.SetPushDeviceTokenRequest
|
||||
if err := json.NewDecoder(r.Body).Decode(¶ms); err != nil {
|
||||
http.Error(w, "invalid JSON body", 400)
|
||||
return
|
||||
}
|
||||
hostinfo.SetPushDeviceToken(params.PushDeviceToken)
|
||||
h.b.ResendHostinfoIfNeeded()
|
||||
w.WriteHeader(http.StatusOK)
|
||||
}
|
||||
|
||||
func (h *Handler) serveUploadClientMetrics(w http.ResponseWriter, r *http.Request) {
|
||||
if r.Method != "POST" {
|
||||
http.Error(w, "unsupported method", http.StatusMethodNotAllowed)
|
||||
|
@ -4,9 +4,16 @@
|
||||
package localapi
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"io"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"testing"
|
||||
|
||||
"tailscale.com/client/tailscale/apitype"
|
||||
"tailscale.com/hostinfo"
|
||||
"tailscale.com/ipn/ipnlocal"
|
||||
)
|
||||
|
||||
func TestValidHost(t *testing.T) {
|
||||
@ -32,3 +39,43 @@ func TestValidHost(t *testing.T) {
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestSetPushDeviceToken(t *testing.T) {
|
||||
origValidLocalHost := validLocalHost
|
||||
validLocalHost = true
|
||||
defer func() {
|
||||
validLocalHost = origValidLocalHost
|
||||
}()
|
||||
|
||||
h := &Handler{
|
||||
PermitWrite: true,
|
||||
b: &ipnlocal.LocalBackend{},
|
||||
}
|
||||
s := httptest.NewServer(h)
|
||||
defer s.Close()
|
||||
c := s.Client()
|
||||
|
||||
want := "my-test-device-token"
|
||||
body, err := json.Marshal(apitype.SetPushDeviceTokenRequest{PushDeviceToken: want})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
req, err := http.NewRequest("POST", s.URL+"/localapi/v0/set-push-device-token", bytes.NewReader(body))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
res, err := c.Do(req)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
body, err = io.ReadAll(res.Body)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if res.StatusCode != 200 {
|
||||
t.Errorf("res.StatusCode=%d, want 200. body: %s", res.StatusCode, body)
|
||||
}
|
||||
if got := hostinfo.New().PushDeviceToken; got != want {
|
||||
t.Errorf("hostinfo.PushDeviceToken=%q, want %q", got, want)
|
||||
}
|
||||
}
|
||||
|
@ -525,6 +525,7 @@ type Hostinfo struct {
|
||||
Desktop opt.Bool `json:",omitempty"` // if a desktop was detected on Linux
|
||||
Package string `json:",omitempty"` // Tailscale package to disambiguate ("choco", "appstore", etc; "" for unknown)
|
||||
DeviceModel string `json:",omitempty"` // mobile phone model ("Pixel 3a", "iPhone12,3")
|
||||
PushDeviceToken string `json:",omitempty"` // macOS/iOS APNs device token for notifications (and Android in the future)
|
||||
Hostname string `json:",omitempty"` // name of the host the client runs on
|
||||
ShieldsUp bool `json:",omitempty"` // indicates whether the host is blocking incoming connections
|
||||
ShareeNode bool `json:",omitempty"` // indicates this node exists in netmap because it's owned by a shared-to user
|
||||
|
@ -131,6 +131,7 @@ func (src *Hostinfo) Clone() *Hostinfo {
|
||||
Desktop opt.Bool
|
||||
Package string
|
||||
DeviceModel string
|
||||
PushDeviceToken string
|
||||
Hostname string
|
||||
ShieldsUp bool
|
||||
ShareeNode bool
|
||||
|
@ -44,6 +44,7 @@ func TestHostinfoEqual(t *testing.T) {
|
||||
"Desktop",
|
||||
"Package",
|
||||
"DeviceModel",
|
||||
"PushDeviceToken",
|
||||
"Hostname",
|
||||
"ShieldsUp",
|
||||
"ShareeNode",
|
||||
|
@ -257,29 +257,30 @@ func (v *HostinfoView) UnmarshalJSON(b []byte) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (v HostinfoView) IPNVersion() string { return v.ж.IPNVersion }
|
||||
func (v HostinfoView) FrontendLogID() string { return v.ж.FrontendLogID }
|
||||
func (v HostinfoView) BackendLogID() string { return v.ж.BackendLogID }
|
||||
func (v HostinfoView) OS() string { return v.ж.OS }
|
||||
func (v HostinfoView) OSVersion() string { return v.ж.OSVersion }
|
||||
func (v HostinfoView) Container() opt.Bool { return v.ж.Container }
|
||||
func (v HostinfoView) Env() string { return v.ж.Env }
|
||||
func (v HostinfoView) Distro() string { return v.ж.Distro }
|
||||
func (v HostinfoView) DistroVersion() string { return v.ж.DistroVersion }
|
||||
func (v HostinfoView) DistroCodeName() string { return v.ж.DistroCodeName }
|
||||
func (v HostinfoView) Desktop() opt.Bool { return v.ж.Desktop }
|
||||
func (v HostinfoView) Package() string { return v.ж.Package }
|
||||
func (v HostinfoView) DeviceModel() string { return v.ж.DeviceModel }
|
||||
func (v HostinfoView) Hostname() string { return v.ж.Hostname }
|
||||
func (v HostinfoView) ShieldsUp() bool { return v.ж.ShieldsUp }
|
||||
func (v HostinfoView) ShareeNode() bool { return v.ж.ShareeNode }
|
||||
func (v HostinfoView) NoLogsNoSupport() bool { return v.ж.NoLogsNoSupport }
|
||||
func (v HostinfoView) WireIngress() bool { return v.ж.WireIngress }
|
||||
func (v HostinfoView) AllowsUpdate() bool { return v.ж.AllowsUpdate }
|
||||
func (v HostinfoView) Machine() string { return v.ж.Machine }
|
||||
func (v HostinfoView) GoArch() string { return v.ж.GoArch }
|
||||
func (v HostinfoView) GoArchVar() string { return v.ж.GoArchVar }
|
||||
func (v HostinfoView) GoVersion() string { return v.ж.GoVersion }
|
||||
func (v HostinfoView) IPNVersion() string { return v.ж.IPNVersion }
|
||||
func (v HostinfoView) FrontendLogID() string { return v.ж.FrontendLogID }
|
||||
func (v HostinfoView) BackendLogID() string { return v.ж.BackendLogID }
|
||||
func (v HostinfoView) OS() string { return v.ж.OS }
|
||||
func (v HostinfoView) OSVersion() string { return v.ж.OSVersion }
|
||||
func (v HostinfoView) Container() opt.Bool { return v.ж.Container }
|
||||
func (v HostinfoView) Env() string { return v.ж.Env }
|
||||
func (v HostinfoView) Distro() string { return v.ж.Distro }
|
||||
func (v HostinfoView) DistroVersion() string { return v.ж.DistroVersion }
|
||||
func (v HostinfoView) DistroCodeName() string { return v.ж.DistroCodeName }
|
||||
func (v HostinfoView) Desktop() opt.Bool { return v.ж.Desktop }
|
||||
func (v HostinfoView) Package() string { return v.ж.Package }
|
||||
func (v HostinfoView) DeviceModel() string { return v.ж.DeviceModel }
|
||||
func (v HostinfoView) PushDeviceToken() string { return v.ж.PushDeviceToken }
|
||||
func (v HostinfoView) Hostname() string { return v.ж.Hostname }
|
||||
func (v HostinfoView) ShieldsUp() bool { return v.ж.ShieldsUp }
|
||||
func (v HostinfoView) ShareeNode() bool { return v.ж.ShareeNode }
|
||||
func (v HostinfoView) NoLogsNoSupport() bool { return v.ж.NoLogsNoSupport }
|
||||
func (v HostinfoView) WireIngress() bool { return v.ж.WireIngress }
|
||||
func (v HostinfoView) AllowsUpdate() bool { return v.ж.AllowsUpdate }
|
||||
func (v HostinfoView) Machine() string { return v.ж.Machine }
|
||||
func (v HostinfoView) GoArch() string { return v.ж.GoArch }
|
||||
func (v HostinfoView) GoArchVar() string { return v.ж.GoArchVar }
|
||||
func (v HostinfoView) GoVersion() string { return v.ж.GoVersion }
|
||||
func (v HostinfoView) RoutableIPs() views.IPPrefixSlice {
|
||||
return views.IPPrefixSliceOf(v.ж.RoutableIPs)
|
||||
}
|
||||
@ -307,6 +308,7 @@ func (v HostinfoView) Equal(v2 HostinfoView) bool { return v.ж.Equal(v2.
|
||||
Desktop opt.Bool
|
||||
Package string
|
||||
DeviceModel string
|
||||
PushDeviceToken string
|
||||
Hostname string
|
||||
ShieldsUp bool
|
||||
ShareeNode bool
|
||||
|
Loading…
x
Reference in New Issue
Block a user