Trouble getting reverse proxy working with TLS (to proxied server) in Golang with Gin

Viewed 646

I've been tasked with creating a reverse proxy that is required to make a TLS connection to the proxied service. The certificates I have are unique per request, and in-memory.

I haven't had much luck getting it right, and I've tried a number of things. Here's where I'm at now, hopefully someone can help:

func proxy(c *gin.Context) {
   /* snip: magic here to get the x509 cert strings and remoteUrl */

   proxy := httputil.NewSingleHostReverseProxy(remoteUrl)

   cert := pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: []byte(signedCertString)})
   key:= pem.EncodeToMemory(&pem.Block{Type: "PRIVATE KEY", Bytes: []byte(privateKeyString)})

   certificate, err := tls.X509KeyPair(cert, key)
   if err != nil {
      c.JSON(400, gin.H{"message": "Invalid certificate"})
      return
   }

   proxy.Transport = &http.Transport{
     TLSClientConfig: &tls.Config{
       Certificates: []tls.Certificate{certificate},
       InsecureSkipVerify: true,
     }
   }

   c.Request.Host = remote.Host
   c.Request.URL.Scheme = remote.Scheme
   c.Request.URL.Host = remote.Host
   c.Request.URL.Path = remote.Path

   proxy.ServeHTTP(c.Writer, c.Request)
}

I've also tried setting RootCAs (figuring maybe I'm just twisted about now TLS needs to work in this scenario):


func proxy(c *gin.Context) {
   /* snip: magic here to get the x509 cert strings and remoteUrl */

   proxy := httputil.NewSingleHostReverseProxy(remoteUrl)

   cert := pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: []byte(signedCertString)})
  
   certPool := x509.NewCertPool()
   certPool.AppendCertsFromPEM(cert)

   proxy.Transport = &http.Transport{
     TLSClientConfig: &tls.Config{
       RootCAs: certPool,
       InsecureSkipVerify: true,
     }
   }

   c.Request.Host = remote.Host
   c.Request.URL.Scheme = remote.Scheme
   c.Request.URL.Host = remote.Host
   c.Request.URL.Path = remote.Path

   proxy.ServeHTTP(c.Writer, c.Request)
}

In any case, the server I'm targeting doesn't seem to pick up that the proxied request was TLS, and I'm not really sure where to proceed with this.

3 Answers

For TLS (ie https, which is http with TLS) you must connect to the correct server port. It is usually port 43 or port 8443. It is never the same as the port used for http. So this means that to start, the server must provide a port for TLS, although most do.

The only code you've shared has nothing to do with the connection to the server.

Since you've not shared any of the code making the request to the server, it is not possible to show where it is wrong.

Here is a sample

I am unable to fully reproduce your setup right now, but in principle, you are supposed to alter the request inside ReverseProxy.Director function:

Director must be a function which modifies the request into a new request to be sent using Transport.

So in short:

   proxy.Transport = &http.Transport{
     TLSClientConfig: &tls.Config{
       Certificates: []tls.Certificate{certificate},
       RootCAs: certPool,
       InsecureSkipVerify: true,
     }
   }

   proxy.Director = func(req *http.Request) {
       req.Host = remote.Host
       req.URL.Scheme = remote.Scheme
       req.URL.Host = remote.Host
       req.URL.Path = remote.Path
  }

  proxy.ServeHTTP(c.Writer, c.Request)

Also you might need both the certificates and the rootCAs in the TLS config. The certificates are those you send to the server, and the rootCA is for verifying those presented by the server.

I ended up switching to use ReverseProxy directly. But the big problem is I was using RootCAs and not ClientCAs. Here is what I ended up with:


clientCAs := x509.NewCertPool()
clientCAs.AppendCertsFromPEM(signedCert)
certificate, err := tls.X509KeyPair(signedCert, privateKey)

proxy := &httputil.ReverseProxy({
  Transport: &http.Transport{
    TLSClientConfig: &tls.Config{
      Certificates: []tls.Certificate{certificate},
      ClientCAs: clientCAs
    },
    Director: func (req *http.Request) {
      // Alter request here
    }
  },
})

proxy.ServeHTTP(w, r)

After this, everything is working swimmingly. Thanks, all!

Related