From mboxrd@z Thu Jan 1 00:00:00 1970 Return-Path: Received: from firstgate.proxmox.com (firstgate.proxmox.com [IPv6:2a01:7e0:0:424::9]) by lore.proxmox.com (Postfix) with ESMTPS id 254CC1FF17E for ; Thu, 30 Oct 2025 15:33:52 +0100 (CET) Received: from firstgate.proxmox.com (localhost [127.0.0.1]) by firstgate.proxmox.com (Proxmox) with ESMTP id 4BF212435D; Thu, 30 Oct 2025 15:34:23 +0100 (CET) From: Hannes Laimer To: pdm-devel@lists.proxmox.com Date: Thu, 30 Oct 2025 15:34:04 +0100 Message-ID: <20251030143406.193744-12-h.laimer@proxmox.com> X-Mailer: git-send-email 2.47.3 In-Reply-To: <20251030143406.193744-1-h.laimer@proxmox.com> References: <20251030143406.193744-1-h.laimer@proxmox.com> MIME-Version: 1.0 X-Bm-Milter-Handled: 55990f41-d878-4baa-be0a-ee34c49e34d2 X-Bm-Transport-Timestamp: 1761834836326 X-SPAM-LEVEL: Spam detection results: 0 AWL -0.108 Adjusted score from AWL reputation of From: address BAYES_00 -1.9 Bayes spam probability is 0 to 1% DMARC_MISSING 0.1 Missing DMARC policy KAM_DMARC_STATUS 0.01 Test Rule for DKIM or SPF Failure with Strict Alignment POISEN_SPAM_PILL 0.1 Meta: its spam POISEN_SPAM_PILL_1 0.1 random spam to be learned in bayes POISEN_SPAM_PILL_3 0.1 random spam to be learned in bayes RCVD_IN_MSPIKE_H2 0.001 Average reputation (+2) SPF_HELO_NONE 0.001 SPF: HELO does not publish an SPF Record SPF_PASS -0.001 SPF: sender matches SPF record Subject: [pdm-devel] [PATCH proxmox-datacenter-manager 2/4] api: firewall: add option, rules and status endpoints X-BeenThere: pdm-devel@lists.proxmox.com X-Mailman-Version: 2.1.29 Precedence: list List-Id: Proxmox Datacenter Manager development discussion List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Reply-To: Proxmox Datacenter Manager development discussion Content-Type: text/plain; charset="us-ascii" Content-Transfer-Encoding: 7bit Errors-To: pdm-devel-bounces@lists.proxmox.com Sender: "pdm-devel" 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 --- 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, + node: String, + guests: &[ClusterResource], +) -> Vec { + 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, + nodes: &[ClusterResource], + guests: &[ClusterResource], +) -> Vec { + 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, Error> { + let (remotes, _) = pdm_config::remotes::config()?; + let remote_ids: Vec = 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 { + 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 { + 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 { + 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 { + 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, 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, + vmid: u32, + _rpcenv: &mut dyn RpcEnvironment, +) -> Result { + 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, 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, + vmid: u32, + _rpcenv: &mut dyn RpcEnvironment, +) -> Result { + 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, + 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, + 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, + vmid: u32, + _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.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, + vmid: u32, + _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.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