summaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorBrad Fitzpatrick <bradfitz@tailscale.com>2022-01-26 11:58:56 -0800
committerBrad Fitzpatrick <bradfitz@tailscale.com>2022-01-26 11:58:56 -0800
commitf37daace4eb88d3bec0b1bf698a94a7e2f79ec8b (patch)
treefc7400a12974304e29f9eca93d7b70debaed8e8e
parent2fd82287b6649b87a8095435893820b1b1fb1d9b (diff)
downloadtailscale-f37daace4eb88d3bec0b1bf698a94a7e2f79ec8b.tar.xz
tailscale-f37daace4eb88d3bec0b1bf698a94a7e2f79ec8b.zip
cmd/derpprobe: check derper TLS certs toobradfitz/use_netstack_upstream
Change-Id: If8c48e012b294570ebbb1a46bacdc58fafbfbcc5 Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
-rw-r--r--cmd/derpprobe/derpprobe.go46
-rw-r--r--derp/derphttp/derphttp_client.go21
2 files changed, 64 insertions, 3 deletions
diff --git a/cmd/derpprobe/derpprobe.go b/cmd/derpprobe/derpprobe.go
index 3adde9f81..bc81d6be4 100644
--- a/cmd/derpprobe/derpprobe.go
+++ b/cmd/derpprobe/derpprobe.go
@@ -9,7 +9,9 @@ import (
"bytes"
"context"
crand "crypto/rand"
+ "crypto/x509"
"encoding/json"
+ "errors"
"flag"
"fmt"
"html"
@@ -38,6 +40,7 @@ var (
state = map[nodePair]pairStatus{}
lastDERPMap *tailcfg.DERPMap
lastDERPMapAt time.Time
+ certs = map[string]*x509.Certificate{}
)
func main() {
@@ -46,6 +49,13 @@ func main() {
log.Fatal(http.ListenAndServe(*listen, http.HandlerFunc(serve)))
}
+func setCert(name string, cert *x509.Certificate) {
+ mu.Lock()
+ defer mu.Unlock()
+ certs[name] = cert
+ log.Printf("Cert %q: not before/after: %v, %v", name, cert.NotBefore, cert.NotAfter)
+}
+
type overallStatus struct {
good, bad []string
}
@@ -93,6 +103,27 @@ func getOverallStatus() (o overallStatus) {
}
}
}
+
+ var subjs []string
+ for k := range certs {
+ subjs = append(subjs, k)
+ }
+ sort.Strings(subjs)
+ reissueTime := time.Unix(1643226768, 0) // assume certs before this need reissuance by LetsEncrypt due to ALPN bug
+ soon := time.Now().Add(14 * 24 * time.Hour) // in 2 weeks; autocert does 30 days by default
+ for _, s := range subjs {
+ cert := certs[s]
+ if cert.NotBefore.Before(reissueTime) {
+ o.addBadf("cert %q needs reissuing; NotBefore=%v", s, cert.NotBefore.Format(time.RFC3339))
+ continue
+ }
+ if cert.NotAfter.Before(soon) {
+ o.addBadf("cert %q expiring soon (%v); wasn't auto-refreshed", s, cert.NotAfter.Format(time.RFC3339))
+ continue
+ }
+ o.addGoodf("cert %q good %v - %v", s, cert.NotBefore.Format(time.RFC3339), cert.NotAfter.Format(time.RFC3339))
+ }
+
return
}
@@ -359,6 +390,21 @@ func newConn(ctx context.Context, dm *tailcfg.DERPMap, n *tailcfg.DERPNode) (*de
if err != nil {
return nil, err
}
+ cs, ok := dc.TLSConnectionState()
+ if !ok {
+ dc.Close()
+ return nil, errors.New("no TLS state")
+ }
+ if len(cs.PeerCertificates) == 0 {
+ dc.Close()
+ return nil, errors.New("no peer certificates")
+ }
+ if cs.ServerName != n.HostName {
+ dc.Close()
+ return nil, fmt.Errorf("TLS server name %q != derp hostname %q", cs.ServerName, n.HostName)
+ }
+ setCert(cs.ServerName, cs.PeerCertificates[0])
+
errc := make(chan error, 1)
go func() {
m, err := dc.Recv()
diff --git a/derp/derphttp/derphttp_client.go b/derp/derphttp/derphttp_client.go
index 1e47e1e13..e520faf26 100644
--- a/derp/derphttp/derphttp_client.go
+++ b/derp/derphttp/derphttp_client.go
@@ -72,6 +72,7 @@ type Client struct {
client *derp.Client
connGen int // incremented once per new connection; valid values are >0
serverPubKey key.NodePublic
+ tlsState *tls.ConnectionState
pingOut map[derp.PingMessage]chan<- bool // chan to send to on pong
}
@@ -124,6 +125,17 @@ func (c *Client) Connect(ctx context.Context) error {
return err
}
+// TLSConnectionState returns the last TLS connection state, if any.
+// The client must already be connected.
+func (c *Client) TLSConnectionState() (_ *tls.ConnectionState, ok bool) {
+ c.mu.Lock()
+ defer c.mu.Unlock()
+ if c.closed || c.client == nil {
+ return nil, false
+ }
+ return c.tlsState, c.tlsState != nil
+}
+
// ServerPublicKey returns the server's public key.
//
// It only returns a non-zero value once a connection has succeeded
@@ -318,6 +330,7 @@ func (c *Client) connect(ctx context.Context, caller string) (client *derp.Clien
var httpConn net.Conn // a TCP conn or a TLS conn; what we speak HTTP to
var serverPub key.NodePublic // or zero if unknown (if not using TLS or TLS middlebox eats it)
var serverProtoVersion int
+ var tlsState *tls.ConnectionState
if c.useHTTPS() {
tlsConn := c.tlsClient(tcpConn, node)
httpConn = tlsConn
@@ -340,9 +353,10 @@ func (c *Client) connect(ctx context.Context, caller string) (client *derp.Clien
// Note that we're not specifically concerned about TLS downgrade
// attacks. TLS handles that fine:
// https://blog.gypsyengineer.com/en/security/how-does-tls-1-3-protect-against-downgrade-attacks.html
- connState := tlsConn.ConnectionState()
- if connState.Version >= tls.VersionTLS13 {
- serverPub, serverProtoVersion = parseMetaCert(connState.PeerCertificates)
+ cs := tlsConn.ConnectionState()
+ tlsState = &cs
+ if cs.Version >= tls.VersionTLS13 {
+ serverPub, serverProtoVersion = parseMetaCert(cs.PeerCertificates)
}
} else {
httpConn = tcpConn
@@ -409,6 +423,7 @@ func (c *Client) connect(ctx context.Context, caller string) (client *derp.Clien
c.serverPubKey = derpClient.ServerPublicKey()
c.client = derpClient
c.netConn = tcpConn
+ c.tlsState = tlsState
c.connGen++
return c.client, c.connGen, nil
}