0
0
MicroservicesHow-ToIntermediate ยท 4 min read

How to Use Mutual TLS in Microservices for Secure Communication

To use mutual TLS (mTLS) in microservices, both client and server authenticate each other using certificates during the TLS handshake. This ensures encrypted communication and verifies identities, preventing unauthorized access between services.
๐Ÿ“

Syntax

Mutual TLS requires both client and server to present certificates during the TLS handshake. The server verifies the client's certificate, and the client verifies the server's certificate.

Key parts include:

  • Server certificate: Proves server identity to client.
  • Client certificate: Proves client identity to server.
  • Certificate Authority (CA): Trusted entity that signs certificates.
  • TLS handshake: Process where certificates are exchanged and verified.
go
tlsConfig := &tls.Config{
    Certificates: []tls.Certificate{serverCert},
    ClientAuth: tls.RequireAndVerifyClientCert,
    ClientCAs: clientCACertPool,
}

server := &http.Server{
    Addr: ":443",
    TLSConfig: tlsConfig,
}

// Client side
clientCert, _ := tls.LoadX509KeyPair("client.crt", "client.key")
clientCACertPool := x509.NewCertPool()
clientCACertPool.AppendCertsFromPEM(caCertPEM)

clientTLSConfig := &tls.Config{
    Certificates: []tls.Certificate{clientCert},
    RootCAs: clientCACertPool,
}

client := &http.Client{
    Transport: &http.Transport{
        TLSClientConfig: clientTLSConfig,
    },
}
๐Ÿ’ป

Example

This example shows a simple Go HTTP server and client using mutual TLS. The server requires client certificates, and the client presents its certificate to authenticate.

go
package main

import (
    "crypto/tls"
    "crypto/x509"
    "fmt"
    "io/ioutil"
    "log"
    "net/http"
)

func main() {
    go startServer()
    startClient()
}

func startServer() {
    // Load server certificate and key
    serverCert, err := tls.LoadX509KeyPair("server.crt", "server.key")
    if err != nil {
        log.Fatalf("server: loadkeys: %s", err)
    }

    // Load client CA certificate
    clientCACert, err := ioutil.ReadFile("ca.crt")
    if err != nil {
        log.Fatalf("server: read client CA cert: %s", err)
    }
    clientCACertPool := x509.NewCertPool()
    clientCACertPool.AppendCertsFromPEM(clientCACert)

    tlsConfig := &tls.Config{
        Certificates: []tls.Certificate{serverCert},
        ClientAuth: tls.RequireAndVerifyClientCert,
        ClientCAs: clientCACertPool,
    }

    server := &http.Server{
        Addr: ":8443",
        TLSConfig: tlsConfig,
        Handler: http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
            fmt.Fprintf(w, "Hello, mutual TLS client!\n")
        }),
    }

    log.Println("Starting server on https://localhost:8443")
    log.Fatal(server.ListenAndServeTLS("", ""))
}

func startClient() {
    // Load client certificate and key
    clientCert, err := tls.LoadX509KeyPair("client.crt", "client.key")
    if err != nil {
        log.Fatalf("client: loadkeys: %s", err)
    }

    // Load CA certificate
    caCert, err := ioutil.ReadFile("ca.crt")
    if err != nil {
        log.Fatalf("client: read CA cert: %s", err)
    }
    caCertPool := x509.NewCertPool()
    caCertPool.AppendCertsFromPEM(caCert)

    tlsConfig := &tls.Config{
        Certificates: []tls.Certificate{clientCert},
        RootCAs: caCertPool,
    }

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

    resp, err := client.Get("https://localhost:8443")
    if err != nil {
        log.Fatalf("client: get: %s", err)
    }
    defer resp.Body.Close()

    body, err := ioutil.ReadAll(resp.Body)
    if err != nil {
        log.Fatalf("client: read body: %s", err)
    }

    fmt.Printf("Server response: %s", body)
}
Output
2024/06/01 00:00:00 Starting server on https://localhost:8443 Server response: Hello, mutual TLS client!
โš ๏ธ

Common Pitfalls

  • Missing client certificate: Server rejects connection if client does not present a valid certificate.
  • Untrusted CA: Certificates must be signed by a CA trusted by both client and server.
  • Certificate expiration: Expired certificates cause handshake failures.
  • Incorrect TLS config: Forgetting to set ClientAuth: tls.RequireAndVerifyClientCert on server disables client verification.
  • Hostname mismatch: Client must verify server certificate hostname matches the server address.
go
/* Wrong: Server does not require client certs */
tlsConfig := &tls.Config{
    Certificates: []tls.Certificate{serverCert},
    ClientAuth: tls.NoClientCert, // client certs not required
}

/* Right: Server requires and verifies client certs */
tlsConfig := &tls.Config{
    Certificates: []tls.Certificate{serverCert},
    ClientAuth: tls.RequireAndVerifyClientCert,
    ClientCAs: clientCACertPool,
}
๐Ÿ“Š

Quick Reference

Remember these key points when using mutual TLS in microservices:

  • Both client and server must have certificates signed by a trusted CA.
  • Server must be configured to require and verify client certificates.
  • Clients must trust the server's CA and present their own certificate.
  • Keep certificates up to date and securely stored.
  • Test connections with tools like openssl s_client to debug TLS issues.
โœ…

Key Takeaways

Mutual TLS ensures both client and server authenticate each other using certificates.
Configure the server to require and verify client certificates for secure microservice communication.
Use certificates signed by a trusted CA on both sides to avoid handshake failures.
Keep TLS configurations correct and certificates valid to prevent connection errors.
Test and debug mTLS setups with tools like openssl to ensure proper security.