mirror of
https://github.com/jpillora/chisel
synced 2026-06-08 15:07:02 +00:00
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:
@@ -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
|
||||
|
||||
@@ -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
@@ -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
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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-----
|
||||
@@ -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-----
|
||||
@@ -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-----
|
||||
@@ -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-----
|
||||
@@ -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-----
|
||||
@@ -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
@@ -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")
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user