Skip to Content
Course content

155: Basic TLS Configuration

Click on the "Edit" button in the top corner of the screen to edit your slide content.

I've spent a lot of time reviewing PRs from developers who are just starting to implement secure communication in Go. Almost every single one of them falls into the same trap when dealing with internal services or self-signed certificates. They think InsecureSkipVerify: true is a "development mode" toggle that's perfectly fine for internal traffic.

The "InsecureSkipVerify" Trap

The misconception is that by setting InsecureSkipVerify: true in your tls.Config, you're still getting the benefits of encryption, just without the "annoyance" of managing certificates. You'll often see code like this:

tr := &tls.Config{
    InsecureSkipVerify: true, // "It's just an internal microservice, it's fine"
}
client := &http.Client{
    Transport: &http.Transport{TLSClientConfig: tr},
}

Here is why that is dangerous: TLS provides two things—encryption and identity. Encryption stops people from reading your data, but identity (the certificate check) stops you from sending your encrypted data to a hacker who is pretending to be your server. When you set InsecureSkipVerify to true, you are telling Go, "I don't care who is on the other end of this connection, as long as they speak TLS." This makes you trivial to attack via a Man-in-the-Middle (MitM) exploit, even on a private network.

Building a Trust Chain with RootCAs

The correct way to handle "non-public" certificates isn't to disable security, but to tell your Go application exactly who to trust. Instead of trusting the entire world's list of Certificate Authorities (CAs), you provide your own trusted root certificate.

I usually recommend creating a custom x509.CertPool. Think of the pool as a "VIP list" of certificates. If the server presents a certificate signed by someone on that list, Go lets the connection proceed.

// Load the CA cert that signed our server's certificate
caCert, err := os.ReadFile("internal-ca.crt")
if err != nil {
    log.Fatal(err)
}

caCertPool := x509.NewCertPool()
caCertPool.AppendCertsFromPEM(caCert)

// Now we create a config that is SECURE but trusts our internal CA
tlsConfig := &tls.Config{
    RootCAs: caCertPool,
}

client := &http.Client{
    Transport: &http.Transport{
        TLSClientConfig: tlsConfig,
    },
}

Now, if someone tries to intercept your traffic with a fake certificate, the handshake will fail because that fake cert isn't signed by your specific internal-ca.crt. This is how real production systems handle internal mTLS (mutual TLS) and private PKI.

Tuning the tls.Config for Production

Once you have the trust chain working, you might want to tighten the screws. By default, Go is pretty sensible, but in high-security environments, I always explicitly define the MinVersion. TLS 1.0 and 1.1 are ancient and broken. Don't let your app negotiate down to them.

I generally set the minimum to tls.VersionTLS12, though tls.VersionTLS13 is the gold standard if you control both ends of the wire. You can also specify CipherSuites if your security auditor is breathing down your neck, but for 99% of use cases, the default Go suites are excellent.

tlsConfig := &tls.Config{
    RootCAs:    caCertPool,
    MinVersion: tls.VersionTLS12,
    // We leave CipherSuites alone unless we have a specific compliance requirement
}

Remember: the goal isn't just to "make the error go away" so the code runs. The goal is to ensure that when your client says "Hello" to a server, it actually knows who it's talking to.




📋 Practical Task

Secure Internal API Client with Custom CA Trust

You have been handed a project where a legacy internal API server uses a self-signed certificate. Your task is to write a Go client that can successfully make an HTTPS request to this server without using InsecureSkipVerify: true.

Requirements:

  • Create a program that reads a file named rootCA.pem from the local directory.
  • Initialize an x509.CertPool and add the contents of that file to it.
  • Configure an http.Client with a tls.Config that uses this pool as its RootCAs.
  • Set the MinVersion of the TLS configuration to tls.VersionTLS12.
  • Perform a GET request to https://internal-api.local/health and print the status code.

Note: Since you aren't actually running a live server here, focus on the structural implementation of the tls.Config and the CertPool logic.

Rating
0 0

There are no comments for now.

to be the first to leave a comment.