From 002d9768b2886f34ab2a572bfa462583e5dd5680 Mon Sep 17 00:00:00 2001 From: Abhisar Sinha <63767682+abh1sar@users.noreply.github.com> Date: Mon, 19 Jan 2026 13:18:37 +0530 Subject: [PATCH] Add settings to mark cryptographic algorithms in vpn customer gateways as excluded or obsolete (#12193) This PR introduces several configuration settings using which an operator can mark certain cryptographic algorithms and parameters as excluded or obsolete for VPN Customer Gateway creation for Site-to-Site VPN. Cloud providers following modern security frameworks (e.g., ISO 27001/27017) are required to enforce and communicate approved cryptographic standards. CloudStack currently accepts several weak or deprecated algorithms without guidance to users. This PR closes that gap by giving operators explicit control over what is disallowed vs discouraged, improving security posture without breaking existing deployments. These settings are: 1. vpn.customer.gateway.excluded.encryption.algorithms 2. vpn.customer.gateway.excluded.hashing.algorithms 3. vpn.customer.gateway.excluded.ike.versions 4. vpn.customer.gateway.excluded.dh.group 5. vpn.customer.gateway.obsolete.encryption.algorithms 6. vpn.customer.gateway.obsolete.hashing.algorithms 7. vpn.customer.gateway.obsolete.ike.versions 8. vpn.customer.gateway.obsolete.dh.group --- .../main/java/com/cloud/event/EventTypes.java | 2 + .../apache/cloudstack/alert/AlertService.java | 1 + .../apache/cloudstack/api/ApiConstants.java | 4 + .../user/config/ListCapabilitiesCmd.java | 16 + .../api/response/CapabilitiesResponse.java | 10 + .../Site2SiteCustomerGatewayResponse.java | 16 + .../ConfigKeyScheduledExecutionWrapper.java | 2 +- .../com/cloud/alert/AlertManagerImpl.java | 3 +- .../java/com/cloud/api/ApiResponseHelper.java | 13 + .../network/vpn/Site2SiteVpnManager.java | 6 + .../network/vpn/Site2SiteVpnManagerImpl.java | 279 +++++- .../cloud/server/ManagementServerImpl.java | 56 +- .../vpn/Site2SiteVpnManagerImplTest.java | 944 ++++++++++++++++++ .../vpc/MockSite2SiteVpnManagerImpl.java | 17 +- ui/public/locales/en.json | 22 +- ui/src/components/view/ListView.vue | 8 + ui/src/config/section/network.js | 10 +- .../network/CreateVpnCustomerGateway.vue | 360 +------ .../network/UpdateVpnCustomerGateway.vue | 129 +++ ui/src/views/network/VpnCustomerGateway.vue | 581 +++++++++++ 20 files changed, 2122 insertions(+), 357 deletions(-) create mode 100644 server/src/test/java/com/cloud/network/vpn/Site2SiteVpnManagerImplTest.java create mode 100644 ui/src/views/network/UpdateVpnCustomerGateway.vue create mode 100644 ui/src/views/network/VpnCustomerGateway.vue diff --git a/api/src/main/java/com/cloud/event/EventTypes.java b/api/src/main/java/com/cloud/event/EventTypes.java index 38e601c790a..d2989a8ffdc 100644 --- a/api/src/main/java/com/cloud/event/EventTypes.java +++ b/api/src/main/java/com/cloud/event/EventTypes.java @@ -503,6 +503,7 @@ public class EventTypes { public static final String EVENT_S2S_VPN_CUSTOMER_GATEWAY_CREATE = "VPN.S2S.CUSTOMER.GATEWAY.CREATE"; public static final String EVENT_S2S_VPN_CUSTOMER_GATEWAY_DELETE = "VPN.S2S.CUSTOMER.GATEWAY.DELETE"; public static final String EVENT_S2S_VPN_CUSTOMER_GATEWAY_UPDATE = "VPN.S2S.CUSTOMER.GATEWAY.UPDATE"; + public static final String EVENT_S2S_VPN_GATEWAY_OBSOLETE_PARAMS = "VPN.S2S.GATEWAY.OBSOLETE.PARAMS"; public static final String EVENT_S2S_VPN_CONNECTION_CREATE = "VPN.S2S.CONNECTION.CREATE"; public static final String EVENT_S2S_VPN_CONNECTION_DELETE = "VPN.S2S.CONNECTION.DELETE"; public static final String EVENT_S2S_VPN_CONNECTION_RESET = "VPN.S2S.CONNECTION.RESET"; @@ -1151,6 +1152,7 @@ public class EventTypes { entityEventDetails.put(EVENT_S2S_VPN_CUSTOMER_GATEWAY_CREATE, Site2SiteCustomerGateway.class); entityEventDetails.put(EVENT_S2S_VPN_CUSTOMER_GATEWAY_DELETE, Site2SiteCustomerGateway.class); entityEventDetails.put(EVENT_S2S_VPN_CUSTOMER_GATEWAY_UPDATE, Site2SiteCustomerGateway.class); + entityEventDetails.put(EVENT_S2S_VPN_GATEWAY_OBSOLETE_PARAMS, Site2SiteCustomerGateway.class); entityEventDetails.put(EVENT_S2S_VPN_CONNECTION_CREATE, Site2SiteVpnConnection.class); entityEventDetails.put(EVENT_S2S_VPN_CONNECTION_DELETE, Site2SiteVpnConnection.class); entityEventDetails.put(EVENT_S2S_VPN_CONNECTION_RESET, Site2SiteVpnConnection.class); diff --git a/api/src/main/java/org/apache/cloudstack/alert/AlertService.java b/api/src/main/java/org/apache/cloudstack/alert/AlertService.java index d8e471756a0..cc3188feeca 100644 --- a/api/src/main/java/org/apache/cloudstack/alert/AlertService.java +++ b/api/src/main/java/org/apache/cloudstack/alert/AlertService.java @@ -74,6 +74,7 @@ public interface AlertService { public static final AlertType ALERT_TYPE_VR_PUBLIC_IFACE_MTU = new AlertType((short)32, "ALERT.VR.PUBLIC.IFACE.MTU", true); public static final AlertType ALERT_TYPE_VR_PRIVATE_IFACE_MTU = new AlertType((short)32, "ALERT.VR.PRIVATE.IFACE.MTU", true); public static final AlertType ALERT_TYPE_EXTENSION_PATH_NOT_READY = new AlertType((short)33, "ALERT.TYPE.EXTENSION.PATH.NOT.READY", true); + public static final AlertType ALERT_TYPE_VPN_GATEWAY_OBSOLETE_PARAMETERS = new AlertType((short)34, "ALERT.S2S.VPN.GATEWAY.OBSOLETE.PARAMETERS", true); public static final AlertType ALERT_TYPE_BACKUP_STORAGE = new AlertType(Capacity.CAPACITY_TYPE_BACKUP_STORAGE, "ALERT.STORAGE.BACKUP", true); public static final AlertType ALERT_TYPE_OBJECT_STORAGE = new AlertType(Capacity.CAPACITY_TYPE_OBJECT_STORAGE, "ALERT.STORAGE.OBJECT", true); diff --git a/api/src/main/java/org/apache/cloudstack/api/ApiConstants.java b/api/src/main/java/org/apache/cloudstack/api/ApiConstants.java index 3e36d933772..896031806d5 100644 --- a/api/src/main/java/org/apache/cloudstack/api/ApiConstants.java +++ b/api/src/main/java/org/apache/cloudstack/api/ApiConstants.java @@ -1364,6 +1364,10 @@ public class ApiConstants { public static final String RECURSIVE_DOMAINS = "recursivedomains"; + public static final String VPN_CUSTOMER_GATEWAY_PARAMETERS = "vpncustomergatewayparameters"; + public static final String OBSOLETE_PARAMETERS = "obsoleteparameters"; + public static final String EXCLUDED_PARAMETERS = "excludedparameters"; + /** * This enum specifies IO Drivers, each option controls specific policies on I/O. * Qemu guests support "threads" and "native" options Since 0.8.8 ; "io_uring" is supported Since 6.3.0 (QEMU 5.0). diff --git a/api/src/main/java/org/apache/cloudstack/api/command/user/config/ListCapabilitiesCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/user/config/ListCapabilitiesCmd.java index ed1bd7b063b..94b6062b621 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/user/config/ListCapabilitiesCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/user/config/ListCapabilitiesCmd.java @@ -21,7 +21,9 @@ import java.util.Map; import org.apache.cloudstack.api.APICommand; import org.apache.cloudstack.api.ApiConstants; import org.apache.cloudstack.api.BaseCmd; +import org.apache.cloudstack.api.Parameter; import org.apache.cloudstack.api.response.CapabilitiesResponse; +import org.apache.cloudstack.api.response.DomainResponse; import org.apache.cloudstack.config.ApiServiceConfiguration; import com.cloud.user.Account; @@ -30,12 +32,22 @@ import com.cloud.user.Account; requestHasSensitiveInfo = false, responseHasSensitiveInfo = false) public class ListCapabilitiesCmd extends BaseCmd { + @Parameter(name = ApiConstants.DOMAIN_ID, + type = CommandType.UUID, + entityType = DomainResponse.class, + description = "the domain for listing capabilities.", + since = "4.23.0") + private Long domainId; @Override public long getEntityOwnerId() { return Account.ACCOUNT_ID_SYSTEM; } + public Long getDomainId() { + return domainId; + } + @Override public void execute() { Map capabilities = _mgr.listCapabilities(this); @@ -76,6 +88,10 @@ public class ListCapabilitiesCmd extends BaseCmd { response.setExtensionsPath((String)capabilities.get(ApiConstants.EXTENSIONS_PATH)); response.setDynamicScalingEnabled((Boolean) capabilities.get(ApiConstants.DYNAMIC_SCALING_ENABLED)); response.setAdditionalConfigEnabled((Boolean) capabilities.get(ApiConstants.ADDITONAL_CONFIG_ENABLED)); + if (capabilities.containsKey(ApiConstants.VPN_CUSTOMER_GATEWAY_PARAMETERS)) { + Map vpnCustomerGatewayParameters = (Map) capabilities.get(ApiConstants.VPN_CUSTOMER_GATEWAY_PARAMETERS); + response.setVpnCustomerGatewayParameters(vpnCustomerGatewayParameters); + } response.setObjectName("capability"); response.setResponseName(getCommandName()); this.setResponseObject(response); diff --git a/api/src/main/java/org/apache/cloudstack/api/response/CapabilitiesResponse.java b/api/src/main/java/org/apache/cloudstack/api/response/CapabilitiesResponse.java index 930d1a50de0..81621696280 100644 --- a/api/src/main/java/org/apache/cloudstack/api/response/CapabilitiesResponse.java +++ b/api/src/main/java/org/apache/cloudstack/api/response/CapabilitiesResponse.java @@ -16,6 +16,8 @@ // under the License. package org.apache.cloudstack.api.response; +import java.util.Map; + import org.apache.cloudstack.acl.RoleType; import org.apache.cloudstack.api.ApiConstants; import org.apache.cloudstack.api.BaseResponse; @@ -153,6 +155,10 @@ public class CapabilitiesResponse extends BaseResponse { @Param(description = "true if additional configurations or extraconfig can be passed to Instances", since = "4.20.2") private Boolean additionalConfigEnabled; + @SerializedName(ApiConstants.VPN_CUSTOMER_GATEWAY_PARAMETERS) + @Param(description = "Excluded and obsolete VPN customer gateway cryptographic parameters") + private Map vpnCustomerGatewayParameters; + public void setSecurityGroupsEnabled(boolean securityGroupsEnabled) { this.securityGroupsEnabled = securityGroupsEnabled; } @@ -280,4 +286,8 @@ public class CapabilitiesResponse extends BaseResponse { public void setAdditionalConfigEnabled(Boolean additionalConfigEnabled) { this.additionalConfigEnabled = additionalConfigEnabled; } + + public void setVpnCustomerGatewayParameters(Map vpnCustomerGatewayParameters) { + this.vpnCustomerGatewayParameters = vpnCustomerGatewayParameters; + } } diff --git a/api/src/main/java/org/apache/cloudstack/api/response/Site2SiteCustomerGatewayResponse.java b/api/src/main/java/org/apache/cloudstack/api/response/Site2SiteCustomerGatewayResponse.java index 4e5820279a2..b121ef7ce61 100644 --- a/api/src/main/java/org/apache/cloudstack/api/response/Site2SiteCustomerGatewayResponse.java +++ b/api/src/main/java/org/apache/cloudstack/api/response/Site2SiteCustomerGatewayResponse.java @@ -114,6 +114,14 @@ public class Site2SiteCustomerGatewayResponse extends BaseResponseWithAnnotation @Param(description = "Which IKE Version to use, one of ike (autoselect), IKEv1, or IKEv2. Defaults to ike") private String ikeVersion; + @SerializedName(ApiConstants.OBSOLETE_PARAMETERS) + @Param(description = "Contains the list of obsolete/insecure cryptographic parameters that the vpn customer gateway is using.", since = "4.23.0") + private String obsoleteParameters; + + @SerializedName(ApiConstants.EXCLUDED_PARAMETERS) + @Param(description = "Contains the list of excluded/not allowed cryptographic parameters that the vpn customer gateway is using.", since = "4.23.0") + private String excludedParameters; + public void setId(String id) { this.id = id; } @@ -202,4 +210,12 @@ public class Site2SiteCustomerGatewayResponse extends BaseResponseWithAnnotation this.domainPath = domainPath; } + public void setContainsObsoleteParameters(String obsoleteParameters) { + this.obsoleteParameters = obsoleteParameters; + } + + public void setContainsExcludedParameters(String excludedParameters) { + this.excludedParameters = excludedParameters; + } + } diff --git a/framework/config/src/main/java/org/apache/cloudstack/framework/config/ConfigKeyScheduledExecutionWrapper.java b/framework/config/src/main/java/org/apache/cloudstack/framework/config/ConfigKeyScheduledExecutionWrapper.java index b8d7e782971..a02ee8abee0 100644 --- a/framework/config/src/main/java/org/apache/cloudstack/framework/config/ConfigKeyScheduledExecutionWrapper.java +++ b/framework/config/src/main/java/org/apache/cloudstack/framework/config/ConfigKeyScheduledExecutionWrapper.java @@ -66,7 +66,7 @@ public class ConfigKeyScheduledExecutionWrapper implements Runnable { this.unit = unit; } - protected ConfigKeyScheduledExecutionWrapper(ScheduledExecutorService executorService, Runnable command, + public ConfigKeyScheduledExecutionWrapper(ScheduledExecutorService executorService, Runnable command, ConfigKey configKey, int enableIntervalSeconds, TimeUnit unit) { validateArgs(executorService, command, configKey); this.executorService = executorService; diff --git a/server/src/main/java/com/cloud/alert/AlertManagerImpl.java b/server/src/main/java/com/cloud/alert/AlertManagerImpl.java index 3eab3fbfeb1..308c4443cb8 100644 --- a/server/src/main/java/com/cloud/alert/AlertManagerImpl.java +++ b/server/src/main/java/com/cloud/alert/AlertManagerImpl.java @@ -112,7 +112,8 @@ public class AlertManagerImpl extends ManagerBase implements AlertManager, Confi , AlertType.ALERT_TYPE_OOBM_AUTH_ERROR , AlertType.ALERT_TYPE_HA_ACTION , AlertType.ALERT_TYPE_CA_CERT - , AlertType.ALERT_TYPE_EXTENSION_PATH_NOT_READY); + , AlertType.ALERT_TYPE_EXTENSION_PATH_NOT_READY + , AlertType.ALERT_TYPE_VPN_GATEWAY_OBSOLETE_PARAMETERS); private static final long INITIAL_CAPACITY_CHECK_DELAY = 30L * 1000L; // Thirty seconds expressed in milliseconds. diff --git a/server/src/main/java/com/cloud/api/ApiResponseHelper.java b/server/src/main/java/com/cloud/api/ApiResponseHelper.java index 3c7eeaf3b8f..ce794cf5388 100644 --- a/server/src/main/java/com/cloud/api/ApiResponseHelper.java +++ b/server/src/main/java/com/cloud/api/ApiResponseHelper.java @@ -50,6 +50,7 @@ import com.cloud.dc.dao.ASNumberRangeDao; import com.cloud.dc.dao.VlanDetailsDao; import com.cloud.hypervisor.Hypervisor; import com.cloud.network.vpc.VpcGateway; +import com.cloud.network.vpn.Site2SiteVpnManager; import com.cloud.storage.BucketVO; import org.apache.cloudstack.acl.ControlledEntity; import org.apache.cloudstack.acl.ControlledEntity.ACLType; @@ -528,6 +529,8 @@ public class ApiResponseHelper implements ResponseGenerator { @Inject RoutedIpv4Manager routedIpv4Manager; @Inject + Site2SiteVpnManager site2SiteVpnManager; + @Inject ResourceIconManager resourceIconManager; public static String getPrettyDomainPath(String path) { @@ -3884,6 +3887,16 @@ public class ApiResponseHelper implements ResponseGenerator { response.setRemoved(result.getRemoved()); response.setIkeVersion(result.getIkeVersion()); response.setSplitConnections(result.getSplitConnections()); + + Set obsoleteParameters = site2SiteVpnManager.getObsoleteVpnGatewayParameters(result); + if (CollectionUtils.isNotEmpty(obsoleteParameters)) { + response.setContainsObsoleteParameters(obsoleteParameters.toString()); + } + Set excludedParameters = site2SiteVpnManager.getExcludedVpnGatewayParameters(result); + if (CollectionUtils.isNotEmpty(excludedParameters)) { + response.setContainsExcludedParameters(excludedParameters.toString()); + } + response.setObjectName("vpncustomergateway"); response.setHasAnnotation(annotationDao.hasAnnotations(result.getUuid(), AnnotationService.EntityType.VPN_CUSTOMER_GATEWAY.name(), _accountMgr.isRootAdmin(CallContext.current().getCallingAccount().getId()))); diff --git a/server/src/main/java/com/cloud/network/vpn/Site2SiteVpnManager.java b/server/src/main/java/com/cloud/network/vpn/Site2SiteVpnManager.java index 25c84d6d956..9cf604f8507 100644 --- a/server/src/main/java/com/cloud/network/vpn/Site2SiteVpnManager.java +++ b/server/src/main/java/com/cloud/network/vpn/Site2SiteVpnManager.java @@ -17,11 +17,17 @@ package com.cloud.network.vpn; import java.util.List; +import java.util.Set; +import com.cloud.network.Site2SiteCustomerGateway; import com.cloud.network.dao.Site2SiteVpnConnectionVO; import com.cloud.vm.DomainRouterVO; public interface Site2SiteVpnManager extends Site2SiteVpnService { + Set getExcludedVpnGatewayParameters(Site2SiteCustomerGateway customerGw); + + Set getObsoleteVpnGatewayParameters(Site2SiteCustomerGateway customerGw); + boolean cleanupVpnConnectionByVpc(long vpcId); boolean cleanupVpnGatewayByVpc(long vpcId); diff --git a/server/src/main/java/com/cloud/network/vpn/Site2SiteVpnManagerImpl.java b/server/src/main/java/com/cloud/network/vpn/Site2SiteVpnManagerImpl.java index ad1d1f02682..09236eb0d6e 100644 --- a/server/src/main/java/com/cloud/network/vpn/Site2SiteVpnManagerImpl.java +++ b/server/src/main/java/com/cloud/network/vpn/Site2SiteVpnManagerImpl.java @@ -17,15 +17,22 @@ package com.cloud.network.vpn; import java.util.ArrayList; +import java.util.HashSet; import java.util.List; import java.util.Map; +import java.util.Set; +import java.util.concurrent.Executors; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.TimeUnit; import javax.inject.Inject; import javax.naming.ConfigurationException; +import org.apache.cloudstack.framework.config.Configurable; import org.apache.commons.collections.CollectionUtils; import org.springframework.stereotype.Component; +import org.apache.cloudstack.alert.AlertService; import org.apache.cloudstack.annotation.AnnotationService; import org.apache.cloudstack.annotation.dao.AnnotationDao; import org.apache.cloudstack.api.command.user.vpn.CreateVpnConnectionCmd; @@ -41,9 +48,16 @@ import org.apache.cloudstack.api.command.user.vpn.ResetVpnConnectionCmd; import org.apache.cloudstack.api.command.user.vpn.UpdateVpnCustomerGatewayCmd; import org.apache.cloudstack.context.CallContext; import org.apache.cloudstack.framework.config.dao.ConfigurationDao; +import org.apache.cloudstack.framework.config.ConfigKey; +import org.apache.cloudstack.framework.config.ConfigKeyScheduledExecutionWrapper; +import org.apache.cloudstack.managed.context.ManagedContextRunnable; +import com.cloud.utils.concurrency.NamedThreadFactory; + +import com.cloud.alert.AlertManager; import com.cloud.configuration.Config; import com.cloud.event.ActionEvent; +import com.cloud.event.ActionEventUtils; import com.cloud.event.EventTypes; import com.cloud.exception.InvalidParameterValueException; import com.cloud.exception.PermissionDeniedException; @@ -72,9 +86,11 @@ import com.cloud.network.vpc.dao.VpcOfferingServiceMapDao; import com.cloud.projects.Project.ListProjectResourcesCriteria; import com.cloud.user.Account; import com.cloud.user.AccountManager; +import com.cloud.user.User; import com.cloud.user.dao.AccountDao; import com.cloud.utils.NumbersUtil; import com.cloud.utils.Pair; +import com.cloud.utils.StringUtils; import com.cloud.utils.Ternary; import com.cloud.utils.component.ManagerBase; import com.cloud.utils.db.DB; @@ -88,7 +104,52 @@ import com.cloud.vm.DomainRouterVO; import com.cloud.vm.dao.DomainRouterDao; @Component -public class Site2SiteVpnManagerImpl extends ManagerBase implements Site2SiteVpnManager { +public class Site2SiteVpnManagerImpl extends ManagerBase implements Site2SiteVpnManager, Configurable { + + // Configuration keys for VPN gateway cryptographic parameter controls + public static final ConfigKey VpnCustomerGatewayExcludedEncryptionAlgorithms = new ConfigKey( + ConfigKey.CATEGORY_NETWORK, String.class, "vpn.customer.gateway.excluded.encryption.algorithms", "", + "Comma-separated list of encryption algorithms that are excluded and cannot be selected by end users for VPN Customer Gateways." + + "Applies to both IKE and ESP phases. Allowed values are aes128, aes192 and aes256 and 3des.", + true, ConfigKey.Scope.Domain); + public static final ConfigKey VpnCustomerGatewayExcludedHashingAlgorithms = new ConfigKey( + ConfigKey.CATEGORY_NETWORK, String.class, "vpn.customer.gateway.excluded.hashing.algorithms", "", + "Comma-separated list of hashing algorithms that are excluded and cannot be selected by end users for VPN Customer Gateways." + + "Applies to both IKE and ESP phases. Allowed values are sha1, sha256, sha384 and sha512 and md5.", + true, ConfigKey.Scope.Domain); + public static final ConfigKey VpnCustomerGatewayExcludedIkeVersions = new ConfigKey( + ConfigKey.CATEGORY_NETWORK, String.class, "vpn.customer.gateway.excluded.ike.versions", "", + "Comma-separated list of IKE versions that are excluded and cannot be selected by end users for VPN Customer Gateways. Allowed values are ikev, ikev1 and ikev2.", + true, ConfigKey.Scope.Domain); + public static final ConfigKey VpnCustomerGatewayExcludedDhGroup = new ConfigKey( + ConfigKey.CATEGORY_NETWORK, String.class, "vpn.customer.gateway.excluded.dh.group", "", + "Comma-separated list of Diffie-Hellman groups that are excluded and cannot be selected by end users for VPN Customer Gateways." + + "Applies to both IKE and ESP phases. Allowed values are modp1024, modp1536, modp2048, modp3072, modp4096, modp6144 and modp8192.", + true, ConfigKey.Scope.Domain); + public static final ConfigKey VpnCustomerGatewayObsoleteEncryptionAlgorithms = new ConfigKey( + ConfigKey.CATEGORY_NETWORK, String.class, "vpn.customer.gateway.obsolete.encryption.algorithms", "", + "Comma-separated list of encryption algorithms that are marked as obsolete/insecure for VPN Customer Gateways." + + "Applies to both IKE and ESP phases. Allowed values are aes128, aes192 and aes256 and 3des.", + true, ConfigKey.Scope.Domain); + public static final ConfigKey VpnCustomerGatewayObsoleteHashingAlgorithms = new ConfigKey( + ConfigKey.CATEGORY_NETWORK, String.class, "vpn.customer.gateway.obsolete.hashing.algorithms", "", + "Comma-separated list of hashing algorithms that are marked as obsolete/insecure for VPN Customer Gateways." + + "Applies to both IKE and ESP phases. Allowed values are sha1, sha256, sha384 and sha512 and md5.", + true, ConfigKey.Scope.Domain); + public static final ConfigKey VpnCustomerGatewayObsoleteIkeVersions = new ConfigKey( + ConfigKey.CATEGORY_NETWORK, String.class, "vpn.customer.gateway.obsolete.ike.versions", "", + "Comma-separated list of IKE versions that are marked as obsolete/insecure for VPN Customer Gateways. Allowed values are ikev, ikev1 and ikev2.", + true, ConfigKey.Scope.Domain); + public static final ConfigKey VpnCustomerGatewayObsoleteDhGroup = new ConfigKey( + ConfigKey.CATEGORY_NETWORK, String.class, "vpn.customer.gateway.obsolete.dh.group", "", + "Comma-separated list of Diffie-Hellman groups that are marked as obsolete/insecure for VPN Customer Gateways." + + "Applies to both IKE and ESP phases. Allowed values are modp1024, modp1536, modp2048, modp3072, modp4096, modp6144 and modp8192.", + true, ConfigKey.Scope.Domain); + public static final ConfigKey VpnCustomerGatewayObsoleteCheckInterval = new ConfigKey( + ConfigKey.CATEGORY_NETWORK, Long.class, "vpn.customer.gateway.obsolete.check.interval", "0", + "Interval in hours to periodically check VPN customer gateways for obsolete/excluded parameters and generate events and alerts. " + + "Set to 0 to disable. Default: 0 (disabled).", + true, ConfigKey.Scope.Global); List _s2sProviders; @Inject @@ -117,9 +178,12 @@ public class Site2SiteVpnManagerImpl extends ManagerBase implements Site2SiteVpn private IpAddressManager ipAddressManager; @Inject private VpcManager vpcManager; + @Inject + private AlertManager _alertMgr; int _connLimit; int _subnetsLimit; + private ScheduledExecutorService _vpnCheckExecutor; @Override public boolean configure(String name, Map params) throws ConfigurationException { @@ -127,6 +191,7 @@ public class Site2SiteVpnManagerImpl extends ManagerBase implements Site2SiteVpn _connLimit = NumbersUtil.parseInt(configs.get(Config.Site2SiteVpnConnectionPerVpnGatewayLimit.key()), 4); _subnetsLimit = NumbersUtil.parseInt(configs.get(Config.Site2SiteVpnSubnetsPerCustomerGatewayLimit.key()), 10); assert (_s2sProviders.iterator().hasNext()) : "Did not get injected with a list of S2S providers!"; + _vpnCheckExecutor = Executors.newScheduledThreadPool(1, new NamedThreadFactory("VpnCustomerGateway-ExcludedAndObsoleteCheck")); return true; } @@ -146,7 +211,7 @@ public class Site2SiteVpnManagerImpl extends ManagerBase implements Site2SiteVpn } Site2SiteVpnGatewayVO gws = _vpnGatewayDao.findByVpcId(vpcId); if (gws != null) { - throw new InvalidParameterValueException(String.format("The VPN gateway of VPC %s already existed!", vpc)); + throw new InvalidParameterValueException(String.format("The VPN gateway of VPC %s already exists!", vpc)); } IPAddressVO requestedIp = _ipAddressDao.findById(cmd.getIpAddressId()); @@ -187,6 +252,113 @@ public class Site2SiteVpnManagerImpl extends ManagerBase implements Site2SiteVpn } } + private void validateVpnCryptographicParameters(String ikePolicy, String espPolicy, String ikeVersion, Long domainId) { + String excludedEncryption = VpnCustomerGatewayExcludedEncryptionAlgorithms.valueIn(domainId); + String excludedHashing = VpnCustomerGatewayExcludedHashingAlgorithms.valueIn(domainId); + String excludedIkeVersions = VpnCustomerGatewayExcludedIkeVersions.valueIn(domainId); + String excludedDhGroup = VpnCustomerGatewayExcludedDhGroup.valueIn(domainId); + + Set excludedParameters = getVpnGatewayParametersInBlockedList(ikePolicy, espPolicy, ikeVersion, + excludedEncryption, excludedHashing, excludedIkeVersions, excludedDhGroup); + if (!excludedParameters.isEmpty()) { + throw new InvalidParameterValueException("The following excluded cryptographic parameter(s) cannot be used in a VPN Customer Gateway: " + excludedParameters.toString()); + } + } + + @Override + public Set getExcludedVpnGatewayParameters(Site2SiteCustomerGateway customerGw) { + Long domainId = customerGw.getDomainId(); + String excludedEncryption = VpnCustomerGatewayExcludedEncryptionAlgorithms.valueIn(domainId); + String excludedHashing = VpnCustomerGatewayExcludedHashingAlgorithms.valueIn(domainId); + String excludedIkeVersions = VpnCustomerGatewayExcludedIkeVersions.valueIn(domainId); + String excludedDhGroup = VpnCustomerGatewayExcludedDhGroup.valueIn(domainId); + + return getVpnGatewayParametersInBlockedList(customerGw.getIkePolicy(), customerGw.getEspPolicy(), customerGw.getIkeVersion(), + excludedEncryption, excludedHashing, excludedIkeVersions, excludedDhGroup); + } + + @Override + public Set getObsoleteVpnGatewayParameters(Site2SiteCustomerGateway customerGw) { + Long domainId = customerGw.getDomainId(); + String obsoleteEncryption = VpnCustomerGatewayObsoleteEncryptionAlgorithms.valueIn(domainId); + String obsoleteHashing = VpnCustomerGatewayObsoleteHashingAlgorithms.valueIn(domainId); + String obsoleteIkeVersions = VpnCustomerGatewayObsoleteIkeVersions.valueIn(domainId); + String obsoleteDhGroup = VpnCustomerGatewayObsoleteDhGroup.valueIn(domainId); + + return getVpnGatewayParametersInBlockedList(customerGw.getIkePolicy(), customerGw.getEspPolicy(), customerGw.getIkeVersion(), + obsoleteEncryption, obsoleteHashing, obsoleteIkeVersions, obsoleteDhGroup); + } + + private Set getVpnGatewayParametersInBlockedList(String ikePolicy, String espPolicy, String ikeVersion, + String blockedEncryptionList, String blockedHashingList, + String blockedIkeVersionList, String blockedDhGroupList) { + + Set blockedParameters = new HashSet<>(); + if (StringUtils.isEmpty(blockedEncryptionList) + && StringUtils.isEmpty(blockedHashingList) + && StringUtils.isEmpty(blockedIkeVersionList) + && StringUtils.isEmpty(blockedDhGroupList)) { + return blockedParameters; + } + + if (isParameterInList(ikeVersion, blockedIkeVersionList)) { + blockedParameters.add(ikeVersion); + } + + Set ikePolicyResult = getVpnGatewayPolicyParametersInBlockedList(ikePolicy, "IKE", blockedEncryptionList, blockedHashingList, blockedDhGroupList); + if (CollectionUtils.isNotEmpty(ikePolicyResult)) { + blockedParameters.addAll(ikePolicyResult); + } + + Set espPolicyResult = getVpnGatewayPolicyParametersInBlockedList(espPolicy, "ESP", blockedEncryptionList, blockedHashingList, blockedDhGroupList); + if (CollectionUtils.isNotEmpty(espPolicyResult)) { + blockedParameters.addAll(espPolicyResult); + } + + return blockedParameters; + } + + private Set getVpnGatewayPolicyParametersInBlockedList(String policy, String policyType, String blockedEncryptionList, String blockedHashingList, String blockedDhGroupList) { + + String trimmedPolicy = policy.trim(); + String cipherHash = trimmedPolicy.split(";")[0]; + String[] parts = cipherHash.split("-"); + + String encryption = parts[0].trim(); + String hashing = parts.length > 1 ? parts[1].trim() : ""; + + Set blockedParameters = new HashSet<>(); + if (isParameterInList(encryption, blockedEncryptionList)) { + blockedParameters.add(encryption); + } + + if (isParameterInList(hashing, blockedHashingList)) { + blockedParameters.add(hashing); + } + + if (!trimmedPolicy.equals(cipherHash)) { + String dhGroup = trimmedPolicy.split(";")[1].trim(); + if (isParameterInList(dhGroup, blockedDhGroupList)) { + blockedParameters.add(dhGroup); + } + } + return blockedParameters; + } + + private boolean isParameterInList(String parameter, String list) { + if (StringUtils.isEmpty(list) || StringUtils.isEmpty(parameter)) { + return false; + } + + String[] entries = list.split(","); + for (String item : entries) { + if (item != null && item.trim().equalsIgnoreCase(parameter.trim())) { + return true; + } + } + return false; + } + protected void checkCustomerGatewayCidrList(String guestCidrList) { String[] cidrList = guestCidrList.split(","); if (cidrList.length > _subnetsLimit) { @@ -235,6 +407,13 @@ public class Site2SiteVpnManagerImpl extends ManagerBase implements Site2SiteVpn if (!NetUtils.isValidS2SVpnPolicy("esp", espPolicy)) { throw new InvalidParameterValueException("The customer gateway ESP policy " + espPolicy + " is invalid!"); } + + String ikeVersion = cmd.getIkeVersion(); + if (ikeVersion == null) { + ikeVersion = "ike"; + } + validateVpnCryptographicParameters(ikePolicy, espPolicy, ikeVersion, owner.getDomainId()); + Long ikeLifetime = cmd.getIkeLifetime(); if (ikeLifetime == null) { // Default value of lifetime is 1 day @@ -264,7 +443,7 @@ public class Site2SiteVpnManagerImpl extends ManagerBase implements Site2SiteVpn long accountId = owner.getAccountId(); if (_customerGatewayDao.findByNameAndAccountId(name, accountId) != null) { - throw new InvalidParameterValueException("The customer gateway with name " + name + " already existed!"); + throw new InvalidParameterValueException("The customer gateway with name " + name + " already exists!"); } Boolean splitConnections = cmd.getSplitConnections(); @@ -272,11 +451,6 @@ public class Site2SiteVpnManagerImpl extends ManagerBase implements Site2SiteVpn splitConnections = false; } - String ikeVersion = cmd.getIkeVersion(); - if (ikeVersion == null) { - ikeVersion = "ike"; - } - checkCustomerGatewayCidrList(peerCidrList); Site2SiteCustomerGatewayVO gw = @@ -374,7 +548,7 @@ public class Site2SiteVpnManagerImpl extends ManagerBase implements Site2SiteVpn private void validateVpnConnectionDoesntExist(Site2SiteCustomerGateway customerGateway, Site2SiteVpnGateway vpnGateway) { if (_vpnConnectionDao.findByVpnGatewayIdAndCustomerGatewayId(vpnGateway.getId(), customerGateway.getId()) != null) { - throw new InvalidParameterValueException(String.format("The vpn connection with customer gateway %s and vpn gateway %s already existed!", customerGateway, vpnGateway)); + throw new InvalidParameterValueException(String.format("The vpn connection with customer gateway %s and vpn gateway %s already exists!", customerGateway, vpnGateway)); } } @@ -521,6 +695,10 @@ public class Site2SiteVpnManagerImpl extends ManagerBase implements Site2SiteVpn if (!NetUtils.isValidS2SVpnPolicy("esp", espPolicy)) { throw new InvalidParameterValueException("The customer gateway ESP policy" + espPolicy + " is invalid!"); } + + String ikeVersion = cmd.getIkeVersion(); + validateVpnCryptographicParameters(ikePolicy, espPolicy, ikeVersion, gw.getDomainId()); + Long ikeLifetime = cmd.getIkeLifetime(); if (ikeLifetime == null) { // Default value of lifetime is 1 day @@ -550,14 +728,12 @@ public class Site2SiteVpnManagerImpl extends ManagerBase implements Site2SiteVpn Boolean splitConnections = cmd.getSplitConnections(); - String ikeVersion = cmd.getIkeVersion(); - checkCustomerGatewayCidrList(guestCidrList); long accountId = gw.getAccountId(); Site2SiteCustomerGatewayVO existedGw = _customerGatewayDao.findByNameAndAccountId(name, accountId); if (existedGw != null && existedGw.getId() != gw.getId()) { - throw new InvalidParameterValueException("The customer gateway with name " + name + " already existed!"); + throw new InvalidParameterValueException("The customer gateway with name " + name + " already exists!"); } gw.setName(name); @@ -977,4 +1153,83 @@ public class Site2SiteVpnManagerImpl extends ManagerBase implements Site2SiteVpn return _vpnGatewayDao.findById(id); } + + @Override + public boolean start() { + ConfigKeyScheduledExecutionWrapper runner = new ConfigKeyScheduledExecutionWrapper( + _vpnCheckExecutor, + new CheckVpnCustomerGatewayObsoleteParametersTask(), + VpnCustomerGatewayObsoleteCheckInterval, + 3600, + TimeUnit.HOURS); + runner.start(); + return true; + } + + @Override + public boolean stop() { + if (_vpnCheckExecutor != null) { + _vpnCheckExecutor.shutdownNow(); + } + return true; + } + + @Override + public String getConfigComponentName() { + return Site2SiteVpnManager.class.getSimpleName(); + } + + @Override + public ConfigKey[] getConfigKeys() { + return new ConfigKey[] { VpnCustomerGatewayExcludedEncryptionAlgorithms, VpnCustomerGatewayExcludedHashingAlgorithms, + VpnCustomerGatewayExcludedIkeVersions, VpnCustomerGatewayExcludedDhGroup, VpnCustomerGatewayObsoleteEncryptionAlgorithms, + VpnCustomerGatewayObsoleteHashingAlgorithms, VpnCustomerGatewayObsoleteIkeVersions, VpnCustomerGatewayObsoleteDhGroup, + VpnCustomerGatewayObsoleteCheckInterval}; + } + + protected class CheckVpnCustomerGatewayObsoleteParametersTask extends ManagedContextRunnable { + + @Override + protected void runInContext() { + List allGateways = _customerGatewayDao.listAll(); + int obsoleteCount = 0; + int excludedCount = 0; + + for (Site2SiteCustomerGatewayVO gateway : allGateways) { + Set excludedParameters = getExcludedVpnGatewayParameters(gateway); + Set obsoleteParameters = getObsoleteVpnGatewayParameters(gateway); + + List message = new ArrayList<>(); + if (CollectionUtils.isNotEmpty(excludedParameters)) { + excludedCount++; + message.add("excluded parameter(s) " + excludedParameters.toString()); + } + if (CollectionUtils.isNotEmpty(obsoleteParameters)) { + obsoleteCount++; + message.add("obsolete parameter(s) " + obsoleteParameters.toString()); + } + + if (CollectionUtils.isNotEmpty(message)) { + Account account = _accountDao.findById(gateway.getAccountId()); + String description = String.format("VPN customer gateway '%s' (Account: %s) contains %s.", + gateway.getName(), account.getAccountName(), String.join(" and ", message)); + ActionEventUtils.onActionEvent(User.UID_SYSTEM, gateway.getAccountId(), gateway.getDomainId(), + EventTypes.EVENT_S2S_VPN_GATEWAY_OBSOLETE_PARAMS, description, + gateway.getId(), Site2SiteCustomerGateway.class.getSimpleName()); + } + } + + List message = new ArrayList<>(); + if (excludedCount > 0) { + message.add("excluded parameters: " + excludedCount); + } + if (obsoleteCount > 0) { + message.add("obsolete parameters: " + obsoleteCount); + } + if (CollectionUtils.isNotEmpty(message)) { + String subject = String.format("VPN customer gateways using " + String.join(", ", message)); + _alertMgr.sendAlert(AlertService.AlertType.ALERT_TYPE_VPN_GATEWAY_OBSOLETE_PARAMETERS, 0L, 0L, subject, null); + } + } + } } diff --git a/server/src/main/java/com/cloud/server/ManagementServerImpl.java b/server/src/main/java/com/cloud/server/ManagementServerImpl.java index 47dcf60eb32..8ee35cc35ec 100644 --- a/server/src/main/java/com/cloud/server/ManagementServerImpl.java +++ b/server/src/main/java/com/cloud/server/ManagementServerImpl.java @@ -720,6 +720,7 @@ import com.cloud.deploy.DeploymentPlan; import com.cloud.deploy.DeploymentPlanner; import com.cloud.deploy.DeploymentPlanner.ExcludeList; import com.cloud.deploy.DeploymentPlanningManager; +import com.cloud.domain.Domain; import com.cloud.domain.DomainVO; import com.cloud.domain.dao.DomainDao; import com.cloud.event.ActionEvent; @@ -761,6 +762,7 @@ import com.cloud.network.IpAddressManagerImpl; import com.cloud.network.Network; import com.cloud.network.NetworkModel; import com.cloud.network.Networks; +import com.cloud.network.vpn.Site2SiteVpnManagerImpl; import com.cloud.network.dao.IPAddressDao; import com.cloud.network.dao.IPAddressVO; import com.cloud.network.dao.LoadBalancerDao; @@ -4778,6 +4780,14 @@ public class ManagementServerImpl extends ManagerBase implements ManagementServe final Map capabilities = new HashMap<>(); final Account caller = getCaller(); + Long domainId = cmd.getDomainId(); + if (domainId == null) { + domainId = caller.getDomainId(); + } else { + Domain domain = _domainDao.findById(domainId); + _accountService.checkAccess(caller, domain); + } + final boolean isCallerRootAdmin = _accountService.isRootAdmin(caller.getId()); final boolean isCallerAdmin = isCallerRootAdmin || _accountService.isAdmin(caller.getId()); boolean securityGroupsEnabled = false; @@ -4812,7 +4822,7 @@ public class ManagementServerImpl extends ManagerBase implements ManagementServe final boolean allowUserExpungeRecoverVolume = (VolumeApiServiceImpl.AllowUserExpungeRecoverVolume.valueIn(caller.getId()) | isCallerAdmin); final boolean allowUserForceStopVM = (UserVmManager.AllowUserForceStopVm.valueIn(caller.getId()) | isCallerAdmin); - final boolean allowUserViewAllDomainAccounts = (QueryService.AllowUserViewAllDomainAccounts.valueIn(caller.getDomainId())); + final boolean allowUserViewAllDomainAccounts = (QueryService.AllowUserViewAllDomainAccounts.valueIn(domainId)); final boolean kubernetesServiceEnabled = Boolean.parseBoolean(_configDao.getValue("cloud.kubernetes.service.enabled")); final boolean kubernetesClusterExperimentalFeaturesEnabled = Boolean.parseBoolean(_configDao.getValue("cloud.kubernetes.cluster.experimental.features.enabled")); @@ -4865,9 +4875,53 @@ public class ManagementServerImpl extends ManagerBase implements ManagementServe } capabilities.put(ApiConstants.ADDITONAL_CONFIG_ENABLED, UserVmManager.EnableAdditionalVmConfig.valueIn(caller.getId())); + Map vpnParams = getVpnCustomerGatewayParameters(domainId); + if (!vpnParams.isEmpty()) { + capabilities.put(ApiConstants.VPN_CUSTOMER_GATEWAY_PARAMETERS, vpnParams); + } + return capabilities; } + private Map getVpnCustomerGatewayParameters(Long domainId) { + Map vpnParams = new HashMap<>(); + + String excludedEncryption = Site2SiteVpnManagerImpl.VpnCustomerGatewayExcludedEncryptionAlgorithms.valueIn(domainId); + String excludedHashing = Site2SiteVpnManagerImpl.VpnCustomerGatewayExcludedHashingAlgorithms.valueIn(domainId); + String excludedIkeVersions = Site2SiteVpnManagerImpl.VpnCustomerGatewayExcludedIkeVersions.valueIn(domainId); + String excludedDhGroup = Site2SiteVpnManagerImpl.VpnCustomerGatewayExcludedDhGroup.valueIn(domainId); + String obsoleteEncryption = Site2SiteVpnManagerImpl.VpnCustomerGatewayObsoleteEncryptionAlgorithms.valueIn(domainId); + String obsoleteHashing = Site2SiteVpnManagerImpl.VpnCustomerGatewayObsoleteHashingAlgorithms.valueIn(domainId); + String obsoleteIkeVersions = Site2SiteVpnManagerImpl.VpnCustomerGatewayObsoleteIkeVersions.valueIn(domainId); + String obsoleteDhGroup = Site2SiteVpnManagerImpl.VpnCustomerGatewayObsoleteDhGroup.valueIn(domainId); + + if (!excludedEncryption.isEmpty()) { + vpnParams.put("excludedencryptionalgorithms", excludedEncryption); + } + if (!obsoleteEncryption.isEmpty()) { + vpnParams.put("obsoleteencryptionalgorithms", obsoleteEncryption); + } + if (!excludedHashing.isEmpty()) { + vpnParams.put("excludedhashingalgorithms", excludedHashing); + } + if (!obsoleteHashing.isEmpty()) { + vpnParams.put("obsoletehashingalgorithms", obsoleteHashing); + } + if (!excludedIkeVersions.isEmpty()) { + vpnParams.put("excludedikeversions", excludedIkeVersions); + } + if (!obsoleteIkeVersions.isEmpty()) { + vpnParams.put("obsoleteikeversions", obsoleteIkeVersions); + } + if (!excludedDhGroup.isEmpty()) { + vpnParams.put("excludeddhgroups", excludedDhGroup); + } + if (!obsoleteDhGroup.isEmpty()) { + vpnParams.put("obsoletedhgroups", obsoleteDhGroup); + } + return vpnParams; + } + @Override public GuestOSVO getGuestOs(final Long guestOsId) { return _guestOSDao.findById(guestOsId); diff --git a/server/src/test/java/com/cloud/network/vpn/Site2SiteVpnManagerImplTest.java b/server/src/test/java/com/cloud/network/vpn/Site2SiteVpnManagerImplTest.java new file mode 100644 index 00000000000..291d3a4aa81 --- /dev/null +++ b/server/src/test/java/com/cloud/network/vpn/Site2SiteVpnManagerImplTest.java @@ -0,0 +1,944 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package com.cloud.network.vpn; + +import com.cloud.exception.InvalidParameterValueException; +import com.cloud.exception.ResourceUnavailableException; +import com.cloud.network.Site2SiteVpnConnection; +import com.cloud.network.Site2SiteVpnConnection.State; +import com.cloud.network.Site2SiteVpnGateway; +import com.cloud.network.dao.IPAddressDao; +import com.cloud.network.dao.IPAddressVO; +import com.cloud.network.dao.Site2SiteCustomerGatewayDao; +import com.cloud.network.dao.Site2SiteCustomerGatewayVO; +import com.cloud.network.dao.Site2SiteVpnConnectionDao; +import com.cloud.network.dao.Site2SiteVpnConnectionVO; +import com.cloud.network.dao.Site2SiteVpnGatewayDao; +import com.cloud.network.dao.Site2SiteVpnGatewayVO; +import com.cloud.network.element.Site2SiteVpnServiceProvider; +import com.cloud.network.vpc.VpcManager; +import com.cloud.network.vpc.VpcVO; +import com.cloud.network.vpc.dao.VpcDao; +import com.cloud.network.vpc.dao.VpcOfferingServiceMapDao; +import com.cloud.user.Account; +import com.cloud.user.AccountManager; +import com.cloud.user.AccountVO; +import com.cloud.user.User; +import com.cloud.user.UserVO; +import com.cloud.utils.exception.CloudRuntimeException; +import com.cloud.utils.net.NetUtils; +import com.cloud.vm.DomainRouterVO; +import org.apache.cloudstack.acl.SecurityChecker; +import org.apache.cloudstack.annotation.dao.AnnotationDao; +import org.apache.cloudstack.api.command.user.vpn.CreateVpnConnectionCmd; +import org.apache.cloudstack.api.command.user.vpn.CreateVpnCustomerGatewayCmd; +import org.apache.cloudstack.api.command.user.vpn.CreateVpnGatewayCmd; +import org.apache.cloudstack.api.command.user.vpn.DeleteVpnConnectionCmd; +import org.apache.cloudstack.api.command.user.vpn.DeleteVpnCustomerGatewayCmd; +import org.apache.cloudstack.api.command.user.vpn.DeleteVpnGatewayCmd; +import org.apache.cloudstack.api.command.user.vpn.ResetVpnConnectionCmd; +import org.apache.cloudstack.context.CallContext; +import org.apache.cloudstack.framework.config.ConfigKey; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.MockedStatic; +import org.mockito.Mockito; +import org.mockito.junit.MockitoJUnitRunner; + +import java.lang.reflect.Field; +import java.util.ArrayList; +import java.util.List; +import java.util.UUID; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertTrue; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyBoolean; +import static org.mockito.ArgumentMatchers.anyLong; +import static org.mockito.ArgumentMatchers.nullable; +import static org.mockito.Mockito.doNothing; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +@RunWith(MockitoJUnitRunner.Silent.class) +public class Site2SiteVpnManagerImplTest { + + @Mock + private Site2SiteCustomerGatewayDao _customerGatewayDao; + @Mock + private Site2SiteVpnGatewayDao _vpnGatewayDao; + @Mock + private Site2SiteVpnConnectionDao _vpnConnectionDao; + @Mock + private VpcDao _vpcDao; + @Mock + private IPAddressDao _ipAddressDao; + @Mock + private VpcManager _vpcMgr; + @Mock + private AccountManager _accountMgr; + @Mock + private AnnotationDao annotationDao; + @Mock + private List _s2sProviders; + @Mock + VpcOfferingServiceMapDao vpcOfferingServiceMapDao; + + @InjectMocks + private Site2SiteVpnManagerImpl site2SiteVpnManager; + + private AccountVO account; + private UserVO user; + private VpcVO vpc; + private IPAddressVO ipAddress; + private Site2SiteVpnGatewayVO vpnGateway; + private Site2SiteCustomerGatewayVO customerGateway; + private Site2SiteVpnConnectionVO vpnConnection; + + private static final Long ACCOUNT_ID = 1L; + private static final Long DOMAIN_ID = 2L; + private static final Long VPC_ID = 3L; + private static final Long VPN_GATEWAY_ID = 4L; + private static final Long CUSTOMER_GATEWAY_ID = 5L; + private static final Long VPN_CONNECTION_ID = 6L; + private static final Long IP_ADDRESS_ID = 7L; + + @Before + public void setUp() throws Exception { + account = new AccountVO("testaccount", DOMAIN_ID, "networkdomain", Account.Type.NORMAL, UUID.randomUUID().toString()); + account.setId(ACCOUNT_ID); + user = new UserVO(1, "testuser", "password", "firstname", "lastName", "email", "timezone", + UUID.randomUUID().toString(), User.Source.UNKNOWN); + CallContext.register(user, account); + + vpc = mock(VpcVO.class); + when(vpc.getId()).thenReturn(VPC_ID); + when(vpc.getAccountId()).thenReturn(ACCOUNT_ID); + when(vpc.getDomainId()).thenReturn(DOMAIN_ID); + when(vpc.getCidr()).thenReturn("10.0.0.0/16"); + + ipAddress = mock(IPAddressVO.class); + when(ipAddress.getId()).thenReturn(IP_ADDRESS_ID); + when(ipAddress.getVpcId()).thenReturn(VPC_ID); + + vpnGateway = mock(Site2SiteVpnGatewayVO.class); + when(vpnGateway.getId()).thenReturn(VPN_GATEWAY_ID); + when(vpnGateway.getVpcId()).thenReturn(VPC_ID); + when(vpnGateway.getAccountId()).thenReturn(ACCOUNT_ID); + when(vpnGateway.getDomainId()).thenReturn(DOMAIN_ID); + + customerGateway = mock(Site2SiteCustomerGatewayVO.class); + when(customerGateway.getId()).thenReturn(CUSTOMER_GATEWAY_ID); + when(customerGateway.getAccountId()).thenReturn(ACCOUNT_ID); + when(customerGateway.getDomainId()).thenReturn(DOMAIN_ID); + when(customerGateway.getGuestCidrList()).thenReturn("192.168.1.0/24"); + when(customerGateway.getIkePolicy()).thenReturn("aes128-sha256;modp2048"); + when(customerGateway.getEspPolicy()).thenReturn("aes128-sha256;modp2048"); + when(customerGateway.getIkeVersion()).thenReturn("ike"); + + vpnConnection = new Site2SiteVpnConnectionVO(ACCOUNT_ID, DOMAIN_ID, VPN_GATEWAY_ID, CUSTOMER_GATEWAY_ID, false); + vpnConnection.setState(State.Pending); + + when(_accountMgr.getAccount(ACCOUNT_ID)).thenReturn(account); + doNothing().when(_accountMgr).checkAccess(any(Account.class), nullable(SecurityChecker.AccessType.class), anyBoolean(), any()); + } + + @After + public void tearDown() throws Exception { + resetConfigKeyValue(Site2SiteVpnManagerImpl.VpnCustomerGatewayExcludedIkeVersions); + resetConfigKeyValue(Site2SiteVpnManagerImpl.VpnCustomerGatewayExcludedEncryptionAlgorithms); + resetConfigKeyValue(Site2SiteVpnManagerImpl.VpnCustomerGatewayExcludedHashingAlgorithms); + resetConfigKeyValue(Site2SiteVpnManagerImpl.VpnCustomerGatewayExcludedDhGroup); + resetConfigKeyValue(Site2SiteVpnManagerImpl.VpnCustomerGatewayObsoleteIkeVersions); + resetConfigKeyValue(Site2SiteVpnManagerImpl.VpnCustomerGatewayObsoleteEncryptionAlgorithms); + resetConfigKeyValue(Site2SiteVpnManagerImpl.VpnCustomerGatewayObsoleteHashingAlgorithms); + resetConfigKeyValue(Site2SiteVpnManagerImpl.VpnCustomerGatewayObsoleteDhGroup); + CallContext.unregister(); + } + + private void setConfigKeyValue(ConfigKey configKey, String value) { + try { + Field valueField = ConfigKey.class.getDeclaredField("_value"); + valueField.setAccessible(true); + valueField.set(configKey, value); + + Field dynamicField = ConfigKey.class.getDeclaredField("_isDynamic"); + dynamicField.setAccessible(true); + dynamicField.setBoolean(configKey, false); + } catch (IllegalAccessException | NoSuchFieldException e) { + throw new RuntimeException("Failed to set ConfigKey value", e); + } + } + + private void resetConfigKeyValue(ConfigKey configKey) { + try { + Field valueField = ConfigKey.class.getDeclaredField("_value"); + valueField.setAccessible(true); + valueField.set(configKey, null); + + Field dynamicField = ConfigKey.class.getDeclaredField("_isDynamic"); + dynamicField.setAccessible(true); + dynamicField.setBoolean(configKey, true); + } catch (IllegalAccessException | NoSuchFieldException e) { + throw new RuntimeException("Failed to reset ConfigKey value", e); + } + } + + @Test + public void testCreateVpnGatewaySuccess() { + CreateVpnGatewayCmd cmd = mock(CreateVpnGatewayCmd.class); + when(cmd.getVpcId()).thenReturn(VPC_ID); + when(cmd.getEntityOwnerId()).thenReturn(ACCOUNT_ID); + when(cmd.isDisplay()).thenReturn(true); + + when(_vpcDao.findById(VPC_ID)).thenReturn(vpc); + when(_vpnGatewayDao.findByVpcId(VPC_ID)).thenReturn(null); + when(_ipAddressDao.listByAssociatedVpc(VPC_ID, true)).thenReturn(List.of(ipAddress)); + when(_vpnGatewayDao.persist(any(Site2SiteVpnGatewayVO.class))).thenReturn(vpnGateway); + + Site2SiteVpnGateway result = site2SiteVpnManager.createVpnGateway(cmd); + + assertNotNull(result); + verify(_vpnGatewayDao).persist(any(Site2SiteVpnGatewayVO.class)); + } + + @Test(expected = InvalidParameterValueException.class) + public void testCreateVpnGatewayInvalidVpc() { + CreateVpnGatewayCmd cmd = mock(CreateVpnGatewayCmd.class); + when(cmd.getVpcId()).thenReturn(VPC_ID); + when(cmd.getEntityOwnerId()).thenReturn(ACCOUNT_ID); + + when(_vpcDao.findById(VPC_ID)).thenReturn(null); + + site2SiteVpnManager.createVpnGateway(cmd); + } + + @Test(expected = InvalidParameterValueException.class) + public void testCreateVpnGatewayAlreadyExists() { + CreateVpnGatewayCmd cmd = mock(CreateVpnGatewayCmd.class); + when(cmd.getVpcId()).thenReturn(VPC_ID); + when(cmd.getEntityOwnerId()).thenReturn(ACCOUNT_ID); + + when(_vpcDao.findById(VPC_ID)).thenReturn(vpc); + when(_vpnGatewayDao.findByVpcId(VPC_ID)).thenReturn(vpnGateway); + + site2SiteVpnManager.createVpnGateway(cmd); + } + + @Test(expected = CloudRuntimeException.class) + public void testCreateVpnGatewayNoSourceNatIp() { + CreateVpnGatewayCmd cmd = mock(CreateVpnGatewayCmd.class); + when(cmd.getVpcId()).thenReturn(VPC_ID); + when(cmd.getEntityOwnerId()).thenReturn(ACCOUNT_ID); + + when(_vpcDao.findById(VPC_ID)).thenReturn(vpc); + when(_vpnGatewayDao.findByVpcId(VPC_ID)).thenReturn(null); + when(_ipAddressDao.listByAssociatedVpc(VPC_ID, true)).thenReturn(new ArrayList<>()); + + site2SiteVpnManager.createVpnGateway(cmd); + } + + @Test(expected = InvalidParameterValueException.class) + public void testCreateCustomerGatewayInvalidIp() { + CreateVpnCustomerGatewayCmd cmd = mock(CreateVpnCustomerGatewayCmd.class); + when(cmd.getGatewayIp()).thenReturn("invalid-ip"); + when(cmd.getEntityOwnerId()).thenReturn(ACCOUNT_ID); + + try (MockedStatic netUtilsMock = Mockito.mockStatic(NetUtils.class)) { + netUtilsMock.when(() -> NetUtils.isValidIp4("invalid-ip")).thenReturn(false); + netUtilsMock.when(() -> NetUtils.verifyDomainName("invalid-ip")).thenReturn(false); + + site2SiteVpnManager.createCustomerGateway(cmd); + } + } + + @Test(expected = InvalidParameterValueException.class) + public void testCreateCustomerGatewayInvalidCidrList() { + CreateVpnCustomerGatewayCmd cmd = mock(CreateVpnCustomerGatewayCmd.class); + when(cmd.getGatewayIp()).thenReturn("1.2.3.4"); + when(cmd.getGuestCidrList()).thenReturn("invalid-cidr"); + when(cmd.getEntityOwnerId()).thenReturn(ACCOUNT_ID); + + try (MockedStatic netUtilsMock = Mockito.mockStatic(NetUtils.class)) { + netUtilsMock.when(() -> NetUtils.isValidIp4("1.2.3.4")).thenReturn(true); + netUtilsMock.when(() -> NetUtils.isValidCidrList("invalid-cidr")).thenReturn(false); + + site2SiteVpnManager.createCustomerGateway(cmd); + } + } + + @Test(expected = InvalidParameterValueException.class) + public void testCreateCustomerGatewayInvalidIkePolicy() { + CreateVpnCustomerGatewayCmd cmd = mock(CreateVpnCustomerGatewayCmd.class); + when(cmd.getGatewayIp()).thenReturn("1.2.3.4"); + when(cmd.getGuestCidrList()).thenReturn("192.168.1.0/24"); + when(cmd.getIkePolicy()).thenReturn("invalid-policy"); + when(cmd.getEntityOwnerId()).thenReturn(ACCOUNT_ID); + + try (MockedStatic netUtilsMock = Mockito.mockStatic(NetUtils.class)) { + netUtilsMock.when(() -> NetUtils.isValidIp4("1.2.3.4")).thenReturn(true); + netUtilsMock.when(() -> NetUtils.isValidCidrList("192.168.1.0/24")).thenReturn(true); + netUtilsMock.when(() -> NetUtils.isValidS2SVpnPolicy("ike", "invalid-policy")).thenReturn(false); + + site2SiteVpnManager.createCustomerGateway(cmd); + } + } + + @Test(expected = InvalidParameterValueException.class) + public void testCreateCustomerGatewayInvalidEspPolicy() { + CreateVpnCustomerGatewayCmd cmd = mock(CreateVpnCustomerGatewayCmd.class); + when(cmd.getGatewayIp()).thenReturn("1.2.3.4"); + when(cmd.getGuestCidrList()).thenReturn("192.168.1.0/24"); + when(cmd.getIkePolicy()).thenReturn("aes128-sha256;modp2048"); + when(cmd.getEspPolicy()).thenReturn("invalid-policy"); + when(cmd.getEntityOwnerId()).thenReturn(ACCOUNT_ID); + + try (MockedStatic netUtilsMock = Mockito.mockStatic(NetUtils.class)) { + netUtilsMock.when(() -> NetUtils.isValidIp4("1.2.3.4")).thenReturn(true); + netUtilsMock.when(() -> NetUtils.isValidCidrList("192.168.1.0/24")).thenReturn(true); + netUtilsMock.when(() -> NetUtils.isValidS2SVpnPolicy("ike", "aes128-sha256;modp2048")).thenReturn(true); + netUtilsMock.when(() -> NetUtils.isValidS2SVpnPolicy("esp", "invalid-policy")).thenReturn(false); + + site2SiteVpnManager.createCustomerGateway(cmd); + } + } + + @Test(expected = InvalidParameterValueException.class) + public void testCreateCustomerGatewayWithExcludedParameters() throws Exception { + CreateVpnCustomerGatewayCmd cmd = mock(CreateVpnCustomerGatewayCmd.class); + when(cmd.getName()).thenReturn("test-gateway"); + when(cmd.getGatewayIp()).thenReturn("1.2.3.4"); + when(cmd.getGuestCidrList()).thenReturn("192.168.1.0/24"); + when(cmd.getIpsecPsk()).thenReturn("test-psk"); + when(cmd.getIkePolicy()).thenReturn("3des-sha256;modp2048"); + when(cmd.getEspPolicy()).thenReturn("aes128-sha256;modp2048"); + when(cmd.getIkeVersion()).thenReturn("ike"); + when(cmd.getEntityOwnerId()).thenReturn(ACCOUNT_ID); + + setConfigKeyValue(Site2SiteVpnManagerImpl.VpnCustomerGatewayExcludedEncryptionAlgorithms, "3des"); + setConfigKeyValue(Site2SiteVpnManagerImpl.VpnCustomerGatewayExcludedHashingAlgorithms, ""); + setConfigKeyValue(Site2SiteVpnManagerImpl.VpnCustomerGatewayExcludedIkeVersions, ""); + setConfigKeyValue(Site2SiteVpnManagerImpl.VpnCustomerGatewayExcludedDhGroup, ""); + + try (MockedStatic netUtilsMock = Mockito.mockStatic(NetUtils.class)) { + netUtilsMock.when(() -> NetUtils.isValidIp4("1.2.3.4")).thenReturn(true); + netUtilsMock.when(() -> NetUtils.isValidCidrList("192.168.1.0/24")).thenReturn(true); + netUtilsMock.when(() -> NetUtils.isValidS2SVpnPolicy("ike", "3des-sha256;modp2048")).thenReturn(true); + netUtilsMock.when(() -> NetUtils.isValidS2SVpnPolicy("esp", "aes128-sha256;modp2048")).thenReturn(true); + + site2SiteVpnManager.createCustomerGateway(cmd); + } + } + + @Test(expected = InvalidParameterValueException.class) + public void testCreateCustomerGatewayDuplicateName() { + CreateVpnCustomerGatewayCmd cmd = mock(CreateVpnCustomerGatewayCmd.class); + when(cmd.getName()).thenReturn("test-gateway"); + when(cmd.getGatewayIp()).thenReturn("1.2.3.4"); + when(cmd.getGuestCidrList()).thenReturn("192.168.1.0/24"); + when(cmd.getIkePolicy()).thenReturn("aes128-sha256;modp2048"); + when(cmd.getEspPolicy()).thenReturn("aes128-sha256;modp2048"); + when(cmd.getEntityOwnerId()).thenReturn(ACCOUNT_ID); + + try (MockedStatic netUtilsMock = Mockito.mockStatic(NetUtils.class)) { + netUtilsMock.when(() -> NetUtils.isValidIp4("1.2.3.4")).thenReturn(true); + netUtilsMock.when(() -> NetUtils.isValidCidrList("192.168.1.0/24")).thenReturn(true); + netUtilsMock.when(() -> NetUtils.isValidS2SVpnPolicy("ike", "aes128-sha256;modp2048")).thenReturn(true); + netUtilsMock.when(() -> NetUtils.isValidS2SVpnPolicy("esp", "aes128-sha256;modp2048")).thenReturn(true); + + when(_customerGatewayDao.findByNameAndAccountId("test-gateway", ACCOUNT_ID)).thenReturn(customerGateway); + + site2SiteVpnManager.createCustomerGateway(cmd); + } + } + + @Test(expected = InvalidParameterValueException.class) + public void testCreateCustomerGatewayInvalidIkeLifetime() { + CreateVpnCustomerGatewayCmd cmd = mock(CreateVpnCustomerGatewayCmd.class); + when(cmd.getGatewayIp()).thenReturn("1.2.3.4"); + when(cmd.getGuestCidrList()).thenReturn("192.168.1.0/24"); + when(cmd.getIkePolicy()).thenReturn("aes128-sha256;modp2048"); + when(cmd.getEspPolicy()).thenReturn("aes128-sha256;modp2048"); + when(cmd.getIkeLifetime()).thenReturn(86401L); + when(cmd.getEntityOwnerId()).thenReturn(ACCOUNT_ID); + + try (MockedStatic netUtilsMock = Mockito.mockStatic(NetUtils.class)) { + netUtilsMock.when(() -> NetUtils.isValidIp4("1.2.3.4")).thenReturn(true); + netUtilsMock.when(() -> NetUtils.isValidCidrList("192.168.1.0/24")).thenReturn(true); + netUtilsMock.when(() -> NetUtils.isValidS2SVpnPolicy("ike", "aes128-sha256;modp2048")).thenReturn(true); + netUtilsMock.when(() -> NetUtils.isValidS2SVpnPolicy("esp", "aes128-sha256;modp2048")).thenReturn(true); + + site2SiteVpnManager.createCustomerGateway(cmd); + } + } + + @Test(expected = InvalidParameterValueException.class) + public void testCreateCustomerGatewayInvalidEspLifetime() { + CreateVpnCustomerGatewayCmd cmd = mock(CreateVpnCustomerGatewayCmd.class); + when(cmd.getGatewayIp()).thenReturn("1.2.3.4"); + when(cmd.getGuestCidrList()).thenReturn("192.168.1.0/24"); + when(cmd.getIkePolicy()).thenReturn("aes128-sha256;modp2048"); + when(cmd.getEspPolicy()).thenReturn("aes128-sha256;modp2048"); + when(cmd.getEspLifetime()).thenReturn(86401L); + when(cmd.getEntityOwnerId()).thenReturn(ACCOUNT_ID); + + try (MockedStatic netUtilsMock = Mockito.mockStatic(NetUtils.class)) { + netUtilsMock.when(() -> NetUtils.isValidIp4("1.2.3.4")).thenReturn(true); + netUtilsMock.when(() -> NetUtils.isValidCidrList("192.168.1.0/24")).thenReturn(true); + netUtilsMock.when(() -> NetUtils.isValidS2SVpnPolicy("ike", "aes128-sha256;modp2048")).thenReturn(true); + netUtilsMock.when(() -> NetUtils.isValidS2SVpnPolicy("esp", "aes128-sha256;modp2048")).thenReturn(true); + + site2SiteVpnManager.createCustomerGateway(cmd); + } + } + + @Test(expected = InvalidParameterValueException.class) + public void testCreateCustomerGatewayTooManySubnets() { + CreateVpnCustomerGatewayCmd cmd = mock(CreateVpnCustomerGatewayCmd.class); + when(cmd.getGatewayIp()).thenReturn("1.2.3.4"); + String tooManyCidrs = "192.168.1.0/24,192.168.2.0/24,192.168.3.0/24,192.168.4.0/24,192.168.5.0/24," + + "192.168.6.0/24,192.168.7.0/24,192.168.8.0/24,192.168.9.0/24,192.168.10.0/24,192.168.11.0/24"; + when(cmd.getGuestCidrList()).thenReturn(tooManyCidrs); + when(cmd.getIkePolicy()).thenReturn("aes128-sha256;modp2048"); + when(cmd.getEspPolicy()).thenReturn("aes128-sha256;modp2048"); + when(cmd.getEntityOwnerId()).thenReturn(ACCOUNT_ID); + + try (MockedStatic netUtilsMock = Mockito.mockStatic(NetUtils.class)) { + netUtilsMock.when(() -> NetUtils.isValidIp4("1.2.3.4")).thenReturn(true); + netUtilsMock.when(() -> NetUtils.isValidCidrList(tooManyCidrs)).thenReturn(true); + netUtilsMock.when(() -> NetUtils.getCleanIp4CidrList(tooManyCidrs)).thenReturn(tooManyCidrs); + netUtilsMock.when(() -> NetUtils.isValidS2SVpnPolicy("ike", "aes128-sha256;modp2048")).thenReturn(true); + netUtilsMock.when(() -> NetUtils.isValidS2SVpnPolicy("esp", "aes128-sha256;modp2048")).thenReturn(true); + + site2SiteVpnManager.createCustomerGateway(cmd); + } + } + + @Test(expected = InvalidParameterValueException.class) + public void testCreateCustomerGatewayOverlappingSubnets() { + CreateVpnCustomerGatewayCmd cmd = mock(CreateVpnCustomerGatewayCmd.class); + when(cmd.getGatewayIp()).thenReturn("1.2.3.4"); + when(cmd.getGuestCidrList()).thenReturn("192.168.1.0/24,192.168.1.0/25"); + when(cmd.getIkePolicy()).thenReturn("aes128-sha256;modp2048"); + when(cmd.getEspPolicy()).thenReturn("aes128-sha256;modp2048"); + when(cmd.getEntityOwnerId()).thenReturn(ACCOUNT_ID); + + try (MockedStatic netUtilsMock = Mockito.mockStatic(NetUtils.class)) { + String cidrList = "192.168.1.0/24,192.168.1.0/25"; + netUtilsMock.when(() -> NetUtils.isValidIp4("1.2.3.4")).thenReturn(true); + netUtilsMock.when(() -> NetUtils.isValidCidrList(cidrList)).thenReturn(true); + netUtilsMock.when(() -> NetUtils.getCleanIp4CidrList(cidrList)).thenReturn(cidrList); + netUtilsMock.when(() -> NetUtils.isValidS2SVpnPolicy("ike", "aes128-sha256;modp2048")).thenReturn(true); + netUtilsMock.when(() -> NetUtils.isValidS2SVpnPolicy("esp", "aes128-sha256;modp2048")).thenReturn(true); + netUtilsMock.when(() -> NetUtils.isNetworksOverlap("192.168.1.0/24", "192.168.1.0/25")).thenReturn(true); + + site2SiteVpnManager.createCustomerGateway(cmd); + } + } + + @Test(expected = InvalidParameterValueException.class) + public void testCreateVpnConnectionCidrOverlapWithVpc() { + CreateVpnConnectionCmd cmd = mock(CreateVpnConnectionCmd.class); + when(cmd.getVpnGatewayId()).thenReturn(VPN_GATEWAY_ID); + when(cmd.getCustomerGatewayId()).thenReturn(CUSTOMER_GATEWAY_ID); + when(cmd.getEntityOwnerId()).thenReturn(ACCOUNT_ID); + + Site2SiteCustomerGatewayVO customerGw = mock(Site2SiteCustomerGatewayVO.class); + when(customerGw.getGuestCidrList()).thenReturn("10.0.0.0/24"); + when(customerGw.getAccountId()).thenReturn(ACCOUNT_ID); + when(customerGw.getDomainId()).thenReturn(DOMAIN_ID); + + when(_customerGatewayDao.findById(CUSTOMER_GATEWAY_ID)).thenReturn(customerGw); + when(_vpnGatewayDao.findById(VPN_GATEWAY_ID)).thenReturn(vpnGateway); + when(_vpnConnectionDao.findByVpnGatewayIdAndCustomerGatewayId(VPN_GATEWAY_ID, CUSTOMER_GATEWAY_ID)).thenReturn(null); + when(_vpnGatewayDao.findByVpcId(VPC_ID)).thenReturn(vpnGateway); + when(_vpcDao.findById(VPC_ID)).thenReturn(vpc); + + try (MockedStatic netUtilsMock = Mockito.mockStatic(NetUtils.class)) { + netUtilsMock.when(() -> NetUtils.isNetworksOverlap("10.0.0.0/16", "10.0.0.0/24")).thenReturn(true); + + site2SiteVpnManager.createVpnConnection(cmd); + } + } + + @Test(expected = InvalidParameterValueException.class) + public void testCreateVpnConnectionExceedsLimit() { + CreateVpnConnectionCmd cmd = mock(CreateVpnConnectionCmd.class); + when(cmd.getVpnGatewayId()).thenReturn(VPN_GATEWAY_ID); + when(cmd.getCustomerGatewayId()).thenReturn(CUSTOMER_GATEWAY_ID); + when(cmd.getEntityOwnerId()).thenReturn(ACCOUNT_ID); + + when(_customerGatewayDao.findById(CUSTOMER_GATEWAY_ID)).thenReturn(customerGateway); + when(_vpnGatewayDao.findById(VPN_GATEWAY_ID)).thenReturn(vpnGateway); + when(_vpnConnectionDao.findByVpnGatewayIdAndCustomerGatewayId(VPN_GATEWAY_ID, CUSTOMER_GATEWAY_ID)).thenReturn(null); + when(_vpnGatewayDao.findByVpcId(VPC_ID)).thenReturn(vpnGateway); + when(_vpcDao.findById(VPC_ID)).thenReturn(vpc); + + List existingConns = new ArrayList<>(); + for (int i = 0; i < 4; i++) { + existingConns.add(mock(Site2SiteVpnConnectionVO.class)); + } + when(_vpnConnectionDao.listByVpnGatewayId(VPN_GATEWAY_ID)).thenReturn(existingConns); + + site2SiteVpnManager.createVpnConnection(cmd); + } + + @Test + public void testDeleteCustomerGatewaySuccess() { + DeleteVpnCustomerGatewayCmd cmd = mock(DeleteVpnCustomerGatewayCmd.class); + when(cmd.getId()).thenReturn(CUSTOMER_GATEWAY_ID); + + when(_customerGatewayDao.findById(CUSTOMER_GATEWAY_ID)).thenReturn(customerGateway); + when(_vpnConnectionDao.listByCustomerGatewayId(CUSTOMER_GATEWAY_ID)).thenReturn(new ArrayList<>()); + + boolean result = site2SiteVpnManager.deleteCustomerGateway(cmd); + + assertTrue(result); + verify(_customerGatewayDao).remove(CUSTOMER_GATEWAY_ID); + } + + @Test(expected = InvalidParameterValueException.class) + public void testDeleteCustomerGatewayWithConnections() { + DeleteVpnCustomerGatewayCmd cmd = mock(DeleteVpnCustomerGatewayCmd.class); + when(cmd.getId()).thenReturn(CUSTOMER_GATEWAY_ID); + + when(_customerGatewayDao.findById(CUSTOMER_GATEWAY_ID)).thenReturn(customerGateway); + when(_vpnConnectionDao.listByCustomerGatewayId(CUSTOMER_GATEWAY_ID)).thenReturn(List.of(vpnConnection)); + + site2SiteVpnManager.deleteCustomerGateway(cmd); + } + + @Test + public void testDeleteVpnGatewaySuccess() { + DeleteVpnGatewayCmd cmd = mock(DeleteVpnGatewayCmd.class); + when(cmd.getId()).thenReturn(VPN_GATEWAY_ID); + + when(_vpnGatewayDao.findById(VPN_GATEWAY_ID)).thenReturn(vpnGateway); + when(_vpnConnectionDao.listByVpnGatewayId(VPN_GATEWAY_ID)).thenReturn(new ArrayList<>()); + + boolean result = site2SiteVpnManager.deleteVpnGateway(cmd); + + assertTrue(result); + verify(_vpnGatewayDao).remove(VPN_GATEWAY_ID); + } + + @Test(expected = InvalidParameterValueException.class) + public void testDeleteVpnGatewayWithConnections() { + DeleteVpnGatewayCmd cmd = mock(DeleteVpnGatewayCmd.class); + when(cmd.getId()).thenReturn(VPN_GATEWAY_ID); + + when(_vpnGatewayDao.findById(VPN_GATEWAY_ID)).thenReturn(vpnGateway); + when(_vpnConnectionDao.listByVpnGatewayId(VPN_GATEWAY_ID)).thenReturn(List.of(vpnConnection)); + + site2SiteVpnManager.deleteVpnGateway(cmd); + } + + @Test + public void testDeleteVpnConnectionSuccess() throws ResourceUnavailableException { + DeleteVpnConnectionCmd cmd = mock(DeleteVpnConnectionCmd.class); + when(cmd.getId()).thenReturn(VPN_CONNECTION_ID); + + when(_vpnConnectionDao.findById(VPN_CONNECTION_ID)).thenReturn(vpnConnection); + vpnConnection.setState(State.Pending); + when(_vpnGatewayDao.findById(VPN_GATEWAY_ID)).thenReturn(vpnGateway); + when(_vpcMgr.applyStaticRouteForVpcVpnIfNeeded(anyLong(), anyBoolean())).thenReturn(true); + + boolean result = site2SiteVpnManager.deleteVpnConnection(cmd); + + assertTrue(result); + verify(_vpnConnectionDao).remove(VPN_CONNECTION_ID); + } + + @Test + public void testStartVpnConnectionSuccess() throws ResourceUnavailableException { + when(_vpnConnectionDao.acquireInLockTable(VPN_CONNECTION_ID)).thenReturn(vpnConnection); + vpnConnection.setState(State.Pending); + when(_vpnGatewayDao.findById(VPN_GATEWAY_ID)).thenReturn(vpnGateway); + Site2SiteVpnServiceProvider provider = mock(Site2SiteVpnServiceProvider.class); + when(provider.startSite2SiteVpn(any(Site2SiteVpnConnection.class))).thenReturn(true); + when(_s2sProviders.iterator()).thenReturn(List.of(provider).iterator()); + when(_vpnConnectionDao.persist(any(Site2SiteVpnConnectionVO.class))).thenReturn(vpnConnection); + when(_vpcMgr.applyStaticRouteForVpcVpnIfNeeded(anyLong(), anyBoolean())).thenReturn(true); + + Site2SiteVpnConnection result = site2SiteVpnManager.startVpnConnection(VPN_CONNECTION_ID); + + assertNotNull(result); + verify(_vpnConnectionDao, org.mockito.Mockito.atLeastOnce()).persist(any(Site2SiteVpnConnectionVO.class)); + } + + @Test(expected = InvalidParameterValueException.class) + public void testStartVpnConnectionWrongState() throws ResourceUnavailableException { + when(_vpnConnectionDao.acquireInLockTable(VPN_CONNECTION_ID)).thenReturn(vpnConnection); + vpnConnection.setState(State.Connected); + + site2SiteVpnManager.startVpnConnection(VPN_CONNECTION_ID); + } + + @Test + public void testResetVpnConnectionSuccess() throws ResourceUnavailableException { + ResetVpnConnectionCmd cmd = mock(ResetVpnConnectionCmd.class); + when(cmd.getId()).thenReturn(VPN_CONNECTION_ID); + + when(_vpnConnectionDao.findById(VPN_CONNECTION_ID)).thenReturn(vpnConnection); + vpnConnection.setState(State.Connected); + when(_vpnConnectionDao.acquireInLockTable(VPN_CONNECTION_ID)).thenReturn(vpnConnection); + when(_vpnGatewayDao.findById(VPN_GATEWAY_ID)).thenReturn(vpnGateway); + Site2SiteVpnServiceProvider provider = mock(Site2SiteVpnServiceProvider.class); + when(provider.stopSite2SiteVpn(any(Site2SiteVpnConnection.class))).thenReturn(true); + when(provider.startSite2SiteVpn(any(Site2SiteVpnConnection.class))).thenReturn(true); + when(_s2sProviders.iterator()).thenReturn(List.of(provider).iterator()); + when(_vpnConnectionDao.persist(any(Site2SiteVpnConnectionVO.class))).thenReturn(vpnConnection); + when(_vpcMgr.applyStaticRouteForVpcVpnIfNeeded(anyLong(), anyBoolean())).thenReturn(true); + + Site2SiteVpnConnection result = site2SiteVpnManager.resetVpnConnection(cmd); + + assertNotNull(result); + } + + @Test + public void testCleanupVpnConnectionByVpc() { + when(_vpnConnectionDao.listByVpcId(VPC_ID)).thenReturn(List.of(vpnConnection)); + + boolean result = site2SiteVpnManager.cleanupVpnConnectionByVpc(VPC_ID); + + assertTrue(result); + verify(_vpnConnectionDao).remove(vpnConnection.getId()); + } + + @Test + public void testCleanupVpnGatewayByVpc() { + when(_vpnGatewayDao.findByVpcId(VPC_ID)).thenReturn(vpnGateway); + when(_vpnConnectionDao.listByVpnGatewayId(VPN_GATEWAY_ID)).thenReturn(new ArrayList<>()); + + boolean result = site2SiteVpnManager.cleanupVpnGatewayByVpc(VPC_ID); + + assertTrue(result); + verify(_vpnGatewayDao).remove(VPN_GATEWAY_ID); + } + + @Test + public void testCleanupVpnGatewayByVpcNotFound() { + when(_vpnGatewayDao.findByVpcId(VPC_ID)).thenReturn(null); + + boolean result = site2SiteVpnManager.cleanupVpnGatewayByVpc(VPC_ID); + + assertTrue(result); + verify(_vpnGatewayDao, never()).remove(anyLong()); + } + + @Test + public void testGetConnectionsForRouter() { + DomainRouterVO router = mock(DomainRouterVO.class); + when(router.getVpcId()).thenReturn(VPC_ID); + when(_vpnConnectionDao.listByVpcId(VPC_ID)).thenReturn(List.of(vpnConnection)); + + List result = site2SiteVpnManager.getConnectionsForRouter(router); + + assertNotNull(result); + assertEquals(1, result.size()); + } + + @Test + public void testGetConnectionsForRouterNoVpc() { + DomainRouterVO router = mock(DomainRouterVO.class); + when(router.getVpcId()).thenReturn(null); + + List result = site2SiteVpnManager.getConnectionsForRouter(router); + + assertNotNull(result); + assertTrue(result.isEmpty()); + } + + @Test + public void testDeleteCustomerGatewayByAccount() { + when(_customerGatewayDao.listByAccountId(ACCOUNT_ID)).thenReturn(List.of(customerGateway)); + when(_vpnConnectionDao.listByCustomerGatewayId(CUSTOMER_GATEWAY_ID)).thenReturn(new ArrayList<>()); + + boolean result = site2SiteVpnManager.deleteCustomerGatewayByAccount(ACCOUNT_ID); + + assertTrue(result); + verify(_customerGatewayDao).remove(CUSTOMER_GATEWAY_ID); + } + + @Test + public void testReconnectDisconnectedVpnByVpc() throws ResourceUnavailableException { + Site2SiteVpnConnectionVO conn = mock(Site2SiteVpnConnectionVO.class); + when(conn.getId()).thenReturn(VPN_CONNECTION_ID); + when(conn.getState()).thenReturn(State.Disconnected); + when(conn.getCustomerGatewayId()).thenReturn(CUSTOMER_GATEWAY_ID); + when(conn.getVpnGatewayId()).thenReturn(VPN_GATEWAY_ID); + when(_vpnConnectionDao.listByVpcId(VPC_ID)).thenReturn(List.of(conn)); + when(_customerGatewayDao.findById(CUSTOMER_GATEWAY_ID)).thenReturn(customerGateway); + when(_vpnConnectionDao.acquireInLockTable(VPN_CONNECTION_ID)).thenReturn(conn); + when(_vpnGatewayDao.findById(VPN_GATEWAY_ID)).thenReturn(vpnGateway); + Site2SiteVpnServiceProvider provider = mock(Site2SiteVpnServiceProvider.class); + when(provider.startSite2SiteVpn(any(Site2SiteVpnConnection.class))).thenReturn(true); + when(_s2sProviders.iterator()).thenReturn(List.of(provider).iterator()); + when(_vpnConnectionDao.persist(any(Site2SiteVpnConnectionVO.class))).thenReturn(conn); + when(_vpcMgr.applyStaticRouteForVpcVpnIfNeeded(anyLong(), anyBoolean())).thenReturn(true); + + site2SiteVpnManager.reconnectDisconnectedVpnByVpc(VPC_ID); + + verify(_vpnConnectionDao, org.mockito.Mockito.atLeastOnce()).persist(any(Site2SiteVpnConnectionVO.class)); + } + + @Test + public void testUpdateVpnConnection() { + when(_vpnConnectionDao.findById(VPN_CONNECTION_ID)).thenReturn(vpnConnection); + when(_vpnConnectionDao.update(anyLong(), any(Site2SiteVpnConnectionVO.class))).thenReturn(true); + when(_vpnConnectionDao.findById(VPN_CONNECTION_ID)).thenReturn(vpnConnection); + + Site2SiteVpnConnection result = site2SiteVpnManager.updateVpnConnection(VPN_CONNECTION_ID, "custom-id", true); + + assertNotNull(result); + } + + @Test + public void testUpdateVpnGateway() { + when(_vpnGatewayDao.findById(VPN_GATEWAY_ID)).thenReturn(vpnGateway); + when(_vpnGatewayDao.update(anyLong(), any(Site2SiteVpnGatewayVO.class))).thenReturn(true); + when(_vpnGatewayDao.findById(VPN_GATEWAY_ID)).thenReturn(vpnGateway); + + Site2SiteVpnGateway result = site2SiteVpnManager.updateVpnGateway(VPN_GATEWAY_ID, "custom-id", true); + + assertNotNull(result); + } + + @Test + public void testVpnGatewayContainsExcludedParametersWithExcludedIkeVersion() throws Exception { + Site2SiteCustomerGatewayVO gw = mock(Site2SiteCustomerGatewayVO.class); + when(gw.getIkePolicy()).thenReturn("aes128-sha256;modp2048"); + when(gw.getEspPolicy()).thenReturn("aes128-sha256;modp2048"); + when(gw.getIkeVersion()).thenReturn("ikev1"); + when(gw.getDomainId()).thenReturn(DOMAIN_ID); + + setConfigKeyValue(Site2SiteVpnManagerImpl.VpnCustomerGatewayExcludedIkeVersions, "ikev1"); + setConfigKeyValue(Site2SiteVpnManagerImpl.VpnCustomerGatewayExcludedEncryptionAlgorithms, ""); + setConfigKeyValue(Site2SiteVpnManagerImpl.VpnCustomerGatewayExcludedHashingAlgorithms, ""); + setConfigKeyValue(Site2SiteVpnManagerImpl.VpnCustomerGatewayExcludedDhGroup, ""); + + java.util.Set result = site2SiteVpnManager.getExcludedVpnGatewayParameters(gw); + assertFalse("Should detect excluded IKE version", result.isEmpty()); + assertEquals("Should detect excluded IKE version", "[ikev1]", result.toString()); + } + + @Test + public void testVpnGatewayContainsExcludedParametersWithExcludedEncryption() throws Exception { + Site2SiteCustomerGatewayVO gw = mock(Site2SiteCustomerGatewayVO.class); + when(gw.getIkePolicy()).thenReturn("3des-sha256;modp2048"); + when(gw.getEspPolicy()).thenReturn("aes128-sha256;modp2048"); + when(gw.getIkeVersion()).thenReturn("ike"); + when(gw.getDomainId()).thenReturn(DOMAIN_ID); + + setConfigKeyValue(Site2SiteVpnManagerImpl.VpnCustomerGatewayExcludedIkeVersions, ""); + setConfigKeyValue(Site2SiteVpnManagerImpl.VpnCustomerGatewayExcludedEncryptionAlgorithms, "3des"); + setConfigKeyValue(Site2SiteVpnManagerImpl.VpnCustomerGatewayExcludedHashingAlgorithms, ""); + setConfigKeyValue(Site2SiteVpnManagerImpl.VpnCustomerGatewayExcludedDhGroup, ""); + + java.util.Set result = site2SiteVpnManager.getExcludedVpnGatewayParameters(gw); + assertFalse("Should detect excluded encryption algorithm", result.isEmpty()); + assertEquals("Should detect excluded encryption algorithm", "[3des]", result.toString()); + } + + @Test + public void testVpnGatewayContainsExcludedParametersWithExcludedHashing() throws Exception { + Site2SiteCustomerGatewayVO gw = mock(Site2SiteCustomerGatewayVO.class); + when(gw.getIkePolicy()).thenReturn("aes128-md5;modp2048"); + when(gw.getEspPolicy()).thenReturn("aes128-sha256;modp2048"); + when(gw.getIkeVersion()).thenReturn("ike"); + when(gw.getDomainId()).thenReturn(DOMAIN_ID); + + setConfigKeyValue(Site2SiteVpnManagerImpl.VpnCustomerGatewayExcludedIkeVersions, ""); + setConfigKeyValue(Site2SiteVpnManagerImpl.VpnCustomerGatewayExcludedEncryptionAlgorithms, "aes128"); + setConfigKeyValue(Site2SiteVpnManagerImpl.VpnCustomerGatewayExcludedHashingAlgorithms, "md5"); + setConfigKeyValue(Site2SiteVpnManagerImpl.VpnCustomerGatewayExcludedDhGroup, ""); + + java.util.Set result = site2SiteVpnManager.getExcludedVpnGatewayParameters(gw); + assertFalse("Should detect excluded algorithms", result.isEmpty()); + assertEquals("Should detect excluded algorithms", "[aes128, md5]", result.toString()); + } + + @Test + public void testVpnGatewayContainsExcludedParametersWithExcludedDhGroup() { + Site2SiteCustomerGatewayVO gw = mock(Site2SiteCustomerGatewayVO.class); + when(gw.getIkePolicy()).thenReturn("aes128-sha256;modp1024"); + when(gw.getEspPolicy()).thenReturn("aes128-sha256;modp2048"); + when(gw.getIkeVersion()).thenReturn("ike"); + when(gw.getDomainId()).thenReturn(DOMAIN_ID); + + setConfigKeyValue(Site2SiteVpnManagerImpl.VpnCustomerGatewayExcludedIkeVersions, ""); + setConfigKeyValue(Site2SiteVpnManagerImpl.VpnCustomerGatewayExcludedEncryptionAlgorithms, ""); + setConfigKeyValue(Site2SiteVpnManagerImpl.VpnCustomerGatewayExcludedHashingAlgorithms, ""); + setConfigKeyValue(Site2SiteVpnManagerImpl.VpnCustomerGatewayExcludedDhGroup, "modp1024"); + + java.util.Set result = site2SiteVpnManager.getExcludedVpnGatewayParameters(gw); + assertFalse("Should detect excluded DH group", result.isEmpty()); + assertEquals("Should detect excluded DH group", "[modp1024]", result.toString()); + } + + @Test + public void testVpnGatewayContainsExcludedParametersNoExcludedParameters() { + Site2SiteCustomerGatewayVO gw = mock(Site2SiteCustomerGatewayVO.class); + when(gw.getIkePolicy()).thenReturn("aes128-sha256;modp2048"); + when(gw.getEspPolicy()).thenReturn("aes128-sha256;modp2048"); + when(gw.getIkeVersion()).thenReturn("ike"); + when(gw.getDomainId()).thenReturn(DOMAIN_ID); + + setConfigKeyValue(Site2SiteVpnManagerImpl.VpnCustomerGatewayExcludedIkeVersions, ""); + setConfigKeyValue(Site2SiteVpnManagerImpl.VpnCustomerGatewayExcludedEncryptionAlgorithms, ""); + setConfigKeyValue(Site2SiteVpnManagerImpl.VpnCustomerGatewayExcludedHashingAlgorithms, ""); + setConfigKeyValue(Site2SiteVpnManagerImpl.VpnCustomerGatewayExcludedDhGroup, ""); + + java.util.Set result = site2SiteVpnManager.getExcludedVpnGatewayParameters(gw); + assertTrue("Should not detect excluded parameters when none are configured", result.isEmpty()); + } + + @Test + public void testVpnGatewayContainsExcludedParametersWithExcludedEspPolicy() { + Site2SiteCustomerGatewayVO gw = mock(Site2SiteCustomerGatewayVO.class); + when(gw.getIkePolicy()).thenReturn("aes128-sha256;modp2048"); + when(gw.getEspPolicy()).thenReturn("3des-sha256;modp2048"); + when(gw.getIkeVersion()).thenReturn("ike"); + when(gw.getDomainId()).thenReturn(DOMAIN_ID); + + setConfigKeyValue(Site2SiteVpnManagerImpl.VpnCustomerGatewayExcludedIkeVersions, ""); + setConfigKeyValue(Site2SiteVpnManagerImpl.VpnCustomerGatewayExcludedEncryptionAlgorithms, "3des"); + setConfigKeyValue(Site2SiteVpnManagerImpl.VpnCustomerGatewayExcludedHashingAlgorithms, ""); + setConfigKeyValue(Site2SiteVpnManagerImpl.VpnCustomerGatewayExcludedDhGroup, ""); + + java.util.Set result = site2SiteVpnManager.getExcludedVpnGatewayParameters(gw); + assertFalse("Should detect excluded encryption in ESP policy", result.isEmpty()); + assertEquals("Should detect excluded encryption in ESP policy", "[3des]", result.toString()); + } + + @Test + public void testVpnGatewayContainsObsoleteParametersWithObsoleteIkeVersion() { + Site2SiteCustomerGatewayVO gw = mock(Site2SiteCustomerGatewayVO.class); + when(gw.getIkePolicy()).thenReturn("aes128-sha256;modp2048"); + when(gw.getEspPolicy()).thenReturn("aes128-sha256;modp2048"); + when(gw.getIkeVersion()).thenReturn("ikev1"); + when(gw.getDomainId()).thenReturn(DOMAIN_ID); + + setConfigKeyValue(Site2SiteVpnManagerImpl.VpnCustomerGatewayObsoleteIkeVersions, "ikev1"); + setConfigKeyValue(Site2SiteVpnManagerImpl.VpnCustomerGatewayObsoleteEncryptionAlgorithms, ""); + setConfigKeyValue(Site2SiteVpnManagerImpl.VpnCustomerGatewayObsoleteHashingAlgorithms, ""); + setConfigKeyValue(Site2SiteVpnManagerImpl.VpnCustomerGatewayObsoleteDhGroup, ""); + + java.util.Set result = site2SiteVpnManager.getObsoleteVpnGatewayParameters(gw); + assertFalse("Should detect obsolete IKE version", result.isEmpty()); + assertEquals("Should detect obsolete IKE version", "[ikev1]", result.toString()); + } + + @Test + public void testVpnGatewayContainsObsoleteParametersWithObsoleteEncryption() { + Site2SiteCustomerGatewayVO gw = mock(Site2SiteCustomerGatewayVO.class); + when(gw.getIkePolicy()).thenReturn("3des-sha256;modp2048"); + when(gw.getEspPolicy()).thenReturn("aes128-sha256;modp2048"); + when(gw.getIkeVersion()).thenReturn("ike"); + when(gw.getDomainId()).thenReturn(DOMAIN_ID); + + setConfigKeyValue(Site2SiteVpnManagerImpl.VpnCustomerGatewayObsoleteIkeVersions, ""); + setConfigKeyValue(Site2SiteVpnManagerImpl.VpnCustomerGatewayObsoleteEncryptionAlgorithms, "3des"); + setConfigKeyValue(Site2SiteVpnManagerImpl.VpnCustomerGatewayObsoleteHashingAlgorithms, ""); + setConfigKeyValue(Site2SiteVpnManagerImpl.VpnCustomerGatewayObsoleteDhGroup, ""); + + java.util.Set result = site2SiteVpnManager.getObsoleteVpnGatewayParameters(gw); + assertFalse("Should detect obsolete encryption algorithm", result.isEmpty()); + assertEquals("Should detect obsolete encryption algorithm", "[3des]", result.toString()); + } + + @Test + public void testVpnGatewayContainsObsoleteParametersWithObsoleteHashing() { + Site2SiteCustomerGatewayVO gw = mock(Site2SiteCustomerGatewayVO.class); + when(gw.getIkePolicy()).thenReturn("aes128-md5;modp2048"); + when(gw.getEspPolicy()).thenReturn("aes128-sha256;modp2048"); + when(gw.getIkeVersion()).thenReturn("ike"); + when(gw.getDomainId()).thenReturn(DOMAIN_ID); + + setConfigKeyValue(Site2SiteVpnManagerImpl.VpnCustomerGatewayObsoleteIkeVersions, ""); + setConfigKeyValue(Site2SiteVpnManagerImpl.VpnCustomerGatewayObsoleteEncryptionAlgorithms, ""); + setConfigKeyValue(Site2SiteVpnManagerImpl.VpnCustomerGatewayObsoleteHashingAlgorithms, "md5"); + setConfigKeyValue(Site2SiteVpnManagerImpl.VpnCustomerGatewayObsoleteDhGroup, ""); + + java.util.Set result = site2SiteVpnManager.getObsoleteVpnGatewayParameters(gw); + assertFalse("Should detect obsolete hashing algorithm", result.isEmpty()); + assertEquals("Should detect obsolete hashing algorithm", "[md5]", result.toString()); + } + + @Test + public void testVpnGatewayContainsObsoleteParametersWithObsoleteDhGroup() { + Site2SiteCustomerGatewayVO gw = mock(Site2SiteCustomerGatewayVO.class); + when(gw.getIkePolicy()).thenReturn("aes128-sha256;modp1024"); + when(gw.getEspPolicy()).thenReturn("aes128-sha256;modp2048"); + when(gw.getIkeVersion()).thenReturn("ike"); + when(gw.getDomainId()).thenReturn(DOMAIN_ID); + + setConfigKeyValue(Site2SiteVpnManagerImpl.VpnCustomerGatewayObsoleteIkeVersions, ""); + setConfigKeyValue(Site2SiteVpnManagerImpl.VpnCustomerGatewayObsoleteEncryptionAlgorithms, ""); + setConfigKeyValue(Site2SiteVpnManagerImpl.VpnCustomerGatewayObsoleteHashingAlgorithms, ""); + setConfigKeyValue(Site2SiteVpnManagerImpl.VpnCustomerGatewayObsoleteDhGroup, "modp1024"); + + java.util.Set result = site2SiteVpnManager.getObsoleteVpnGatewayParameters(gw); + assertFalse("Should detect obsolete DH group", result.isEmpty()); + assertEquals("Should detect obsolete DH group", "[modp1024]", result.toString()); + } + + @Test + public void testVpnGatewayContainsObsoleteParametersNoObsoleteParameters() { + Site2SiteCustomerGatewayVO gw = mock(Site2SiteCustomerGatewayVO.class); + when(gw.getIkePolicy()).thenReturn("aes128-sha256;modp2048"); + when(gw.getEspPolicy()).thenReturn("aes128-sha256;modp2048"); + when(gw.getIkeVersion()).thenReturn("ike"); + when(gw.getDomainId()).thenReturn(DOMAIN_ID); + + setConfigKeyValue(Site2SiteVpnManagerImpl.VpnCustomerGatewayObsoleteIkeVersions, ""); + setConfigKeyValue(Site2SiteVpnManagerImpl.VpnCustomerGatewayObsoleteEncryptionAlgorithms, ""); + setConfigKeyValue(Site2SiteVpnManagerImpl.VpnCustomerGatewayObsoleteHashingAlgorithms, ""); + setConfigKeyValue(Site2SiteVpnManagerImpl.VpnCustomerGatewayObsoleteDhGroup, ""); + + java.util.Set result = site2SiteVpnManager.getObsoleteVpnGatewayParameters(gw); + assertTrue("Should not detect obsolete parameters when none are configured", result.isEmpty()); + } + + @Test + public void testVpnGatewayContainsObsoleteParametersWithObsoleteEspPolicy() { + Site2SiteCustomerGatewayVO gw = mock(Site2SiteCustomerGatewayVO.class); + when(gw.getIkePolicy()).thenReturn("aes128-sha256;modp2048"); + when(gw.getEspPolicy()).thenReturn("3des-sha256;modp2048"); + when(gw.getIkeVersion()).thenReturn("ike"); + when(gw.getDomainId()).thenReturn(DOMAIN_ID); + + setConfigKeyValue(Site2SiteVpnManagerImpl.VpnCustomerGatewayObsoleteIkeVersions, ""); + setConfigKeyValue(Site2SiteVpnManagerImpl.VpnCustomerGatewayObsoleteEncryptionAlgorithms, "3des"); + setConfigKeyValue(Site2SiteVpnManagerImpl.VpnCustomerGatewayObsoleteHashingAlgorithms, ""); + setConfigKeyValue(Site2SiteVpnManagerImpl.VpnCustomerGatewayObsoleteDhGroup, ""); + + java.util.Set result = site2SiteVpnManager.getObsoleteVpnGatewayParameters(gw); + assertFalse("Should detect obsolete encryption in ESP policy", result.isEmpty()); + assertEquals("Should detect obsolete encryption in ESP policy", "[3des]", result.toString()); + } +} diff --git a/server/src/test/java/com/cloud/vpc/MockSite2SiteVpnManagerImpl.java b/server/src/test/java/com/cloud/vpc/MockSite2SiteVpnManagerImpl.java index 3558eed1cfa..af6c1c599aa 100644 --- a/server/src/test/java/com/cloud/vpc/MockSite2SiteVpnManagerImpl.java +++ b/server/src/test/java/com/cloud/vpc/MockSite2SiteVpnManagerImpl.java @@ -23,7 +23,6 @@ import com.cloud.network.Site2SiteVpnConnection; import com.cloud.network.Site2SiteVpnGateway; import com.cloud.network.dao.Site2SiteVpnConnectionVO; import com.cloud.network.vpn.Site2SiteVpnManager; -import com.cloud.network.vpn.Site2SiteVpnService; import com.cloud.utils.Pair; import com.cloud.utils.component.ManagerBase; import com.cloud.vm.DomainRouterVO; @@ -41,11 +40,13 @@ import org.apache.cloudstack.api.command.user.vpn.UpdateVpnCustomerGatewayCmd; import org.springframework.stereotype.Component; import javax.naming.ConfigurationException; +import java.util.HashSet; import java.util.List; import java.util.Map; +import java.util.Set; @Component -public class MockSite2SiteVpnManagerImpl extends ManagerBase implements Site2SiteVpnManager, Site2SiteVpnService { +public class MockSite2SiteVpnManagerImpl extends ManagerBase implements Site2SiteVpnManager { /* (non-Javadoc) * @see com.cloud.network.vpn.Site2SiteVpnService#createVpnGateway(org.apache.cloudstack.api.commands.CreateVpnGatewayCmd) @@ -275,4 +276,16 @@ public class MockSite2SiteVpnManagerImpl extends ManagerBase implements Site2Sit return null; } + @Override + public Set getExcludedVpnGatewayParameters(Site2SiteCustomerGateway customerGw) { + // TODO Auto-generated method stub + return new HashSet<>(); + } + + @Override + public Set getObsoleteVpnGatewayParameters(Site2SiteCustomerGateway customerGw) { + // TODO Auto-generated method stub + return new HashSet<>(); + } + } diff --git a/ui/public/locales/en.json b/ui/public/locales/en.json index 86df45411e9..c20827f2100 100644 --- a/ui/public/locales/en.json +++ b/ui/public/locales/en.json @@ -807,7 +807,7 @@ "label.delete.tungsten.service.group": "Delete Service Group", "label.delete.volumes": "Data volumes to be deleted", "label.delete.vpn.connection": "Delete VPN connection", -"label.delete.vpn.customer.gateway": "Delete VPN customer gateway", +"label.delete.vpn.customer.gateway": "Delete VPN Customer Gateway", "label.delete.vpn.gateway": "Delete VPN gateway", "label.delete.vpn.user": "Delete VPN User", "label.delete.webhook": "Delete Webhook", @@ -2629,6 +2629,7 @@ "label.update.to": "updated to", "label.update.traffic.label": "Update traffic labels", "label.update.vmware.datacenter": "Update VMWare datacenter", +"label.update.vpn.customer.gateway": "Update VPN Customer Gateway", "label.update.webhook": "Update Webhook", "label.updating": "Updating", "label.upgrade.router.newer.template": "Upgrade router to use newer Template", @@ -3093,8 +3094,8 @@ "message.add.volume": "Please fill in the following data to add a new volume.", "message.add.vpn.connection.failed": "Adding VPN connection failed", "message.add.vpn.connection.processing": "Adding VPN Connection...", -"message.add.vpn.customer.gateway": "Adding VPN customer gateway", -"message.add.vpn.customer.gateway.processing": "Creation of VPN customer gateway is in progress", +"message.add.vpn.customer.gateway": "Adding VPN Customer Gateway", +"message.add.vpn.customer.gateway.processing": "Creation of VPN Customer Gateway is in progress", "message.add.vpn.gateway": "Please confirm that you want to add a VPN Gateway.", "message.add.vpn.gateway.failed": "Adding VPN gateway failed", "message.add.vpn.gateway.processing": "Adding VPN gateway...", @@ -3242,7 +3243,7 @@ "message.create.volume.failed": "Failed to create Volume.", "message.create.volume.processing": "Volume creation in progress", "message.create.vpc.offering": "VPC offering created.", -"message.create.vpn.customer.gateway.failed": "VPN customer gateway creation failed.", +"message.create.vpn.customer.gateway.failed": "VPN Customer Gateway creation failed.", "message.creating.autoscale.vmgroup": "Creating AutoScaling Group", "message.creating.autoscale.vmprofile": "Creating AutoScale Instance profile", "message.creating.autoscale.scaledown.conditions": "Creating ScaleDown conditions", @@ -3293,7 +3294,7 @@ "message.delete.tungsten.tag": "Are you sure you want to remove this Tag from this Policy?", "message.delete.user": "Please confirm that you would like to delete this User.", "message.delete.vpn.connection": "Please confirm that you want to delete VPN connection.", -"message.delete.vpn.customer.gateway": "Please confirm that you want to delete this VPN customer gateway.", +"message.delete.vpn.customer.gateway": "Please confirm that you want to delete this VPN Customer Gateway.", "message.delete.vpn.gateway": "Please confirm that you want to delete this VPN Gateway.", "message.delete.vpn.gateway.failed": "Failed to delete VPN Gateway.", "message.delete.webhook": "Please confirm that you want to delete this Webhook.", @@ -3834,7 +3835,7 @@ "message.success.add.tungsten.routing.policy": "Successfully added Tungsten-Fabric routing policy", "message.success.add.vpc": "Successfully added a Virtual Private Cloud", "message.success.add.vpc.network": "Successfully added a VPC network", -"message.success.add.vpn.customer.gateway": "Successfully added VPN customer gateway", +"message.success.add.vpn.customer.gateway": "Successfully added VPN Customer Gateway", "message.success.add.vpn.gateway": "Successfully added VPN gateway", "message.success.add.webhook.filter": "Successfully added Webhook Filter", "message.success.assign.sslcert": "Successfully assigned SSL certificate", @@ -3960,6 +3961,7 @@ "message.success.update.network": "Successfully updated Network", "message.success.update.template": "Successfully updated Template", "message.success.update.user": "Successfully updated User", +"message.success.update.vpn.customer.gateway": "Successfully updated VPN Customer Gateway", "message.success.upgrade.kubernetes": "Successfully upgraded Kubernetes Cluster", "message.success.upload": "Successfully uploaded", "message.success.upload.description": "This ISO file has been uploaded. Please check its status in the Templates menu.", @@ -3987,6 +3989,9 @@ "message.update.condition.failed": "Failed to update condition", "message.update.condition.processing": "Updating condition...", "message.update.failed": "Update failed", +"message.update.vpn.customer.gateway": "Update VPN Customer Gateway", +"message.update.vpn.customer.gateway.failed": "Updating the VPN Customer Gateway failed", +"message.update.vpn.customer.gateway.processing": "Updating VPN Customer Gateway...", "message.test.webhook.delivery": "Test delivery to the Webhook with an optional payload", "message.two.factor.authorization.failed": "Unable to verify 2FA with provided code, please retry.", "message.two.fa.auth": "Open the two-factor authentication app on your mobile device to view your authentication code.", @@ -4074,6 +4079,11 @@ "message.volumes.managed": "Volumes controlled by CloudStack.", "message.volumes.unmanaged": "Volumes not controlled by CloudStack.", "message.vpc.restart.required": "Restart is required for VPC(s). Click here to view VPC(s) which require restart.", +"message.vpn.customer.gateway.contains.excluded.obsolete.parameters": "This VPN Customer Gateway contains cryptographic parameters that are marked as excluded or obsolete by the Administrator. Consider changing them using the Update VPN Customer Gateway form.", +"message.vpn.customer.gateway.excluded.parameter": " is marked as excluded. Please choose another value.", +"message.vpn.customer.gateway.obsolete.parameter": " is marked as obsolete/insecure. Please choose another value.", +"message.vpn.customer.gateway.obsolete.parameter.tooltip": "This parameter value is marked as obsolete/insecure.", +"message.vr.alert.upon.network.offering.creation.l2": "As virtual routers are not created for L2 Networks, the compute offering will not be used.", "message.vr.alert.upon.network.offering.creation.others": "As none of the obligatory services for creating a virtual router (VPN, DHCP, DNS, Firewall, LB, UserData, SourceNat, StaticNat, PortForwarding) are enabled, the virtual router will not be created and the compute offering will not be used.", "message.warn.change.primary.storage.scope": "This feature is tested and supported for the following configurations:
KVM - NFS/Ceph - DefaultPrimary
VMware - NFS - DefaultPrimary
*There might be extra steps involved to make it work for other configurations.", "message.warn.filetype": "jpg, jpeg, png, bmp and svg are the only supported image formats.", diff --git a/ui/src/components/view/ListView.vue b/ui/src/components/view/ListView.vue index ba75465d892..375fddcb55c 100644 --- a/ui/src/components/view/ListView.vue +++ b/ui/src/components/view/ListView.vue @@ -170,6 +170,14 @@ + +   + + + + import('@/views/network/UpdateVpnCustomerGateway.vue'))) }, { api: 'deleteVpnCustomerGateway', diff --git a/ui/src/views/network/CreateVpnCustomerGateway.vue b/ui/src/views/network/CreateVpnCustomerGateway.vue index f71fc4709e8..155765a276f 100644 --- a/ui/src/views/network/CreateVpnCustomerGateway.vue +++ b/ui/src/views/network/CreateVpnCustomerGateway.vue @@ -15,352 +15,58 @@ // specific language governing permissions and limitations // under the License. + - diff --git a/ui/src/views/network/UpdateVpnCustomerGateway.vue b/ui/src/views/network/UpdateVpnCustomerGateway.vue new file mode 100644 index 00000000000..2d54e8e031e --- /dev/null +++ b/ui/src/views/network/UpdateVpnCustomerGateway.vue @@ -0,0 +1,129 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + + + diff --git a/ui/src/views/network/VpnCustomerGateway.vue b/ui/src/views/network/VpnCustomerGateway.vue new file mode 100644 index 00000000000..c1b1ed78ce0 --- /dev/null +++ b/ui/src/views/network/VpnCustomerGateway.vue @@ -0,0 +1,581 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + + + +