|
19 | 19 |
|
20 | 20 | import com.cloud.exception.InvalidParameterValueException; |
21 | 21 | import com.cloud.exception.NetworkRuleConflictException; |
| 22 | +import com.cloud.exception.PermissionDeniedException; |
22 | 23 | import com.cloud.exception.ResourceUnavailableException; |
| 24 | +import com.cloud.network.IpAddress; |
23 | 25 | import com.cloud.network.IpAddressManager; |
24 | 26 | import com.cloud.network.Network; |
25 | 27 | import com.cloud.network.Network.Capability; |
26 | 28 | import com.cloud.network.Network.Service; |
27 | 29 | import com.cloud.network.NetworkModel; |
| 30 | +import com.cloud.network.NetworkModelImpl; |
28 | 31 | import com.cloud.network.NetworkRuleApplier; |
29 | 32 | import com.cloud.network.dao.FirewallRulesDao; |
30 | 33 | import com.cloud.network.dao.IPAddressDao; |
31 | 34 | import com.cloud.network.dao.IPAddressVO; |
32 | 35 | import com.cloud.network.dao.NetworkDao; |
33 | 36 | import com.cloud.network.dao.NetworkVO; |
34 | 37 | import com.cloud.network.element.FirewallServiceProvider; |
| 38 | +import com.cloud.network.element.NetworkElement; |
35 | 39 | import com.cloud.network.element.VirtualRouterElement; |
36 | 40 | import com.cloud.network.element.VpcVirtualRouterElement; |
37 | 41 | import com.cloud.network.rules.FirewallRule; |
|
43 | 47 | import com.cloud.user.Account; |
44 | 48 | import com.cloud.user.AccountManager; |
45 | 49 | import com.cloud.user.DomainManager; |
| 50 | +import com.cloud.user.User; |
46 | 51 | import com.cloud.utils.component.ComponentContext; |
| 52 | +import com.cloud.utils.db.Transaction; |
| 53 | +import com.cloud.utils.db.TransactionCallbackWithException; |
47 | 54 | import com.cloud.utils.exception.CloudRuntimeException; |
| 55 | +import com.cloud.utils.net.Ip; |
| 56 | +import org.apache.cloudstack.context.CallContext; |
48 | 57 | import org.apache.cloudstack.engine.orchestration.service.NetworkOrchestrationService; |
49 | 58 | import org.apache.cloudstack.network.RoutedIpv4Manager; |
50 | 59 | import org.junit.After; |
|
55 | 64 | import org.junit.runner.RunWith; |
56 | 65 | import org.mockito.InjectMocks; |
57 | 66 | import org.mockito.Mock; |
| 67 | +import org.mockito.MockedStatic; |
58 | 68 | import org.mockito.Mockito; |
59 | 69 | import org.mockito.MockitoAnnotations; |
60 | 70 | import org.mockito.Spy; |
61 | 71 | import org.mockito.junit.MockitoJUnitRunner; |
| 72 | +import org.springframework.test.util.ReflectionTestUtils; |
62 | 73 |
|
63 | 74 | import java.util.ArrayList; |
64 | 75 | import java.util.Arrays; |
@@ -605,6 +616,215 @@ public void testValidateFirewallRuleForVpcSucceedsWithSupportedProtocolAndTraffi |
605 | 616 | verify(_accountMgr, times(1)).checkAccess(caller, null, true, ipAddress); |
606 | 617 | } |
607 | 618 |
|
| 619 | + @Test |
| 620 | + public void testCreateIngressFirewallRuleRejectsReservedVpcIpUnderLock() throws NetworkRuleConflictException { |
| 621 | + Account caller = mock(Account.class); |
| 622 | + IPAddressVO sourceIp = createVpcIpAddress(); |
| 623 | + IPAddressVO lockedIp = createVpcIpAddress(); |
| 624 | + when(_ipAddressDao.findById(1L)).thenReturn(sourceIp); |
| 625 | + when(_ipAddressDao.acquireInLockTable(1L)).thenReturn(lockedIp); |
| 626 | + configureVpcFirewallCapabilities(); |
| 627 | + NetworkElement owningProvider = mock(NetworkElement.class); |
| 628 | + NetworkModelImpl networkModel = useProviderReservation(owningProvider); |
| 629 | + when(owningProvider.isIpAddressReserved(lockedIp)).thenReturn(true); |
| 630 | + |
| 631 | + CallContext.register(mock(User.class), caller); |
| 632 | + try (MockedStatic<Transaction> transactions = Mockito.mockStatic(Transaction.class)) { |
| 633 | + InvalidParameterValueException exception = Assert.assertThrows(InvalidParameterValueException.class, |
| 634 | + () -> _firewallMgr.createIngressFirewallRule(createVpcIngressRule())); |
| 635 | + Assert.assertTrue(exception.getMessage().contains("reserved by a network provider")); |
| 636 | + transactions.verifyNoInteractions(); |
| 637 | + } finally { |
| 638 | + CallContext.unregister(); |
| 639 | + } |
| 640 | + |
| 641 | + org.mockito.InOrder order = Mockito.inOrder(_ipAddressDao, _accountMgr, owningProvider); |
| 642 | + order.verify(_ipAddressDao).findById(1L); |
| 643 | + order.verify(_ipAddressDao).acquireInLockTable(1L); |
| 644 | + order.verify(_accountMgr).checkAccess(caller, null, true, lockedIp); |
| 645 | + order.verify(owningProvider).isIpAddressReserved(lockedIp); |
| 646 | + order.verify(_ipAddressDao).releaseFromLockTable(1L); |
| 647 | + verify(owningProvider, never()).isIpAddressReserved(sourceIp); |
| 648 | + verify(networkModel, never()).checkIpForService(any(), any(), any()); |
| 649 | + Mockito.verifyNoInteractions(_firewallDao); |
| 650 | + } |
| 651 | + |
| 652 | + @Test |
| 653 | + public void testCreateIngressFirewallRuleAllowsUnreservedVpcIpWithoutTierAssociation() throws NetworkRuleConflictException { |
| 654 | + Account caller = mock(Account.class); |
| 655 | + IPAddressVO ipAddress = createVpcIpAddress(); |
| 656 | + Assert.assertNull(ipAddress.getAssociatedWithNetworkId()); |
| 657 | + when(_ipAddressDao.findById(1L)).thenReturn(ipAddress); |
| 658 | + when(_ipAddressDao.acquireInLockTable(1L)).thenReturn(ipAddress); |
| 659 | + configureVpcFirewallCapabilities(); |
| 660 | + NetworkElement owningProvider = mock(NetworkElement.class); |
| 661 | + NetworkModelImpl networkModel = useProviderReservation(owningProvider); |
| 662 | + List<FirewallRuleVO> persistedRules = mockFirewallRulePersistence(); |
| 663 | + when(_firewallDao.listByIpAndPurposeAndNotRevoked(1L, null)).thenReturn(persistedRules); |
| 664 | + |
| 665 | + FirewallRule result; |
| 666 | + CallContext.register(mock(User.class), caller); |
| 667 | + try (MockedStatic<Transaction> transactions = mockFirewallRuleTransactions()) { |
| 668 | + result = _firewallMgr.createIngressFirewallRule(createVpcIngressRule()); |
| 669 | + Assert.assertEquals(result.getId(), CallContext.current().getContextParameter(FirewallRule.class)); |
| 670 | + } finally { |
| 671 | + CallContext.unregister(); |
| 672 | + } |
| 673 | + |
| 674 | + Assert.assertSame(persistedRules.get(0), result); |
| 675 | + Assert.assertEquals(Long.valueOf(10L), result.getVpcId()); |
| 676 | + Assert.assertNull(result.getNetworkId()); |
| 677 | + Assert.assertEquals(Long.valueOf(1L), result.getSourceIpAddressId()); |
| 678 | + Assert.assertEquals(3L, result.getAccountId()); |
| 679 | + Assert.assertEquals(4L, result.getDomainId()); |
| 680 | + Assert.assertEquals(FirewallRule.State.Add, result.getState()); |
| 681 | + org.mockito.InOrder order = Mockito.inOrder(_ipAddressDao, _accountMgr, owningProvider, _firewallDao); |
| 682 | + order.verify(_ipAddressDao).acquireInLockTable(1L); |
| 683 | + order.verify(_accountMgr).checkAccess(caller, null, true, ipAddress); |
| 684 | + order.verify(owningProvider).isIpAddressReserved(ipAddress); |
| 685 | + order.verify(_firewallDao).persist((FirewallRuleVO) result); |
| 686 | + order.verify(_firewallDao).setStateToAdd((FirewallRuleVO) result); |
| 687 | + order.verify(_ipAddressDao).releaseFromLockTable(1L); |
| 688 | + verify(networkModel, never()).checkIpForService(any(), any(), any()); |
| 689 | + } |
| 690 | + |
| 691 | + @Test |
| 692 | + public void testCreateIngressFirewallRuleChecksVpcIpAccessBeforeReservation() throws NetworkRuleConflictException { |
| 693 | + Account caller = mock(Account.class); |
| 694 | + IPAddressVO ipAddress = createVpcIpAddress(); |
| 695 | + when(_ipAddressDao.findById(1L)).thenReturn(ipAddress); |
| 696 | + when(_ipAddressDao.acquireInLockTable(1L)).thenReturn(ipAddress); |
| 697 | + PermissionDeniedException denied = new PermissionDeniedException("No access to this IP address"); |
| 698 | + Mockito.doThrow(denied).when(_accountMgr).checkAccess(caller, null, true, ipAddress); |
| 699 | + |
| 700 | + CallContext.register(mock(User.class), caller); |
| 701 | + try (MockedStatic<Transaction> transactions = Mockito.mockStatic(Transaction.class)) { |
| 702 | + Assert.assertSame(denied, Assert.assertThrows(PermissionDeniedException.class, |
| 703 | + () -> _firewallMgr.createIngressFirewallRule(createVpcIngressRule()))); |
| 704 | + transactions.verifyNoInteractions(); |
| 705 | + } finally { |
| 706 | + CallContext.unregister(); |
| 707 | + } |
| 708 | + |
| 709 | + org.mockito.InOrder order = Mockito.inOrder(_ipAddressDao, _accountMgr); |
| 710 | + order.verify(_ipAddressDao).acquireInLockTable(1L); |
| 711 | + order.verify(_accountMgr).checkAccess(caller, null, true, ipAddress); |
| 712 | + order.verify(_ipAddressDao).releaseFromLockTable(1L); |
| 713 | + Mockito.verifyNoInteractions(_networkModel, _vpcMgr, _firewallDao); |
| 714 | + } |
| 715 | + |
| 716 | + @Test |
| 717 | + public void testCreateIngressFirewallRuleCannotUseVpcIpWithoutLock() throws NetworkRuleConflictException { |
| 718 | + Account caller = mock(Account.class); |
| 719 | + when(_ipAddressDao.findById(1L)).thenReturn(createVpcIpAddress()); |
| 720 | + |
| 721 | + CallContext.register(mock(User.class), caller); |
| 722 | + try (MockedStatic<Transaction> transactions = Mockito.mockStatic(Transaction.class)) { |
| 723 | + InvalidParameterValueException exception = Assert.assertThrows(InvalidParameterValueException.class, |
| 724 | + () -> _firewallMgr.createIngressFirewallRule(createVpcIngressRule())); |
| 725 | + Assert.assertTrue(exception.getMessage().contains("couldn't locate IP address")); |
| 726 | + transactions.verifyNoInteractions(); |
| 727 | + } finally { |
| 728 | + CallContext.unregister(); |
| 729 | + } |
| 730 | + |
| 731 | + org.mockito.InOrder order = Mockito.inOrder(_ipAddressDao); |
| 732 | + order.verify(_ipAddressDao).acquireInLockTable(1L); |
| 733 | + order.verify(_ipAddressDao).releaseFromLockTable(1L); |
| 734 | + Mockito.verifyNoInteractions(_accountMgr, _networkModel, _vpcMgr, _firewallDao); |
| 735 | + } |
| 736 | + |
| 737 | + @Test |
| 738 | + public void testCreateFirewallRuleAllowsVpcSystemRuleWithoutIp() throws NetworkRuleConflictException { |
| 739 | + Account caller = mock(Account.class); |
| 740 | + Vpc vpc = mock(Vpc.class); |
| 741 | + when(_vpcMgr.getActiveVpc(10L)).thenReturn(vpc); |
| 742 | + when(vpc.getAccountId()).thenReturn(3L); |
| 743 | + when(vpc.getDomainId()).thenReturn(4L); |
| 744 | + List<FirewallRuleVO> persistedRules = mockFirewallRulePersistence(); |
| 745 | + |
| 746 | + FirewallRule result; |
| 747 | + CallContext.register(mock(User.class), caller); |
| 748 | + try (MockedStatic<Transaction> transactions = mockFirewallRuleTransactions()) { |
| 749 | + result = _firewallMgr.createFirewallRule(null, caller, "xid", 80, 80, "tcp", |
| 750 | + Collections.singletonList("0.0.0.0/0"), null, null, null, null, |
| 751 | + FirewallRuleType.System, null, 10L, FirewallRule.TrafficType.Egress, true); |
| 752 | + } finally { |
| 753 | + CallContext.unregister(); |
| 754 | + } |
| 755 | + |
| 756 | + Assert.assertSame(persistedRules.get(0), result); |
| 757 | + Assert.assertEquals(Long.valueOf(10L), result.getVpcId()); |
| 758 | + Assert.assertNull(result.getNetworkId()); |
| 759 | + Assert.assertNull(result.getSourceIpAddressId()); |
| 760 | + Assert.assertEquals(3L, result.getAccountId()); |
| 761 | + Assert.assertEquals(4L, result.getDomainId()); |
| 762 | + Assert.assertEquals(FirewallRuleType.System, result.getType()); |
| 763 | + Assert.assertEquals(FirewallRule.State.Add, result.getState()); |
| 764 | + verify(_firewallDao).setStateToAdd((FirewallRuleVO) result); |
| 765 | + Mockito.verifyNoInteractions(_ipAddressDao, _accountMgr, _networkModel); |
| 766 | + } |
| 767 | + |
| 768 | + private IPAddressVO createVpcIpAddress() { |
| 769 | + IPAddressVO ipAddress = new IPAddressVO(new Ip("198.51.100.10"), 1L, 0L, 1L, false); |
| 770 | + ReflectionTestUtils.setField(ipAddress, "id", 1L); |
| 771 | + ipAddress.setState(IpAddress.State.Allocated); |
| 772 | + ipAddress.setVpcId(10L); |
| 773 | + ipAddress.setAllocatedToAccountId(3L); |
| 774 | + ipAddress.setAllocatedInDomainId(4L); |
| 775 | + return ipAddress; |
| 776 | + } |
| 777 | + |
| 778 | + private FirewallRule createVpcIngressRule() { |
| 779 | + return new FirewallRuleVO("xid", 1L, 80, 80, "tcp", null, 3L, 4L, Purpose.Firewall, |
| 780 | + Collections.singletonList("0.0.0.0/0"), null, null, null, FirewallRule.TrafficType.Ingress); |
| 781 | + } |
| 782 | + |
| 783 | + private void configureVpcFirewallCapabilities() { |
| 784 | + FirewallServiceProvider firewallProvider = mock(FirewallServiceProvider.class); |
| 785 | + Map<Capability, String> firewallCaps = new HashMap<>(); |
| 786 | + firewallCaps.put(Capability.SupportedProtocols, "tcp,udp,icmp"); |
| 787 | + firewallCaps.put(Capability.SupportedTrafficDirection, "ingress,egress"); |
| 788 | + when(_vpcMgr.getActiveVpc(10L)).thenReturn(mock(Vpc.class)); |
| 789 | + when(firewallProvider.getProvider()).thenReturn(Network.Provider.VPCVirtualRouter); |
| 790 | + when(firewallProvider.getCapabilities()).thenReturn(Collections.singletonMap(Service.Firewall, firewallCaps)); |
| 791 | + when(_vpcMgr.isProviderSupportServiceInVpc(10L, Service.Firewall, Network.Provider.VPCVirtualRouter)).thenReturn(true); |
| 792 | + _firewallMgr._firewallElements = Collections.singletonList(firewallProvider); |
| 793 | + } |
| 794 | + |
| 795 | + private NetworkModelImpl useProviderReservation(NetworkElement owningProvider) { |
| 796 | + NetworkModelImpl networkModel = spy(new NetworkModelImpl()); |
| 797 | + networkModel.setNetworkElements(Collections.singletonList(owningProvider)); |
| 798 | + _firewallMgr._networkModel = networkModel; |
| 799 | + return networkModel; |
| 800 | + } |
| 801 | + |
| 802 | + private List<FirewallRuleVO> mockFirewallRulePersistence() { |
| 803 | + List<FirewallRuleVO> persistedRules = new ArrayList<>(); |
| 804 | + when(_firewallDao.persist(any(FirewallRuleVO.class))).thenAnswer(invocation -> { |
| 805 | + FirewallRuleVO rule = invocation.getArgument(0); |
| 806 | + ReflectionTestUtils.setField(rule, "id", 100L); |
| 807 | + persistedRules.add(rule); |
| 808 | + return rule; |
| 809 | + }); |
| 810 | + when(_firewallDao.setStateToAdd(any(FirewallRuleVO.class))).thenAnswer(invocation -> { |
| 811 | + FirewallRuleVO rule = invocation.getArgument(0); |
| 812 | + rule.setState(FirewallRule.State.Add); |
| 813 | + return true; |
| 814 | + }); |
| 815 | + return persistedRules; |
| 816 | + } |
| 817 | + |
| 818 | + private MockedStatic<Transaction> mockFirewallRuleTransactions() { |
| 819 | + MockedStatic<Transaction> transactions = Mockito.mockStatic(Transaction.class); |
| 820 | + transactions.when(() -> Transaction.execute(Mockito.<TransactionCallbackWithException<FirewallRuleVO, NetworkRuleConflictException>>any())) |
| 821 | + .thenAnswer(invocation -> { |
| 822 | + TransactionCallbackWithException<FirewallRuleVO, NetworkRuleConflictException> callback = invocation.getArgument(0); |
| 823 | + return callback.doInTransaction(null); |
| 824 | + }); |
| 825 | + return transactions; |
| 826 | + } |
| 827 | + |
608 | 828 | @Test |
609 | 829 | public void testCreateFirewallRuleRoutesToVpcWhenVpcIdProvided() throws NetworkRuleConflictException { |
610 | 830 | Account caller = Mockito.mock(Account.class); |
|
0 commit comments