Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

adding the root_cas option to SessionBuilder #139

Merged
merged 2 commits into from
May 22, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ mio = { version = "=0.8.6" }
# Default enable napi4 feature, see https://nodejs.org/api/n-api.html#node-api-version-matrix
napi = { version = "2.12.1", default-features = false, features = ["napi4", "tokio_rt"] }
napi-derive = "2.12.1"
ngrok = { version = "0.14.0-pre.12" }
ngrok = { version = "=0.14.0-pre.13" }
parking_lot = "0.12.1"
regex = "1.9.5"
rustls = "0.22.2"
Expand Down
4 changes: 4 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -223,6 +223,10 @@ const listener = await ngrok.forward({
console.log(`disconnected, addr ${addr} error: ${error}`);
},
session_metadata: "Online in One Line",
// advanced session connection configuration
server_addr: "example.com:443",
root_cas: "trusted",
session_ca_cert: fs.readFileSync("ca.pem", "utf8"),
// listener configuration
metadata: "example listener metadata from javascript",
domain: "<domain>",
Expand Down
43 changes: 41 additions & 2 deletions __test__/connect.spec.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,7 @@ test("forward https", async (t) => {
});

test("forward http2", async (t) => {
const httpServer = await makeHttp({useHttp2: true});
const httpServer = await makeHttp({ useHttp2: true });
const listener = await ngrok.forward({
// numeric port
addr: parseInt(httpServer.listenTo.split(":")[1], 10),
Expand All @@ -100,7 +100,7 @@ test("forward http2", async (t) => {
});

test("forward http2 no cert validation", async (t) => {
const httpServer = await makeHttp({useHttp2: true});
const httpServer = await makeHttp({ useHttp2: true });
const listener = await ngrok.forward({
// numeric port
addr: parseInt(httpServer.listenTo.split(":")[1], 10),
Expand Down Expand Up @@ -280,6 +280,45 @@ test.serial("forward bad domain", async (t) => {
{ instanceOf: Error }
);
t.is("ERR_NGROK_326", error.errorCode, error.message);

await shutdown(null, httpServer.socket);
});

// serial to not run into double error on a session issue
test.serial("root_cas", async (t) => {
// remove any lingering sessions
await ngrok.disconnect();

const httpServer = await makeHttp();
ngrok.authtoken(process.env["NGROK_AUTHTOKEN"]);

// tls error connecting to marketing site
var error = await t.throwsAsync(
async () => {
await ngrok.forward({
addr: httpServer.listenTo,
force_new_session: true,
root_cas: "trusted",
server_addr: "ngrok.com:443",
});
},
{ instanceOf: Error }
);
t.true(error.message.includes("tls handshake"), error.message);

// non-tls error connecting to marketing site with "host" root_cas
error = await t.throwsAsync(
async () => {
await ngrok.forward({
addr: httpServer.listenTo,
force_new_session: true,
root_cas: "host",
server_addr: "ngrok.com:443",
});
},
{ instanceOf: Error }
);
t.false(error.message.includes("tls handshake"), error.message);
});

test("policy", async (t) => {
Expand Down
4 changes: 3 additions & 1 deletion __test__/online.spec.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -143,7 +143,9 @@ test("tls backend", async (t) => {

test("unverified tls backend", async (t) => {
const session = await makeSession();
const listener = await session.httpEndpoint().verifyUpstreamTls(false)
const listener = await session
.httpEndpoint()
.verifyUpstreamTls(false)
.listenAndForward("https://dashboard.ngrok.com");

const error = await t.throwsAsync(
Expand Down
1 change: 0 additions & 1 deletion examples/nextjs/ngrok.config.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
const ngrok = require("@ngrok/ngrok");


// setup ngrok ingress in the parent process,
// in forked processes "send" will exist.
const makeListener = process.send === undefined;
Expand Down
45 changes: 45 additions & 0 deletions index.d.ts

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

32 changes: 32 additions & 0 deletions src/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,9 @@ pub struct Config {
/// and the API.
#[napi(js_name = "forwards_to")]
pub forwards_to: Option<String>,
/// Force a new session connection to be made.
#[napi(js_name = "force_new_session")]
pub force_new_session: Option<bool>,
/// Unused, will warn and be ignored
#[napi(js_name = "host_header")]
pub host_header: Option<String>,
Expand Down Expand Up @@ -249,11 +252,32 @@ pub struct Config {
/// [ngrok dashboard]: https://dashboard.ngrok.com/cloud-edge/tcp-addresses
#[napi(js_name = "remote_addr")]
pub remote_addr: Option<String>,
/// Sets the file path to a default certificate in PEM format to validate ngrok Session TLS connections.
/// Setting to "trusted" is the default, using the ngrok CA certificate.
/// Setting to "host" will verify using the certificates on the host operating system.
/// A client config set via tls_config after calling root_cas will override this value.
///
/// Corresponds to the [root_cas parameter in the ngrok docs]
///
/// [root_cas parameter in the ngrok docs]: https://ngrok.com/docs/ngrok-agent/config#root_cas
#[napi(js_name = "root_cas")]
pub root_cas: Option<String>,
/// The scheme that this edge should use.
/// "HTTPS" or "HTTP", defaults to "HTTPS".
/// If multiple are given only the last one is used.
#[napi(ts_type = "string|Array<string>")]
pub schemes: Option<Vec<String>>,
/// Configures the TLS certificate used to connect to the ngrok service while
/// establishing the session. Use this option only if you are connecting through
/// a man-in-the-middle or deep packet inspection proxy. Pass in the bytes of the certificate
/// to be used to validate the connection, then override the address to connect to via
/// the server_addr call.
///
/// Roughly corresponds to the [root_cas parameter in the ngrok docs].
///
/// [root_cas parameter in the ngrok docs]: https://ngrok.com/docs/ngrok-agent/config#root_cas
#[napi(js_name = "session_ca_cert")]
pub session_ca_cert: Option<String>,
/// Configures the opaque, machine-readable metadata string for this session.
/// Metadata is made available to you in the ngrok dashboard and the Agents API
/// resource. It is a useful way to allow you to uniquely identify sessions. We
Expand All @@ -264,6 +288,14 @@ pub struct Config {
/// [metdata parameter in the ngrok docs]: https://ngrok.com/docs/ngrok-agent/config#metadata
#[napi(js_name = "session_metadata")]
pub session_metadata: Option<String>,
/// Configures the network address to dial to connect to the ngrok service.
/// Use this option only if you are connecting to a custom agent ingress.
///
/// See the [server_addr parameter in the ngrok docs] for additional details.
///
/// [server_addr parameter in the ngrok docs]: https://ngrok.com/docs/ngrok-agent/config#server_addr
#[napi(js_name = "server_addr")]
pub server_addr: Option<String>,
/// Unused, use domain instead, will warn and be ignored
pub subdomain: Option<String>,
/// Unused, will warn and be ignored
Expand Down
18 changes: 17 additions & 1 deletion src/connect.rs
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,15 @@ macro_rules! plumb {
};
}

/// Single string configuration with result
macro_rules! plumb_with_result {
($builder:tt, $config:tt, $name:tt, $config_name:tt) => {
if let Some(ref $name) = $config.$config_name {
$builder.$name($name.clone())?;
bobzilladev marked this conversation as resolved.
Show resolved Hide resolved
}
};
}

/// Boolean configuration
macro_rules! plumb_bool {
($builder:tt, $config:tt, $name:tt) => {
Expand Down Expand Up @@ -148,6 +157,11 @@ pub fn forward(
plumb!(s_builder, cfg, authtoken);
plumb_bool!(s_builder, cfg, authtoken_from_env);
plumb!(s_builder, cfg, metadata, session_metadata);
if let Some(ref ca_cert) = cfg.session_ca_cert {
s_builder.ca_cert(Uint8Array::new(ca_cert.as_bytes().to_vec()));
}
plumb_with_result!(s_builder, cfg, root_cas, root_cas);
plumb_with_result!(s_builder, cfg, server_addr, server_addr);
bobzilladev marked this conversation as resolved.
Show resolved Hide resolved
if let Some(func) = on_connection {
s_builder.handle_connection(env, func);
}
Expand All @@ -161,9 +175,11 @@ pub fn forward(

/// Connect the session, configure and start the listener
async fn async_connect(s_builder: SessionBuilder, config: Config) -> Result<Listener> {
let force_new_session = config.force_new_session.unwrap_or(false);

// Using a singleton session for connect use cases
let mut opt = SESSION.lock().await;
if opt.is_none() {
if opt.is_none() || force_new_session {
opt.replace(s_builder.connect().await?);
}
let session = opt.as_ref().unwrap();
Expand Down
17 changes: 17 additions & 0 deletions src/session.rs
Original file line number Diff line number Diff line change
Expand Up @@ -213,6 +213,23 @@ impl SessionBuilder {
Ok(self)
}

/// Sets the file path to a default certificate in PEM format to validate ngrok Session TLS connections.
/// Setting to "trusted" is the default, using the ngrok CA certificate.
/// Setting to "host" will verify using the certificates on the host operating system.
/// A client config set via tls_config after calling root_cas will override this value.
///
/// Corresponds to the [root_cas parameter in the ngrok docs]
///
/// [root_cas parameter in the ngrok docs]: https://ngrok.com/docs/ngrok-agent/config#root_cas
#[napi]
pub fn root_cas(&mut self, root_cas: String) -> Result<&Self> {
let mut builder = self.raw_builder.lock();
builder
.root_cas(root_cas)
.map_err(|e| napi_err(format!("{e}")))?;
Ok(self)
}

/// Configures the TLS certificate used to connect to the ngrok service while
/// establishing the session. Use this option only if you are connecting through
/// a man-in-the-middle or deep packet inspection proxy. Pass in the bytes of the certificate
Expand Down
Loading