Mutual TLS
Overview
This module performs mutual TLS (mTLS) authentication for your TLS endpoints. The client must present a valid TLS certificate that is signed by one of the specified CAs or the connection will be rejected.
Mutual TLS can only be enforced where TLS is terminated. This means ngrok's Mutual TLS module may only be used where you also use ngrok to terminates TLS on your behalf. You may choose whether Mutual TLS is enforced at ngrok's edge or in the ngrok agent.
Example Usage
Remember that by default, TLS endpoints do not terminate TLS connections and send them through to your upstream service. Thus, all examples you see for mutual TLS will also include TLS termination.
mTLS at ngrok edge
Only allow connections which present a client certificate signed by one of the CAs present in the PEM bundle.
- Agent CLI
- Agent Config
- SSH
- Go
- Javascript
- Python
- Rust
- Kubernetes Controller
ngrok tls 80 \
--domain app.example.com \
--terminate-at edge \
--mutual-tls-cas /path/to/cas.pem
tunnels:
example:
proto: "tls"
addr: 80
domain: "app.example.com"
terminate_at: "edge"
mutual_tls_cas: "/path/to/cas.pem"
Mutual TLS is not supported via SSH.
import (
"context"
"crypto/x509"
"encoding/pem"
"net"
"os"
"golang.ngrok.com/ngrok"
"golang.ngrok.com/ngrok/config"
)
func ngrokListener(ctx context.Context) (net.Listener, error) {
caBytes, _ := os.ReadFile("/path/to/cas.pem")
der, _ := pem.Decode(caBytes)
certs, _ := x509.ParseCertificates(der.Bytes)
return ngrok.Listen(ctx,
config.TLSEndpoint(
config.WithDomain("app.example.com"),
config.WithTLSTermination(
config.WithTLSTerminationAt(config.TLSAtEdge),
),
config.WithMutualTLSCA(certs...),
),
ngrok.WithAuthtokenFromEnv(),
)
}
Go Package Docs:
const ngrok = require("@ngrok/ngrok");
const fs = require("fs");
(async function () {
const listener = await ngrok.forward({
addr: 8080,
authtoken_from_env: true,
proto: "tls",
domain: "app.example.com",
crt: fs.readFileSync("/path/to/app-example-com-crt.pem", "utf8"),
key: fs.readFileSync("/path/to/app-example-com-key.pem", "utf8"),
mutual_tls_cas: [fs.readFileSync("/path/to/cas.pem", "utf8")],
});
console.log(`Ingress established at: ${listener.url()}`);
})();
Javascript SDK Docs:
-
https://ngrok.github.io/ngrok-javascript/interfaces/Config.html#crt
-
https://ngrok.github.io/ngrok-javascript/interfaces/Config.html#key
-
https://ngrok.github.io/ngrok-javascript/interfaces/Config.html#mutual_tls_cas
-
https://ngrok.github.io/ngrok-javascript/classes/TlsListenerBuilder.html#termination
-
https://ngrok.github.io/ngrok-javascript/classes/TlsListenerBuilder.html#mutualTlsca
import ngrok
def load_file(name):
with open(name, "r") as crt:
return bytearray(crt.read().encode())
listener = ngrok.forward("localhost:8080", authtoken_from_env=True,
proto="tls",
domain="app.example.com",
crt=load_file("/path/to/app-example-com-crt.pem"),
key=load_file("/path/to/app-example-com-key.pem"),
mutual_tls_cas=load_file("/path/to/cas.pem"))
print(f"Ingress established at: {listener.url()}");
Python SDK Docs:
use ngrok::prelude::*;
async fn listen_ngrok() -> anyhow::Result<impl Tunnel> {
let cert: &[u8] = load_bytes!("/path/to/app-example-com-crt.pem");
let key: &[u8] = load_bytes!("/path/to/app-example-com-key.pem");
let ca_cert: &[u8] = load_bytes!("/path/to/cas.pem");
let sess = ngrok::Session::builder()
.authtoken_from_env()
.connect()
.await?;
let tun = sess
.tls_endpoint()
.domain("app.example.com")
.termination(cert.into(), key.into())
.mutual_tlsca(ca_cert.into())
.listen()
.await?;
println!("Listening on URL: {:?}", tun.url());
Ok(tun)
}
Rust Crate Docs:
TLS endpoints are not supported by the Kubernetes Ingress Controller
mTLS at ngrok agent
Mutual TLS enforcement at the ngrok agent is used with Zero-Knowledge TLS end-to-end encryption.
- Agent CLI
- Agent Config
- SSH
- Go
- Javascript
- Python
- Rust
- Kubernetes Controller
ngrok tls 80 \
--domain app.example.com \
--terminate-at agent \
--crt /path/to/app-example-com-crt.pem \
--key /path/to/app-example-com-crt.key \
--mutual-tls-cas /path/to/cas.pem
tunnels:
example:
proto: "tls"
addr: 80
domain: "app.example.com"
terminate_at: "agent"
mutual_tls_cas: "/path/to/cas.pem"
crt: "/path/to/app-example-com-crt.pem"
key: "/path/to/app-example-com-crt.key"
Mutual TLS is not supported via SSH.
The Go SDK does not support Zero-Knowledge Mutual TLS enforcement at the SDK.
The Javascript SDK does not support Zero-Knowledge Mutual TLS enforcement at the SDK.
The Python SDK does not support Zero-Knowledge Mutual TLS enforcement at the SDK.
The Rust SDK does not support Zero-Knowledge Mutual TLS enforcement at the SDK.
TLS endpoints are not supported by the Kubernetes Ingress Controller
Behavior
Multiple CAs
You may specify multiple CAs to be used for mTLS authentication. A connection is considered authenticated if it presents a certificate signed by any of the specified CAs. Agents allow you to specify multiple CAs by simply specifying a PEM file that contains multiple x509 CA certificates concatenated together. A file like that might look like:
-----BEGIN CERTIFICATE-----
...
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
...
-----END CERTIFICATE-----
CA Basic Constraint
x509 certificates contain a basic constraint attribute called cA
which
defines whether or not the certificate may be used as a CA.
ngrok will refuse to accept a certificate as an mTLS certificate authority unless this constraint is set to true.
See RFC 5280 4.2.1.9.