Skip to content

Commit 56fda6f

Browse files
authored
Merge pull request #7 from staaldraad/mtls
Add mTLS to tcpprox
2 parents 0f996f3 + adca040 commit 56fda6f

4 files changed

Lines changed: 143 additions & 25 deletions

File tree

Makefile

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
build:
2+
go build -o tcpprox tcpprox.go
3+
4+
run:
5+
go run tcpprox.go
6+
7+
build all:
8+
# 32-bit
9+
# Linux
10+
GOOS=linux GOARCH=386 go build -o tcpprox-linux86
11+
sha256sum tcpprox-linux86
12+
# Windows
13+
GOOS=windows GOARCH=386 go build -o tcpprox-win86.exe
14+
sha256sum tcpprox-win86.exe
15+
# OSX
16+
GOOS=darwin GOARCH=386 go build -o tcpprox-osx86
17+
sha256sum tcpprox-osx86
18+
19+
# 64-bit
20+
# Linux
21+
GOOS=linux GOARCH=amd64 go build -o tcpprox-linux64
22+
sha256sum tcpprox-linux64
23+
# Windows
24+
GOOS=windows GOARCH=amd64 go build -o tcpprox-win64.exe
25+
sha256sum tcpprox-win64.exe
26+
# OSX
27+
GOOS=darwin GOARCH=amd64 go build -o tcpprox-osx64
28+
sha256sum tcpprox-osx64

README.md

Lines changed: 46 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -10,27 +10,65 @@ _The command line arguments have precidence and override the config file_
1010

1111
To create a TLS proxy using the supplied config file:
1212

13-
`tcpprox -s -c config.json -r 172.16.0.12:4550`
13+
```
14+
tcpprox -s -c config.json -r 172.16.0.12:4550
15+
```
1416

1517
To create a normal TCP proxy, no config file:
1618

17-
`tcpprox -l 0.0.0.0 -p 8081 -r 172.16.0.12:8081`
19+
```
20+
tcpprox -l 0.0.0.0 -p 8081 -r 172.16.0.12:8081
21+
```
22+
23+
To specify a custom certificate to use (PEM format) you can use the -cert and -key options (must be used together):
1824

19-
To specify a custom certificate to use (PEM format) you can use the -cert option:
25+
___Note (breaking change)__ for previous versions of tcpprox, the `-cert` and `-key` arguments were combined into one argument `-cert`. This previous arg would take the supplied value and automatically append **.pem** and **.key**. This is no longer the case and the supplied filepaths for `-cert` and `-key` must be complete and for valid, matching files._
2026

21-
`tcpprox -s -c config.json -cert server`
27+
```
28+
tcpprox -s -c config.json -cert server.pem -key server.key
29+
```
2230

23-
Where server is the prefix to server.pem and server.key (I'm lazy...)
2431
To generate valid certificate and key:
2532

26-
`
33+
```
2734
openssl genrsa -out server.key 2048
2835
openssl req -new -x509 -key server.key -out server.pem -days 3650
29-
`
36+
```
37+
3038
To convert the certificate to DER format:
3139

32-
`openssl x509 -in server.pem -out server.crt -outform der`
40+
```
41+
openssl x509 -in server.pem -out server.crt -outform der
42+
```
43+
44+
45+
## mTLS
46+
47+
If proxying a connection where the upstream server uses mTLS, tcpprox can be configured to use mTLS to authenticate.
48+
49+
```
50+
tcpprox -s -c config.json -cert server.pem -key server.key -clientCert client.pem -clientKey client.key
51+
```
52+
53+
The application that is being proxied through tcpprox does not have to supply a client certificate (although it can).
54+
55+
This allows for either:
56+
57+
```
58+
client <---TLS---> tcpprox <---mTLS---> server
59+
```
60+
61+
or
62+
63+
```
64+
client <---mTLS---> tcpprox <---mTLS---> server
65+
```
66+
67+
Tcpprox will allow both types of connections through, as long as tcpprox is able to use mTLS to connect to the server, the client is oblivious of what is happening upstream.
68+
69+
## Config File
3370

71+
The config file can be used instead of supplying all information on the command line. The options specified in the file will be overwritten by any matching command line arguments. This allows for using a config file and overriding one or more options for testing / variation between hosts.
3472

3573
# Using Docker
3674

config.json

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,5 +7,8 @@
77
"Org":["Staaldraad"],
88
"CommonName":"*.domain.com"
99
},
10-
"Certfile":""
10+
"CACertFile":"",
11+
"CAKeyFile":"",
12+
"ClientCertFile":"",
13+
"ClientKeyFile":""
1114
}

tcpprox.go

Lines changed: 65 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import (
1212
"fmt"
1313
"io"
1414
"io/ioutil"
15+
"log"
1516
"math/big"
1617
"net"
1718
"os"
@@ -25,11 +26,14 @@ type TLS struct {
2526
}
2627

2728
type Config struct {
28-
Remotehost string
29-
Localhost string
30-
Localport int
31-
TLS *TLS
32-
CertFile string ""
29+
Remotehost string `json:"remotehost"`
30+
Localhost string `json:"localhost"`
31+
Localport int `json:"localport"`
32+
TLS *TLS `json:"TLS"`
33+
CACertFile string `json:"CACertFile"`
34+
CAKeyFile string `json:"CAKeyFile"`
35+
ClientCertFile string `json:"ClientCertFile"` // client cert for mTLS
36+
ClientKeyFile string `json:"ClientKeyFile"` // client priv key for mTLS
3337
}
3438

3539
var config Config
@@ -47,9 +51,9 @@ func genCert() ([]byte, *rsa.PrivateKey) {
4751
NotAfter: time.Now().AddDate(10, 0, 0),
4852
SubjectKeyId: []byte{1, 2, 3, 4, 5},
4953
BasicConstraintsValid: true,
50-
IsCA: true,
51-
ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageClientAuth, x509.ExtKeyUsageServerAuth},
52-
KeyUsage: x509.KeyUsageDigitalSignature | x509.KeyUsageCertSign,
54+
IsCA: true,
55+
ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageClientAuth, x509.ExtKeyUsageServerAuth},
56+
KeyUsage: x509.KeyUsageDigitalSignature | x509.KeyUsageCertSign,
5357
}
5458

5559
priv, _ := rsa.GenerateKey(rand.Reader, 1024)
@@ -83,12 +87,22 @@ func handleConnection(conn net.Conn, isTLS bool) {
8387

8488
if isTLS == true {
8589
conf := tls.Config{InsecureSkipVerify: true}
90+
91+
if config.ClientKeyFile != "" { //use mtls
92+
cert, err := tls.LoadX509KeyPair(config.ClientCertFile, config.ClientKeyFile)
93+
if err != nil {
94+
log.Fatal(err)
95+
}
96+
conf.Certificates = []tls.Certificate{cert}
97+
}
98+
8699
connR, err = tls.Dial("tcp", config.Remotehost, &conf)
87100
} else {
88101
connR, err = net.Dial("tcp", config.Remotehost)
89102
}
90103

91104
if err != nil {
105+
log.Fatal(err)
92106
return
93107
}
94108

@@ -121,20 +135,36 @@ func startListener(isTLS bool) {
121135
var cert tls.Certificate
122136

123137
if isTLS == true {
124-
if config.CertFile != "" {
125-
cert, _ = tls.LoadX509KeyPair(fmt.Sprint(config.CertFile, ".pem"), fmt.Sprint(config.CertFile, ".key"))
138+
if config.CACertFile != "" {
139+
cert, _ = tls.LoadX509KeyPair(fmt.Sprint(config.CACertFile), fmt.Sprint(config.CAKeyFile))
126140
} else {
127-
fmt.Println("[*] Generating cert")
141+
fmt.Println("[*] Generating cert")
128142
ca_b, priv := genCert()
129143
cert = tls.Certificate{
130144
Certificate: [][]byte{ca_b},
131145
PrivateKey: priv,
132146
}
133147
}
134148

149+
// we don't have to set mTLS on the listener, it will simply accept connection with or
150+
// without the client supplying a cert. The mTLS part happens with the connection to the
151+
// upstream host
135152
conf := tls.Config{
136153
Certificates: []tls.Certificate{cert},
137154
}
155+
156+
/* optional to add mTLS on the listener side
157+
if config.ClientKeyFile != "" {
158+
caCert, err := ioutil.ReadFile(config.ClientKeyFile)
159+
if err != nil {
160+
log.Fatal(err)
161+
}
162+
caCertPool := x509.NewCertPool()
163+
caCertPool.AppendCertsFromPEM(caCert)
164+
conf.ClientCAs = caCertPool
165+
conf.ClientAuth = tls.RequireAndVerifyClientCert
166+
} */
167+
138168
conf.Rand = rand.Reader
139169

140170
conn, err = tls.Listen("tcp", fmt.Sprint(config.Localhost, ":", config.Localport), &conf)
@@ -161,7 +191,7 @@ func startListener(isTLS bool) {
161191
conn.Close()
162192
}
163193

164-
func setConfig(configFile string, localPort int, localHost, remoteHost string, certFile string) {
194+
func setConfig(configFile string, localPort int, localHost, remoteHost string, caCertFile, caKeyFile string, clientCertFile, clientKeyFile string) {
165195
if configFile != "" {
166196
data, err := ioutil.ReadFile(configFile)
167197
if err != nil {
@@ -177,8 +207,14 @@ func setConfig(configFile string, localPort int, localHost, remoteHost string, c
177207
config = Config{TLS: &TLS{}}
178208
}
179209

180-
if certFile != "" {
181-
config.CertFile = certFile
210+
if caCertFile != "" {
211+
config.CACertFile = caCertFile
212+
config.CAKeyFile = caKeyFile
213+
}
214+
215+
if clientCertFile != "" {
216+
config.ClientCertFile = clientCertFile
217+
config.ClientKeyFile = clientKeyFile
182218
}
183219

184220
if localPort != 0 {
@@ -198,11 +234,24 @@ func main() {
198234
remoteHostPtr := flag.String("r", "", "Remote Server address host:port")
199235
configPtr := flag.String("c", "", "Use a config file (set TLS ect) - Commandline params overwrite config file")
200236
tlsPtr := flag.Bool("s", false, "Create a TLS Proxy")
201-
certFilePtr := flag.String("cert", "", "Use a specific certificate file")
237+
caCertFilePtr := flag.String("cert", "", "Use a specific ca cert file")
238+
caKeyFilePtr := flag.String("key", "", "Use a specific ca key file (must be set if --cert is set")
239+
clientCertPtr := flag.String("clientCert", "", "A public client cert to use for mTLS")
240+
clientKeyPtr := flag.String("clientKey", "", "A public client key to use for mTLS")
202241

203242
flag.Parse()
204243

205-
setConfig(*configPtr, *localPort, *localHost, *remoteHostPtr, *certFilePtr)
244+
if *caCertFilePtr != "" && *caKeyFilePtr == "" {
245+
fmt.Println("[x] -key is required when -cert is set")
246+
os.Exit(1)
247+
}
248+
249+
if *clientCertPtr != "" && *clientKeyPtr == "" {
250+
fmt.Println("[x] -clientKey is required when -clientCert is set")
251+
os.Exit(1)
252+
}
253+
254+
setConfig(*configPtr, *localPort, *localHost, *remoteHostPtr, *caCertFilePtr, *caKeyFilePtr, *clientCertPtr, *clientKeyPtr)
206255

207256
if config.Remotehost == "" {
208257
fmt.Println("[x] Remote host required")

0 commit comments

Comments
 (0)