How to use TLS in Go ReverseProxy? - ssl

I have sites https://a-b-c.com and https://www.x-y-z.com running behind a reverse proxy on ports 4444 and 5555 respectively.
I had them configured to use letsencrypt tls certificates, but now I get an error when using the reverse proxy and I think I need to use a &tls.config{} that includes their certificates but I don't know how to set it up.
My ReverseProxy looks like:
director := func(req *http.Request) {
log.Println(req.Host)
switch req.Host {
case "a-b-c-.com":
req.URL.Host = "localhost:4444"
req.URL.Scheme = "https"
case "x-y-z.com":
req.URL.Host = "localhost:5555"
req.URL.Scheme = "https"
}
proxy := &httputil.ReverseProxy{Director: director}
proxy.Transport = &http.Transport{
Proxy: http.ProxyFromEnvironment,
Dial: (&net.Dialer{
Timeout: 30 * time.Second,
KeepAlive: 30 * time.Second,
}).Dial,
TLSHandshakeTimeout: 10 * time.Second,
TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
}
log.Fatalln(http.ListenAndServe(":443", proxy))

You don't have to set up your reverse proxy. What you need to do is to set up HTTP server that you attach your reverse proxy to.
Configure TLS listener by passing two pairs of cert/keys:
abcCrt, err := tls.LoadX509KeyPair("a-b-c.crt", "a-b-c.key")
if err != nil {
panic(err)
}
xyzCrt, err := tls.LoadX509KeyPair("x-y-z.crt", "z-y-z.key")
if err != nil {
panic(err)
}
tlsConfig := &tls.Config{Certificates: []tls.Certificate{abcCrt, xyzCrt}}
ln, err := tls.Listen("tcp", ":443", tlsConfig)
if err != nil {
panic(err)
}
http.Serve(ln, proxy)

Related

Go how to use cookiejar for multiple requests?

I'm trying to make a cli for a site that has csrf, needing the csrf token to be sent on headers and on a form.
I can't seem to understand net/http.Client or net/http/cookieJar
This its even good practice? There's a better way of doing csrf login on Go ?
Thx in advance ^v^
This its my code:
package main
import (
"fmt"
"log"
"net/http"
"net/http/cookiejar"
"net/url"
"strings"
"time"
)
var (
httpClient = &http.Client{}
)
func main() {
jar, err := cookiejar.New(nil)
if err != nil {
log.Fatal(err)
}
httpClient = &http.Client{
Timeout: 30 * time.Second,
Jar: jar,
}
requestURL := "https://example.com/"
res, err := httpClient.Get(requestURL)
if err != nil {
log.Fatal(err)
}
log.Println(res.Cookies())
// stdout: cookie as expected
u := &url.URL{}
u.Parse(requestURL)
log.Println(httpClient.Jar.Cookies(u))
// stdout: []
form := make(url.Values)
/* ... */
req, err := http.NewRequest(http.MethodPost, requestURL, strings.NewReader(form.Encode()))
if err != nil {
fmt.Printf("client: could not create request: %s\n", err)
}
res, err = httpClient.Do(req)
if err != nil {
log.Fatal(err)
}
fmt.Println(req)
// stdout: cookie as expected
}

Go - Running cucumbers that uses an API

I'm using the Godog library to implement some cucumbers tests for my api code, right now I'm only testing one endpoint but I'm hitting an error where it looks like it's expecting to have a server open. I created a httptest server that listens to port 8080 but the tests are failing with a 404.
If I run my cucumber in debug mode they work but if I use the run test command they fail cos the expect an open port dial tcp localhost:8080. Could someone point me to the right direction since I quite don't know where I'm failing.
This is my godog_test
`
func mockServer() *httptest.Server {
router := mux.NewRouter()
u, _ := url.Parse("http://localhost:8080")
l, _ := net.Listen("tcp", u.Host)
server := httptest.NewUnstartedServer(router)
_ = server.Listener.Close()
server.Listener = l
server.Start()
return server
}
func killMockServer(server *httptest.Server) {
server.Close()
}
func TestFeatures(t *testing.T) {
suite := godog.TestSuite{
TestSuiteInitializer: InitializeTestSuite,
ScenarioInitializer: InitializeScenario,
Options: &godog.Options{
Format: "pretty",
Paths: []string{"features"},
TestingT: t,
},
}
if suite.Run() != 0 {
t.Fatal("non-zero status returned, failed to run feature tests")
}
}
func InitializeTestSuite(ctx *godog.TestSuiteContext) {
var server *httptest.Server
ctx.BeforeSuite(func() {
server = mockServer()
})
ctx.AfterSuite(func() {
fmt.Println("shutting down everything")
killMockServer(server)
})
}
`
Post step that I'm testing
`
func iCallPOSTTo(path string) error {
req, err := json.Marshal(reqBody)
if err != nil {
return err
}
request, err := http.NewRequest(
http.MethodPost,
endpoint+path,
bytes.NewReader(reqBody),
)
res, err := http.DefaultClient.Do(request)
if err != nil {
return err
}
resBody, err := io.ReadAll(res.Body)
if err != nil {
return err
}
res.Body.Close()
[REDACTED]
return nil
}
`
I tried using a mock server to open port 8080 since at first I was receiving a connection refused error, after that I'm getting a 404 which means that my test is not reaching my actual function that processes the post request. I'm not sure if the mock server is the correct approach on this case.

How to configure SSL authentication

I have certificate.pem that I use to perform client authentication with a remote server. When I access the server, normally Chrome pops up, asks if I want to use that certificate, I say yes, then I'm authenticated. I'm trying to figure out why it's not sending the certificate with the dialer when I call it programmatically:
type DialerHelper func() (io.ReadWriter, error)
func DialIt(addr string, port uint16, config *tls.Config) (Dialer, error) {
address := fmt.Sprintf("%s:%d", addr, port)
return DialerHelper(func() (io.ReadWriter, error) {
return tls.Dial("tcp", address, config)
}), nil
}
caPool := x509.NewCertPool()
cert, err := ioutil.ReadFile("certificate.pem")
if err != nil {
panic(err)
}
ok := caPool.AppendCertsFromPEM(cert)
if !ok {
panic(ok)
}
tlsconfig := &tls.Config{
InsecureSkipVerify: true,
RootCAs: caPool, }
tlsconfig.BuildNameToCertificate()
DialIt("some.address.com", 443, tlsconfig)
I keep getting an error from the server saying there is no client certificate supplied. Am I sending the SSL certificate correctly to the remote server? I'm not an expert with SSL.
Edit: this is the functionality I'm trying to replicate: curl -k --cert /home/me/.ssh/certificate.pem
If the server is using a cert generated from your own Certificate Authority, then the following code will do the trick.
I've never tried Client Cert Authentication in an environment where the server cert is from a public CA, so I'm not sure how you'd achieve that. Perhaps just leaving out setting config.RootCAs.
func loadCertificates(caFileName, certFileName, keyFileName string) (tls.Certificate, *x509.CertPool, error) {
myCert, err := tls.LoadX509KeyPair(certFileName, keyFileName)
if err != nil {
return tls.Certificate{}, nil, err
}
ca, err := ioutil.ReadFile(caFileName)
if err != nil {
return tls.Certificate{}, nil, err
}
certPool := x509.NewCertPool()
if !certPool.AppendCertsFromPEM(ca) {
return tls.Certificate{}, nil, errors.New("Failed appending certs")
}
return myCert, certPool, nil
}
func GetClientTlsConfiguration(caFileName, certFileName, keyFileName string) (*tls.Config, error) {
config := &tls.Config{}
myCert, certPool, err := loadCertificates(caFileName, certFileName, keyFileName)
if err != nil {
return nil, err
}
config.Certificates = make([]tls.Certificate, 1)
config.Certificates[0] = myCert
config.RootCAs = certPool
config.ClientCAs = certPool
return config, nil
}
tlsConfig, err := config.GetClientTlsConfiguration("ca.crt", "client.crt", "client.key")
if err != nil {
log.Fatalf("Error loading tls config - %v", err)
}
client := &http.Client{Transport: &http.Transport{TLSClientConfig: tlsConfig}}
client.Get(.....)

Confirm TLS certificate while performing ReverseProxy in GoLang

In Go I'm using NewSingleHostReverseProxy to perform a reverse proxy, however I need to confirm the SSL certificates of the host site, to make sure I have the correct secure certificate... any ideas how I should do this? Should I be doing this with the handler or transport? I'm new to Go and still getting my head around it.
proxy := httputil.NewSingleHostReverseProxy(&url.URL{
Scheme: "https",
Host: "sha256.badssl.com",
})
http.ListenAndServe("127.0.0.1:80", proxy)
To access the certificate, you will have get access to the ConnectionState. The easiest way to do that is to provide your own version of DialTLS. In there you connect to the server using net.Dial, do the TLS handshake and then you are free to verify.
package main
import (
"crypto/tls"
"log"
"net"
"net/http"
"net/http/httputil"
"net/url"
)
func main() {
proxy := httputil.NewSingleHostReverseProxy(&url.URL{
Scheme: "https",
Host: "sha256.badssl.com",
})
// Set a custom DialTLS to access the TLS connection state
proxy.Transport = &http.Transport{DialTLS: dialTLS}
// Change req.Host so badssl.com host check is passed
director := proxy.Director
proxy.Director = func(req *http.Request) {
director(req)
req.Host = req.URL.Host
}
log.Fatal(http.ListenAndServe("127.0.0.1:3000", proxy))
}
func dialTLS(network, addr string) (net.Conn, error) {
conn, err := net.Dial(network, addr)
if err != nil {
return nil, err
}
host, _, err := net.SplitHostPort(addr)
if err != nil {
return nil, err
}
cfg := &tls.Config{ServerName: host}
tlsConn := tls.Client(conn, cfg)
if err := tlsConn.Handshake(); err != nil {
conn.Close()
return nil, err
}
cs := tlsConn.ConnectionState()
cert := cs.PeerCertificates[0]
// Verify here
cert.VerifyHostname(host)
log.Println(cert.Subject)
return tlsConn, nil
}
To tweak the SSL to the Reverse Host, it is possible to set the transport
options. So if you want to skip the verify you can set it like this.
proxy := httputil.NewSingleHostReverseProxy(&url.URL{
Scheme: "https",
Host: "sha256.badssl.com",
})
proxy.Transport = &http.Transport{
TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
}
http.ListenAndServe("127.0.0.1:80", proxy)

Golang http client handshake failure

Try get webpage:
tr := &http.Transport{
TLSHandshakeTimeout: 30 * time.Second,
DisableKeepAlives: true,
}
client := &http.Client{Transport: tr}
req, err := http.NewRequest("GET", "https://www.fl.ru/", nil)
if err != nil {
log.Fatalf("%s\n", err);
}
resp, err := client.Do(req);
if err != nil {
log.Fatalf("%s\n", err);
}
defer resp.Body.Close()
Get https://www.fl.ru/: remote error: handshake failure.
If I try to get another HTTPS page - all is OK.
That server only supports a few, weak ciphers:
TLS_DHE_RSA_WITH_AES_256_CBC_SHA (0x39) DH 1024 bits (p: 128, g: 1, Ys: 128) FS WEAK
TLS_DHE_RSA_WITH_AES_128_CBC_SHA (0x33) DH 1024 bits (p: 128, g: 1, Ys: 128) FS WEAK
TLS_RSA_WITH_RC4_128_SHA (0x5) WEAK
If you really must connect to that server, Go does support the last cipher in the list, but not by default. Create a client with a new tls.Config specifying the cipher you want:
t := &http.Transport{
Proxy: http.ProxyFromEnvironment,
Dial: (&net.Dialer{
Timeout: 30 * time.Second,
KeepAlive: 30 * time.Second,
}).Dial,
TLSHandshakeTimeout: 10 * time.Second,
TLSClientConfig: &tls.Config{
CipherSuites: []uint16{tls.TLS_RSA_WITH_RC4_128_SHA},
},
}