For the complete documentation index, see llms.txt. This page is also available as Markdown.

Mutual TLS

Mutual TLS authentication between nodes

Standard TLS provides server authentication - the client verifies the server's certificate. Mutual TLS (mTLS) adds client authentication - both sides present and verify certificates. Only clients with certificates signed by a trusted CA can connect.

Configuration

func startSecureNode(name string) (gen.Node, error) {
    // Load node certificate (signed by cluster CA)
    cert, err := tls.LoadX509KeyPair(
        fmt.Sprintf("%s.pem", name),
        fmt.Sprintf("%s-key.pem", name),
    )
    if err != nil {
        return nil, err
    }

    // Load cluster CA
    caCert, err := os.ReadFile("cluster-ca.pem")
    if err != nil {
        return nil, err
    }
    caPool := x509.NewCertPool()
    caPool.AppendCertsFromPEM(caCert)

    certManager := gen.CreateCertAuthManager(cert)
    certManager.SetClientCAs(caPool)                          // verify incoming
    certManager.SetClientAuth(tls.RequireAndVerifyClientCert) // require client cert
    certManager.SetRootCAs(caPool)                            // verify outgoing

    return ergo.StartNode(gen.Atom(name), gen.NodeOptions{
        CertManager: certManager,
    })
}

NodeOptions.CertManager is used for:

  • Default acceptor (created automatically on gen.DefaultPort, 11144)

  • All outgoing connections

To override per-acceptor, use AcceptorOptions.CertManager.

CertAuthManager

gen.CertAuthManager extends CertManager with CA pool and authentication settings:

Every setter is paired with a getter, and the getters are not decoration: they are what the network stack calls. A type that implements only the four setters does not satisfy the interface.

Server-side settings:

Setting
Purpose

ClientCAs

CA pool to verify client certificates

ClientAuth

How strictly to enforce client certificates

ClientAuth values:

Value
Behavior

tls.NoClientCert

Don't request client certificate (default)

tls.RequestClientCert

Request but don't require

tls.RequireAnyClientCert

Require certificate, don't verify against CA

tls.VerifyClientCertIfGiven

Verify against CA if provided

tls.RequireAndVerifyClientCert

Require and verify against CA

Client-side settings:

Setting
Purpose

RootCAs

CA pool to verify server certificates

ServerName

Server name for SNI (if different from host)

Runtime Certificate Rotation

Certificates can be rotated without restart:

New connections use the updated certificate. Existing connections keep their original certificate.

CA pools and ClientAuth are rotatable too, on a gen.CertAuthManager. The listener installs a per-connection TLS callback, so every incoming handshake re-reads ClientCAs() and ClientAuth() from the manager: calling SetClientCAs or SetClientAuth takes effect on the live listener, for the next connection, with no restart. Outgoing connections read RootCAs() and ServerName() at dial time, so those apply from the next dial. Only connections already established keep the settings they were made with.

To use different certificates for specific destinations, see Static Routes.

Troubleshooting

Connection rejected with certificate error

Verify the client certificate is signed by a CA in the server's ClientCAs pool. Check certificate expiration dates.

Server certificate verification failed

The server's certificate must be signed by a CA in the client's RootCAs pool. For development, disable verification with NetworkOptions.InsecureSkipVerify: true.

SNI mismatch

Set ServerName on the client's CertAuthManager if the certificate's Common Name doesn't match the connection address.

Certificate rotation not taking effect

Updates apply to new connections only. Close existing connections to force reconnection with new certificate.

CA pool changes not taking effect

Check that the manager is a gen.CertAuthManager and that it is the one the acceptor holds - the per-connection re-read only happens for that type. Changes apply to the next connection, not to open ones, so an existing connection has to be closed to be re-verified.

Last updated

Was this helpful?