This following reduced test case code works when run locally on my laptop using my own 'developer' certs for accessing internal services
If I run on a remote machine with dynamically generated certs (all of which is handled by a separate team in my organisation) it fails with a 400 and "No required SSL certificate was sent" error
But if I use curl on the remote machine, and specify the same certs as referenced in my Go code, it will work
So seems the certs aren't the issue but the Go code, but that itself doesn't seem to be the issue as it works with my own certs locally
package main
import (
"crypto/tls"
"crypto/x509"
"fmt"
"io/ioutil"
"net/http"
"os"
"time"
)
func main() {
transport, transErr := configureTLS()
if transErr != nil {
fmt.Printf("trans error: %s", transErr.Error())
return
}
timeout := time.Duration(1 * time.Second)
client := http.Client{
Transport: transport,
Timeout: timeout,
}
resp, clientErr := client.Get("https://my-service-with-nginx/")
if clientErr != nil {
fmt.Printf("client error: %s", clientErr.Error())
} else {
defer resp.Body.Close()
contents, contErr := ioutil.ReadAll(resp.Body)
if contErr != nil {
fmt.Printf("contents error: %s", contErr.Error())
}
fmt.Printf("
contents:
%+v", string(contents))
}
}
func configureTLS() (*http.Transport, error) {
certPath := "/path/to/client.crt"
keyPath := "/path/to/client.key"
caPath := "/path/to/ca.crt"
// Load client cert
cert, err := tls.LoadX509KeyPair(certPath, keyPath)
if err != nil {
return nil, err
}
// Load CA cert
caCert, err := ioutil.ReadFile(caPath)
if err != nil {
return nil, err
}
caCertPool := x509.NewCertPool()
caCertPool.AppendCertsFromPEM(caCert)
// Setup HTTPS client
tlsConfig := &tls.Config{
Certificates: []tls.Certificate{cert},
RootCAs: caCertPool,
InsecureSkipVerify: true,
}
tlsConfig.BuildNameToCertificate()
return &http.Transport{TLSClientConfig: tlsConfig}, nil
}
Does anyone know why this would be happening?
I thought it might be the renegotiation bug that Go has (as of 1.6) but I don't think that's the case here as otherwise it would fail for me when running the app locally (but it doesn't, using my own dev certs and running locally works fine - the problem only occurs when run on a remote instance with different certs; and those certs aren't the problem as they work fine when used by curl
)
So the actual problem here is part related to my organisations infrastructure and part related to how nginx uses ssl_client_certificate
.
We have int, test and live environments
I was led to believe that the environments could communicate between each other.
So I had my service setup on int, and I was able to use curl from there to communicate with another service setup in a live environment.
The problem occurred when using Go to communicate across environments.
The quick solution for me was to ensure when I made a GET to this other service, that instead of using:
https://service.live.me.com
I would use:
https://service.int.me.com
Or:
https://service.test.me.com
Depending on the environment my code was running within.
Obviously this isn't a solution for other people who have a similar issue but don't have the same setup.
So for those of you who still need a solution...
What I was also going to try (and apparently worked for this guy) was to get the service that I was trying to communicate with to modify their nginx conf so that they set ssl_client_certificate
to not point to just a client cert, but a combined cert (one that includes the entire CA chain).
This was because apparently Go doesn't send back any certs with its response unless the server has passed a CA along with its response.
I had originally suspected this was the classic renegotiation bug in Go 1.6 and below, but that wasn't the case in this scenario.
Hope this helps anyone else in the same boat.