From eb3cb11ab64dfc8a103258698c8510149be03e85 Mon Sep 17 00:00:00 2001 From: Wei Zhou Date: Wed, 7 Oct 2026 17:06:24 +0200 Subject: [PATCH] network: use calling account instead of network owner for destroy context MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Symptom ------- Deleting a network whose owning account is disabled fails with: com.cloud.exception.PermissionDeniedException: Account Account [{"accountName":"...","id":...}] is disabled. at com.cloud.acl.DomainChecker.checkAccess(DomainChecker.java:149) at com.cloud.user.AccountManagerImpl.checkAccess(AccountManagerImpl.java:831) at com.cloud.network.firewall.FirewallManagerImpl.revokeFirewallRule(FirewallManagerImpl.java:1169) at com.cloud.network.firewall.FirewallManagerImpl.revokeAllFirewallRulesForNetwork(FirewallManagerImpl.java:1356) at org.apache.cloudstack.engine.orchestration.NetworkOrchestrator.cleanupNetworkResources(NetworkOrchestrator.java:4192) at org.apache.cloudstack.engine.orchestration.NetworkOrchestrator.destroyNetwork(NetworkOrchestrator.java:3512) Root cause ---------- NetworkServiceImpl.deleteNetwork, VpcManagerImpl (private network cleanup), and KubernetesClusterDestroyWorker each build the ReservationContext used for destroyNetwork() with the network's *owning* account as the acting account: ReservationContext context = new ReservationContextImpl(null, null, callerUser, owner); That "owner" account is then propagated all the way into FirewallManagerImpl.revokeFirewallRule() as the checkAccess() caller when cleaning up the network's firewall rules. DomainChecker.checkAccess() unconditionally rejects any caller whose account state isn't ENABLED, so if the network's owner account happens to be disabled (e.g. while it is being cleaned up, or simply disabled by an admin without removing it), network deletion is blocked entirely — even for a root admin performing the deletion. By contrast, AccountManagerImpl.cleanupAccount() (the cascade that runs during full account deletion) already builds this same ReservationContext with the actual calling account instead of the owner: ReservationContext context = new ReservationContextImpl(null, null, getActiveUser(callerUserId), caller); ...which is why account deletion itself was never affected — only a direct deleteNetwork/VPC-network-cleanup call against a disabled-but-not- yet-removed account. Fix --- Use the actual calling account (the account performing the operation) instead of the network's owner account when building the ReservationContext for destroyNetwork(), in all three call sites, matching the pattern already used by AccountManagerImpl.cleanupAccount(). Reproduced and verified ------------------------ Reproduced end-to-end against a running management server (pre-fix build) by: creating a test account, creating an isolated network with the Firewall service and implementing it (deploy a VM), adding firewall rules on its source-NAT IP, disabling the account, destroying/expunging the VM, then attempting to delete the network — which failed with the exact same stack trace as above. Re-enabling the account allowed the same deleteNetwork call to succeed immediately, confirming the account's disabled state (via the owner-as-caller path) as the sole cause. --- .../cluster/actionworkers/KubernetesClusterDestroyWorker.java | 2 +- server/src/main/java/com/cloud/network/NetworkServiceImpl.java | 2 +- server/src/main/java/com/cloud/network/vpc/VpcManagerImpl.java | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/plugins/integrations/kubernetes-service/src/main/java/com/cloud/kubernetes/cluster/actionworkers/KubernetesClusterDestroyWorker.java b/plugins/integrations/kubernetes-service/src/main/java/com/cloud/kubernetes/cluster/actionworkers/KubernetesClusterDestroyWorker.java index 62bd8b4576a4..1327b6e45cce 100644 --- a/plugins/integrations/kubernetes-service/src/main/java/com/cloud/kubernetes/cluster/actionworkers/KubernetesClusterDestroyWorker.java +++ b/plugins/integrations/kubernetes-service/src/main/java/com/cloud/kubernetes/cluster/actionworkers/KubernetesClusterDestroyWorker.java @@ -141,7 +141,7 @@ private void destroyKubernetesClusterNetwork() throws ManagementServerException if (network != null && network.getRemoved() == null) { Account owner = accountManager.getAccount(network.getAccountId()); User callerUser = accountManager.getActiveUser(CallContext.current().getCallingUserId()); - ReservationContext context = new ReservationContextImpl(null, null, callerUser, owner); + ReservationContext context = new ReservationContextImpl(null, null, callerUser, CallContext.current().getCallingAccount()); releaseASNumber(kubernetesCluster.getZoneId(), kubernetesCluster.getNetworkId()); boolean networkDestroyed = networkMgr.destroyNetwork(kubernetesCluster.getNetworkId(), context, true); if (!networkDestroyed) { diff --git a/server/src/main/java/com/cloud/network/NetworkServiceImpl.java b/server/src/main/java/com/cloud/network/NetworkServiceImpl.java index 6575da47364b..969c33fd53ab 100644 --- a/server/src/main/java/com/cloud/network/NetworkServiceImpl.java +++ b/server/src/main/java/com/cloud/network/NetworkServiceImpl.java @@ -2924,7 +2924,7 @@ public boolean deleteNetwork(long networkId, boolean forced) { } User callerUser = _accountMgr.getActiveUser(CallContext.current().getCallingUserId()); - ReservationContext context = new ReservationContextImpl(null, null, callerUser, owner); + ReservationContext context = new ReservationContextImpl(null, null, callerUser, caller); return _networkMgr.destroyNetwork(networkId, context, forced); } diff --git a/server/src/main/java/com/cloud/network/vpc/VpcManagerImpl.java b/server/src/main/java/com/cloud/network/vpc/VpcManagerImpl.java index 5717f8745aca..93de0f729fdf 100644 --- a/server/src/main/java/com/cloud/network/vpc/VpcManagerImpl.java +++ b/server/src/main/java/com/cloud/network/vpc/VpcManagerImpl.java @@ -2851,7 +2851,7 @@ protected boolean deletePrivateGatewayFromTheDB(final PrivateGateway gateway) { if (deleteNetworkFinal) { final User callerUser = _accountMgr.getActiveUser(CallContext.current().getCallingUserId()); final Account owner = _accountMgr.getAccount(Account.ACCOUNT_ID_SYSTEM); - final ReservationContext context = new ReservationContextImpl(null, null, callerUser, owner); + final ReservationContext context = new ReservationContextImpl(null, null, callerUser, CallContext.current().getCallingAccount()); _ntwkMgr.destroyNetwork(networkId, context, false); logger.debug("Deleted private network {}", network); }