cmd/derpprobe: check derper TLS certs too
Change-Id: If8c48e012b294570ebbb1a46bacdc58fafbfbcc5 Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>pull/3829/head
parent
1af26222b6
commit
70d71ba1e7
|
@ -9,7 +9,9 @@ import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"context"
|
"context"
|
||||||
crand "crypto/rand"
|
crand "crypto/rand"
|
||||||
|
"crypto/x509"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
|
"errors"
|
||||||
"flag"
|
"flag"
|
||||||
"fmt"
|
"fmt"
|
||||||
"html"
|
"html"
|
||||||
|
@ -33,11 +35,21 @@ var (
|
||||||
listen = flag.String("listen", ":8030", "HTTP listen address")
|
listen = flag.String("listen", ":8030", "HTTP listen address")
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// certReissueAfter is the time after which we expect all certs to be
|
||||||
|
// reissued, at minimum.
|
||||||
|
//
|
||||||
|
// This is currently set to the date of the LetsEncrypt ALPN revocation event of Jan 2022:
|
||||||
|
// https://community.letsencrypt.org/t/questions-about-renewing-before-tls-alpn-01-revocations/170449
|
||||||
|
//
|
||||||
|
// If there's another revocation event, bump this again.
|
||||||
|
var certReissueAfter = time.Unix(1643226768, 0)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
mu sync.Mutex
|
mu sync.Mutex
|
||||||
state = map[nodePair]pairStatus{}
|
state = map[nodePair]pairStatus{}
|
||||||
lastDERPMap *tailcfg.DERPMap
|
lastDERPMap *tailcfg.DERPMap
|
||||||
lastDERPMapAt time.Time
|
lastDERPMapAt time.Time
|
||||||
|
certs = map[string]*x509.Certificate{}
|
||||||
)
|
)
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
|
@ -46,6 +58,12 @@ func main() {
|
||||||
log.Fatal(http.ListenAndServe(*listen, http.HandlerFunc(serve)))
|
log.Fatal(http.ListenAndServe(*listen, http.HandlerFunc(serve)))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func setCert(name string, cert *x509.Certificate) {
|
||||||
|
mu.Lock()
|
||||||
|
defer mu.Unlock()
|
||||||
|
certs[name] = cert
|
||||||
|
}
|
||||||
|
|
||||||
type overallStatus struct {
|
type overallStatus struct {
|
||||||
good, bad []string
|
good, bad []string
|
||||||
}
|
}
|
||||||
|
@ -93,6 +111,27 @@ func getOverallStatus() (o overallStatus) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var subjs []string
|
||||||
|
for k := range certs {
|
||||||
|
subjs = append(subjs, k)
|
||||||
|
}
|
||||||
|
sort.Strings(subjs)
|
||||||
|
|
||||||
|
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(certReissueAfter) {
|
||||||
|
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
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -359,6 +398,21 @@ func newConn(ctx context.Context, dm *tailcfg.DERPMap, n *tailcfg.DERPNode) (*de
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
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)
|
errc := make(chan error, 1)
|
||||||
go func() {
|
go func() {
|
||||||
m, err := dc.Recv()
|
m, err := dc.Recv()
|
||||||
|
|
|
@ -72,6 +72,7 @@ type Client struct {
|
||||||
client *derp.Client
|
client *derp.Client
|
||||||
connGen int // incremented once per new connection; valid values are >0
|
connGen int // incremented once per new connection; valid values are >0
|
||||||
serverPubKey key.NodePublic
|
serverPubKey key.NodePublic
|
||||||
|
tlsState *tls.ConnectionState
|
||||||
pingOut map[derp.PingMessage]chan<- bool // chan to send to on pong
|
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
|
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.
|
// ServerPublicKey returns the server's public key.
|
||||||
//
|
//
|
||||||
// It only returns a non-zero value once a connection has succeeded
|
// 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 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 serverPub key.NodePublic // or zero if unknown (if not using TLS or TLS middlebox eats it)
|
||||||
var serverProtoVersion int
|
var serverProtoVersion int
|
||||||
|
var tlsState *tls.ConnectionState
|
||||||
if c.useHTTPS() {
|
if c.useHTTPS() {
|
||||||
tlsConn := c.tlsClient(tcpConn, node)
|
tlsConn := c.tlsClient(tcpConn, node)
|
||||||
httpConn = tlsConn
|
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
|
// Note that we're not specifically concerned about TLS downgrade
|
||||||
// attacks. TLS handles that fine:
|
// attacks. TLS handles that fine:
|
||||||
// https://blog.gypsyengineer.com/en/security/how-does-tls-1-3-protect-against-downgrade-attacks.html
|
// https://blog.gypsyengineer.com/en/security/how-does-tls-1-3-protect-against-downgrade-attacks.html
|
||||||
connState := tlsConn.ConnectionState()
|
cs := tlsConn.ConnectionState()
|
||||||
if connState.Version >= tls.VersionTLS13 {
|
tlsState = &cs
|
||||||
serverPub, serverProtoVersion = parseMetaCert(connState.PeerCertificates)
|
if cs.Version >= tls.VersionTLS13 {
|
||||||
|
serverPub, serverProtoVersion = parseMetaCert(cs.PeerCertificates)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
httpConn = tcpConn
|
httpConn = tcpConn
|
||||||
|
@ -409,6 +423,7 @@ func (c *Client) connect(ctx context.Context, caller string) (client *derp.Clien
|
||||||
c.serverPubKey = derpClient.ServerPublicKey()
|
c.serverPubKey = derpClient.ServerPublicKey()
|
||||||
c.client = derpClient
|
c.client = derpClient
|
||||||
c.netConn = tcpConn
|
c.netConn = tcpConn
|
||||||
|
c.tlsState = tlsState
|
||||||
c.connGen++
|
c.connGen++
|
||||||
return c.client, c.connGen, nil
|
return c.client, c.connGen, nil
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue