mTLS authentication feature.

1. Adding client cert and key to Chisel client.
2. Adding trusted CAs to Chisel server.
3. Adding TLS/mTLS e2e test case and test certs and keys.
4. Adding flag and help comments.
This commit is contained in:
Will Xia
2020-08-24 18:17:38 -04:00
parent 4f33489f62
commit 7ca9034c47
11 changed files with 207 additions and 13 deletions
+14
View File
@@ -46,6 +46,8 @@ type Config struct {
type TLSConfig struct {
SkipVerify bool
CA string
MtlsCliCrt string
MtlsCliKey string
}
//Client represents a client instance
@@ -117,6 +119,18 @@ func NewClient(c *Config) (*Client, error) {
tc.RootCAs = rootCAs
}
}
//Specify client cert and key pair
if c.TLS.MtlsCliCrt != "" && c.TLS.MtlsCliKey != "" {
tc.GetClientCertificate = func(info *tls.CertificateRequestInfo) (*tls.Certificate, error) {
c, err := tls.LoadX509KeyPair(c.TLS.MtlsCliCrt, c.TLS.MtlsCliKey)
if err != nil {
return nil, fmt.Errorf("Error loading client cert and key pair: %v", err)
}
return &c, nil
}
} else if c.TLS.MtlsCliCrt != "" || c.TLS.MtlsCliKey != "" {
return nil, fmt.Errorf("Please specify client cert and key pair BOTH")
}
client.tlsConfig = tc
}
//validate remotes
+14
View File
@@ -157,6 +157,11 @@ var serverHelp = `
You can modify this path by setting the CHISEL_LE_CACHE variable,
or disable caching by setting this variable to "-". You can optionally
provide a certificate notification email by setting CHISEL_LE_EMAIL.
--tls-mtls-ca-dir, for mTLS support only. Specify the path to load
all self signed client certificates and trusted CAs in PEM-encoded
format. For trusted CAs, any client certificates signed by these CAs
consider to be allowed.
` + commonHelp
func server(args []string) {
@@ -175,6 +180,7 @@ func server(args []string) {
flags.StringVar(&config.TLS.Key, "tls-key", "", "")
flags.StringVar(&config.TLS.Cert, "tls-cert", "", "")
flags.Var(multiFlag{&config.TLS.Domains}, "tls-domain", "")
flags.StringVar(&config.TLS.MtlsCaDir, "tls-mtls-ca-dir", "", "")
host := flags.String("host", "", "")
p := flags.String("p", "", "")
@@ -367,6 +373,12 @@ var clientHelp = `
transport https (wss) connection. Chisel server's public key
may be still verified (see --fingerprint) after inner connection
is established.
--tls-mtls-clicrt, for mTLS support only. PEM-encoded client certificate
for mTLS authentication.
--tls-mtls-clikey, for mTLS support only. PEM-encoded client key for
mTLS authentication.
` + commonHelp
func client(args []string) {
@@ -380,6 +392,8 @@ func client(args []string) {
flags.StringVar(&config.Proxy, "proxy", "", "")
flags.StringVar(&config.TLS.CA, "tls-ca", "", "")
flags.BoolVar(&config.TLS.SkipVerify, "tls-skip-verify", false, "")
flags.StringVar(&config.TLS.MtlsCliCrt, "tls-mtls-clicrt", "", "")
flags.StringVar(&config.TLS.MtlsCliKey, "tls-mtls-clikey", "", "")
flags.Var(&headerFlags{config.Headers}, "header", "")
hostname := flags.String("hostname", "", "")
pid := flags.Bool("pid", false, "")
+40 -9
View File
@@ -2,7 +2,9 @@ package chserver
import (
"crypto/tls"
"crypto/x509"
"errors"
"io/ioutil"
"net"
"os"
"os/user"
@@ -13,9 +15,10 @@ import (
//TLSConfig enables configures TLS
type TLSConfig struct {
Key string
Cert string
Domains []string
Key string
Cert string
Domains []string
MtlsCaDir string
}
func (s *Server) listener(host, port string) (net.Listener, error) {
@@ -30,7 +33,7 @@ func (s *Server) listener(host, port string) (net.Listener, error) {
}
extra := ""
if hasKeyCert {
c, err := tlsKeyCert(s.config.TLS.Key, s.config.TLS.Cert)
c, err := s.tlsKeyCert(s.config.TLS.Key, s.config.TLS.Cert, s.config.TLS.MtlsCaDir)
if err != nil {
return nil, err
}
@@ -82,13 +85,41 @@ func (s *Server) tlsLetsEncrypt(domains []string) *tls.Config {
return m.TLSConfig()
}
func tlsKeyCert(key, cert string) (*tls.Config, error) {
func (s *Server) tlsKeyCert(key, cert string, clientCaPath string) (*tls.Config, error) {
c, err := tls.LoadX509KeyPair(cert, key)
if err != nil {
return nil, err
}
//return file based tls config using tls defaults
return &tls.Config{
Certificates: []tls.Certificate{c},
}, nil
//mTLS requires Client CAs
if clientCaPath != "" {
files, err := ioutil.ReadDir(clientCaPath)
if err != nil {
return nil, err
}
s.Infof("Looking for client CA files from %s", clientCaPath)
clientCAPool := x509.NewCertPool()
//add all cert files from path
for _, file := range files {
f := file.Name()
content, err := ioutil.ReadFile(filepath.Join(clientCaPath, f))
if err == nil {
if clientCAPool.AppendCertsFromPEM(content) {
s.Infof("Add client CA file %s", f)
} else {
s.Errorf("Fail to add client CA file %s", f)
}
}
}
//return file based tls config with client CAs and cert verification enabled
return &tls.Config{
Certificates: []tls.Certificate{c},
ClientCAs: clientCAPool,
ClientAuth: tls.RequireAndVerifyClientCert,
}, nil
} else {
//return file based tls config using tls defaults
return &tls.Config{
Certificates: []tls.Certificate{c},
}, nil
}
}
+6 -1
View File
@@ -71,7 +71,12 @@ func (tl *testLayout) setup(t *testing.T) (server *chserver.Server, client *chcl
}()
//client (with defaults)
tl.client.Fingerprint = server.GetFingerprint()
tl.client.Server = "http://127.0.0.1:" + port
if tl.server.TLS.Key != "" {
//the domain name has to be localhost to match the ssl cert
tl.client.Server = "https://localhost:" + port
} else {
tl.client.Server = "http://127.0.0.1:" + port
}
for i, r := range tl.client.Remotes {
//convert $FILEPORT into the allocated port for this test case
if tl.fileServer {
+16
View File
@@ -0,0 +1,16 @@
-----BEGIN CERTIFICATE-----
MIICezCCAeQCCQDwdWskfbwmzzANBgkqhkiG9w0BAQUFADCBgTELMAkGA1UEBhMC
dXMxCzAJBgNVBAgMAm1hMQ8wDQYDVQQHDAZCb3N0b24xDzANBgNVBAoMBkNoaXNl
bDENMAsGA1UECwwEdGVzdDESMBAGA1UEAwwJbG9jYWxob3N0MSAwHgYJKoZIhvcN
AQkBFhF3aWxseGlhQGdtYWlsLmNvbTAeFw0yMDA4MjQxOTQ4MTdaFw0zMDA4MjIx
OTQ4MTdaMIGBMQswCQYDVQQGEwJ1czELMAkGA1UECAwCbWExDzANBgNVBAcMBkJv
c3RvbjEPMA0GA1UECgwGQ2hpc2VsMQ0wCwYDVQQLDAR0ZXN0MRIwEAYDVQQDDAls
b2NhbGhvc3QxIDAeBgkqhkiG9w0BCQEWEXdpbGx4aWFAZ21haWwuY29tMIGfMA0G
CSqGSIb3DQEBAQUAA4GNADCBiQKBgQC128s6L6YN0eNSNbI40URFHd9xzfnPlUcH
n9n7D6YkJL7LsTAtUfjubNAX0Q1gclDnDZCfYi9UZVzzID4s1gZJZAEZGnce8loO
a+WcPUgIOJngk2bwUHfrWPl+R5mvE9p60rfYNdo86wLMaLAJu+VagNmaoilSU7OS
uZ/AgTUMFQIDAQABMA0GCSqGSIb3DQEBBQUAA4GBAEzq2qsH5VfmjUcvlhS4a7X5
zOAtEIVB1+oef/1NcyT3PaMX0ry0Ddbo3NJs3G9KTF0k+TCGtT7nAG2jRQvs6omZ
3+9C3x+6TQq+95KMBWXuZLZEPNa4iCGFbGrHq4wcWDehBAPSjdctqnmowd8yIgov
gNSN2xEMPNKYIhHt0lyc
-----END CERTIFICATE-----
+16
View File
@@ -0,0 +1,16 @@
-----BEGIN CERTIFICATE-----
MIICfTCCAeYCCQDIXTlEp6na1zANBgkqhkiG9w0BAQUFADCBgjELMAkGA1UEBhMC
dXMxCzAJBgNVBAgMAm1hMQ8wDQYDVQQHDAZCb3N0b24xDzANBgNVBAoMBkNoaXNl
bDENMAsGA1UECwwEdGVzdDETMBEGA1UEAwwKbVRMU0NsaWVudDEgMB4GCSqGSIb3
DQEJARYRd2lsbHhpYUBnbWFpbC5jb20wHhcNMjAwODI0MTk0ODQxWhcNMzAwODIy
MTk0ODQxWjCBgjELMAkGA1UEBhMCdXMxCzAJBgNVBAgMAm1hMQ8wDQYDVQQHDAZC
b3N0b24xDzANBgNVBAoMBkNoaXNlbDENMAsGA1UECwwEdGVzdDETMBEGA1UEAwwK
bVRMU0NsaWVudDEgMB4GCSqGSIb3DQEJARYRd2lsbHhpYUBnbWFpbC5jb20wgZ8w
DQYJKoZIhvcNAQEBBQADgY0AMIGJAoGBAM4jt9TxHsCNegij34we4yzOykAuMVuz
DzW++Jh4/xWeOoU3xb7I2ETIzmusIM70o2lm+e+gy9VfAAXaNgZg63QV54jRn2nk
BWoXJYvYOJwt5YzOsLkbh6epSlrqYI0H34Sy5rEkacXCkcpcEvom/tvJ+SpHyIL1
PYNN1CCx/eg5AgMBAAEwDQYJKoZIhvcNAQEFBQADgYEAJysuLKCgVqMW628SFcpu
ojtBSNy2KETDwmMTaLg/XTaAPOvxAO3W9F7KJ1JxVFf2oIW7ROL9sP862lSMQLZ5
R45pBlPZycb1CQplD50wMqknaaMJ1qnld9Jkv802cJa2riqzdHb5rnjrewmuLOOB
V0cZ9PJA3KdXbJW1o+WjQz4=
-----END CERTIFICATE-----
+15
View File
@@ -0,0 +1,15 @@
-----BEGIN RSA PRIVATE KEY-----
MIICWwIBAAKBgQDOI7fU8R7AjXoIo9+MHuMszspALjFbsw81vviYeP8VnjqFN8W+
yNhEyM5rrCDO9KNpZvnvoMvVXwAF2jYGYOt0FeeI0Z9p5AVqFyWL2DicLeWMzrC5
G4enqUpa6mCNB9+EsuaxJGnFwpHKXBL6Jv7byfkqR8iC9T2DTdQgsf3oOQIDAQAB
AoGAArLhAz6s4mR3xokusgzteHa0myZ/qu2rM07uvkBHRqctqPTT9+11N2FRooM8
Yrk9MnIQr5xxTrfRrkHvFyJJstNX809ve9Klu1vbT+S19se/m+jLKTOtOoYoPRaK
w7ekvjhLct00zQevphEX30xqA2S3HSjWD3HmjVwdadAUgQECQQDz+LShNRkJ97+n
hiRgShHupW7CmAb67hrenbbzkCaY8Kf9cAFiscEmjH+lZsbufCgzVvHKDNKi9/JN
dPTSQvURAkEA2E2D0BqTDOiqjwyueSr2V5m63mzWR0Jd1TAl0dxB6SBumYQQ1FFP
DmQ/J3lcT2RTS+PmKAkuPpSOalw1kqggqQJAbotPVQgZG1IdjguS6epF68sbv6Jg
70v58sqlfgDf7EaG56fbiNuf+BaLM+e41ZB+Kp0Hm5Rp0JvmN0B6OddK8QJAcZbD
UdWiw3SrnNOcDCVzmC0y5Ptiy6kefYX7VmnEcxiE/DlOXTEVwwkB4UjqIQcedwwH
IZ8wmcyJvXEO8SU5gQJAfHxBcFdX2vrDNNjm5GG11zrT86Ii+ieXa0Ty5vapRSsz
FQH3KnM2t7nNDMlFOaHuvVXHmPasudtxBDc5xDoHNA==
-----END RSA PRIVATE KEY-----
+16
View File
@@ -0,0 +1,16 @@
-----BEGIN CERTIFICATE-----
MIICfTCCAeYCCQDIXTlEp6na1zANBgkqhkiG9w0BAQUFADCBgjELMAkGA1UEBhMC
dXMxCzAJBgNVBAgMAm1hMQ8wDQYDVQQHDAZCb3N0b24xDzANBgNVBAoMBkNoaXNl
bDENMAsGA1UECwwEdGVzdDETMBEGA1UEAwwKbVRMU0NsaWVudDEgMB4GCSqGSIb3
DQEJARYRd2lsbHhpYUBnbWFpbC5jb20wHhcNMjAwODI0MTk0ODQxWhcNMzAwODIy
MTk0ODQxWjCBgjELMAkGA1UEBhMCdXMxCzAJBgNVBAgMAm1hMQ8wDQYDVQQHDAZC
b3N0b24xDzANBgNVBAoMBkNoaXNlbDENMAsGA1UECwwEdGVzdDETMBEGA1UEAwwK
bVRMU0NsaWVudDEgMB4GCSqGSIb3DQEJARYRd2lsbHhpYUBnbWFpbC5jb20wgZ8w
DQYJKoZIhvcNAQEBBQADgY0AMIGJAoGBAM4jt9TxHsCNegij34we4yzOykAuMVuz
DzW++Jh4/xWeOoU3xb7I2ETIzmusIM70o2lm+e+gy9VfAAXaNgZg63QV54jRn2nk
BWoXJYvYOJwt5YzOsLkbh6epSlrqYI0H34Sy5rEkacXCkcpcEvom/tvJ+SpHyIL1
PYNN1CCx/eg5AgMBAAEwDQYJKoZIhvcNAQEFBQADgYEAJysuLKCgVqMW628SFcpu
ojtBSNy2KETDwmMTaLg/XTaAPOvxAO3W9F7KJ1JxVFf2oIW7ROL9sP862lSMQLZ5
R45pBlPZycb1CQplD50wMqknaaMJ1qnld9Jkv802cJa2riqzdHb5rnjrewmuLOOB
V0cZ9PJA3KdXbJW1o+WjQz4=
-----END CERTIFICATE-----
+16
View File
@@ -0,0 +1,16 @@
-----BEGIN CERTIFICATE-----
MIICezCCAeQCCQDwdWskfbwmzzANBgkqhkiG9w0BAQUFADCBgTELMAkGA1UEBhMC
dXMxCzAJBgNVBAgMAm1hMQ8wDQYDVQQHDAZCb3N0b24xDzANBgNVBAoMBkNoaXNl
bDENMAsGA1UECwwEdGVzdDESMBAGA1UEAwwJbG9jYWxob3N0MSAwHgYJKoZIhvcN
AQkBFhF3aWxseGlhQGdtYWlsLmNvbTAeFw0yMDA4MjQxOTQ4MTdaFw0zMDA4MjIx
OTQ4MTdaMIGBMQswCQYDVQQGEwJ1czELMAkGA1UECAwCbWExDzANBgNVBAcMBkJv
c3RvbjEPMA0GA1UECgwGQ2hpc2VsMQ0wCwYDVQQLDAR0ZXN0MRIwEAYDVQQDDAls
b2NhbGhvc3QxIDAeBgkqhkiG9w0BCQEWEXdpbGx4aWFAZ21haWwuY29tMIGfMA0G
CSqGSIb3DQEBAQUAA4GNADCBiQKBgQC128s6L6YN0eNSNbI40URFHd9xzfnPlUcH
n9n7D6YkJL7LsTAtUfjubNAX0Q1gclDnDZCfYi9UZVzzID4s1gZJZAEZGnce8loO
a+WcPUgIOJngk2bwUHfrWPl+R5mvE9p60rfYNdo86wLMaLAJu+VagNmaoilSU7OS
uZ/AgTUMFQIDAQABMA0GCSqGSIb3DQEBBQUAA4GBAEzq2qsH5VfmjUcvlhS4a7X5
zOAtEIVB1+oef/1NcyT3PaMX0ry0Ddbo3NJs3G9KTF0k+TCGtT7nAG2jRQvs6omZ
3+9C3x+6TQq+95KMBWXuZLZEPNa4iCGFbGrHq4wcWDehBAPSjdctqnmowd8yIgov
gNSN2xEMPNKYIhHt0lyc
-----END CERTIFICATE-----
+15
View File
@@ -0,0 +1,15 @@
-----BEGIN RSA PRIVATE KEY-----
MIICXAIBAAKBgQC128s6L6YN0eNSNbI40URFHd9xzfnPlUcHn9n7D6YkJL7LsTAt
UfjubNAX0Q1gclDnDZCfYi9UZVzzID4s1gZJZAEZGnce8loOa+WcPUgIOJngk2bw
UHfrWPl+R5mvE9p60rfYNdo86wLMaLAJu+VagNmaoilSU7OSuZ/AgTUMFQIDAQAB
AoGBAJva4JLfXyqc5HsCNdlnz2CEt4irBBsZTiSEpKX7xWFYdIPROP6+L972NmkS
6qnrjtZV08oktXdY344l5eM7EWrFnqKH1pyTRUPnyKGY53jY4yZMad1GYMXLo8Mj
gkEOsfIhuieEBKGXAX54moDLTFzn14q+V+7g3OrLmMYXFN7JAkEA54S00eeuy0Eg
6+qx9dO4iDBp3qut5PShjda4M11MWobRQH71gO0g25qSrnw0x7BXJQl1hhgYtSUy
zbaF+5ZORwJBAMkWxt55YDpXdP4vudugmzP7F8aSB6rUysowlv1uStFhARNYwgs+
Tl9EGhFPF0ganNv4di1iYLarIKoWas8nNMMCQEex4ekKzSdmUNKeCGQvH3sVOwPY
uG4pj4oED2DgqI90JoLJji9Rv5YiBQCBuDqKkkIG7t0Kw0P9dAEeX9lsT2sCQD5x
iznEmSQkyliwe1d/LRLcMwrfh+/9eieFJS33lNYl+E6IrmENbQraO/oKBGHImdMY
+aGoPf4bb95BbdN8Cj8CQECXWVHkFFVQ2B78r7ENWnmbVG5XJW/iwfaZtmbsPPrI
KqGlB8leQcLLlCC48SCLlc64VtWOaJVxBDyvO4NuD/U=
-----END RSA PRIVATE KEY-----
+39 -3
View File
@@ -1,5 +1,41 @@
package e2e_test
//TODO tests for:
// PRE-REQ generate local certs
// - client TLS (using local CA) -> server TLS (using local files)
import (
"testing"
chclient "github.com/jpillora/chisel/client"
chserver "github.com/jpillora/chisel/server"
)
func TestTls(t *testing.T) {
tmpPort := availablePort()
//setup server, client, fileserver
teardown := simpleSetup(t,
&chserver.Config{
TLS: chserver.TLSConfig{
Cert: "tls/server-crt/server.crt",
Key: "tls/server-crt/server.key",
MtlsCaDir: "tls/server-ca",
},
},
&chclient.Config{
Remotes: []string{tmpPort + ":$FILEPORT"},
TLS: chclient.TLSConfig{
SkipVerify: false,
//for self signed cert, it needs the server cert, for real cert, this need to be the trusted CA cert
CA: "tls/client-ca/server.crt",
MtlsCliCrt: "tls/client-crt/client.crt",
MtlsCliKey: "tls/client-crt/client.key",
},
Server: "https://localhost:" + tmpPort,
})
defer teardown()
//test remote
result, err := post("http://localhost:"+tmpPort, "foo")
if err != nil {
t.Fatal(err)
}
if result != "foo!" {
t.Fatalf("expected exclamation mark added")
}
}