From: Hannes Laimer <h.laimer@proxmox.com>
To: pdm-devel@lists.proxmox.com
Subject: [pdm-devel] [PATCH proxmox-datacenter-manager v2 2/4] api: firewall: add option, rules and status endpoints
Date: Wed, 5 Nov 2025 17:35:44 +0100 [thread overview]
Message-ID: <20251105163546.450094-11-h.laimer@proxmox.com> (raw)
In-Reply-To: <20251105163546.450094-1-h.laimer@proxmox.com>
This adds the following endpoints
* for all PVE remotes:
- GET /pve/firewall/status
* for PVE remotes
- GET pve/remotes/{remote}/firewall/options
- PUT pve/remotes/{remote}/firewall/options
- GET pve/remotes/{remote}/firewall/rules
- GET pve/remotes/{remote}/firewall/status
* for PVE node
- GET pve/remotes/{remote}/nodes/{node}/firewall/options
- PUT pve/remotes/{remote}/nodes/{node}/firewall/options
- GET pve/remotes/{remote}/nodes/{node}/firewall/rules
- GET pve/remotes/{remote}/nodes/{node}/firewall/status
* for guests (both lxc and qemu)
- GET pve/remotes/{remote}/[lxc|qemu]/{vmid}/firewall/options
- PUT pve/remotes/{remote}/[lxc|qemu]/{vmid}/firewall/options
- GET pve/remotes/{remote}/[lxc|qemu]/{vmid}/firewall/rules
`options` endpoints are for recieving and updating the configured
firewall options for remotes, nodes and guests. Both lxc and qemu guests
share the same type for getting and upating their options.
`rules` endpoints return the list of firewall rules that exist on the
entity. All remotes, nodes and guests return a list with items of the
same type.
`status` endpoints return the firewall status of the entity, this
includes:
- name/id
- optional status(enabled, count of enabled rules)
- list of 'child-statuses', so:
for pve status (all remotes) -> list of remote-statuses
for remote status -> list of node-statuses
for node status -> list of guest-statuses
for guest status -> no list
-(only guest) type of guest
Like this we have a way to limit the amount of requests the PDM has to
make in order to collect all the needed data. Given the rather large
amoutn of requests needed to assemble all the data this made more sense
than always loading everything and filtering on the client side.
Data to build the status response is fetch in parallel using our
ParallelFetcher. But only for `all remotes` or `single remote`
status requests. The status for single nodes is done sequentially.
Signed-off-by: Hannes Laimer <h.laimer@proxmox.com>
---
server/src/api/pve/firewall.rs | 854 +++++++++++++++++++++++++++++++++
server/src/api/pve/lxc.rs | 1 +
server/src/api/pve/mod.rs | 3 +
server/src/api/pve/node.rs | 1 +
server/src/api/pve/qemu.rs | 1 +
5 files changed, 860 insertions(+)
create mode 100644 server/src/api/pve/firewall.rs
diff --git a/server/src/api/pve/firewall.rs b/server/src/api/pve/firewall.rs
new file mode 100644
index 0000000..fb8ee82
--- /dev/null
+++ b/server/src/api/pve/firewall.rs
@@ -0,0 +1,854 @@
+use anyhow::Error;
+use pdm_api_types::{PRIV_RESOURCE_AUDIT, PRIV_RESOURCE_MODIFY, PRIV_SYS_AUDIT, PRIV_SYS_MODIFY};
+use proxmox_router::{list_subdirs_api_method, Permission, Router, RpcEnvironment, SubdirMap};
+use proxmox_schema::api;
+use proxmox_sortable_macro::sortable;
+use pve_api_types::{ClusterResource, ClusterResourceKind, ClusterResourceType};
+use std::sync::Arc;
+
+use pdm_api_types::firewall::{
+ FirewallStatus, GuestFirewallStatus, GuestKind, NodeFirewallStatus, RemoteFirewallStatus,
+ RuleStat,
+};
+use pdm_api_types::remotes::{Remote, REMOTE_ID_SCHEMA};
+use pdm_api_types::{NODE_SCHEMA, VMID_SCHEMA};
+
+use super::{connect_to_remote, find_node_for_vm};
+use crate::connection::PveClient;
+use crate::parallel_fetcher::ParallelFetcher;
+
+// top-level firewall routers
+pub const PVE_FW_ROUTER: Router = Router::new()
+ .get(&list_subdirs_api_method!(PVE_FW_SUBDIRS))
+ .subdirs(PVE_FW_SUBDIRS);
+
+pub const CLUSTER_FW_ROUTER: Router = Router::new()
+ .get(&list_subdirs_api_method!(CLUSTER_FW_SUBDIRS))
+ .subdirs(CLUSTER_FW_SUBDIRS);
+
+pub const NODE_FW_ROUTER: Router = Router::new()
+ .get(&list_subdirs_api_method!(NODE_FW_SUBDIRS))
+ .subdirs(NODE_FW_SUBDIRS);
+
+pub const LXC_FW_ROUTER: Router = Router::new()
+ .get(&list_subdirs_api_method!(LXC_FW_SUBDIRS))
+ .subdirs(LXC_FW_SUBDIRS);
+pub const QEMU_FW_ROUTER: Router = Router::new()
+ .get(&list_subdirs_api_method!(QEMU_FW_SUBDIRS))
+ .subdirs(QEMU_FW_SUBDIRS);
+
+// pve
+#[sortable]
+const PVE_FW_SUBDIRS: SubdirMap = &sorted!([("status", &PVE_STATUS_ROUTER),]);
+
+// cluster
+#[sortable]
+const CLUSTER_FW_SUBDIRS: SubdirMap = &sorted!([
+ ("options", &CLUSTER_OPTIONS_ROUTER),
+ ("rules", &CLUSTER_RULES_ROUTER),
+ ("status", &CLUSTER_STATUS_ROUTER),
+]);
+
+// node
+#[sortable]
+const NODE_FW_SUBDIRS: SubdirMap = &sorted!([
+ ("options", &NODE_OPTIONS_ROUTER),
+ ("rules", &NODE_RULES_ROUTER),
+ ("status", &NODE_STATUS_ROUTER),
+]);
+
+// guest
+#[sortable]
+const LXC_FW_SUBDIRS: SubdirMap = &sorted!([
+ ("options", &LXC_OPTIONS_ROUTER),
+ ("rules", &LXC_RULES_ROUTER),
+]);
+#[sortable]
+const QEMU_FW_SUBDIRS: SubdirMap = &sorted!([
+ ("options", &QEMU_OPTIONS_ROUTER),
+ ("rules", &QEMU_RULES_ROUTER),
+]);
+
+// /options
+const CLUSTER_OPTIONS_ROUTER: Router = Router::new()
+ .get(&API_METHOD_CLUSTER_FIREWALL_OPTIONS)
+ .put(&API_METHOD_UPDATE_CLUSTER_FIREWALL_OPTIONS);
+
+const NODE_OPTIONS_ROUTER: Router = Router::new()
+ .get(&API_METHOD_NODE_FIREWALL_OPTIONS)
+ .put(&API_METHOD_UPDATE_NODE_FIREWALL_OPTIONS);
+
+const LXC_OPTIONS_ROUTER: Router = Router::new()
+ .get(&API_METHOD_LXC_FIREWALL_OPTIONS)
+ .put(&API_METHOD_UPDATE_LXC_FIREWALL_OPTIONS);
+const QEMU_OPTIONS_ROUTER: Router = Router::new()
+ .get(&API_METHOD_QEMU_FIREWALL_OPTIONS)
+ .put(&API_METHOD_UPDATE_QEMU_FIREWALL_OPTIONS);
+
+// /rules
+const CLUSTER_RULES_ROUTER: Router = Router::new().get(&API_METHOD_CLUSTER_FIREWALL_RULES);
+const NODE_RULES_ROUTER: Router = Router::new().get(&API_METHOD_NODE_FIREWALL_RULES);
+const LXC_RULES_ROUTER: Router = Router::new().get(&API_METHOD_LXC_FIREWALL_RULES);
+const QEMU_RULES_ROUTER: Router = Router::new().get(&API_METHOD_QEMU_FIREWALL_RULES);
+
+// /status
+const PVE_STATUS_ROUTER: Router = Router::new().get(&API_METHOD_PVE_FIREWALL_STATUS);
+const CLUSTER_STATUS_ROUTER: Router = Router::new().get(&API_METHOD_CLUSTER_FIREWALL_STATUS);
+const NODE_STATUS_ROUTER: Router = Router::new().get(&API_METHOD_NODE_FIREWALL_STATUS);
+
+#[derive(Clone)]
+struct FirewallFetchContext {
+ guests: Arc<Vec<ClusterResource>>,
+}
+
+#[derive(Clone, Debug)]
+struct ClusterFirewallData {
+ status: Option<FirewallStatus>,
+ guests: Vec<ClusterResource>,
+}
+
+async fn fetch_cluster_firewall_data(
+ _context: (),
+ remote: Remote,
+ _node: String, // unused for cluster-level data
+) -> Result<ClusterFirewallData, Error> {
+ let pve = crate::connection::make_pve_client(&remote)?;
+
+ let guests = match pve.cluster_resources(Some(ClusterResourceKind::Vm)).await {
+ Ok(guests) => guests,
+ Err(_) => {
+ return Ok(ClusterFirewallData {
+ status: None,
+ guests: vec![],
+ });
+ }
+ };
+
+ let options_response = pve.cluster_firewall_options();
+ let rules_response = pve.list_cluster_firewall_rules();
+
+ let enabled = options_response.await.map(|opts| opts.enable != Some(0));
+ let rules = rules_response.await.map(|rules| {
+ let all = rules.len();
+ let active = rules.iter().filter(|r| r.enable == Some(1)).count();
+ RuleStat { all, active }
+ });
+
+ let status = match (enabled, rules) {
+ (Ok(enabled), Ok(rules)) => Some(FirewallStatus { enabled, rules }),
+ _ => None,
+ };
+
+ Ok(ClusterFirewallData { status, guests })
+}
+
+async fn load_guests_firewall_status(
+ pve: Arc<PveClient>,
+ node: String,
+ guests: &[ClusterResource],
+) -> Vec<GuestFirewallStatus> {
+ let mut result = vec![];
+
+ let guests: Vec<(u32, String, GuestKind)> = guests
+ .iter()
+ .filter(|g| g.node.as_ref() == Some(&node))
+ .filter_map(|g| {
+ let vmid = g.vmid?;
+ let name = g.name.clone().unwrap_or("".to_string());
+ match g.ty {
+ ClusterResourceType::Lxc => Some((vmid, name, GuestKind::Lxc)),
+ ClusterResourceType::Qemu => Some((vmid, name, GuestKind::Qemu)),
+ _ => None,
+ }
+ })
+ .collect();
+
+ for (vmid, name, kind) in guests {
+ let options_response = match kind {
+ GuestKind::Lxc => pve.lxc_firewall_options(&node, vmid),
+ GuestKind::Qemu => pve.qemu_firewall_options(&node, vmid),
+ };
+ let rules_response = match kind {
+ GuestKind::Lxc => pve.list_lxc_firewall_rules(&node, vmid),
+ GuestKind::Qemu => pve.list_qemu_firewall_rules(&node, vmid),
+ };
+
+ let enabled = options_response
+ .await
+ .map(|opts| opts.enable.unwrap_or_default());
+ let rules = rules_response.await.map(|rules| {
+ let all = rules.len();
+ let active = rules.iter().filter(|r| r.enable == Some(1)).count();
+ RuleStat { all, active }
+ });
+
+ let status = match (enabled, rules) {
+ (Ok(enabled), Ok(rules)) => Some(FirewallStatus { enabled, rules }),
+ _ => None,
+ };
+
+ result.push(GuestFirewallStatus {
+ vmid,
+ name,
+ status,
+ kind,
+ });
+ }
+ result
+}
+
+async fn fetch_node_firewall_status(
+ context: FirewallFetchContext,
+ remote: Remote,
+ node: String,
+) -> Result<NodeFirewallStatus, Error> {
+ let pve = crate::connection::make_pve_client(&remote)?;
+
+ let options_response = pve.node_firewall_options(&node);
+ let rules_response = pve.list_node_firewall_rules(&node);
+
+ let enabled = options_response
+ .await
+ .map(|opts| opts.enable.unwrap_or_default());
+ let rules = rules_response.await.map(|rules| {
+ let all = rules.len();
+ let active = rules.iter().filter(|r| r.enable == Some(1)).count();
+ RuleStat { all, active }
+ });
+
+ let status = match (enabled, rules) {
+ (Ok(enabled), Ok(rules)) => Some(FirewallStatus { enabled, rules }),
+ _ => None,
+ };
+
+ let guests_status = load_guests_firewall_status(pve, node.clone(), &context.guests).await;
+
+ Ok(NodeFirewallStatus {
+ node,
+ status,
+ guests: guests_status,
+ })
+}
+
+#[api(
+ returns: {
+ type: Array,
+ description: "Get firewall status of remotes",
+ items: { type: RemoteFirewallStatus },
+ },
+ access: {
+ permission: &Permission::Privilege(&["resource", "{remote}"], PRIV_SYS_AUDIT, false),
+ },
+)]
+/// Get firewall status of all PVE remotes.
+pub async fn pve_firewall_status(
+ _rpcenv: &mut dyn RpcEnvironment,
+) -> Result<Vec<RemoteFirewallStatus>, Error> {
+ use std::collections::HashMap;
+
+ let (remote_config, _) = pdm_config::remotes::config()?;
+
+ let pve_remotes: Vec<Remote> = remote_config
+ .iter()
+ .filter_map(|(_, remote)| match remote.ty {
+ pdm_api_types::remotes::RemoteType::Pve => Some(remote.clone()),
+ pdm_api_types::remotes::RemoteType::Pbs => None,
+ })
+ .collect();
+
+ if pve_remotes.is_empty() {
+ return Ok(vec![]);
+ }
+
+ // 1: fetch cluster-level data (status + guests)
+ let cluster_fetcher = ParallelFetcher::new(());
+ let cluster_results = cluster_fetcher
+ .do_for_all_remotes(pve_remotes.iter().cloned(), fetch_cluster_firewall_data)
+ .await;
+
+ // 2: build context with guests for each remote and fetch node-level data
+ let mut guests_per_remote = HashMap::new();
+ for (remote_id, remote_result) in &cluster_results.remote_results {
+ if let Ok(remote_result) = remote_result {
+ if let Ok(node_result) = remote_result.node_results.get("localhost").unwrap() {
+ guests_per_remote
+ .insert(remote_id.clone(), Arc::new(node_result.data.guests.clone()));
+ }
+ }
+ }
+
+ let context = FirewallFetchContext {
+ guests: Arc::new(vec![]),
+ };
+
+ let node_fetcher = ParallelFetcher::new(context);
+ let node_results = node_fetcher
+ .do_for_all_remote_nodes(pve_remotes.iter().cloned(), move |mut ctx, remote, node| {
+ if let Some(guests) = guests_per_remote.get(&remote.id) {
+ ctx.guests = guests.clone();
+ }
+ fetch_node_firewall_status(ctx, remote, node)
+ })
+ .await;
+
+ // 3: combine results
+ let mut result = Vec::new();
+ for remote in &pve_remotes {
+ let mut cluster_status = cluster_results
+ .remote_results
+ .get(&remote.id)
+ .and_then(|r| r.as_ref().ok())
+ .and_then(|r| r.node_results.get("localhost"))
+ .and_then(|n| n.as_ref().ok())
+ .and_then(|n| n.data.status.clone());
+
+ let node_fetch_result = node_results.remote_results.get(&remote.id);
+
+ let nodes = node_fetch_result
+ .and_then(|r| r.as_ref().ok())
+ .map(|r| {
+ r.node_results
+ .values()
+ .filter_map(|n| n.as_ref().ok().map(|n| n.data.clone()))
+ .collect()
+ })
+ .unwrap_or_default();
+
+ if node_fetch_result.and_then(|r| r.as_ref().err()).is_some() {
+ cluster_status = None;
+ }
+
+ result.push(RemoteFirewallStatus {
+ remote: remote.id.clone(),
+ status: cluster_status,
+ nodes,
+ });
+ }
+
+ Ok(result)
+}
+
+#[api(
+ input: {
+ properties: {
+ remote: { schema: REMOTE_ID_SCHEMA },
+ },
+ },
+ returns: {
+ type: Array,
+ description: "Get firewall options.",
+ items: { type: pve_api_types::ClusterFirewallOptions },
+ },
+ access: {
+ permission: &Permission::Privilege(&["resource", "{remote}"], PRIV_SYS_AUDIT, false),
+ },
+)]
+/// Get cluster firewall options.
+pub async fn cluster_firewall_options(
+ remote: String,
+ _rpcenv: &mut dyn RpcEnvironment,
+) -> Result<pve_api_types::ClusterFirewallOptions, Error> {
+ let (remotes, _) = pdm_config::remotes::config()?;
+ let pve = connect_to_remote(&remotes, &remote)?;
+
+ Ok(pve.cluster_firewall_options().await?)
+}
+
+#[api(
+ input: {
+ properties: {
+ remote: { schema: REMOTE_ID_SCHEMA },
+ },
+ },
+ returns: {
+ type: RemoteFirewallStatus,
+ },
+ access: {
+ permission: &Permission::Privilege(&["resource", "{remote}"], PRIV_SYS_AUDIT, false),
+ },
+)]
+/// Get firewall status of a specific remote.
+pub async fn cluster_firewall_status(
+ remote: String,
+ _rpcenv: &mut dyn RpcEnvironment,
+) -> Result<RemoteFirewallStatus, Error> {
+ let (remote_config, _) = pdm_config::remotes::config()?;
+
+ let remote_obj = remote_config
+ .iter()
+ .find(|(id, _)| *id == &remote)
+ .map(|(_, r)| r.clone())
+ .ok_or_else(|| anyhow::format_err!("Remote '{}' not found", remote))?;
+
+ // 1: fetch cluster-level data (status + guests)
+ let cluster_fetcher = ParallelFetcher::new(());
+ let cluster_results = cluster_fetcher
+ .do_for_all_remotes(
+ std::iter::once(remote_obj.clone()),
+ fetch_cluster_firewall_data,
+ )
+ .await;
+
+ let cluster_data = cluster_results
+ .remote_results
+ .get(&remote)
+ .and_then(|r| r.as_ref().ok())
+ .and_then(|r| r.node_results.get("localhost"))
+ .and_then(|n| n.as_ref().ok())
+ .map(|n| &n.data);
+
+ let (cluster_status, guests) = match cluster_data {
+ Some(data) => (data.status.clone(), data.guests.clone()),
+ None => {
+ return Ok(RemoteFirewallStatus {
+ remote,
+ status: None,
+ nodes: vec![],
+ });
+ }
+ };
+
+ // 2: fetch node-level data
+ let context = FirewallFetchContext {
+ guests: Arc::new(guests),
+ };
+
+ let node_fetcher = ParallelFetcher::new(context);
+ let node_results = node_fetcher
+ .do_for_all_remote_nodes(std::iter::once(remote_obj), fetch_node_firewall_status)
+ .await;
+
+ // 3: collect node results
+ let node_fetch_result = node_results.remote_results.get(&remote);
+
+ let nodes = node_fetch_result
+ .and_then(|r| r.as_ref().ok())
+ .map(|r| {
+ r.node_results
+ .values()
+ .filter_map(|n| n.as_ref().ok().map(|n| n.data.clone()))
+ .collect()
+ })
+ .unwrap_or_default();
+
+ let final_status = if node_fetch_result.and_then(|r| r.as_ref().err()).is_some() {
+ None
+ } else {
+ cluster_status
+ };
+
+ Ok(RemoteFirewallStatus {
+ remote,
+ status: final_status,
+ nodes,
+ })
+}
+
+#[api(
+ input: {
+ properties: {
+ remote: { schema: REMOTE_ID_SCHEMA },
+ node: {
+ schema: NODE_SCHEMA,
+ },
+ },
+ },
+ returns: {
+ type: Array,
+ description: "Get firewall options.",
+ items: { type: pve_api_types::NodeFirewallOptions },
+ },
+ access: {
+ permission: &Permission::Privilege(&["resource", "{remote}"], PRIV_SYS_AUDIT, false),
+ },
+)]
+/// Get nodes firewall options.
+pub async fn node_firewall_options(
+ remote: String,
+ node: String,
+ _rpcenv: &mut dyn RpcEnvironment,
+) -> Result<pve_api_types::NodeFirewallOptions, Error> {
+ let (remotes, _) = pdm_config::remotes::config()?;
+ let pve = connect_to_remote(&remotes, &remote)?;
+
+ Ok(pve.node_firewall_options(&node).await?)
+}
+
+#[api(
+ input: {
+ properties: {
+ remote: { schema: REMOTE_ID_SCHEMA },
+ node: { schema: NODE_SCHEMA },
+ },
+ },
+ returns: {
+ type: NodeFirewallStatus,
+ },
+ access: {
+ permission: &Permission::Privilege(&["resource", "{remote}"], PRIV_SYS_AUDIT, false),
+ },
+)]
+/// Get firewall status of a specific node.
+pub async fn node_firewall_status(
+ remote: String,
+ node: String,
+ _rpcenv: &mut dyn RpcEnvironment,
+) -> Result<NodeFirewallStatus, Error> {
+ let (remotes, _) = pdm_config::remotes::config()?;
+ let pve = connect_to_remote(&remotes, &remote)?;
+
+ let guests = pve.cluster_resources(Some(ClusterResourceKind::Vm)).await?;
+
+ let options_response = pve.node_firewall_options(&node);
+ let rules_response = pve.list_node_firewall_rules(&node);
+
+ let enabled = options_response
+ .await
+ .map(|opts| opts.enable.unwrap_or_default());
+ let rules = rules_response.await.map(|rules| {
+ let all = rules.len();
+ let active = rules.iter().filter(|r| r.enable == Some(1)).count();
+ RuleStat { all, active }
+ });
+
+ let status = match (enabled, rules) {
+ (Ok(enabled), Ok(rules)) => Some(FirewallStatus { enabled, rules }),
+ _ => None,
+ };
+
+ let guests_status = load_guests_firewall_status(pve, node.clone(), &guests).await;
+
+ Ok(NodeFirewallStatus {
+ node,
+ status,
+ guests: guests_status,
+ })
+}
+
+#[api(
+ input: {
+ properties: {
+ remote: { schema: REMOTE_ID_SCHEMA },
+ },
+ },
+ returns: {
+ type: Array,
+ description: "List cluster firewall rules.",
+ items: { type: pve_api_types::ListFirewallRules },
+ },
+ access: {
+ permission: &Permission::Privilege(&["resource", "{remote}"], PRIV_SYS_AUDIT, false),
+ },
+)]
+/// Get cluster firewall rules.
+pub async fn cluster_firewall_rules(
+ remote: String,
+ _rpcenv: &mut dyn RpcEnvironment,
+) -> Result<Vec<pve_api_types::ListFirewallRules>, Error> {
+ let (remotes, _) = pdm_config::remotes::config()?;
+ let pve = connect_to_remote(&remotes, &remote)?;
+
+ Ok(pve.list_cluster_firewall_rules().await?)
+}
+
+#[api(
+ input: {
+ properties: {
+ remote: { schema: REMOTE_ID_SCHEMA },
+ node: {
+ schema: NODE_SCHEMA,
+ optional: true,
+ },
+ vmid: { schema: VMID_SCHEMA },
+ },
+ },
+ returns: {
+ type: Array,
+ description: "Get firewall options.",
+ items: { type: pve_api_types::GuestFirewallOptions },
+ },
+ access: {
+ permission: &Permission::Privilege(&["resource", "{remote}", "guest", "{vmid}"], PRIV_RESOURCE_AUDIT, false),
+ },
+)]
+/// Get LXC firewall options.
+pub async fn lxc_firewall_options(
+ remote: String,
+ node: Option<String>,
+ vmid: u32,
+ _rpcenv: &mut dyn RpcEnvironment,
+) -> Result<pve_api_types::GuestFirewallOptions, Error> {
+ let (remotes, _) = pdm_config::remotes::config()?;
+
+ let pve = connect_to_remote(&remotes, &remote)?;
+
+ let node = find_node_for_vm(node, vmid, pve.as_ref()).await?;
+
+ Ok(pve.lxc_firewall_options(&node, vmid).await?)
+}
+
+#[api(
+ input: {
+ properties: {
+ remote: { schema: REMOTE_ID_SCHEMA },
+ update: {
+ type: pve_api_types::UpdateClusterFirewallOptions,
+ flatten: true,
+ },
+ },
+ },
+ access: {
+ permission: &Permission::Privilege(&["resource", "{remote}"], PRIV_SYS_MODIFY, false),
+ },
+)]
+/// Update cluster firewall configuration
+pub async fn update_cluster_firewall_options(
+ remote: String,
+ update: pve_api_types::UpdateClusterFirewallOptions,
+ _rpcenv: &mut dyn RpcEnvironment,
+) -> Result<(), Error> {
+ let (remotes, _) = pdm_config::remotes::config()?;
+ let pve = connect_to_remote(&remotes, &remote)?;
+
+ Ok(pve.set_cluster_firewall_options(update).await?)
+}
+
+#[api(
+ input: {
+ properties: {
+ remote: { schema: REMOTE_ID_SCHEMA },
+ node: {
+ schema: NODE_SCHEMA,
+ },
+ update: {
+ type: pve_api_types::UpdateNodeFirewallOptions,
+ flatten: true,
+ },
+ },
+ },
+ access: {
+ permission: &Permission::Privilege(&["resource", "{remote}"], PRIV_SYS_MODIFY, false),
+ },
+)]
+/// Update a nodes firewall configuration
+pub async fn update_node_firewall_options(
+ remote: String,
+ node: String,
+ update: pve_api_types::UpdateNodeFirewallOptions,
+ _rpcenv: &mut dyn RpcEnvironment,
+) -> Result<(), Error> {
+ let (remotes, _) = pdm_config::remotes::config()?;
+ let pve = connect_to_remote(&remotes, &remote)?;
+
+ Ok(pve.set_node_firewall_options(&node, update).await?)
+}
+
+#[api(
+ input: {
+ properties: {
+ remote: { schema: REMOTE_ID_SCHEMA },
+ node: {
+ schema: NODE_SCHEMA,
+ },
+ },
+ },
+ returns: {
+ type: Array,
+ description: "List node firewall rules.",
+ items: { type: pve_api_types::ListFirewallRules },
+ },
+ access: {
+ permission: &Permission::Privilege(&["resource", "{remote}"], PRIV_SYS_AUDIT, false),
+ },
+)]
+/// Get node firewall rules.
+pub async fn node_firewall_rules(
+ remote: String,
+ node: String,
+ _rpcenv: &mut dyn RpcEnvironment,
+) -> Result<Vec<pve_api_types::ListFirewallRules>, Error> {
+ let (remotes, _) = pdm_config::remotes::config()?;
+ let pve = connect_to_remote(&remotes, &remote)?;
+
+ Ok(pve.list_node_firewall_rules(&node).await?)
+}
+
+#[api(
+ input: {
+ properties: {
+ remote: { schema: REMOTE_ID_SCHEMA },
+ node: {
+ schema: NODE_SCHEMA,
+ optional: true,
+ },
+ vmid: { schema: VMID_SCHEMA },
+ },
+ },
+ returns: {
+ type: Array,
+ description: "Get firewall options.",
+ items: { type: pve_api_types::GuestFirewallOptions },
+ },
+ access: {
+ permission: &Permission::Privilege(&["resource", "{remote}", "guest", "{vmid}"], PRIV_RESOURCE_AUDIT, false),
+ },
+)]
+/// Get QEMU firewall options.
+pub async fn qemu_firewall_options(
+ remote: String,
+ node: Option<String>,
+ vmid: u32,
+ _rpcenv: &mut dyn RpcEnvironment,
+) -> Result<pve_api_types::GuestFirewallOptions, Error> {
+ let (remotes, _) = pdm_config::remotes::config()?;
+
+ let pve = connect_to_remote(&remotes, &remote)?;
+
+ let node = find_node_for_vm(node, vmid, pve.as_ref()).await?;
+
+ Ok(pve.qemu_firewall_options(&node, vmid).await?)
+}
+
+#[api(
+ input: {
+ properties: {
+ remote: { schema: REMOTE_ID_SCHEMA },
+ node: {
+ schema: NODE_SCHEMA,
+ optional: true,
+ },
+ vmid: { schema: VMID_SCHEMA, },
+ update: {
+ type: pve_api_types::UpdateGuestFirewallOptions,
+ flatten: true,
+ },
+ },
+ },
+ access: {
+ permission: &Permission::Privilege(&["resource", "{remote}", "guest", "{vmid}"], PRIV_RESOURCE_MODIFY, false),
+ },
+)]
+/// Update LXC firewall options
+pub async fn update_lxc_firewall_options(
+ remote: String,
+ node: Option<String>,
+ vmid: u32,
+ update: pve_api_types::UpdateGuestFirewallOptions,
+ _rpcenv: &mut dyn RpcEnvironment,
+) -> Result<(), Error> {
+ let (remotes, _) = pdm_config::remotes::config()?;
+
+ let pve = connect_to_remote(&remotes, &remote)?;
+
+ let node = find_node_for_vm(node, vmid, pve.as_ref()).await?;
+
+ Ok(pve.set_lxc_firewall_options(&node, vmid, update).await?)
+}
+
+#[api(
+ input: {
+ properties: {
+ remote: { schema: REMOTE_ID_SCHEMA },
+ node: {
+ schema: NODE_SCHEMA,
+ optional: true,
+ },
+ vmid: { schema: VMID_SCHEMA, },
+ update: {
+ type: pve_api_types::UpdateGuestFirewallOptions,
+ flatten: true,
+ },
+ },
+ },
+ access: {
+ permission: &Permission::Privilege(&["resource", "{remote}", "guest", "{vmid}"], PRIV_RESOURCE_MODIFY, false),
+ },
+)]
+/// Update QEMU firewall options
+pub async fn update_qemu_firewall_options(
+ remote: String,
+ node: Option<String>,
+ vmid: u32,
+ update: pve_api_types::UpdateGuestFirewallOptions,
+ _rpcenv: &mut dyn RpcEnvironment,
+) -> Result<(), Error> {
+ let (remotes, _) = pdm_config::remotes::config()?;
+
+ let pve = connect_to_remote(&remotes, &remote)?;
+
+ let node = find_node_for_vm(node, vmid, pve.as_ref()).await?;
+
+ Ok(pve.set_qemu_firewall_options(&node, vmid, update).await?)
+}
+
+#[api(
+ input: {
+ properties: {
+ remote: { schema: REMOTE_ID_SCHEMA },
+ node: {
+ schema: NODE_SCHEMA,
+ optional: true,
+ },
+ vmid: { schema: VMID_SCHEMA },
+ },
+ },
+ returns: {
+ type: Array,
+ description: "List LXC firewall rules.",
+ items: { type: pve_api_types::ListFirewallRules },
+ },
+ access: {
+ permission: &Permission::Privilege(&["resource", "{remote}", "guest", "{vmid}"], PRIV_RESOURCE_AUDIT, false),
+ },
+)]
+/// Get LXC firewall rules.
+pub async fn lxc_firewall_rules(
+ remote: String,
+ node: Option<String>,
+ vmid: u32,
+ _rpcenv: &mut dyn RpcEnvironment,
+) -> Result<Vec<pve_api_types::ListFirewallRules>, Error> {
+ let (remotes, _) = pdm_config::remotes::config()?;
+
+ let pve = connect_to_remote(&remotes, &remote)?;
+
+ let node = find_node_for_vm(node, vmid, pve.as_ref()).await?;
+
+ Ok(pve.list_lxc_firewall_rules(&node, vmid).await?)
+}
+
+#[api(
+ input: {
+ properties: {
+ remote: { schema: REMOTE_ID_SCHEMA },
+ node: {
+ schema: NODE_SCHEMA,
+ optional: true,
+ },
+ vmid: { schema: VMID_SCHEMA },
+ },
+ },
+ returns: {
+ type: Array,
+ description: "List QEMU firewall rules.",
+ items: { type: pve_api_types::ListFirewallRules },
+ },
+ access: {
+ permission: &Permission::Privilege(&["resource", "{remote}", "guest", "{vmid}"], PRIV_RESOURCE_AUDIT, false),
+ },
+)]
+/// Get QEMU firewall rules.
+pub async fn qemu_firewall_rules(
+ remote: String,
+ node: Option<String>,
+ vmid: u32,
+ _rpcenv: &mut dyn RpcEnvironment,
+) -> Result<Vec<pve_api_types::ListFirewallRules>, Error> {
+ let (remotes, _) = pdm_config::remotes::config()?;
+
+ let pve = connect_to_remote(&remotes, &remote)?;
+
+ let node = find_node_for_vm(node, vmid, pve.as_ref()).await?;
+
+ Ok(pve.list_qemu_firewall_rules(&node, vmid).await?)
+}
diff --git a/server/src/api/pve/lxc.rs b/server/src/api/pve/lxc.rs
index 1b05a30..e1658d7 100644
--- a/server/src/api/pve/lxc.rs
+++ b/server/src/api/pve/lxc.rs
@@ -33,6 +33,7 @@ const LXC_VM_ROUTER: Router = Router::new()
#[sortable]
const LXC_VM_SUBDIRS: SubdirMap = &sorted!([
("config", &Router::new().get(&API_METHOD_LXC_GET_CONFIG)),
+ ("firewall", &super::firewall::LXC_FW_ROUTER),
("rrddata", &super::rrddata::LXC_RRD_ROUTER),
("start", &Router::new().post(&API_METHOD_LXC_START)),
("status", &Router::new().get(&API_METHOD_LXC_GET_STATUS)),
diff --git a/server/src/api/pve/mod.rs b/server/src/api/pve/mod.rs
index 2b50afb..34d9b76 100644
--- a/server/src/api/pve/mod.rs
+++ b/server/src/api/pve/mod.rs
@@ -33,6 +33,7 @@ use crate::connection::PveClient;
use crate::connection::{self, probe_tls_connection};
use crate::remote_tasks;
+mod firewall;
mod lxc;
mod node;
mod qemu;
@@ -47,6 +48,7 @@ pub const ROUTER: Router = Router::new()
#[sortable]
const SUBDIRS: SubdirMap = &sorted!([
("remotes", &REMOTES_ROUTER),
+ ("firewall", &firewall::PVE_FW_ROUTER),
("probe-tls", &Router::new().post(&API_METHOD_PROBE_TLS)),
("scan", &Router::new().post(&API_METHOD_SCAN_REMOTE_PVE)),
(
@@ -66,6 +68,7 @@ const MAIN_ROUTER: Router = Router::new()
#[sortable]
const REMOTE_SUBDIRS: SubdirMap = &sorted!([
("lxc", &lxc::ROUTER),
+ ("firewall", &firewall::CLUSTER_FW_ROUTER),
("nodes", &NODES_ROUTER),
("qemu", &qemu::ROUTER),
("resources", &RESOURCES_ROUTER),
diff --git a/server/src/api/pve/node.rs b/server/src/api/pve/node.rs
index 301c0b1..3c4fba8 100644
--- a/server/src/api/pve/node.rs
+++ b/server/src/api/pve/node.rs
@@ -16,6 +16,7 @@ pub const ROUTER: Router = Router::new()
#[sortable]
const SUBDIRS: SubdirMap = &sorted!([
("apt", &crate::api::remote_updates::APT_ROUTER),
+ ("firewall", &super::firewall::NODE_FW_ROUTER),
("rrddata", &super::rrddata::NODE_RRD_ROUTER),
("network", &Router::new().get(&API_METHOD_GET_NETWORK)),
("storage", &STORAGE_ROUTER),
diff --git a/server/src/api/pve/qemu.rs b/server/src/api/pve/qemu.rs
index 05fa92c..9a446ac 100644
--- a/server/src/api/pve/qemu.rs
+++ b/server/src/api/pve/qemu.rs
@@ -33,6 +33,7 @@ const QEMU_VM_ROUTER: Router = Router::new()
#[sortable]
const QEMU_VM_SUBDIRS: SubdirMap = &sorted!([
("config", &Router::new().get(&API_METHOD_QEMU_GET_CONFIG)),
+ ("firewall", &super::firewall::QEMU_FW_ROUTER),
("rrddata", &super::rrddata::QEMU_RRD_ROUTER),
("start", &Router::new().post(&API_METHOD_QEMU_START)),
("status", &Router::new().get(&API_METHOD_QEMU_GET_STATUS)),
--
2.47.3
_______________________________________________
pdm-devel mailing list
pdm-devel@lists.proxmox.com
https://lists.proxmox.com/cgi-bin/mailman/listinfo/pdm-devel
next prev parent reply other threads:[~2025-11-05 16:35 UTC|newest]
Thread overview: 13+ messages / expand[flat|nested] mbox.gz Atom feed top
2025-11-05 16:35 [pdm-devel] [PATCH proxmox{, -yew-comp, -datacenter-manager} v2 00/12] add basic integration of PVE firewall Hannes Laimer
2025-11-05 16:35 ` [pdm-devel] [PATCH proxmox v2 1/4] pve-api-types: update pve-api.json Hannes Laimer
2025-11-05 16:35 ` [pdm-devel] [PATCH proxmox v2 2/4] pve-api-types: add get/update firewall options endpoints Hannes Laimer
2025-11-05 16:35 ` [pdm-devel] [PATCH proxmox v2 3/4] pve-api-types: add list firewall rules endpoints Hannes Laimer
2025-11-05 16:35 ` [pdm-devel] [PATCH proxmox v2 4/4] pve-api-types: regenerate Hannes Laimer
2025-11-05 16:35 ` [pdm-devel] [PATCH proxmox-yew-comp v2 1/4] form: add helpers for extractig data out of schemas Hannes Laimer
2025-11-05 16:35 ` [pdm-devel] [PATCH proxmox-yew-comp v2 2/4] firewall: add FirewallContext Hannes Laimer
2025-11-05 16:35 ` [pdm-devel] [PATCH proxmox-yew-comp v2 3/4] firewall: add options edit form Hannes Laimer
2025-11-05 16:35 ` [pdm-devel] [PATCH proxmox-yew-comp v2 4/4] firewall: add rules table Hannes Laimer
2025-11-05 16:35 ` [pdm-devel] [PATCH proxmox-datacenter-manager v2 1/4] pdm-api-types: add firewall status types Hannes Laimer
2025-11-05 16:35 ` Hannes Laimer [this message]
2025-11-05 16:35 ` [pdm-devel] [PATCH proxmox-datacenter-manager v2 3/4] pdm-client: add api methods for firewall options, rules and status endpoints Hannes Laimer
2025-11-05 16:35 ` [pdm-devel] [PATCH proxmox-datacenter-manager v2 4/4] ui: add firewall status tree Hannes Laimer
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=20251105163546.450094-11-h.laimer@proxmox.com \
--to=h.laimer@proxmox.com \
--cc=pdm-devel@lists.proxmox.com \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.