mirror of
https://github.com/tailscale/tailscale.git
synced 2025-10-24 09:39:39 +00:00
We still try the host's x509 roots first, but if that fails (like if
the host is old), we fall back to using LetsEncrypt's root and
retrying with that.
tlsdial was used in the three main places: logs, control, DERP. But it
was missing in dnsfallback. So added it there too, so we can run fine
now on a machine with no DNS config and no root CAs configured.
Also, move SSLKEYLOGFILE support out of DERP. tlsdial is the logical place
for that support.
Fixes #1609
Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
(cherry picked from commit 7cf8ec8108)
132 lines
2.7 KiB
Go
132 lines
2.7 KiB
Go
// Copyright (c) 2021 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 tlsdial
|
|
|
|
import (
|
|
"crypto/x509"
|
|
"io"
|
|
"net"
|
|
"net/http"
|
|
"os"
|
|
"os/exec"
|
|
"path/filepath"
|
|
"reflect"
|
|
"runtime"
|
|
"sync/atomic"
|
|
"testing"
|
|
)
|
|
|
|
func resetOnce() {
|
|
rv := reflect.ValueOf(&bakedInRootsOnce).Elem()
|
|
rv.Set(reflect.Zero(rv.Type()))
|
|
}
|
|
|
|
func TestBakedInRoots(t *testing.T) {
|
|
resetOnce()
|
|
p := bakedInRoots()
|
|
got := p.Subjects()
|
|
if len(got) != 1 {
|
|
t.Errorf("subjects = %v; want 1", len(got))
|
|
}
|
|
}
|
|
|
|
func TestFallbackRootWorks(t *testing.T) {
|
|
defer resetOnce()
|
|
|
|
const debug = false
|
|
if runtime.GOOS != "linux" {
|
|
t.Skip("test assumes Linux")
|
|
}
|
|
d := t.TempDir()
|
|
crtFile := filepath.Join(d, "tlsdial.test.crt")
|
|
keyFile := filepath.Join(d, "tlsdial.test.key")
|
|
caFile := filepath.Join(d, "rootCA.pem")
|
|
cmd := exec.Command(filepath.Join(runtime.GOROOT(), "bin", "go"),
|
|
"run", "filippo.io/mkcert",
|
|
"--cert-file="+crtFile,
|
|
"--key-file="+keyFile,
|
|
"tlsdial.test")
|
|
cmd.Env = append(os.Environ(), "CAROOT="+d)
|
|
out, err := cmd.CombinedOutput()
|
|
if err != nil {
|
|
t.Fatalf("mkcert: %v, %s", err, out)
|
|
}
|
|
if debug {
|
|
t.Logf("Ran: %s", out)
|
|
dents, err := os.ReadDir(d)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
for _, de := range dents {
|
|
t.Logf(" - %v", de)
|
|
}
|
|
}
|
|
|
|
caPEM, err := os.ReadFile(caFile)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
resetOnce()
|
|
bakedInRootsOnce.Do(func() {
|
|
p := x509.NewCertPool()
|
|
if !p.AppendCertsFromPEM(caPEM) {
|
|
t.Fatal("failed to add")
|
|
}
|
|
bakedInRootsOnce.p = p
|
|
})
|
|
|
|
ln, err := net.Listen("tcp", "127.0.0.1:0")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
defer ln.Close()
|
|
if debug {
|
|
t.Logf("listener running at %v", ln.Addr())
|
|
}
|
|
done := make(chan struct{})
|
|
defer close(done)
|
|
|
|
errc := make(chan error, 1)
|
|
go func() {
|
|
err := http.ServeTLS(ln, http.HandlerFunc(sayHi), crtFile, keyFile)
|
|
select {
|
|
case <-done:
|
|
return
|
|
default:
|
|
t.Logf("ServeTLS: %v", err)
|
|
errc <- err
|
|
}
|
|
}()
|
|
|
|
tr := &http.Transport{
|
|
Dial: func(network, addr string) (net.Conn, error) {
|
|
return net.Dial("tcp", ln.Addr().String())
|
|
},
|
|
DisableKeepAlives: true, // for test cleanup ease
|
|
}
|
|
tr.TLSClientConfig = Config("tlsdial.test", tr.TLSClientConfig)
|
|
c := &http.Client{Transport: tr}
|
|
|
|
ctr0 := atomic.LoadInt32(&counterFallbackOK)
|
|
|
|
res, err := c.Get("https://tlsdial.test/")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
defer res.Body.Close()
|
|
if res.StatusCode != 200 {
|
|
t.Fatal(res.Status)
|
|
}
|
|
|
|
ctrDelta := atomic.LoadInt32(&counterFallbackOK) - ctr0
|
|
if ctrDelta != 1 {
|
|
t.Errorf("fallback root success count = %d; want 1", ctrDelta)
|
|
}
|
|
}
|
|
|
|
func sayHi(w http.ResponseWriter, r *http.Request) {
|
|
io.WriteString(w, "hi")
|
|
}
|