net/tlsdial: call out firewalls blocking Tailscale in health warnings (#13840)
Updates tailscale/tailscale#13839 Adds a new blockblame package which can detect common MITM SSL certificates used by network appliances. We use this in `tlsdial` to display a dedicated health warning when we cannot connect to control, and a network appliance MITM attack is detected. Signed-off-by: Andrea Gottardo <andrea@gottardo.me>main
parent
e711ee5d22
commit
fd77965f23
@ -0,0 +1,104 @@ |
||||
// Copyright (c) Tailscale Inc & AUTHORS
|
||||
// SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
// Package blockblame blames specific firewall manufacturers for blocking Tailscale,
|
||||
// by analyzing the SSL certificate presented when attempting to connect to a remote
|
||||
// server.
|
||||
package blockblame |
||||
|
||||
import ( |
||||
"crypto/x509" |
||||
"strings" |
||||
) |
||||
|
||||
// VerifyCertificate checks if the given certificate c is issued by a firewall manufacturer
|
||||
// that is known to block Tailscale connections. It returns true and the Manufacturer of
|
||||
// the equipment if it is, or false and nil if it is not.
|
||||
func VerifyCertificate(c *x509.Certificate) (m *Manufacturer, ok bool) { |
||||
for _, m := range Manufacturers { |
||||
if m.match != nil && m.match(c) { |
||||
return m, true |
||||
} |
||||
} |
||||
return nil, false |
||||
} |
||||
|
||||
// Manufacturer represents a firewall manufacturer that may be blocking Tailscale.
|
||||
type Manufacturer struct { |
||||
// Name is the name of the firewall manufacturer to be
|
||||
// mentioned in health warning messages, e.g. "Fortinet".
|
||||
Name string |
||||
// match is a function that returns true if the given certificate looks like it might
|
||||
// be issued by this manufacturer.
|
||||
match matchFunc |
||||
} |
||||
|
||||
var Manufacturers = []*Manufacturer{ |
||||
{ |
||||
Name: "Aruba Networks", |
||||
match: issuerContains("Aruba"), |
||||
}, |
||||
{ |
||||
Name: "Cisco", |
||||
match: issuerContains("Cisco"), |
||||
}, |
||||
{ |
||||
Name: "Fortinet", |
||||
match: matchAny( |
||||
issuerContains("Fortinet"), |
||||
certEmail("support@fortinet.com"), |
||||
), |
||||
}, |
||||
{ |
||||
Name: "Huawei", |
||||
match: certEmail("mobile@huawei.com"), |
||||
}, |
||||
{ |
||||
Name: "Palo Alto Networks", |
||||
match: matchAny( |
||||
issuerContains("Palo Alto Networks"), |
||||
issuerContains("PAN-FW"), |
||||
), |
||||
}, |
||||
{ |
||||
Name: "Sophos", |
||||
match: issuerContains("Sophos"), |
||||
}, |
||||
{ |
||||
Name: "Ubiquiti", |
||||
match: matchAny( |
||||
issuerContains("UniFi"), |
||||
issuerContains("Ubiquiti"), |
||||
), |
||||
}, |
||||
} |
||||
|
||||
type matchFunc func(*x509.Certificate) bool |
||||
|
||||
func issuerContains(s string) matchFunc { |
||||
return func(c *x509.Certificate) bool { |
||||
return strings.Contains(strings.ToLower(c.Issuer.String()), strings.ToLower(s)) |
||||
} |
||||
} |
||||
|
||||
func certEmail(v string) matchFunc { |
||||
return func(c *x509.Certificate) bool { |
||||
for _, email := range c.EmailAddresses { |
||||
if strings.Contains(strings.ToLower(email), strings.ToLower(v)) { |
||||
return true |
||||
} |
||||
} |
||||
return false |
||||
} |
||||
} |
||||
|
||||
func matchAny(fs ...matchFunc) matchFunc { |
||||
return func(c *x509.Certificate) bool { |
||||
for _, f := range fs { |
||||
if f(c) { |
||||
return true |
||||
} |
||||
} |
||||
return false |
||||
} |
||||
} |
||||
@ -0,0 +1,54 @@ |
||||
// Copyright (c) Tailscale Inc & AUTHORS
|
||||
// SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
package blockblame |
||||
|
||||
import ( |
||||
"crypto/x509" |
||||
"encoding/pem" |
||||
"testing" |
||||
) |
||||
|
||||
const controlplaneDotTailscaleDotComPEM = ` |
||||
-----BEGIN CERTIFICATE----- |
||||
MIIDkzCCAxqgAwIBAgISA2GOahsftpp59yuHClbDuoduMAoGCCqGSM49BAMDMDIx |
||||
CzAJBgNVBAYTAlVTMRYwFAYDVQQKEw1MZXQncyBFbmNyeXB0MQswCQYDVQQDEwJF |
||||
NjAeFw0yNDEwMTIxNjE2NDVaFw0yNTAxMTAxNjE2NDRaMCUxIzAhBgNVBAMTGmNv |
||||
bnRyb2xwbGFuZS50YWlsc2NhbGUuY29tMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcD |
||||
QgAExfraDUc1t185zuGtZlnPDtEJJSDBqvHN4vQcXSzSTPSAdDYHcA8fL5woU2Kg |
||||
jK/2C0wm/rYy2Rre/ulhkS4wB6OCAhswggIXMA4GA1UdDwEB/wQEAwIHgDAdBgNV |
||||
HSUEFjAUBggrBgEFBQcDAQYIKwYBBQUHAwIwDAYDVR0TAQH/BAIwADAdBgNVHQ4E |
||||
FgQUpArnpDj8Yh6NTgMOZjDPx0TuLmcwHwYDVR0jBBgwFoAUkydGmAOpUWiOmNbE |
||||
QkjbI79YlNIwVQYIKwYBBQUHAQEESTBHMCEGCCsGAQUFBzABhhVodHRwOi8vZTYu |
||||
by5sZW5jci5vcmcwIgYIKwYBBQUHMAKGFmh0dHA6Ly9lNi5pLmxlbmNyLm9yZy8w |
||||
JQYDVR0RBB4wHIIaY29udHJvbHBsYW5lLnRhaWxzY2FsZS5jb20wEwYDVR0gBAww |
||||
CjAIBgZngQwBAgEwggEDBgorBgEEAdZ5AgQCBIH0BIHxAO8AdgDgkrP8DB3I52g2 |
||||
H95huZZNClJ4GYpy1nLEsE2lbW9UBAAAAZKBujCyAAAEAwBHMEUCIQDHMgUaL4H9 |
||||
ZJa090ZOpBeEVu3+t+EF4HlHI1NqAai6uQIgeY/lLfjAXfcVgxBHHR4zjd0SzhaP |
||||
TREHXzwxzN/8blkAdQDPEVbu1S58r/OHW9lpLpvpGnFnSrAX7KwB0lt3zsw7CAAA |
||||
AZKBujh8AAAEAwBGMEQCICQwhMk45t9aiFjfwOC/y6+hDbszqSCpIv63kFElweUy |
||||
AiAqTdkqmbqUVpnav5JdWkNERVAIlY4jqrThLsCLZYbNszAKBggqhkjOPQQDAwNn |
||||
ADBkAjALyfgAt1XQp1uSfxy4GapR5OsmjEMBRVq6IgsPBlCRBfmf0Q3/a6mF0pjb |
||||
Sj4oa+cCMEhZk4DmBTIdZY9zjuh8s7bXNfKxUQS0pEhALtXqyFr+D5dF7JcQo9+s |
||||
Z98JY7/PCA== |
||||
-----END CERTIFICATE-----` |
||||
|
||||
func TestVerifyCertificateOurControlPlane(t *testing.T) { |
||||
p, _ := pem.Decode([]byte(controlplaneDotTailscaleDotComPEM)) |
||||
if p == nil { |
||||
t.Fatalf("failed to extract certificate bytes for controlplane.tailscale.com") |
||||
return |
||||
} |
||||
cert, err := x509.ParseCertificate(p.Bytes) |
||||
if err != nil { |
||||
t.Fatalf("failed to parse certificate: %v", err) |
||||
return |
||||
} |
||||
m, found := VerifyCertificate(cert) |
||||
if found { |
||||
t.Fatalf("expected to not get a result for the controlplane.tailscale.com certificate") |
||||
} |
||||
if m != nil { |
||||
t.Fatalf("expected nil manufacturer for controlplane.tailscale.com certificate") |
||||
} |
||||
} |
||||
Loading…
Reference in new issue