From: Hannes Laimer <h.laimer@proxmox.com>
To: pdm-devel@lists.proxmox.com
Subject: [pdm-devel] [PATCH proxmox-datacenter-manager 2/4] api: firewall: add option, rules and status endpoints
Date: Thu, 30 Oct 2025 15:34:04 +0100	[thread overview]
Message-ID: <20251030143406.193744-12-h.laimer@proxmox.com> (raw)
In-Reply-To: <20251030143406.193744-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.
Signed-off-by: Hannes Laimer <h.laimer@proxmox.com>
---
 server/src/api/pve/firewall.rs | 756 +++++++++++++++++++++++++++++++++
 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, 762 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..869eb48
--- /dev/null
+++ b/server/src/api/pve/firewall.rs
@@ -0,0 +1,756 @@
+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_ID_SCHEMA;
+use pdm_api_types::{NODE_SCHEMA, VMID_SCHEMA};
+
+use super::{connect_to_remote, find_node_for_vm};
+use crate::connection::PveClient;
+
+// 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);
+
+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 load_nodes_firewall_status(
+    pve: Arc<PveClient>,
+    nodes: &[ClusterResource],
+    guests: &[ClusterResource],
+) -> Vec<NodeFirewallStatus> {
+    let mut result = vec![];
+    for node in nodes.iter().filter_map(|n| n.node.clone()) {
+        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 = load_guests_firewall_status(pve.clone(), node.clone(), guests).await;
+        result.push(NodeFirewallStatus {
+            node,
+            status,
+            guests,
+        });
+    }
+    result
+}
+
+#[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> {
+    let (remotes, _) = pdm_config::remotes::config()?;
+    let remote_ids: Vec<String> = remotes
+        .iter()
+        .filter_map(
+            |(remote, pdm_api_types::remotes::Remote { ty, .. })| match ty {
+                pdm_api_types::remotes::RemoteType::Pve => Some(remote.to_string()),
+                pdm_api_types::remotes::RemoteType::Pbs => None,
+            },
+        )
+        .collect();
+    let mut result = vec![];
+    for id in remote_ids {
+        let fetch = async {
+            let pve = connect_to_remote(&remotes, &id).ok()?;
+            let nodes = pve
+                .cluster_resources(Some(ClusterResourceKind::Node))
+                .await
+                .ok()?;
+            let guests = pve
+                .cluster_resources(Some(ClusterResourceKind::Vm))
+                .await
+                .ok()?;
+            Some((pve, nodes, guests))
+        }
+        .await;
+
+        let Some((pve, nodes, guests)) = fetch else {
+            result.push(RemoteFirewallStatus {
+                remote: id.to_string(),
+                status: None,
+                nodes: vec![],
+            });
+            continue;
+        };
+
+        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,
+        };
+
+        result.push(RemoteFirewallStatus {
+            remote: id.to_string(),
+            status,
+            nodes: load_nodes_firewall_status(pve, &nodes, &guests).await,
+        });
+    }
+    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 (remotes, _) = pdm_config::remotes::config()?;
+    let fetch = async {
+        let pve = connect_to_remote(&remotes, &remote).ok()?;
+        let nodes = pve
+            .cluster_resources(Some(ClusterResourceKind::Node))
+            .await
+            .ok()?;
+        let guests = pve
+            .cluster_resources(Some(ClusterResourceKind::Vm))
+            .await
+            .ok()?;
+        Some((pve, nodes, guests))
+    }
+    .await;
+
+    let Some((pve, nodes, guests)) = fetch else {
+        return Ok(RemoteFirewallStatus {
+            remote,
+            status: None,
+            nodes: 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(RemoteFirewallStatus {
+        remote,
+        status,
+        nodes: load_nodes_firewall_status(pve, &nodes, &guests).await,
+    })
+}
+
+#[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 61db8ff..8cd3aa6 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 fd4ea54..058fefe 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 6158bef..d521467 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-10-30 14:33 UTC|newest]
Thread overview: 14+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2025-10-30 14:33 [pdm-devel] [PATCH proxmox{, -yew-comp, -datacenter-manager} 00/13] add basic integration of PVE firewall Hannes Laimer
2025-10-30 14:33 ` [pdm-devel] [PATCH proxmox 1/5] pve-api-types: update pve-api.json Hannes Laimer
2025-10-30 14:33 ` [pdm-devel] [PATCH proxmox 2/5] pve-api-types: add get/update firewall options endpoints Hannes Laimer
2025-10-30 14:33 ` [pdm-devel] [PATCH proxmox 3/5] pve-api-types: schema2rust: handle `macro` keyword like we do `type` Hannes Laimer
2025-10-30 14:33 ` [pdm-devel] [PATCH proxmox 4/5] pve-api-types: add list firewall rules endpoints Hannes Laimer
2025-10-30 14:33 ` [pdm-devel] [PATCH proxmox 5/5] pve-api-types: regenerate Hannes Laimer
2025-10-30 14:33 ` [pdm-devel] [PATCH proxmox-yew-comp 1/4] form: add helpers for extractig data out of schemas Hannes Laimer
2025-10-30 14:34 ` [pdm-devel] [PATCH proxmox-yew-comp 2/4] firewall: add FirewallContext Hannes Laimer
2025-10-30 14:34 ` [pdm-devel] [PATCH proxmox-yew-comp 3/4] firewall: add options edit form Hannes Laimer
2025-10-30 14:34 ` [pdm-devel] [PATCH proxmox-yew-comp 4/4] firewall: add rules table Hannes Laimer
2025-10-30 14:34 ` [pdm-devel] [PATCH proxmox-datacenter-manager 1/4] pdm-api-types: add firewall status types Hannes Laimer
2025-10-30 14:34 ` Hannes Laimer [this message]
2025-10-30 14:34 ` [pdm-devel] [PATCH proxmox-datacenter-manager 3/4] pdm-client: add api methods for firewall options, rules and status endpoints Hannes Laimer
2025-10-30 14:34 ` [pdm-devel] [PATCH proxmox-datacenter-manager 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=20251030143406.193744-12-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 a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox