From 455a77b23c83262457026635756f94878fe1b732 Mon Sep 17 00:00:00 2001 From: Marian Fischer Date: Thu, 12 Nov 2026 11:18:24 +0100 Subject: [PATCH] CKS: allow the APIs of the Kubernetes provider in the project role The CloudStack Kubernetes Provider in a cluster in a project uses the API keys of the account that CKS creates in the project. The role of that account does not allow several APIs that the provider calls: - listCapabilities: since v1.3.0 the provider reads the CloudStack version with it when it starts, unless its cloud config sets the version. The cloud config that CKS writes does not. - listNetworkACLLists: since v1.2.0 the provider reads the ACL list of a VPC tier before it adds an ACL rule for a LoadBalancer Service. - updateNetworkACLItem: since v1.3.0 the provider takes over the ACL rules that earlier releases created. - listLBStickinessPolicies, createLBStickinessPolicy and deleteLBStickinessPolicy: since v1.3.0 the provider manages stickiness policies. - listVPCs: to find out whether the VPC of a tier has the Firewall service, which a VPC offering can include since 4.23. Allow them in the role. --- .../cluster/KubernetesClusterManagerImpl.java | 14 ++++++++ .../KubernetesClusterManagerImplTest.java | 35 +++++++++++++++++++ 2 files changed, 49 insertions(+) diff --git a/plugins/integrations/kubernetes-service/src/main/java/com/cloud/kubernetes/cluster/KubernetesClusterManagerImpl.java b/plugins/integrations/kubernetes-service/src/main/java/com/cloud/kubernetes/cluster/KubernetesClusterManagerImpl.java index eb4ebb78ee48..b4d4450e4a3b 100644 --- a/plugins/integrations/kubernetes-service/src/main/java/com/cloud/kubernetes/cluster/KubernetesClusterManagerImpl.java +++ b/plugins/integrations/kubernetes-service/src/main/java/com/cloud/kubernetes/cluster/KubernetesClusterManagerImpl.java @@ -73,6 +73,7 @@ import org.apache.cloudstack.api.command.user.address.AssociateIPAddrCmd; import org.apache.cloudstack.api.command.user.address.DisassociateIPAddrCmd; import org.apache.cloudstack.api.command.user.address.ListPublicIpAddressesCmd; +import org.apache.cloudstack.api.command.user.config.ListCapabilitiesCmd; import org.apache.cloudstack.api.command.user.firewall.CreateFirewallRuleCmd; import org.apache.cloudstack.api.command.user.firewall.DeleteFirewallRuleCmd; import org.apache.cloudstack.api.command.user.firewall.ListFirewallRulesCmd; @@ -92,16 +93,21 @@ import org.apache.cloudstack.api.command.user.kubernetes.cluster.UpdateKubernetesClusterAffinityGroupCmd; import org.apache.cloudstack.api.command.user.kubernetes.cluster.UpgradeKubernetesClusterCmd; import org.apache.cloudstack.api.command.user.loadbalancer.AssignToLoadBalancerRuleCmd; +import org.apache.cloudstack.api.command.user.loadbalancer.CreateLBStickinessPolicyCmd; import org.apache.cloudstack.api.command.user.loadbalancer.CreateLoadBalancerRuleCmd; +import org.apache.cloudstack.api.command.user.loadbalancer.DeleteLBStickinessPolicyCmd; import org.apache.cloudstack.api.command.user.loadbalancer.DeleteLoadBalancerRuleCmd; +import org.apache.cloudstack.api.command.user.loadbalancer.ListLBStickinessPoliciesCmd; import org.apache.cloudstack.api.command.user.loadbalancer.ListLoadBalancerRuleInstancesCmd; import org.apache.cloudstack.api.command.user.loadbalancer.ListLoadBalancerRulesCmd; import org.apache.cloudstack.api.command.user.loadbalancer.RemoveFromLoadBalancerRuleCmd; import org.apache.cloudstack.api.command.user.loadbalancer.UpdateLoadBalancerRuleCmd; import org.apache.cloudstack.api.command.user.network.CreateNetworkACLCmd; import org.apache.cloudstack.api.command.user.network.DeleteNetworkACLCmd; +import org.apache.cloudstack.api.command.user.network.ListNetworkACLListsCmd; import org.apache.cloudstack.api.command.user.network.ListNetworkACLsCmd; import org.apache.cloudstack.api.command.user.network.ListNetworksCmd; +import org.apache.cloudstack.api.command.user.network.UpdateNetworkACLItemCmd; import org.apache.cloudstack.api.command.user.snapshot.CreateSnapshotCmd; import org.apache.cloudstack.api.command.user.snapshot.DeleteSnapshotCmd; import org.apache.cloudstack.api.command.user.snapshot.ListSnapshotsCmd; @@ -112,6 +118,7 @@ import org.apache.cloudstack.api.command.user.volume.DetachVolumeCmd; import org.apache.cloudstack.api.command.user.volume.ListVolumesCmd; import org.apache.cloudstack.api.command.user.volume.ResizeVolumeCmd; +import org.apache.cloudstack.api.command.user.vpc.ListVPCsCmd; import org.apache.cloudstack.api.response.KubernetesClusterConfigResponse; import org.apache.cloudstack.api.response.KubernetesClusterResponse; import org.apache.cloudstack.api.response.KubernetesUserVmResponse; @@ -267,6 +274,7 @@ public class KubernetesClusterManagerImpl extends ManagerBase implements Kuberne private static final String DEFAULT_NETWORK_OFFERING_FOR_KUBERNETES_SERVICE_NAME = "DefaultNetworkOfferingforKubernetesService"; private static final List> PROJECT_KUBERNETES_ACCOUNT_ROLE_ALLOWED_APIS = Arrays.asList( QueryAsyncJobResultCmd.class, + ListCapabilitiesCmd.class, ListVMsCmd.class, ListVolumesCmd.class, CreateVolumeCmd.class, @@ -275,6 +283,7 @@ public class KubernetesClusterManagerImpl extends ManagerBase implements Kuberne DetachVolumeCmd.class, ResizeVolumeCmd.class, ListNetworksCmd.class, + ListVPCsCmd.class, CreateSnapshotCmd.class, ListSnapshotsCmd.class, DeleteSnapshotCmd.class, @@ -288,6 +297,9 @@ public class KubernetesClusterManagerImpl extends ManagerBase implements Kuberne AssignToLoadBalancerRuleCmd.class, RemoveFromLoadBalancerRuleCmd.class, ListLoadBalancerRuleInstancesCmd.class, + ListLBStickinessPoliciesCmd.class, + CreateLBStickinessPolicyCmd.class, + DeleteLBStickinessPolicyCmd.class, ListFirewallRulesCmd.class, CreateFirewallRuleCmd.class, UpdateFirewallRuleCmd.class, @@ -295,6 +307,8 @@ public class KubernetesClusterManagerImpl extends ManagerBase implements Kuberne ListNetworkACLsCmd.class, CreateNetworkACLCmd.class, DeleteNetworkACLCmd.class, + ListNetworkACLListsCmd.class, + UpdateNetworkACLItemCmd.class, ListKubernetesClustersCmd.class, ScaleKubernetesClusterCmd.class ); diff --git a/plugins/integrations/kubernetes-service/src/test/java/com/cloud/kubernetes/cluster/KubernetesClusterManagerImplTest.java b/plugins/integrations/kubernetes-service/src/test/java/com/cloud/kubernetes/cluster/KubernetesClusterManagerImplTest.java index 1fab5420c3c3..87d03cd85bd8 100644 --- a/plugins/integrations/kubernetes-service/src/test/java/com/cloud/kubernetes/cluster/KubernetesClusterManagerImplTest.java +++ b/plugins/integrations/kubernetes-service/src/test/java/com/cloud/kubernetes/cluster/KubernetesClusterManagerImplTest.java @@ -49,6 +49,11 @@ import com.cloud.vm.dao.VMInstanceDao; import com.cloud.host.HostVO; import com.cloud.host.dao.HostDao; +import org.apache.cloudstack.acl.Role; +import org.apache.cloudstack.acl.RolePermissionEntity; +import org.apache.cloudstack.acl.RoleService; +import org.apache.cloudstack.acl.RoleType; +import org.apache.cloudstack.acl.Rule; import org.apache.cloudstack.affinity.AffinityGroupVO; import org.apache.cloudstack.affinity.dao.AffinityGroupDao; import org.apache.cloudstack.api.BaseCmd; @@ -63,6 +68,7 @@ import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; +import org.mockito.ArgumentCaptor; import org.mockito.InjectMocks; import org.mockito.Mock; import org.mockito.Mockito; @@ -118,6 +124,9 @@ public class KubernetesClusterManagerImplTest { @Mock private HostDao hostDao; + @Mock + private RoleService roleService; + @Spy @InjectMocks KubernetesClusterManagerImpl kubernetesClusterManager; @@ -914,4 +923,30 @@ public void testValidateNodeAffinityGroupsAffinityNoExistingWorkers() { Mockito.verify(kubernetesClusterAffinityGroupMapDao).listAffinityGroupIdsByClusterIdAndNodeType(1L, WORKER.name()); } + @Test + public void testProjectKubernetesAccountRoleAllowsTheProviderApis() { + Role role = Mockito.mock(Role.class); + Mockito.when(roleService.createRole(Mockito.anyString(), Mockito.any(RoleType.class), Mockito.anyString(), Mockito.anyBoolean())).thenReturn(role); + + kubernetesClusterManager.createProjectKubernetesAccountRole(); + + ArgumentCaptor rules = ArgumentCaptor.forClass(Rule.class); + ArgumentCaptor permissions = ArgumentCaptor.forClass(RolePermissionEntity.Permission.class); + Mockito.verify(roleService, Mockito.atLeastOnce()).createRolePermission(Mockito.eq(role), rules.capture(), permissions.capture(), Mockito.anyString()); + List ruleStrings = new ArrayList<>(); + for (Rule rule : rules.getAllValues()) { + ruleStrings.add(rule.getRuleString()); + } + + for (String api : Arrays.asList("listCapabilities", "listVPCs", "listNetworkACLLists", "updateNetworkACLItem", + "listLBStickinessPolicies", "createLBStickinessPolicy", "deleteLBStickinessPolicy")) { + int index = ruleStrings.indexOf(api); + Assert.assertTrue(api + " is not in the role: " + ruleStrings, index >= 0); + Assert.assertEquals(api, RolePermissionEntity.Permission.ALLOW, permissions.getAllValues().get(index)); + } + int last = ruleStrings.size() - 1; + Assert.assertEquals("*", ruleStrings.get(last)); + Assert.assertEquals(RolePermissionEntity.Permission.DENY, permissions.getAllValues().get(last)); + } + }