Skip to content

Commit ad90d23

Browse files
authored
Merge pull request #26 from hyperlight-dev/port-allowlist
host: add --port flag for inbound listen-port allowlist
2 parents 0eb9887 + 1c303ff commit ad90d23

9 files changed

Lines changed: 303 additions & 30 deletions

File tree

.github/workflows/test-examples.yml

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -237,6 +237,10 @@ jobs:
237237
memory: "512Mi"
238238
args: "--net -- /urllib_get.py"
239239
expect: "SUCCESS: urllib GET worked!"
240+
- example: networking-py
241+
memory: "512Mi"
242+
args: "--port 8080 -- /echo_server_test.py"
243+
expect: "SUCCESS: bind\\+listen on port 8080 allowed"
240244
steps:
241245
- uses: actions/checkout@v4
242246

@@ -569,6 +573,10 @@ jobs:
569573
memory: "512Mi"
570574
args: "--net -- /urllib_get.py"
571575
expect: "SUCCESS: urllib GET worked!"
576+
- example: networking-py
577+
memory: "512Mi"
578+
args: "--port 8080 -- /echo_server_test.py"
579+
expect: "SUCCESS: bind\\+listen on port 8080 allowed"
572580
steps:
573581
- uses: actions/checkout@v4
574582

examples/networking-py/Dockerfile

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ COPY http_get.py /http_get.py
99
COPY echo_server.py /echo_server.py
1010
COPY urllib_get.py /urllib_get.py
1111
COPY https_test.py /https_test.py
12+
COPY echo_server_test.py /echo_server_test.py
1213

1314
# --- CPIO rootfs builder ---
1415
FROM alpine:3.20 AS cpio

examples/networking-py/Justfile

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,9 +26,9 @@ run-urllib:
2626
run-https:
2727
hyperlight-unikraft {{kernel}} --initrd {{initrd}} --memory {{memory}} --net -- /https_test.py
2828

29-
# Run echo server
29+
# Run echo server (--port implies --net)
3030
run-echo:
31-
hyperlight-unikraft {{kernel}} --initrd {{initrd}} --memory {{memory}} --net -- /echo_server.py
31+
hyperlight-unikraft {{kernel}} --initrd {{initrd}} --memory {{memory}} --port 8080 -- /echo_server.py
3232

3333
# Build rootfs via Docker (cross-platform)
3434
rootfs:
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
"""Test that --port allows bind+listen and no --port would reject it.
2+
3+
This is a run-to-completion test for CI: it binds, listens, then exits.
4+
The fact that bind+listen succeed (instead of raising OSError) proves
5+
the --port allowlist is working.
6+
"""
7+
import socket
8+
9+
HOST = "0.0.0.0"
10+
PORT = 8080
11+
12+
srv = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
13+
srv.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
14+
srv.bind((HOST, PORT))
15+
srv.listen(1)
16+
srv.close()
17+
print("SUCCESS: bind+listen on port 8080 allowed")

host/examples/pyhl_as_library.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ fn main() -> anyhow::Result<()> {
2222
// expose host directories via the guest's hostfs.
2323
let mounts: &[Preopen] = &[];
2424

25-
let mut rt = pyhl::Runtime::new(&home, mounts, None)?;
25+
let mut rt = pyhl::Runtime::new(&home, mounts, None, None)?;
2626

2727
eprintln!("-- first run (hermetic from loaded snapshot) --");
2828
let t1 = rt.run_code(&code)?;

host/src/bin/pyhl.rs

Lines changed: 41 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ use hyperlight_unikraft::pyhl::{
2626
copy_replace, discover_source_artifacts, extract_from_ghcr, GHCR_INITRD_IMAGE,
2727
GHCR_KERNEL_IMAGE,
2828
};
29-
use hyperlight_unikraft::{AllowList, BlockList, NetworkPolicy, Preopen, Sandbox};
29+
use hyperlight_unikraft::{AllowList, BlockList, ListenPorts, NetworkPolicy, Preopen, Sandbox};
3030
use std::fs;
3131
use std::path::{Path, PathBuf};
3232
use std::time::Instant;
@@ -41,6 +41,7 @@ fn build_network_policy(
4141
net: bool,
4242
net_allow: &[String],
4343
net_block: &[String],
44+
has_ports: bool,
4445
) -> Result<Option<NetworkPolicy>> {
4546
if !net_allow.is_empty() {
4647
Ok(Some(NetworkPolicy::AllowList(AllowList::from_hosts(
@@ -50,13 +51,21 @@ fn build_network_policy(
5051
Ok(Some(NetworkPolicy::BlockList(BlockList::from_hosts(
5152
net_block,
5253
)?)))
53-
} else if net {
54+
} else if net || has_ports {
5455
Ok(Some(NetworkPolicy::AllowAll))
5556
} else {
5657
Ok(None)
5758
}
5859
}
5960

61+
fn build_listen_ports(ports: &[u16]) -> Option<ListenPorts> {
62+
if ports.is_empty() {
63+
None
64+
} else {
65+
Some(ListenPorts::from_ports(ports.iter().copied()))
66+
}
67+
}
68+
6069
/// Keep in sync with `py_initialize_once` in examples/python-agent-driver/
6170
/// hl_pydriver.c. These modules are imported during `pyhl setup`'s warmup
6271
/// so they're already in `sys.modules` in every `pyhl run` invocation —
@@ -187,6 +196,12 @@ struct SetupArgs {
187196
conflicts_with = "net_allow"
188197
)]
189198
net_block: Vec<String>,
199+
200+
/// Allow the guest to bind (listen) on the given port. Implies --net.
201+
/// Without this flag, `net_bind` is rejected (outbound-only).
202+
/// Repeatable: `--port 8080 --port 3000`.
203+
#[arg(long, value_name = "PORT")]
204+
port: Vec<u16>,
190205
}
191206

192207
#[derive(Args)]
@@ -236,6 +251,12 @@ struct RunArgs {
236251
)]
237252
net_block: Vec<String>,
238253

254+
/// Allow the guest to bind (listen) on the given port. Implies --net.
255+
/// Without this flag, `net_bind` is rejected (outbound-only).
256+
/// Repeatable: `--port 8080 --port 3000`.
257+
#[arg(long, value_name = "PORT")]
258+
port: Vec<u16>,
259+
239260
/// Print evolve/warmup/per-run timing to stderr. Off by default so the
240261
/// user's script output is clean.
241262
#[arg(short = 'v', long = "verbose")]
@@ -393,7 +414,13 @@ fn cmd_setup(args: SetupArgs) -> Result<()> {
393414
.iter()
394415
.map(|m| parse_mount(m))
395416
.collect::<Result<_>>()?;
396-
let network = build_network_policy(args.net, &args.net_allow, &args.net_block)?;
417+
let listen_ports = build_listen_ports(&args.port);
418+
let network = build_network_policy(
419+
args.net,
420+
&args.net_allow,
421+
&args.net_block,
422+
listen_ports.is_some(),
423+
)?;
397424

398425
eprintln!("pyhl: warming up Python and persisting snapshot…");
399426
let t_warm = Instant::now();
@@ -407,6 +434,9 @@ fn cmd_setup(args: SetupArgs) -> Result<()> {
407434
if let Some(ref policy) = network {
408435
builder = builder.network(policy.clone());
409436
}
437+
if let Some(ref lp) = listen_ports {
438+
builder = builder.listen_ports(lp.clone());
439+
}
410440
let mut sbox = builder.build()?;
411441
sbox.restore()?;
412442
let _: () = sbox.call_named("run", "pass".to_string())?;
@@ -531,7 +561,13 @@ fn cmd_run(args: RunArgs) -> Result<()> {
531561
.iter()
532562
.map(|m| parse_mount(m))
533563
.collect::<Result<_>>()?;
534-
let network = build_network_policy(args.net, &args.net_allow, &args.net_block)?;
564+
let listen_ports = build_listen_ports(&args.port);
565+
let network = build_network_policy(
566+
args.net,
567+
&args.net_allow,
568+
&args.net_block,
569+
listen_ports.is_some(),
570+
)?;
535571

536572
let initrd = home.join(INITRD_FILE);
537573

@@ -546,6 +582,7 @@ fn cmd_run(args: RunArgs) -> Result<()> {
546582
&run_preopens,
547583
initrd_ref,
548584
network.as_ref(),
585+
listen_ports.as_ref(),
549586
)?;
550587
if args.verbose {
551588
eprintln!(

0 commit comments

Comments
 (0)