Skip to content

Commit ae760af

Browse files
committed
adding the root_cas option to SessionBuilder
1 parent 8c1a453 commit ae760af

File tree

9 files changed

+155
-6
lines changed

9 files changed

+155
-6
lines changed

Cargo.toml

+1-1
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ mio = { version = "=0.8.6" }
1717
# Default enable napi4 feature, see https://nodejs.org/api/n-api.html#node-api-version-matrix
1818
napi = { version = "2.12.1", default-features = false, features = ["napi4", "tokio_rt"] }
1919
napi-derive = "2.12.1"
20-
ngrok = { version = "0.14.0-pre.12" }
20+
ngrok = { version = "=0.14.0-pre.13" }
2121
parking_lot = "0.12.1"
2222
regex = "1.9.5"
2323
rustls = "0.22.2"

README.md

+4
Original file line numberDiff line numberDiff line change
@@ -223,6 +223,10 @@ const listener = await ngrok.forward({
223223
console.log(`disconnected, addr ${addr} error: ${error}`);
224224
},
225225
session_metadata: "Online in One Line",
226+
// advanced session connection configuration
227+
server_addr: "example.com:443",
228+
root_cas: "trusted",
229+
session_ca_cert: fs.readFileSync("ca.pem", "utf8"),
226230
// listener configuration
227231
metadata: "example listener metadata from javascript",
228232
domain: "<domain>",

__test__/connect.spec.mjs

+36-2
Original file line numberDiff line numberDiff line change
@@ -81,7 +81,7 @@ test("forward https", async (t) => {
8181
});
8282

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

102102
test("forward http2 no cert validation", async (t) => {
103-
const httpServer = await makeHttp({useHttp2: true});
103+
const httpServer = await makeHttp({ useHttp2: true });
104104
const listener = await ngrok.forward({
105105
// numeric port
106106
addr: parseInt(httpServer.listenTo.split(":")[1], 10),
@@ -282,6 +282,40 @@ test.serial("forward bad domain", async (t) => {
282282
t.is("ERR_NGROK_326", error.errorCode, error.message);
283283
});
284284

285+
// serial to not run into double error on a session issue
286+
test.serial("root_cas", async (t) => {
287+
const httpServer = await makeHttp();
288+
ngrok.authtoken(process.env["NGROK_AUTHTOKEN"]);
289+
290+
// tls error connecting to marketing site
291+
var error = await t.throwsAsync(
292+
async () => {
293+
await ngrok.forward({
294+
addr: httpServer.listenTo,
295+
force_new_session: true,
296+
root_cas: "trusted",
297+
server_addr: "ngrok.com:443",
298+
});
299+
},
300+
{ instanceOf: Error }
301+
);
302+
t.true(error.message.includes("tls handshake"), error.message);
303+
304+
// non-tls error connecting to marketing site with "host" root_cas
305+
error = await t.throwsAsync(
306+
async () => {
307+
await ngrok.forward({
308+
addr: httpServer.listenTo,
309+
force_new_session: true,
310+
root_cas: "host",
311+
server_addr: "ngrok.com:443",
312+
});
313+
},
314+
{ instanceOf: Error }
315+
);
316+
t.false(error.message.includes("tls handshake"), error.message);
317+
});
318+
285319
test("policy", async (t) => {
286320
const policy = fs.readFileSync(path.resolve("__test__", "policy.json"), "utf8");
287321

__test__/online.spec.mjs

+3-1
Original file line numberDiff line numberDiff line change
@@ -143,7 +143,9 @@ test("tls backend", async (t) => {
143143

144144
test("unverified tls backend", async (t) => {
145145
const session = await makeSession();
146-
const listener = await session.httpEndpoint().verifyUpstreamTls(false)
146+
const listener = await session
147+
.httpEndpoint()
148+
.verifyUpstreamTls(false)
147149
.listenAndForward("https://dashboard.ngrok.com");
148150

149151
const error = await t.throwsAsync(

examples/nextjs/ngrok.config.js

-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
const ngrok = require("@ngrok/ngrok");
22

3-
43
// setup ngrok ingress in the parent process,
54
// in forked processes "send" will exist.
65
const makeListener = process.send === undefined;

index.d.ts

+45
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/config.rs

+32
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,9 @@ pub struct Config {
6565
/// and the API.
6666
#[napi(js_name = "forwards_to")]
6767
pub forwards_to: Option<String>,
68+
/// Force a new session connection to be made.
69+
#[napi(js_name = "force_new_session")]
70+
pub force_new_session: Option<bool>,
6871
/// Unused, will warn and be ignored
6972
#[napi(js_name = "host_header")]
7073
pub host_header: Option<String>,
@@ -249,11 +252,32 @@ pub struct Config {
249252
/// [ngrok dashboard]: https://dashboard.ngrok.com/cloud-edge/tcp-addresses
250253
#[napi(js_name = "remote_addr")]
251254
pub remote_addr: Option<String>,
255+
/// Sets the file path to a default certificate in PEM format to validate ngrok Session TLS connections.
256+
/// Setting to "trusted" is the default, using the ngrok CA certificate.
257+
/// Setting to "host" will verify using the certificates on the host operating system.
258+
/// A client config set via tls_config after calling root_cas will override this value.
259+
///
260+
/// Corresponds to the [root_cas parameter in the ngrok docs]
261+
///
262+
/// [root_cas parameter in the ngrok docs]: https://ngrok.com/docs/ngrok-agent/config#root_cas
263+
#[napi(js_name = "root_cas")]
264+
pub root_cas: Option<String>,
252265
/// The scheme that this edge should use.
253266
/// "HTTPS" or "HTTP", defaults to "HTTPS".
254267
/// If multiple are given only the last one is used.
255268
#[napi(ts_type = "string|Array<string>")]
256269
pub schemes: Option<Vec<String>>,
270+
/// Configures the TLS certificate used to connect to the ngrok service while
271+
/// establishing the session. Use this option only if you are connecting through
272+
/// a man-in-the-middle or deep packet inspection proxy. Pass in the bytes of the certificate
273+
/// to be used to validate the connection, then override the address to connect to via
274+
/// the server_addr call.
275+
///
276+
/// Roughly corresponds to the [root_cas parameter in the ngrok docs].
277+
///
278+
/// [root_cas parameter in the ngrok docs]: https://ngrok.com/docs/ngrok-agent/config#root_cas
279+
#[napi(js_name = "session_ca_cert")]
280+
pub session_ca_cert: Option<String>,
257281
/// Configures the opaque, machine-readable metadata string for this session.
258282
/// Metadata is made available to you in the ngrok dashboard and the Agents API
259283
/// resource. It is a useful way to allow you to uniquely identify sessions. We
@@ -264,6 +288,14 @@ pub struct Config {
264288
/// [metdata parameter in the ngrok docs]: https://ngrok.com/docs/ngrok-agent/config#metadata
265289
#[napi(js_name = "session_metadata")]
266290
pub session_metadata: Option<String>,
291+
/// Configures the network address to dial to connect to the ngrok service.
292+
/// Use this option only if you are connecting to a custom agent ingress.
293+
///
294+
/// See the [server_addr parameter in the ngrok docs] for additional details.
295+
///
296+
/// [server_addr parameter in the ngrok docs]: https://ngrok.com/docs/ngrok-agent/config#server_addr
297+
#[napi(js_name = "server_addr")]
298+
pub server_addr: Option<String>,
267299
/// Unused, use domain instead, will warn and be ignored
268300
pub subdomain: Option<String>,
269301
/// Unused, will warn and be ignored

src/connect.rs

+17-1
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,15 @@ macro_rules! plumb {
3939
};
4040
}
4141

42+
/// Single string configuration with result
43+
macro_rules! plumb_with_result {
44+
($builder:tt, $config:tt, $name:tt, $config_name:tt) => {
45+
if let Some(ref $name) = $config.$config_name {
46+
$builder.$name($name.clone())?;
47+
}
48+
};
49+
}
50+
4251
/// Boolean configuration
4352
macro_rules! plumb_bool {
4453
($builder:tt, $config:tt, $name:tt) => {
@@ -148,6 +157,11 @@ pub fn forward(
148157
plumb!(s_builder, cfg, authtoken);
149158
plumb_bool!(s_builder, cfg, authtoken_from_env);
150159
plumb!(s_builder, cfg, metadata, session_metadata);
160+
if let Some(ref ca_cert) = cfg.session_ca_cert {
161+
s_builder.ca_cert(Uint8Array::new(ca_cert.as_bytes().to_vec()));
162+
}
163+
plumb_with_result!(s_builder, cfg, root_cas, root_cas);
164+
plumb_with_result!(s_builder, cfg, server_addr, server_addr);
151165
if let Some(func) = on_connection {
152166
s_builder.handle_connection(env, func);
153167
}
@@ -161,9 +175,11 @@ pub fn forward(
161175

162176
/// Connect the session, configure and start the listener
163177
async fn async_connect(s_builder: SessionBuilder, config: Config) -> Result<Listener> {
178+
let force_new_session = config.force_new_session.unwrap_or(false);
179+
164180
// Using a singleton session for connect use cases
165181
let mut opt = SESSION.lock().await;
166-
if opt.is_none() {
182+
if opt.is_none() || force_new_session {
167183
opt.replace(s_builder.connect().await?);
168184
}
169185
let session = opt.as_ref().unwrap();

src/session.rs

+17
Original file line numberDiff line numberDiff line change
@@ -213,6 +213,23 @@ impl SessionBuilder {
213213
Ok(self)
214214
}
215215

216+
/// Sets the file path to a default certificate in PEM format to validate ngrok Session TLS connections.
217+
/// Setting to "trusted" is the default, using the ngrok CA certificate.
218+
/// Setting to "host" will verify using the certificates on the host operating system.
219+
/// A client config set via tls_config after calling root_cas will override this value.
220+
///
221+
/// Corresponds to the [root_cas parameter in the ngrok docs]
222+
///
223+
/// [root_cas parameter in the ngrok docs]: https://ngrok.com/docs/ngrok-agent/config#root_cas
224+
#[napi]
225+
pub fn root_cas(&mut self, root_cas: String) -> Result<&Self> {
226+
let mut builder = self.raw_builder.lock();
227+
builder
228+
.root_cas(root_cas)
229+
.map_err(|e| napi_err(format!("{e}")))?;
230+
Ok(self)
231+
}
232+
216233
/// Configures the TLS certificate used to connect to the ngrok service while
217234
/// establishing the session. Use this option only if you are connecting through
218235
/// a man-in-the-middle or deep packet inspection proxy. Pass in the bytes of the certificate

0 commit comments

Comments
 (0)