fix(fips): app launch ports allowed through the mesh firewall
The companion opens catalog apps by direct port over the mesh; the fips0 default-deny baseline blocked every one of them (apps 'stuck' from the phone, 2026-07-26). Core now writes a second fips.d drop-in from the generated catalog launch-port list on every install/upgrade. Service/RPC ports (bitcoind 8332, LND 10009, Tor) stay closed. generate-app-catalog.py emits the Rust port list alongside the TS one. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
parent
325b9ea9c9
commit
02917cc22c
44
core/archipelago/src/fips/app_ports.rs
Normal file
44
core/archipelago/src/fips/app_ports.rs
Normal file
@ -0,0 +1,44 @@
|
|||||||
|
//! Generated by scripts/generate-app-catalog.py. Do not edit manually.
|
||||||
|
//!
|
||||||
|
//! Catalog app launch ports (the web UIs the companion opens by direct
|
||||||
|
//! port). Used to write the fips0 firewall allowance drop-in so app UIs
|
||||||
|
//! are reachable over the mesh; ports of apps that aren't installed have
|
||||||
|
//! no listener, so allowing them is inert.
|
||||||
|
|
||||||
|
pub const APP_LAUNCH_PORTS: &[u16] = &[
|
||||||
|
2283,
|
||||||
|
2342,
|
||||||
|
3000,
|
||||||
|
3001,
|
||||||
|
3002,
|
||||||
|
4080,
|
||||||
|
5180,
|
||||||
|
7778,
|
||||||
|
8080,
|
||||||
|
8081,
|
||||||
|
8082,
|
||||||
|
8083,
|
||||||
|
8084,
|
||||||
|
8085,
|
||||||
|
8087,
|
||||||
|
8088,
|
||||||
|
8089,
|
||||||
|
8090,
|
||||||
|
8096,
|
||||||
|
8123,
|
||||||
|
8175,
|
||||||
|
8176,
|
||||||
|
8240,
|
||||||
|
8334,
|
||||||
|
8888,
|
||||||
|
8999,
|
||||||
|
9000,
|
||||||
|
9100,
|
||||||
|
10380,
|
||||||
|
11434,
|
||||||
|
18081,
|
||||||
|
18083,
|
||||||
|
23000,
|
||||||
|
32838,
|
||||||
|
50002,
|
||||||
|
];
|
||||||
@ -252,6 +252,29 @@ pub async fn install(identity_dir: &Path) -> Result<()> {
|
|||||||
let nft_install = sudo_install_file(&nft_stage, "/etc/fips/fips.d/80-web-ui.nft", "0644").await;
|
let nft_install = sudo_install_file(&nft_stage, "/etc/fips/fips.d/80-web-ui.nft", "0644").await;
|
||||||
let _ = tokio::fs::remove_file(&nft_stage).await;
|
let _ = tokio::fs::remove_file(&nft_stage).await;
|
||||||
nft_install?;
|
nft_install?;
|
||||||
|
|
||||||
|
// App launch ports: the companion opens catalog apps by direct port
|
||||||
|
// over the mesh. Ports of apps that aren't installed have no listener,
|
||||||
|
// so the allowance is inert until an app exists to answer.
|
||||||
|
let port_list = super::app_ports::APP_LAUNCH_PORTS
|
||||||
|
.iter()
|
||||||
|
.map(|p| p.to_string())
|
||||||
|
.collect::<Vec<_>>()
|
||||||
|
.join(", ");
|
||||||
|
let app_dropin = format!(
|
||||||
|
"# Written by archipelago on every daemon config install.\n\
|
||||||
|
# Catalog app launch ports (web UIs) allowed through the fips0\n\
|
||||||
|
# default-deny inbound baseline. Service/RPC ports stay closed.\n\
|
||||||
|
tcp dport {{ {port_list} }} accept\n"
|
||||||
|
);
|
||||||
|
let app_stage = std::env::temp_dir().join(format!("fips-appports-{}.nft", std::process::id()));
|
||||||
|
tokio::fs::write(&app_stage, app_dropin)
|
||||||
|
.await
|
||||||
|
.context("Failed to stage app-ports nft drop-in")?;
|
||||||
|
let app_install =
|
||||||
|
sudo_install_file(&app_stage, "/etc/fips/fips.d/85-app-ports.nft", "0644").await;
|
||||||
|
let _ = tokio::fs::remove_file(&app_stage).await;
|
||||||
|
app_install?;
|
||||||
// Make the allowance live immediately; a no-op error when the
|
// Make the allowance live immediately; a no-op error when the
|
||||||
// hardening baseline isn't installed on this node yet.
|
// hardening baseline isn't installed on this node yet.
|
||||||
if tokio::fs::try_exists("/etc/fips/fips.nft").await.unwrap_or(false) {
|
if tokio::fs::try_exists("/etc/fips/fips.nft").await.unwrap_or(false) {
|
||||||
|
|||||||
@ -26,6 +26,7 @@
|
|||||||
#![allow(dead_code)]
|
#![allow(dead_code)]
|
||||||
|
|
||||||
pub mod anchors;
|
pub mod anchors;
|
||||||
|
pub mod app_ports;
|
||||||
pub mod config;
|
pub mod config;
|
||||||
pub mod dial;
|
pub mod dial;
|
||||||
pub mod iface;
|
pub mod iface;
|
||||||
|
|||||||
@ -141,6 +141,32 @@ def render_app_session_config(manifests: dict[str, dict[str, Any]]) -> str:
|
|||||||
return "\n".join(lines)
|
return "\n".join(lines)
|
||||||
|
|
||||||
|
|
||||||
|
def render_rust_ports(ports: dict[str, int], extra_ports: list[int]) -> str:
|
||||||
|
"""Rust constant of catalog launch ports for the fips0 firewall drop-in
|
||||||
|
(core/archipelago/src/fips/app_ports.rs). Extra ports cover the frontend's
|
||||||
|
APP_PORTS overrides (companions/aliases) that have no manifest of their own.
|
||||||
|
"""
|
||||||
|
distinct = sorted(set(list(ports.values()) + extra_ports))
|
||||||
|
lines = [
|
||||||
|
"//! Generated by scripts/generate-app-catalog.py. Do not edit manually.",
|
||||||
|
"//!",
|
||||||
|
"//! Catalog app launch ports (the web UIs the companion opens by direct",
|
||||||
|
"//! port). Used to write the fips0 firewall allowance drop-in so app UIs",
|
||||||
|
"//! are reachable over the mesh; ports of apps that aren\'t installed have",
|
||||||
|
"//! no listener, so allowing them is inert.",
|
||||||
|
"",
|
||||||
|
"pub const APP_LAUNCH_PORTS: &[u16] = &[",
|
||||||
|
]
|
||||||
|
lines.extend(f" {port}," for port in distinct)
|
||||||
|
lines.extend(["];", ""])
|
||||||
|
return "\n".join(lines)
|
||||||
|
|
||||||
|
|
||||||
|
# Keep in lockstep with APP_PORTS overrides in
|
||||||
|
# neode-ui/src/views/appSession/appSessionConfig.ts.
|
||||||
|
RUST_EXTRA_PORTS = [8334, 50002, 18083, 11434, 8081, 8240, 8175, 8176, 8080]
|
||||||
|
|
||||||
|
|
||||||
def sync_catalog(path: Path, manifests: dict[str, dict[str, Any]]) -> int:
|
def sync_catalog(path: Path, manifests: dict[str, dict[str, Any]]) -> int:
|
||||||
with path.open("r", encoding="utf-8") as fh:
|
with path.open("r", encoding="utf-8") as fh:
|
||||||
catalog = json.load(fh)
|
catalog = json.load(fh)
|
||||||
@ -178,6 +204,11 @@ def main() -> int:
|
|||||||
default=[],
|
default=[],
|
||||||
help="Catalog JSON path to update. May be passed multiple times.",
|
help="Catalog JSON path to update. May be passed multiple times.",
|
||||||
)
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
"--rust-app-ports",
|
||||||
|
default="core/archipelago/src/fips/app_ports.rs",
|
||||||
|
help="Generated Rust launch-port list for the fips0 firewall drop-in. Empty string to skip.",
|
||||||
|
)
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
"--app-session-config",
|
"--app-session-config",
|
||||||
default="neode-ui/src/views/appSession/generatedAppSessionConfig.ts",
|
default="neode-ui/src/views/appSession/generatedAppSessionConfig.ts",
|
||||||
@ -201,6 +232,20 @@ def main() -> int:
|
|||||||
print(f"{path}: updated")
|
print(f"{path}: updated")
|
||||||
else:
|
else:
|
||||||
print(f"{path}: updated 0 fields")
|
print(f"{path}: updated 0 fields")
|
||||||
|
if args.rust_app_ports:
|
||||||
|
ports = {
|
||||||
|
app_id: port
|
||||||
|
for app_id, app in manifests.items()
|
||||||
|
if (port := manifest_launch_port(app))
|
||||||
|
}
|
||||||
|
rust_path = Path(args.rust_app_ports)
|
||||||
|
rust_content = render_rust_ports(ports, RUST_EXTRA_PORTS)
|
||||||
|
rust_old = rust_path.read_text(encoding="utf-8") if rust_path.exists() else ""
|
||||||
|
if rust_old != rust_content:
|
||||||
|
rust_path.write_text(rust_content, encoding="utf-8")
|
||||||
|
print(f"{rust_path}: updated")
|
||||||
|
else:
|
||||||
|
print(f"{rust_path}: updated 0 fields")
|
||||||
print(f"total_updated={total}")
|
print(f"total_updated={total}")
|
||||||
return 0
|
return 0
|
||||||
|
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user