Como implementar mTLS em aplicações Go

Hoje vamos explorar como implementar o mTLS (Mutual TLS), que é uma técnica de segurança para autenticação mútua entre cliente e servidor, em uma aplicação Go.

Mas antes de começar, vamos entender um pouco melhor o que é TLS (Transport Layer Security) e qual sua diferença com mTLS.

TLS vs mTLS

O TLS é usado para criptografar a comunicação entre cliente e servidor. Nele, comumente usado em conexões HTTPS, apenas o servidor se autentica perante o cliente, usando um certificado digital para garantir que o cliente está se comunicando com o servidor correto.

Já no mTLS, tanto o cliente quanto o servidor se autenticam mutuamente usando certificados. Isso significa que, além do servidor provar sua identidade ao cliente, o cliente também deve provar sua identidade ao servidor.

Assim, com o mTLS, adicionamos uma camada extra de segurança. Essa é uma prática comum em ambientes corporativos e aplicações que exigem alto nível de confiança e segurança nas comunicações.

Gerando certificado (CA)

O primeiro passo para implementar o mTLS é ter um certificado de segurança.

Os serviços mais conhecidos para se obter certificados para mTLS são:

  1. DigiCert: Oferece uma ampla gama de certificados SSL/TLS e é adequado para implementações de mTLS.
  2. GlobalSign: Além dos certificados padrão, possui opções específicas para empresas e pode ser usado para mTLS.
  3. Entrust Datacard: Conhecida por oferecer soluções seguras de certificados digitais, incluindo opções para mTLS.

mTLS no server

Na implementação do servidor, precisamos carregar tantos o certificado do servidor, quanto a CA para verificar o cliente.

package main

import (
    "crypto/tls"
    "log"
    "net/http"
)

func main() {
    // Carregar certificados do servidor e da CA
    cer, err := tls.LoadX509KeyPair("servidor.crt", "servidor.key")
    if err != nil {
        log.Fatal(err)
    }

    // Configurar TLS
    config := &tls.Config{
        Certificates: []tls.Certificate{cer},
        ClientAuth:   tls.RequireAndVerifyClientCert,
        // Certificado da CA para verificar o cliente
        ClientCAs:    loadCA("ca.crt"),
    }

    // Criar servidor com configuração TLS
    server := &http.Server{
        Addr:      ":443",
        TLSConfig: config,
    }

    log.Fatal(server.ListenAndServeTLS("", ""))
}

func loadCA(caFile string) *x509.CertPool {
    caCert, err := ioutil.ReadFile(caFile)
    if err != nil {
        log.Fatal(err)
    }

    caPool := x509.NewCertPool()
    caPool.AppendCertsFromPEM(caCert)
    return caPool
}

No exemplo acima, note que não estamos utilizando a função ListenAndServeTLS do package net/http, mas sim o método ListenAndServeTLS da struct http.Server.

Outro ponto de atenção é que, nessa abordagem, caso estivéssemos utilizando o go-chi para lidar com as rotas, precisamos passá-lo diretamente à struct http.Server, mais especificamente no atributo Handler.

mTLS no client

No lado cliente da conexão, precisamos do certificado do cliente e da CA, para que o servidor consiga verificar a autenticidade do cliente.

package main

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

func main() {
    // Carregar certificado do cliente e chave privada
    cert, err := tls.LoadX509KeyPair("cliente.crt", "cliente.key")
    if err != nil {
        log.Fatal(err)
    }

    // Carregar CA
    caCert, err := ioutil.ReadFile("ca.crt")
    if err != nil {
        log.Fatal(err)
    }
    caCertPool := x509.NewCertPool()
    caCertPool.AppendCertsFromPEM(caCert)

    // Configurar TLS
    tlsConfig := &tls.Config{
        Certificates: []tls.Certificate{cert},
        RootCAs:      caCertPool,
    }
    tlsConfig.BuildNameToCertificate()

    // Criar cliente HTTP com configuração TLS
    client := &http.Client{
        Transport: &http.Transport{
            TLSClientConfig: tlsConfig,
        },
    }

    // Fazer requisição
    resp, err := client.Get("<https://servidor.exemplo.com>")
    if err != nil {
        log.Fatal(err)
    }
    defer resp.Body.Close()
    // Processar resposta...
}

Embora no exemplo eu tenha colocado tudo dentro da função main, idealmente, você pode criar um package que forneça o client já configurado. Assim, você conseguirá facilmente reutilizá-lo em qualquer parte da aplicação.

Agora que já temos a configuração do servidor e cliente, só falta testar a comunicação para garantir que a autenticação mútua está funcionando corretamente.

Conclusão

Implementar mTLS aumenta significativamente a segurança da comunicação entre seus serviços. É um processo mais complexo, mas essencial para ambientes que exigem alta segurança.

Espero que este guia ajude você a começar com mTLS em Go.

Até a próxima!


Faça parte da comunidade!

Receba os melhores conteúdos sobre Go, Kubernetes, arquitetura de software, Cloud e esteja sempre atualizado com as tendências e práticas do mercado.

Livros Recomendados

Abaixo listei alguns dos melhores livros que já li sobre GO.

Deixe uma resposta