tailscale/net/tlsdial/tlsdial_test.go
Brad Fitzpatrick 7cf8ec8108 net/tlsdial: bake in LetsEncrypt's ISRG Root X1 root
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>
2021-10-01 08:30:07 -07:00

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")
}