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 #114

Merged
merged 4 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
4 changes: 4 additions & 0 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,11 @@ jobs:
ls -lah
whoami
env
echo "apk add:"
apk add python3-dev
echo "apk update:"
apk update
echo "create venv:"
python3 -m venv .env
. .env/bin/activate && pip install -r requirements.txt
. .env/bin/activate && pip install patchelf
Expand Down
2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ log = "0.4"
# pin mio until all dependencies are also on windows-sys 0.48
# https://github.com/microsoft/windows-rs/issues/2410#issuecomment-1490802715
mio = { version = "=0.8.6" }
ngrok = { version = "=0.14.0-pre.12" }
ngrok = { version = "=0.14.0-pre.13" }
pyo3 = { version = "0.18.1", features = ["abi3", "abi3-py37", "extension-module", "multiple-pymethods"]}
pyo3-asyncio = { version = "0.18.0", features = ["attributes", "tokio-runtime"] }
pyo3-log = { version = "0.8.1" }
Expand Down
4 changes: 4 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -193,6 +193,10 @@ listener = ngrok.forward(
authtoken_from_env=True,
app_protocol="http2",
session_metadata="Online in One Line",
# advanced session connection configuration
server_addr="example.com:443",
root_cas="trusted",
session_ca_cert=load_file("ca.pem"),
# listener configuration
metadata="example listener metadata from python",
domain="<domain>",
Expand Down
2 changes: 1 addition & 1 deletion requirements.txt
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
aiohttp==3.8.4
aiohttp==3.9.5
black==23.3.0
furo==2022.12.7
maturin==0.14.16
Expand Down
12 changes: 11 additions & 1 deletion src/connect.rs
Original file line number Diff line number Diff line change
Expand Up @@ -252,14 +252,24 @@ fn configure_session(options: &Py<PyDict>) -> Result<SessionBuilder, PyErr> {
plumb!(B, s_builder, cfg, authtoken);
plumb_bool!(B, s_builder, cfg, authtoken_from_env);
plumb!(B, s_builder, cfg, metadata, session_metadata);
plumb_vec!(B, s_builder, cfg, ca_cert, session_ca_cert, vecu8);
plumb!(B, s_builder, cfg, root_cas, root_cas);
plumb!(B, s_builder, cfg, server_addr, server_addr);
Ok(s_builder.replace(SessionBuilder::new()))
})
}

async fn do_connect(options: Py<PyDict>) -> PyResult<PyObject> {
let force_new_session = Python::with_gil(|py| -> PyResult<bool> {
if let Some(v) = options.as_ref(py).get_item("force_new_session") {
return get_bool(v);
}
Ok(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(configure_session(&options)?.async_connect().await?);
}
let session = opt.as_ref().unwrap();
Expand Down
19 changes: 18 additions & 1 deletion src/session.rs
Original file line number Diff line number Diff line change
Expand Up @@ -272,7 +272,24 @@ impl SessionBuilder {
/// .. _server_addr parameter in the ngrok docs: https://ngrok.com/docs/ngrok-agent/config#server_addr
pub fn server_addr(self_: PyRefMut<Self>, addr: String) -> PyRefMut<Self> {
self_.set(|b| {
b.server_addr(addr).expect("fixme");
b.server_addr(&addr)
.unwrap_or_else(|_| panic!("failed to parse addr: {addr}"));
});
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
pub fn root_cas(self_: PyRefMut<Self>, root_cas: String) -> PyRefMut<Self> {
self_.set(|b| {
b.root_cas(&root_cas)
.unwrap_or_else(|_| panic!("failed to invoke root_cas: {root_cas}"));
});
self_
}
Expand Down
32 changes: 32 additions & 0 deletions test/test_connect.py
Original file line number Diff line number Diff line change
Expand Up @@ -242,6 +242,38 @@ async def test_invalid_connect_policy(self):
error = err
self.assertIsInstance(error, ValueError)
self.assertTrue("parse policy" in f"{error}")
shutdown(None, http_server)

def test_root_cas(self):
http_server = test.make_http()
error = None
# tls error connecting to marketing site
try:
listener = ngrok.connect(
http_server.listen_to,
authtoken_from_env=True,
force_new_session=True,
root_cas="trusted",
server_addr="ngrok.com:443",
)
except ValueError as err:
error = err
self.assertIsInstance(error, ValueError)
self.assertTrue("tls handshake" in f"{error}", error)

# non-tls error connecting to marketing site with "host" root_cas
try:
listener = ngrok.connect(
http_server.listen_to,
authtoken_from_env=True,
force_new_session=True,
root_cas="host",
server_addr="ngrok.com:443",
)
except ValueError as err:
error = err
self.assertIsInstance(error, ValueError)
self.assertFalse("tls handshake" in f"{error}", error)


if __name__ == "__main__":
Expand Down
Loading