cmd/tsconnect: make logtail uploading work

Initialize logtail and provide an uploader that works in the
browser (we make a no-cors cross-origin request to avoid having to
open up the logcatcher servers to CORS).

Fixes #5147

Signed-off-by: Mihai Parparita <mihai@tailscale.com>
pull/5300/head
Mihai Parparita 2 years ago committed by Mihai Parparita
parent 4950fe60bd
commit f371a1afd9

@ -31,11 +31,12 @@ import (
"tailscale.com/ipn/ipnlocal" "tailscale.com/ipn/ipnlocal"
"tailscale.com/ipn/ipnserver" "tailscale.com/ipn/ipnserver"
"tailscale.com/ipn/store/mem" "tailscale.com/ipn/store/mem"
"tailscale.com/logpolicy"
"tailscale.com/logtail"
"tailscale.com/net/netns" "tailscale.com/net/netns"
"tailscale.com/net/tsdial" "tailscale.com/net/tsdial"
"tailscale.com/safesocket" "tailscale.com/safesocket"
"tailscale.com/tailcfg" "tailscale.com/tailcfg"
"tailscale.com/types/logger"
"tailscale.com/wgengine" "tailscale.com/wgengine"
"tailscale.com/wgengine/netstack" "tailscale.com/wgengine/netstack"
"tailscale.com/words" "tailscale.com/words"
@ -56,7 +57,25 @@ func main() {
func newIPN(jsConfig js.Value) map[string]any { func newIPN(jsConfig js.Value) map[string]any {
netns.SetEnabled(false) netns.SetEnabled(false)
var logf logger.Logf = log.Printf
jsStateStorage := jsConfig.Get("stateStorage")
var store ipn.StateStore
if jsStateStorage.IsUndefined() {
store = new(mem.Store)
} else {
store = &jsStateStore{jsStateStorage}
}
lpc := getOrCreateLogPolicyConfig(store)
c := logtail.Config{
Collection: lpc.Collection,
PrivateID: lpc.PrivateID,
// NewZstdEncoder is intentionally not passed in, compressed requests
// set HTTP headers that are not supported by the no-cors fetching mode.
HTTPC: &http.Client{Transport: &noCORSTransport{http.DefaultTransport}},
}
logtail := logtail.NewLogger(c, log.Printf)
logf := logtail.Logf
dialer := new(tsdial.Dialer) dialer := new(tsdial.Dialer)
eng, err := wgengine.NewUserspaceEngine(logf, wgengine.Config{ eng, err := wgengine.NewUserspaceEngine(logf, wgengine.Config{
@ -86,14 +105,7 @@ func newIPN(jsConfig js.Value) map[string]any {
return ns.DialContextTCP(ctx, dst) return ns.DialContextTCP(ctx, dst)
} }
jsStateStorage := jsConfig.Get("stateStorage") srv, err := ipnserver.New(logf, lpc.PublicID.String(), store, eng, dialer, nil, ipnserver.Options{
var store ipn.StateStore
if jsStateStorage.IsUndefined() {
store = new(mem.Store)
} else {
store = &jsStateStore{jsStateStorage}
}
srv, err := ipnserver.New(log.Printf, "some-logid", store, eng, dialer, nil, ipnserver.Options{
SurviveDisconnects: true, SurviveDisconnects: true,
LoginFlags: controlclient.LoginEphemeral, LoginFlags: controlclient.LoginEphemeral,
}) })
@ -527,3 +539,40 @@ func makePromise(f func() (any, error)) js.Value {
promiseConstructor := js.Global().Get("Promise") promiseConstructor := js.Global().Get("Promise")
return promiseConstructor.New(handler) return promiseConstructor.New(handler)
} }
const logPolicyStateKey = "log-policy"
func getOrCreateLogPolicyConfig(state ipn.StateStore) *logpolicy.Config {
if configBytes, err := state.ReadState(logPolicyStateKey); err == nil {
if config, err := logpolicy.ConfigFromBytes(configBytes); err == nil {
return config
} else {
log.Printf("Could not parse log policy config: %v", err)
}
} else if err != ipn.ErrStateNotExist {
log.Printf("Could not get log policy config from state store: %v", err)
}
config := logpolicy.NewConfig(logtail.CollectionNode)
if err := state.WriteState(logPolicyStateKey, config.ToBytes()); err != nil {
log.Printf("Could not save log policy config to state store: %v", err)
}
return config
}
// noCORSTransport wraps a RoundTripper and forces the no-cors mode on requests,
// so that we can use it with non-CORS-aware servers.
type noCORSTransport struct {
http.RoundTripper
}
func (t *noCORSTransport) RoundTrip(req *http.Request) (*http.Response, error) {
req.Header.Set("js.fetch:mode", "no-cors")
resp, err := t.RoundTripper.RoundTrip(req)
if err == nil {
// In no-cors mode no response properties are returned. Populate just
// the status so that callers do not think this was an error.
resp.StatusCode = http.StatusOK
resp.Status = http.StatusText(http.StatusOK)
}
return resp, err
}

@ -0,0 +1,22 @@
// Copyright (c) 2022 Tailscale Inc & AUTHORS All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package filch
import (
"os"
)
func saveStderr() (*os.File, error) {
return os.Stderr, nil
}
func unsaveStderr(f *os.File) error {
os.Stderr = f
return nil
}
func dup2Stderr(f *os.File) error {
return nil
}

@ -2,8 +2,8 @@
// Use of this source code is governed by a BSD-style // Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file. // license that can be found in the LICENSE file.
//go:build !windows //go:build !windows && !js
// +build !windows // +build !windows,!js
package filch package filch

Loading…
Cancel
Save