From 49bc0b43f5a1c0cf9ee17b4a665578b3dfaf6aeb Mon Sep 17 00:00:00 2001 From: dcollovigh <48259205+Dav-11@users.noreply.github.com> Date: Wed, 19 Mar 2025 09:10:53 +0100 Subject: [PATCH 1/3] Added createRemoteAccessVpn's new API fields --- .../user/vpn/CreateRemoteAccessVpnCmd.java | 42 ++++++++++++++----- 1 file changed, 32 insertions(+), 10 deletions(-) diff --git a/api/src/main/java/org/apache/cloudstack/api/command/user/vpn/CreateRemoteAccessVpnCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/user/vpn/CreateRemoteAccessVpnCmd.java index 8ecf4b051ced..5d641d63db23 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/user/vpn/CreateRemoteAccessVpnCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/user/vpn/CreateRemoteAccessVpnCmd.java @@ -38,6 +38,8 @@ import com.cloud.network.IpAddress; import com.cloud.network.RemoteAccessVpn; +import java.util.Objects; + @APICommand(name = "createRemoteAccessVpn", description = "Creates a l2tp/ipsec remote access vpn", responseObject = RemoteAccessVpnResponse.class, entityType = {RemoteAccessVpn.class}, requestHasSensitiveInfo = false, responseHasSensitiveInfo = false) public class CreateRemoteAccessVpnCmd extends BaseAsyncCreateCmd { @@ -56,7 +58,6 @@ public class CreateRemoteAccessVpnCmd extends BaseAsyncCreateCmd { @Parameter(name = "iprange", type = CommandType.STRING, - required = false, description = "the range of ip addresses to allocate to vpn clients. The first ip in the range will be taken by the vpn server") private String ipRange; @@ -79,6 +80,22 @@ public class CreateRemoteAccessVpnCmd extends BaseAsyncCreateCmd { @Parameter(name = ApiConstants.FOR_DISPLAY, type = CommandType.BOOLEAN, description = "an optional field, whether to the display the vpn to the end user or not", since = "4.4", authorized = {RoleType.Admin}) private Boolean display; + @Parameter(name = "listenport", + type = CommandType.INTEGER, + description = "Port on which the VPN server will be listening") + private Boolean listenPort; + + @Parameter(name = "protocol", + type = CommandType.STRING, + description = "Name of the protocol used for the VPN") + private String protocol; + + @Parameter(name = "implementationdata", + type = CommandType.STRING, + description = "JSON-encoded protocol-specific data for the VPN") + private String implementationData; + + ///////////////////////////////////////////////////// /////////////////// Accessors /////////////////////// ///////////////////////////////////////////////////// @@ -105,11 +122,19 @@ public void setIpRange(String ipRange) { } public Boolean getOpenFirewall() { - if (openFirewall != null) { - return openFirewall; - } else { - return true; - } + return Objects.requireNonNullElse(openFirewall, true); + } + + public Boolean getListenPort() { + return listenPort; + } + + public String getProtocol() { + return protocol; + } + + public String getImplementationData() { + return implementationData; } ///////////////////////////////////////////////////// @@ -191,10 +216,7 @@ private IpAddress getIp() { @Override public boolean isDisplay() { - if(display == null) - return true; - else - return display; + return Objects.requireNonNullElse(display, true); } @Override From 4c784128275032d5226eefe045cff5ed56e76ca1 Mon Sep 17 00:00:00 2001 From: dcollovigh <48259205+Dav-11@users.noreply.github.com> Date: Mon, 14 Apr 2025 13:22:49 +0200 Subject: [PATCH 2/3] Strat to edit CreateRemoteAccessVpnCmd --- .../VPNProviderNotFoundException.java | 7 ++++ .../com/cloud/network/RemoteAccessVpn.java | 2 ++ .../network/vpn/RemoteAccessVpnProvider.java | 23 ++++++++++++ ...RemoteAccessVpnProviderManagerService.java | 10 ++++++ .../org/apache/cloudstack/api/BaseCmd.java | 3 ++ .../user/vpn/CreateRemoteAccessVpnCmd.java | 35 +++++++++++++----- .../cloud/network/dao/RemoteAccessVpnVO.java | 8 +++++ .../RemoteAccessVpnProviderManagerImpl.java | 36 +++++++++++++++++++ 8 files changed, 116 insertions(+), 8 deletions(-) create mode 100644 api/src/main/java/com/cloud/exception/VPNProviderNotFoundException.java create mode 100644 api/src/main/java/com/cloud/network/vpn/RemoteAccessVpnProvider.java create mode 100644 api/src/main/java/com/cloud/network/vpn/RemoteAccessVpnProviderManagerService.java create mode 100644 server/src/main/java/com/cloud/network/vpn/RemoteAccessVpnProviderManagerImpl.java diff --git a/api/src/main/java/com/cloud/exception/VPNProviderNotFoundException.java b/api/src/main/java/com/cloud/exception/VPNProviderNotFoundException.java new file mode 100644 index 000000000000..955af1db88bc --- /dev/null +++ b/api/src/main/java/com/cloud/exception/VPNProviderNotFoundException.java @@ -0,0 +1,7 @@ +package com.cloud.exception; + +public class VPNProviderNotFoundException extends ManagementServerException { + public VPNProviderNotFoundException(String message) { + super(message); + } +} diff --git a/api/src/main/java/com/cloud/network/RemoteAccessVpn.java b/api/src/main/java/com/cloud/network/RemoteAccessVpn.java index 25b4fbbcdeba..8b3d2f95a6ec 100644 --- a/api/src/main/java/com/cloud/network/RemoteAccessVpn.java +++ b/api/src/main/java/com/cloud/network/RemoteAccessVpn.java @@ -26,6 +26,8 @@ enum State { Added, Running, Removed } + String getProvider(); + long getServerAddressId(); String getIpRange(); diff --git a/api/src/main/java/com/cloud/network/vpn/RemoteAccessVpnProvider.java b/api/src/main/java/com/cloud/network/vpn/RemoteAccessVpnProvider.java new file mode 100644 index 000000000000..11a4518d0653 --- /dev/null +++ b/api/src/main/java/com/cloud/network/vpn/RemoteAccessVpnProvider.java @@ -0,0 +1,23 @@ +package com.cloud.network.vpn; + +import com.cloud.exception.NetworkRuleConflictException; +import com.cloud.exception.ResourceUnavailableException; +import com.cloud.network.RemoteAccessVpn; +import com.cloud.user.Account; + +/** + * This class describe the behaviour of a single vpn server provider (EG: L2TP, Wireguard, OpenVPN) + */ +public interface RemoteAccessVpnProvider { + + String GetName(); + + RemoteAccessVpn createRemoteAccessVpn(long vpnServerAddressId, String ipRange, boolean openFirewall, Boolean forDisplay, Integer listenPort, String implementationData) throws NetworkRuleConflictException; + RemoteAccessVpn startRemoteAccessVpn(long vpnServerId, boolean openFirewall) throws ResourceUnavailableException; + + boolean destroyRemoteAccessVpn(long vpnId, Account caller, boolean forceCleanup) throws ResourceUnavailableException; + + + + //boolean destroyRemoteAccessVpnForIp(long ipId, Account caller, boolean forceCleanup) throws ResourceUnavailableException; +} diff --git a/api/src/main/java/com/cloud/network/vpn/RemoteAccessVpnProviderManagerService.java b/api/src/main/java/com/cloud/network/vpn/RemoteAccessVpnProviderManagerService.java new file mode 100644 index 000000000000..74168744d729 --- /dev/null +++ b/api/src/main/java/com/cloud/network/vpn/RemoteAccessVpnProviderManagerService.java @@ -0,0 +1,10 @@ +package com.cloud.network.vpn; + +import com.cloud.exception.VPNProviderNotFoundException; + +import java.util.Map; + +public interface RemoteAccessVpnProviderManagerService { + Map GetAvailableProviders(); + RemoteAccessVpnProvider GetProvider(String key) throws VPNProviderNotFoundException; +} diff --git a/api/src/main/java/org/apache/cloudstack/api/BaseCmd.java b/api/src/main/java/org/apache/cloudstack/api/BaseCmd.java index f32922819b01..71e317557df2 100644 --- a/api/src/main/java/org/apache/cloudstack/api/BaseCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/BaseCmd.java @@ -31,6 +31,7 @@ import javax.inject.Inject; +import com.cloud.network.vpn.RemoteAccessVpnProviderManagerService; import org.apache.cloudstack.acl.ProjectRoleService; import org.apache.cloudstack.acl.RoleService; import org.apache.cloudstack.acl.RoleType; @@ -160,6 +161,8 @@ public static enum CommandType { @Inject public RemoteAccessVpnService _ravService; @Inject + public RemoteAccessVpnProviderManagerService _ravpmService; + @Inject public ProjectService _projectService; @Inject public FirewallService _firewallService; diff --git a/api/src/main/java/org/apache/cloudstack/api/command/user/vpn/CreateRemoteAccessVpnCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/user/vpn/CreateRemoteAccessVpnCmd.java index 5d641d63db23..0bfa6a3a2023 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/user/vpn/CreateRemoteAccessVpnCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/user/vpn/CreateRemoteAccessVpnCmd.java @@ -16,6 +16,8 @@ // under the License. package org.apache.cloudstack.api.command.user.vpn; +import com.cloud.exception.VPNProviderNotFoundException; +import com.cloud.network.vpn.RemoteAccessVpnProvider; import org.apache.cloudstack.api.ApiCommandResourceType; import org.apache.log4j.Logger; @@ -83,12 +85,12 @@ public class CreateRemoteAccessVpnCmd extends BaseAsyncCreateCmd { @Parameter(name = "listenport", type = CommandType.INTEGER, description = "Port on which the VPN server will be listening") - private Boolean listenPort; + private Integer listenPort; - @Parameter(name = "protocol", + @Parameter(name = "provider", type = CommandType.STRING, description = "Name of the protocol used for the VPN") - private String protocol; + private String providerName; @Parameter(name = "implementationdata", type = CommandType.STRING, @@ -96,6 +98,9 @@ public class CreateRemoteAccessVpnCmd extends BaseAsyncCreateCmd { private String implementationData; + private RemoteAccessVpnProvider provider; + + ///////////////////////////////////////////////////// /////////////////// Accessors /////////////////////// ///////////////////////////////////////////////////// @@ -125,12 +130,12 @@ public Boolean getOpenFirewall() { return Objects.requireNonNullElse(openFirewall, true); } - public Boolean getListenPort() { + public Integer getListenPort() { return listenPort; } - public String getProtocol() { - return protocol; + public String getProviderName() { + return providerName; } public String getImplementationData() { @@ -165,29 +170,43 @@ public String getEventType() { @Override public void create() { try { - RemoteAccessVpn vpn = _ravService.createRemoteAccessVpn(publicIpId, ipRange, getOpenFirewall(), isDisplay()); + + this.provider = _ravpmService.GetProvider(getProviderName()); + + RemoteAccessVpn vpn = provider.createRemoteAccessVpn(publicIpId, ipRange, getOpenFirewall(), isDisplay(), getListenPort(), getImplementationData()); if (vpn != null) { setEntityId(vpn.getId()); setEntityUuid(vpn.getUuid()); + } else { throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, "Failed to create remote access vpn"); } + } catch (NetworkRuleConflictException e) { + s_logger.info("Network rule conflict: " + e.getMessage()); s_logger.trace("Network Rule Conflict: ", e); throw new ServerApiException(ApiErrorCode.NETWORK_RULE_CONFLICT_ERROR, e.getMessage()); + } catch (VPNProviderNotFoundException e) { + + s_logger.info(e.getMessage()); + s_logger.trace(e); + throw new ServerApiException(ApiErrorCode.PARAM_ERROR, e.getMessage()); } } @Override public void execute() { try { - RemoteAccessVpn result = _ravService.startRemoteAccessVpn(publicIpId, getOpenFirewall()); + + RemoteAccessVpn result = this.provider.startRemoteAccessVpn(getEntityId(), getOpenFirewall()); if (result != null) { + RemoteAccessVpnResponse response = _responseGenerator.createRemoteAccessVpnResponse(result); response.setResponseName(getCommandName()); setResponseObject(response); } else { + throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, "Failed to create remote access vpn"); } } catch (ResourceUnavailableException ex) { diff --git a/engine/schema/src/main/java/com/cloud/network/dao/RemoteAccessVpnVO.java b/engine/schema/src/main/java/com/cloud/network/dao/RemoteAccessVpnVO.java index 95e3693a99c5..102cec0f005c 100644 --- a/engine/schema/src/main/java/com/cloud/network/dao/RemoteAccessVpnVO.java +++ b/engine/schema/src/main/java/com/cloud/network/dao/RemoteAccessVpnVO.java @@ -69,6 +69,9 @@ public class RemoteAccessVpnVO implements RemoteAccessVpn { @Column(name = "display", updatable = true, nullable = false) protected boolean display = true; + @Column(name = "provider_name") + private String providerName; + public RemoteAccessVpnVO() { uuid = UUID.randomUUID().toString(); } @@ -100,6 +103,11 @@ public long getAccountId() { return accountId; } + @Override + public String getProvider() { + return providerName; + } + @Override public long getServerAddressId() { return serverAddressId; diff --git a/server/src/main/java/com/cloud/network/vpn/RemoteAccessVpnProviderManagerImpl.java b/server/src/main/java/com/cloud/network/vpn/RemoteAccessVpnProviderManagerImpl.java new file mode 100644 index 000000000000..71ba1213d874 --- /dev/null +++ b/server/src/main/java/com/cloud/network/vpn/RemoteAccessVpnProviderManagerImpl.java @@ -0,0 +1,36 @@ +package com.cloud.network.vpn; + +import com.cloud.exception.VPNProviderNotFoundException; + +import java.util.HashMap; +import java.util.Map; +import java.util.ServiceLoader; + +public class RemoteAccessVpnProviderManagerImpl implements RemoteAccessVpnProviderManagerService { + + private static HashMap providers; + + static { + // Discover and register VPN services at boot + ServiceLoader loader = ServiceLoader.load(RemoteAccessVpnProvider.class); + for (RemoteAccessVpnProvider service : loader) { + providers.put(service.GetName().toLowerCase(), service); + System.out.println("Registered VPN Service: " + service.GetName().toLowerCase()); + } + } + + + @Override + public Map GetAvailableProviders() { + return providers; + } + + public RemoteAccessVpnProvider GetProvider(String key) throws VPNProviderNotFoundException { + + if (!providers.containsKey(key)) { + throw new VPNProviderNotFoundException(key + " is not a valid VPN provider"); + } + + return providers.get(key); + } +} From 1a1cf9f6d32c096fb74df82b0490e7401825f3ca Mon Sep 17 00:00:00 2001 From: dcollovigh <48259205+Dav-11@users.noreply.github.com> Date: Thu, 17 Apr 2025 11:17:52 +0200 Subject: [PATCH 3/3] refactor + experiments --- .../com/cloud/network/RemoteAccessVpn.java | 11 +- .../network/vpn/RemoteAccessVpnProvider.java | 23 -- ...RemoteAccessVpnProviderManagerService.java | 10 - .../network/vpn/RemoteAccessVpnService.java | 12 +- .../vpn/RemoteAccessVpnServiceManager.java | 26 ++ .../org/apache/cloudstack/api/BaseCmd.java | 4 +- .../user/vpn/CreateRemoteAccessVpnCmd.java | 20 +- client/pom.xml | 5 + .../orchestration/NetworkOrchestrator.java | 2 +- .../cloud/network/dao/RemoteAccessVpnDao.java | 8 +- .../network/dao/RemoteAccessVpnDaoImpl.java | 24 +- .../cloud/network/dao/RemoteAccessVpnVO.java | 52 ++- plugins/pom.xml | 10 + plugins/remote-access-vpn/l2tp/pom.xml | 30 ++ .../network/dao/RemoteAccessVpnL2TPDao.java | 90 +++++ .../network/dao/RemoteAccessVpnL2TPVO.java | 60 +++ .../vpn/RemoteAccessVpnL2TPProtocol.java | 375 ++++++++++++++++++ .../com.cloud.network/module.properties | 0 .../com.cloud.network/spring-l2tp-context.xml | 18 + .../VirtualNetworkApplianceManagerImpl.java | 2 +- ...VpcVirtualNetworkApplianceManagerImpl.java | 2 +- .../vpn/RemoteAccessVpnManagerImpl.java | 37 +- .../vpn/RemoteAccessVpnProtocolBase.java | 130 ++++++ .../RemoteAccessVpnProviderManagerImpl.java | 36 -- .../RemoteAccessVpnServiceManagerImpl.java | 53 +++ 25 files changed, 910 insertions(+), 130 deletions(-) delete mode 100644 api/src/main/java/com/cloud/network/vpn/RemoteAccessVpnProvider.java delete mode 100644 api/src/main/java/com/cloud/network/vpn/RemoteAccessVpnProviderManagerService.java create mode 100644 api/src/main/java/com/cloud/network/vpn/RemoteAccessVpnServiceManager.java create mode 100644 plugins/remote-access-vpn/l2tp/pom.xml create mode 100644 plugins/remote-access-vpn/l2tp/src/main/java/com/cloud/network/dao/RemoteAccessVpnL2TPDao.java create mode 100644 plugins/remote-access-vpn/l2tp/src/main/java/com/cloud/network/dao/RemoteAccessVpnL2TPVO.java create mode 100644 plugins/remote-access-vpn/l2tp/src/main/java/com/cloud/network/vpn/RemoteAccessVpnL2TPProtocol.java create mode 100644 plugins/remote-access-vpn/l2tp/src/main/resources/META-INF/com.cloud.network/module.properties create mode 100644 plugins/remote-access-vpn/l2tp/src/main/resources/META-INF/com.cloud.network/spring-l2tp-context.xml create mode 100644 server/src/main/java/com/cloud/network/vpn/RemoteAccessVpnProtocolBase.java delete mode 100644 server/src/main/java/com/cloud/network/vpn/RemoteAccessVpnProviderManagerImpl.java create mode 100644 server/src/main/java/com/cloud/network/vpn/RemoteAccessVpnServiceManagerImpl.java diff --git a/api/src/main/java/com/cloud/network/RemoteAccessVpn.java b/api/src/main/java/com/cloud/network/RemoteAccessVpn.java index 8b3d2f95a6ec..c8c090a77f6b 100644 --- a/api/src/main/java/com/cloud/network/RemoteAccessVpn.java +++ b/api/src/main/java/com/cloud/network/RemoteAccessVpn.java @@ -26,12 +26,17 @@ enum State { Added, Running, Removed } - String getProvider(); + String getProviderName(); - long getServerAddressId(); + Long getServerAddressId(); String getIpRange(); + // TODO: DELETE THIS, it is only for ipsec + + /** + * @deprecated + */ String getIpsecPresharedKey(); String getLocalIp(); @@ -42,6 +47,8 @@ enum State { State getState(); + Integer getPort(); + @Override boolean isDisplay(); } diff --git a/api/src/main/java/com/cloud/network/vpn/RemoteAccessVpnProvider.java b/api/src/main/java/com/cloud/network/vpn/RemoteAccessVpnProvider.java deleted file mode 100644 index 11a4518d0653..000000000000 --- a/api/src/main/java/com/cloud/network/vpn/RemoteAccessVpnProvider.java +++ /dev/null @@ -1,23 +0,0 @@ -package com.cloud.network.vpn; - -import com.cloud.exception.NetworkRuleConflictException; -import com.cloud.exception.ResourceUnavailableException; -import com.cloud.network.RemoteAccessVpn; -import com.cloud.user.Account; - -/** - * This class describe the behaviour of a single vpn server provider (EG: L2TP, Wireguard, OpenVPN) - */ -public interface RemoteAccessVpnProvider { - - String GetName(); - - RemoteAccessVpn createRemoteAccessVpn(long vpnServerAddressId, String ipRange, boolean openFirewall, Boolean forDisplay, Integer listenPort, String implementationData) throws NetworkRuleConflictException; - RemoteAccessVpn startRemoteAccessVpn(long vpnServerId, boolean openFirewall) throws ResourceUnavailableException; - - boolean destroyRemoteAccessVpn(long vpnId, Account caller, boolean forceCleanup) throws ResourceUnavailableException; - - - - //boolean destroyRemoteAccessVpnForIp(long ipId, Account caller, boolean forceCleanup) throws ResourceUnavailableException; -} diff --git a/api/src/main/java/com/cloud/network/vpn/RemoteAccessVpnProviderManagerService.java b/api/src/main/java/com/cloud/network/vpn/RemoteAccessVpnProviderManagerService.java deleted file mode 100644 index 74168744d729..000000000000 --- a/api/src/main/java/com/cloud/network/vpn/RemoteAccessVpnProviderManagerService.java +++ /dev/null @@ -1,10 +0,0 @@ -package com.cloud.network.vpn; - -import com.cloud.exception.VPNProviderNotFoundException; - -import java.util.Map; - -public interface RemoteAccessVpnProviderManagerService { - Map GetAvailableProviders(); - RemoteAccessVpnProvider GetProvider(String key) throws VPNProviderNotFoundException; -} diff --git a/api/src/main/java/com/cloud/network/vpn/RemoteAccessVpnService.java b/api/src/main/java/com/cloud/network/vpn/RemoteAccessVpnService.java index bbb9771d27aa..07caad93be01 100644 --- a/api/src/main/java/com/cloud/network/vpn/RemoteAccessVpnService.java +++ b/api/src/main/java/com/cloud/network/vpn/RemoteAccessVpnService.java @@ -17,6 +17,7 @@ package com.cloud.network.vpn; import java.util.List; +import java.util.Map; import org.apache.cloudstack.api.command.user.vpn.ListRemoteAccessVpnsCmd; import org.apache.cloudstack.api.command.user.vpn.ListVpnUsersCmd; @@ -31,11 +32,18 @@ public interface RemoteAccessVpnService { static final String RemoteAccessVpnClientIpRangeCK = "remote.access.vpn.client.iprange"; - RemoteAccessVpn createRemoteAccessVpn(long vpnServerAddressId, String ipRange, boolean openFirewall, Boolean forDisplay) throws NetworkRuleConflictException; + String GetName(); + + // TODO: add method to export all available specific fields with + // - name + // - required flags + // - description + + RemoteAccessVpn createRemoteAccessVpn(long vpnServerAddressId, String ipRange, boolean openFirewall, Boolean forDisplay, Integer listenPort, Map implementationData) throws NetworkRuleConflictException; boolean destroyRemoteAccessVpnForIp(long ipId, Account caller, boolean forceCleanup) throws ResourceUnavailableException; - RemoteAccessVpn startRemoteAccessVpn(long vpnServerAddressId, boolean openFirewall) throws ResourceUnavailableException; + RemoteAccessVpn startRemoteAccessVpn(long vpnServerId, boolean openFirewall) throws ResourceUnavailableException; VpnUser addVpnUser(long vpnOwnerId, String userName, String password); diff --git a/api/src/main/java/com/cloud/network/vpn/RemoteAccessVpnServiceManager.java b/api/src/main/java/com/cloud/network/vpn/RemoteAccessVpnServiceManager.java new file mode 100644 index 000000000000..0e3761b0bb29 --- /dev/null +++ b/api/src/main/java/com/cloud/network/vpn/RemoteAccessVpnServiceManager.java @@ -0,0 +1,26 @@ +// 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.VPNProviderNotFoundException; + +import java.util.Map; + +public interface RemoteAccessVpnServiceManager { + Map GetAvailableProviders(); + RemoteAccessVpnService GetProvider(String key) throws VPNProviderNotFoundException; +} diff --git a/api/src/main/java/org/apache/cloudstack/api/BaseCmd.java b/api/src/main/java/org/apache/cloudstack/api/BaseCmd.java index 71e317557df2..f8f224f8f022 100644 --- a/api/src/main/java/org/apache/cloudstack/api/BaseCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/BaseCmd.java @@ -31,7 +31,7 @@ import javax.inject.Inject; -import com.cloud.network.vpn.RemoteAccessVpnProviderManagerService; +import com.cloud.network.vpn.RemoteAccessVpnServiceManager; import org.apache.cloudstack.acl.ProjectRoleService; import org.apache.cloudstack.acl.RoleService; import org.apache.cloudstack.acl.RoleType; @@ -161,7 +161,7 @@ public static enum CommandType { @Inject public RemoteAccessVpnService _ravService; @Inject - public RemoteAccessVpnProviderManagerService _ravpmService; + public RemoteAccessVpnServiceManager _ravpmService; @Inject public ProjectService _projectService; @Inject diff --git a/api/src/main/java/org/apache/cloudstack/api/command/user/vpn/CreateRemoteAccessVpnCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/user/vpn/CreateRemoteAccessVpnCmd.java index 0bfa6a3a2023..b6c6248e75a4 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/user/vpn/CreateRemoteAccessVpnCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/user/vpn/CreateRemoteAccessVpnCmd.java @@ -17,7 +17,7 @@ package org.apache.cloudstack.api.command.user.vpn; import com.cloud.exception.VPNProviderNotFoundException; -import com.cloud.network.vpn.RemoteAccessVpnProvider; +import com.cloud.network.vpn.RemoteAccessVpnService; import org.apache.cloudstack.api.ApiCommandResourceType; import org.apache.log4j.Logger; @@ -40,6 +40,8 @@ import com.cloud.network.IpAddress; import com.cloud.network.RemoteAccessVpn; +import java.util.HashMap; +import java.util.Map; import java.util.Objects; @APICommand(name = "createRemoteAccessVpn", description = "Creates a l2tp/ipsec remote access vpn", responseObject = RemoteAccessVpnResponse.class, entityType = {RemoteAccessVpn.class}, @@ -93,12 +95,12 @@ public class CreateRemoteAccessVpnCmd extends BaseAsyncCreateCmd { private String providerName; @Parameter(name = "implementationdata", - type = CommandType.STRING, - description = "JSON-encoded protocol-specific data for the VPN") - private String implementationData; + type = CommandType.MAP, + description = "protocol-specific data for the VPN") + private Map implementationData; - private RemoteAccessVpnProvider provider; + private RemoteAccessVpnService vpnProtocol; ///////////////////////////////////////////////////// @@ -138,7 +140,7 @@ public String getProviderName() { return providerName; } - public String getImplementationData() { + public Map getImplementationData() { return implementationData; } @@ -171,9 +173,9 @@ public String getEventType() { public void create() { try { - this.provider = _ravpmService.GetProvider(getProviderName()); + this.vpnProtocol = _ravpmService.GetProvider(getProviderName()); - RemoteAccessVpn vpn = provider.createRemoteAccessVpn(publicIpId, ipRange, getOpenFirewall(), isDisplay(), getListenPort(), getImplementationData()); + RemoteAccessVpn vpn = vpnProtocol.createRemoteAccessVpn(publicIpId, ipRange, getOpenFirewall(), isDisplay(), getListenPort(), getImplementationData()); if (vpn != null) { setEntityId(vpn.getId()); setEntityUuid(vpn.getUuid()); @@ -199,7 +201,7 @@ public void create() { public void execute() { try { - RemoteAccessVpn result = this.provider.startRemoteAccessVpn(getEntityId(), getOpenFirewall()); + RemoteAccessVpn result = this.vpnProtocol.startRemoteAccessVpn(getEntityId(), getOpenFirewall()); if (result != null) { RemoteAccessVpnResponse response = _responseGenerator.createRemoteAccessVpnResponse(result); diff --git a/client/pom.xml b/client/pom.xml index 53baebbc2b4d..e26fcee489ce 100644 --- a/client/pom.xml +++ b/client/pom.xml @@ -642,6 +642,11 @@ cloud-utils ${project.version} + + org.apache.cloudstack + cloud-plugin-remote-access-vpn-l2tp + ${project.version} + diff --git a/engine/orchestration/src/main/java/org/apache/cloudstack/engine/orchestration/NetworkOrchestrator.java b/engine/orchestration/src/main/java/org/apache/cloudstack/engine/orchestration/NetworkOrchestrator.java index 0232e3aeb9c3..6a7d1716be0f 100644 --- a/engine/orchestration/src/main/java/org/apache/cloudstack/engine/orchestration/NetworkOrchestrator.java +++ b/engine/orchestration/src/main/java/org/apache/cloudstack/engine/orchestration/NetworkOrchestrator.java @@ -1907,7 +1907,7 @@ public void doInTransactionWithoutResult(TransactionStatus status) { //do not remove vpn service for vpc networks. if (services.contains(Service.Vpn.getName()) && network.getVpcId() == null) { - RemoteAccessVpnVO vpn = _remoteAccessVpnDao.findByAccountAndNetwork(network.getAccountId(), networkId); + RemoteAccessVpnVO vpn = _remoteAccessVpnDao.findByAccountNetworkAndPort(network.getAccountId(), networkId); try { _vpnMgr.destroyRemoteAccessVpnForIp(vpn.getServerAddressId(), caller, true); } catch (ResourceUnavailableException ex) { diff --git a/engine/schema/src/main/java/com/cloud/network/dao/RemoteAccessVpnDao.java b/engine/schema/src/main/java/com/cloud/network/dao/RemoteAccessVpnDao.java index 8113c06c866e..43a9f30861dc 100644 --- a/engine/schema/src/main/java/com/cloud/network/dao/RemoteAccessVpnDao.java +++ b/engine/schema/src/main/java/com/cloud/network/dao/RemoteAccessVpnDao.java @@ -22,13 +22,13 @@ import com.cloud.utils.db.GenericDao; public interface RemoteAccessVpnDao extends GenericDao { - RemoteAccessVpnVO findByPublicIpAddress(long ipAddressId); + RemoteAccessVpnVO findByPublicIpAddressAndPort(long ipAddressId, Integer port); - RemoteAccessVpnVO findByPublicIpAddressAndState(long ipAddressId, RemoteAccessVpn.State state); + RemoteAccessVpnVO findByPublicIpAddressStateAndPort(long ipAddressId, RemoteAccessVpn.State state, Integer port); - RemoteAccessVpnVO findByAccountAndNetwork(Long accountId, Long networkId); + RemoteAccessVpnVO findByAccountNetworkAndPort(Long accountId, Long networkId, Integer port); - RemoteAccessVpnVO findByAccountAndVpc(Long accountId, Long vpcId); + RemoteAccessVpnVO findByAccountVpcAndPort(Long accountId, Long vpcId, Integer port); List findByAccount(Long accountId); diff --git a/engine/schema/src/main/java/com/cloud/network/dao/RemoteAccessVpnDaoImpl.java b/engine/schema/src/main/java/com/cloud/network/dao/RemoteAccessVpnDaoImpl.java index 3aa2e749712e..fe240f537987 100644 --- a/engine/schema/src/main/java/com/cloud/network/dao/RemoteAccessVpnDaoImpl.java +++ b/engine/schema/src/main/java/com/cloud/network/dao/RemoteAccessVpnDaoImpl.java @@ -44,41 +44,45 @@ protected RemoteAccessVpnDaoImpl() { } @Override - public RemoteAccessVpnVO findByPublicIpAddress(long ipAddressId) { + public RemoteAccessVpnVO findByPublicIpAddressAndPort(long ipAddressId, Integer port) { SearchCriteria sc = AllFieldsSearch.create(); sc.setParameters("ipAddress", ipAddressId); + sc.setParameters("port", port); return findOneBy(sc); } @Override - public RemoteAccessVpnVO findByAccountAndNetwork(Long accountId, Long networkId) { + public RemoteAccessVpnVO findByAccountNetworkAndPort(Long accountId, Long networkId, Integer port) { SearchCriteria sc = AllFieldsSearch.create(); sc.setParameters("accountId", accountId); sc.setParameters("networkId", networkId); + sc.setParameters("port", port); return findOneBy(sc); } @Override - public RemoteAccessVpnVO findByAccountAndVpc(Long accountId, Long vpcId) { + public RemoteAccessVpnVO findByAccountVpcAndPort(Long accountId, Long vpcId, Integer port) { SearchCriteria sc = AllFieldsSearch.create(); sc.setParameters("accountId", accountId); sc.setParameters("vpcId", vpcId); + sc.setParameters("port", port); return findOneBy(sc); } @Override - public List findByAccount(Long accountId) { + public RemoteAccessVpnVO findByPublicIpAddressStateAndPort(long ipAddressId, RemoteAccessVpn.State state, Integer port) { SearchCriteria sc = AllFieldsSearch.create(); - sc.setParameters("accountId", accountId); - return listBy(sc); + sc.setParameters("ipAddress", ipAddressId); + sc.setParameters("state", state); + sc.setParameters("port", port); + return findOneBy(sc); } @Override - public RemoteAccessVpnVO findByPublicIpAddressAndState(long ipAddressId, RemoteAccessVpn.State state) { + public List findByAccount(Long accountId) { SearchCriteria sc = AllFieldsSearch.create(); - sc.setParameters("ipAddress", ipAddressId); - sc.setParameters("state", state); - return findOneBy(sc); + sc.setParameters("accountId", accountId); + return listBy(sc); } @Override diff --git a/engine/schema/src/main/java/com/cloud/network/dao/RemoteAccessVpnVO.java b/engine/schema/src/main/java/com/cloud/network/dao/RemoteAccessVpnVO.java index 102cec0f005c..4d8fa953ac1d 100644 --- a/engine/schema/src/main/java/com/cloud/network/dao/RemoteAccessVpnVO.java +++ b/engine/schema/src/main/java/com/cloud/network/dao/RemoteAccessVpnVO.java @@ -17,7 +17,6 @@ package com.cloud.network.dao; import com.cloud.network.RemoteAccessVpn; -import com.cloud.utils.db.Encrypt; import javax.persistence.Column; import javax.persistence.Entity; @@ -30,8 +29,14 @@ @Entity @Table(name = "remote_access_vpn") public class RemoteAccessVpnVO implements RemoteAccessVpn { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + @Column(name = "id") + private long id; + @Column(name = "account_id") - private long accountId; + private Long accountId; @Column(name = "network_id") private Long networkId; @@ -40,7 +45,7 @@ public class RemoteAccessVpnVO implements RemoteAccessVpn { private long domainId; @Column(name = "vpn_server_addr_id") - private long serverAddressId; + private Long serverAddressId; @Column(name = "local_ip") private String localIp; @@ -48,17 +53,11 @@ public class RemoteAccessVpnVO implements RemoteAccessVpn { @Column(name = "ip_range") private String ipRange; - @Encrypt - @Column(name = "ipsec_psk") - private String ipsecPresharedKey; - @Column(name = "state") private State state; - @Id - @GeneratedValue(strategy = GenerationType.IDENTITY) - @Column(name = "id") - private long id; + @Column(name= "port") + private Integer port; @Column(name = "uuid") private String uuid; @@ -76,11 +75,10 @@ public RemoteAccessVpnVO() { uuid = UUID.randomUUID().toString(); } - public RemoteAccessVpnVO(long accountId, long domainId, Long networkId, long publicIpId, Long vpcId, String localIp, String ipRange, String presharedKey) { + public RemoteAccessVpnVO(Long accountId, Long domainId, Long networkId, Long publicIpId, Long vpcId, String localIp, String ipRange) { this.accountId = accountId; serverAddressId = publicIpId; this.ipRange = ipRange; - ipsecPresharedKey = presharedKey; this.localIp = localIp; this.domainId = domainId; this.networkId = networkId; @@ -104,12 +102,25 @@ public long getAccountId() { } @Override - public String getProvider() { + public String getProviderName() { return providerName; } + public void setProviderName(String providerName) { + this.providerName = providerName; + } + + @Override + public Integer getPort() { + return port; + } + + public void setPort(Integer port) { + this.port = port; + } + @Override - public long getServerAddressId() { + public Long getServerAddressId() { return serverAddressId; } @@ -118,17 +129,14 @@ public String getIpRange() { return ipRange; } - public void setIpRange(String ipRange) { - this.ipRange = ipRange; - } - + // TODO: delete @Override public String getIpsecPresharedKey() { - return ipsecPresharedKey; + return ""; } - public void setIpsecPresharedKey(String ipsecPresharedKey) { - this.ipsecPresharedKey = ipsecPresharedKey; + public void setIpRange(String ipRange) { + this.ipRange = ipRange; } @Override diff --git a/plugins/pom.xml b/plugins/pom.xml index 27d316f56c19..c560134f3529 100755 --- a/plugins/pom.xml +++ b/plugins/pom.xml @@ -39,6 +39,14 @@ + + org.apache.maven.plugins + maven-compiler-plugin + + 9 + 9 + + @@ -112,6 +120,8 @@ network-elements/brocade-vcs network-elements/vxlan + remote-access-vpn/l2tp + outofbandmanagement-drivers/ipmitool outofbandmanagement-drivers/nested-cloudstack outofbandmanagement-drivers/redfish diff --git a/plugins/remote-access-vpn/l2tp/pom.xml b/plugins/remote-access-vpn/l2tp/pom.xml new file mode 100644 index 000000000000..57c71d70987d --- /dev/null +++ b/plugins/remote-access-vpn/l2tp/pom.xml @@ -0,0 +1,30 @@ + + 4.0.0 + cloud-plugin-remote-access-vpn-l2tp + Apache CloudStack Plugin - L2TP remote access VPN + + org.apache.cloudstack + cloudstack-plugins + 4.19.2.0-SNAPSHOT + ../../pom.xml + + + + org.apache.cloudstack + cloud-api + ${project.version} + + + org.apache.cloudstack + cloud-utils + ${project.version} + + + org.apache.cloudstack + cloud-server + ${project.version} + + + \ No newline at end of file diff --git a/plugins/remote-access-vpn/l2tp/src/main/java/com/cloud/network/dao/RemoteAccessVpnL2TPDao.java b/plugins/remote-access-vpn/l2tp/src/main/java/com/cloud/network/dao/RemoteAccessVpnL2TPDao.java new file mode 100644 index 000000000000..eef7e3d5434e --- /dev/null +++ b/plugins/remote-access-vpn/l2tp/src/main/java/com/cloud/network/dao/RemoteAccessVpnL2TPDao.java @@ -0,0 +1,90 @@ +// 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.dao; + +import com.cloud.network.RemoteAccessVpn; +import com.cloud.utils.db.GenericDaoBase; +import com.cloud.utils.db.SearchBuilder; +import com.cloud.utils.db.SearchCriteria; +import org.apache.log4j.Logger; + +import java.util.List; +import java.util.stream.Collectors; + +public class RemoteAccessVpnL2TPDao extends GenericDaoBase { + + private static final Logger s_logger = Logger.getLogger(RemoteAccessVpnL2TPDao.class); + + private final SearchBuilder AllFieldsSearch; + + public RemoteAccessVpnL2TPDao() { + AllFieldsSearch = createSearchBuilder(); + AllFieldsSearch.and("accountId", AllFieldsSearch.entity().getAccountId(), SearchCriteria.Op.EQ); + AllFieldsSearch.and("networkId", AllFieldsSearch.entity().getNetworkId(), SearchCriteria.Op.EQ); + AllFieldsSearch.and("vpcId", AllFieldsSearch.entity().getVpcId(), SearchCriteria.Op.EQ); + AllFieldsSearch.and("ipAddress", AllFieldsSearch.entity().getServerAddressId(), SearchCriteria.Op.EQ); + AllFieldsSearch.and("state", AllFieldsSearch.entity().getState(), SearchCriteria.Op.EQ); + AllFieldsSearch.done(); + } + + + public RemoteAccessVpn findByPublicIpAddressAndPort(long ipAddressId, Integer port) { + + SearchCriteria sc = AllFieldsSearch.create(); + sc.setParameters("ipAddress", ipAddressId); + sc.setParameters("port", port); + return findOneBy(sc); + } + + public RemoteAccessVpn findByPublicIpAddressAndState(long ipAddressId, RemoteAccessVpn.State state) { + return null; + } + + public RemoteAccessVpn findByAccountNetworkAndPort(Long accountId, Long networkId, Integer port) { + SearchCriteria sc = AllFieldsSearch.create(); + sc.setParameters("accountId", accountId); + sc.setParameters("networkId", networkId); + sc.setParameters("port", port); + return findOneBy(sc); + } + + public RemoteAccessVpn findByAccountVpcAndPort(Long accountId, Long vpcId, Integer port) { + SearchCriteria sc = AllFieldsSearch.create(); + sc.setParameters("accountId", accountId); + sc.setParameters("vpcId", vpcId); + sc.setParameters("port", port); + return findOneBy(sc); + } + + public List findByAccount(Long accountId) { + SearchCriteria sc = AllFieldsSearch.create(); + sc.setParameters("accountId", accountId); + return listBy(sc) + .stream() + .map(vpn -> (RemoteAccessVpn) vpn) + .collect(Collectors.toList()); + } + + public List listByNetworkId(Long networkId) { + SearchCriteria sc = AllFieldsSearch.create(); + sc.setParameters("networkId", networkId); + return listBy(sc) + .stream() + .map(vpn -> (RemoteAccessVpn) vpn) + .collect(Collectors.toList()); + } +} diff --git a/plugins/remote-access-vpn/l2tp/src/main/java/com/cloud/network/dao/RemoteAccessVpnL2TPVO.java b/plugins/remote-access-vpn/l2tp/src/main/java/com/cloud/network/dao/RemoteAccessVpnL2TPVO.java new file mode 100644 index 000000000000..68d73fb9e21e --- /dev/null +++ b/plugins/remote-access-vpn/l2tp/src/main/java/com/cloud/network/dao/RemoteAccessVpnL2TPVO.java @@ -0,0 +1,60 @@ +// 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.dao; + +import com.cloud.network.vpn.RemoteAccessVpnL2TPProtocol; +import com.cloud.utils.db.Encrypt; + +import javax.persistence.*; + +@Entity +@Table(name = "remote_access_vpn") +@SecondaryTable(name = "remote_access_vpn_l2tp", pkJoinColumns = @PrimaryKeyJoinColumn(name = "vpn_id")) +public class RemoteAccessVpnL2TPVO extends RemoteAccessVpnVO { + + @Encrypt + @Column(name = "preshared_key", table = "remote_access_vpn_l2tp") + private String presharedKey; + + public RemoteAccessVpnL2TPVO() { + super(); + this.setProviderName(RemoteAccessVpnL2TPProtocol.PROTOCOL_NAME); + } + + public RemoteAccessVpnL2TPVO(long accountId, long domainId, Long networkId, long publicIpId, Long vpcId, String localIp, String ipRange, String presharedKey) { + super( + accountId, + domainId, + networkId, + publicIpId, + vpcId, + localIp, + ipRange + ); + + this.presharedKey = presharedKey; + this.setProviderName(RemoteAccessVpnL2TPProtocol.PROTOCOL_NAME); + } + + public String getIpsecPresharedKey() { + return presharedKey; + } + + public void setIpsecPresharedKey(String ipsecPresharedKey) { + this.presharedKey = ipsecPresharedKey; + } +} diff --git a/plugins/remote-access-vpn/l2tp/src/main/java/com/cloud/network/vpn/RemoteAccessVpnL2TPProtocol.java b/plugins/remote-access-vpn/l2tp/src/main/java/com/cloud/network/vpn/RemoteAccessVpnL2TPProtocol.java new file mode 100644 index 000000000000..65783faf4bad --- /dev/null +++ b/plugins/remote-access-vpn/l2tp/src/main/java/com/cloud/network/vpn/RemoteAccessVpnL2TPProtocol.java @@ -0,0 +1,375 @@ +// 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.configuration.Config; +import com.cloud.domain.DomainVO; +import com.cloud.event.ActionEvent; +import com.cloud.event.EventTypes; +import com.cloud.event.UsageEventUtils; +import com.cloud.exception.InvalidParameterValueException; +import com.cloud.exception.NetworkRuleConflictException; +import com.cloud.exception.ResourceUnavailableException; +import com.cloud.network.*; +import com.cloud.network.dao.*; +import com.cloud.network.element.RemoteAccessVPNServiceProvider; +import com.cloud.network.rules.FirewallRule; +import com.cloud.network.vpc.Vpc; +import com.cloud.user.Account; +import com.cloud.utils.NumbersUtil; +import com.cloud.utils.Pair; +import com.cloud.utils.PasswordGenerator; +import com.cloud.utils.db.*; +import com.cloud.utils.exception.CloudRuntimeException; +import com.cloud.utils.net.NetUtils; +import org.apache.cloudstack.api.command.user.vpn.ListRemoteAccessVpnsCmd; +import org.apache.cloudstack.api.command.user.vpn.ListVpnUsersCmd; +import org.apache.cloudstack.context.CallContext; +import org.apache.cloudstack.framework.config.ConfigKey; +import org.apache.log4j.Logger; + +import javax.inject.Inject; +import javax.naming.ConfigurationException; +import java.util.List; +import java.util.Map; + +public class RemoteAccessVpnL2TPProtocol extends RemoteAccessVpnProtocolBase { + + private final static Logger s_logger = Logger.getLogger(RemoteAccessVpnL2TPProtocol.class); + + @Inject + VpnUserDao _vpnUsersDao; + + public static final String PROTOCOL_NAME = "l2tp"; + public static final String PSK_KEY = "preshared_key"; + + + SearchBuilder VpnSearch; + RemoteAccessVpnL2TPDao _remoteAccessVpnDao = new RemoteAccessVpnL2TPDao(); + + @Override + public boolean configure(String name, Map params) throws ConfigurationException { + Map configs = _configDao.getConfiguration(params); + + _userLimit = NumbersUtil.parseInt(configs.get(Config.RemoteAccessVpnUserLimit.key()), 8); + + _pskLength = NumbersUtil.parseInt(configs.get(Config.RemoteAccessVpnPskLength.key()), 24); + + validateRemoteAccessVpnConfiguration(); + + VpnSearch = _remoteAccessVpnDao.createSearchBuilder(); + VpnSearch.and("accountId", VpnSearch.entity().getAccountId(), SearchCriteria.Op.EQ); + SearchBuilder domainSearch = _domainDao.createSearchBuilder(); + domainSearch.and("path", domainSearch.entity().getPath(), SearchCriteria.Op.LIKE); + VpnSearch.join("domainSearch", domainSearch, VpnSearch.entity().getDomainId(), domainSearch.entity().getId(), JoinBuilder.JoinType.INNER); + VpnSearch.done(); + + return true; + } + + public String GetName() { + return PROTOCOL_NAME; + } + + @DB + public RemoteAccessVpn createRemoteAccessVpn(long publicIpId, String ipRange, boolean openFirewall, Boolean forDisplay, Integer port, Map implementationData) throws NetworkRuleConflictException { + + CallContext ctx = CallContext.current(); + final Account caller = ctx.getCallingAccount(); + + final PublicIpAddress ipAddr = _networkMgr.getPublicIpAddress(publicIpId); + if (ipAddr == null) { + throw new InvalidParameterValueException(String.format("Unable to create remote access VPN, invalid public IP address {\"id\": %s}.", publicIpId)); + } + + _accountMgr.checkAccess(caller, null, true, ipAddr); + + if (!ipAddr.readyToUse()) { + throw new InvalidParameterValueException("The Ip address is not ready to be used yet: " + ipAddr.getAddress()); + } + + try { + IPAddressVO ipAddress = _ipAddressDao.acquireInLockTable(publicIpId); + + // + // check if ip and network exists + // + + if (ipAddress == null) { + s_logger.error(String.format("Unable to acquire lock on public IP %s.", publicIpId)); + throw new CloudRuntimeException("Unable to acquire lock on public IP."); + } + + Long networkId = ipAddress.getAssociatedWithNetworkId(); + if (networkId != null) { + _networkMgr.checkIpForService(ipAddress, Network.Service.Vpn, null); + } + + final Long vpcId = ipAddress.getVpcId(); + if (vpcId != null && ipAddress.isSourceNat()) { + assert networkId == null; + openFirewall = false; + } + + final boolean openFirewallFinal = openFirewall; + + if (networkId == null && vpcId == null) { + throw new InvalidParameterValueException("Unable to create remote access vpn for the ipAddress: " + ipAddr.getAddress().addr() + + " as ip is not associated with any network or VPC"); + } + + // + // check if port is already used on given ip + // + + RemoteAccessVpn vpnVO = _remoteAccessVpnDao.findByPublicIpAddressAndPort(publicIpId, port); + if (vpnVO != null) { + if (vpnVO.getState() == RemoteAccessVpn.State.Added) { + return vpnVO; + } + + throw new InvalidParameterValueException(String.format("A remote Access VPN already exists for the public IP address [%s] on port %d.", ipAddr.getAddress().toString(), port)); + } + + // + // validate ip range + // + + if (ipRange == null) { + ipRange = RemoteAccessVpnClientIpRange.valueIn(ipAddr.getAccountId()); + } + + validateIpRange(ipRange, InvalidParameterValueException.class); + String[] range = ipRange.split("-"); + Pair cidr = null; + + if (networkId != null) { + + long ipAddressOwner = ipAddr.getAccountId(); + + // + // ???? + // + + vpnVO = _remoteAccessVpnDao.findByAccountNetworkAndPort(ipAddressOwner, networkId, port); + if (vpnVO != null) { + if (vpnVO.getState() == RemoteAccessVpn.State.Added) { + return vpnVO; + } + + throw new InvalidParameterValueException(String.format("A remote access VPN already exists for the account [%s].", ipAddressOwner)); + } + + // + // Check if network supports vpn + // + + Network network = _networkMgr.getNetwork(networkId); + if (!_networkMgr.areServicesSupportedInNetwork(network.getId(), Network.Service.Vpn)) { + throw new InvalidParameterValueException("Vpn service is not supported in network id=" + ipAddr.getAssociatedWithNetworkId()); + } + + // + // Get CIDR from net + // + cidr = NetUtils.getCidr(network.getCidr()); + + } else { + + // + // Get CIDR from vpc + // + + Vpc vpc = _vpcDao.findById(vpcId); + cidr = NetUtils.getCidr(vpc.getCidr()); + } + + String[] guestIpRange = NetUtils.getIpRangeFromCidr(cidr.first(), cidr.second()); + if (NetUtils.ipRangesOverlap(range[0], range[1], guestIpRange[0], guestIpRange[1])) { + throw new InvalidParameterValueException("Invalid ip range: " + ipRange + " overlaps with guest ip range " + guestIpRange[0] + "-" + guestIpRange[1]); + } + + long startIp = NetUtils.ip2Long(range[0]); + final String newIpRange = NetUtils.long2Ip(++startIp) + "-" + range[1]; + + final String rawPsk = implementationData.get(PSK_KEY); + + final String sharedSecret; + + if (rawPsk != null && !rawPsk.trim().isEmpty() && rawPsk.trim().length() == _pskLength) { + sharedSecret = rawPsk.trim(); + } else { + sharedSecret = PasswordGenerator.generatePresharedKey(_pskLength); + } + + return Transaction.execute((TransactionCallbackWithException) status -> { + + if (vpcId == null) { + _rulesMgr.reservePorts( + ipAddr, + NetUtils.UDP_PROTO, + FirewallRule.Purpose.Vpn, + openFirewallFinal, + caller, + NetUtils.VPN_PORT, + NetUtils.VPN_L2TP_PORT, + NetUtils.VPN_NATT_PORT + ); + } + + RemoteAccessVpnL2TPVO remoteAccessVpnVO = new RemoteAccessVpnL2TPVO( + ipAddr.getAccountId(), + ipAddr.getDomainId(), + ipAddr.getAssociatedWithNetworkId(), + publicIpId, + vpcId, + range[0], + newIpRange, + sharedSecret + ); + + if (forDisplay != null) { + remoteAccessVpnVO.setDisplay(forDisplay); + } + + return _remoteAccessVpnDao.persist(remoteAccessVpnVO); + }); + + } finally { + _ipAddressDao.releaseFromLockTable(publicIpId); + } + } + + @DB + @ActionEvent(eventType = EventTypes.EVENT_REMOTE_ACCESS_VPN_CREATE, eventDescription = "creating remote access vpn", async = true) + public RemoteAccessVpn startRemoteAccessVpn(long vpnServerId, boolean openFirewall) throws ResourceUnavailableException { + + Account caller = CallContext.current().getCallingAccount(); + + final RemoteAccessVpnL2TPVO vpn = _remoteAccessVpnDao.findById(vpnServerId); + if (vpn == null) { + throw new InvalidParameterValueException("Unable to find your vpn: " + vpnServerId); + } + + if (vpn.getVpcId() != null) { + openFirewall = false; + } + + _accountMgr.checkAccess(caller, null, true, vpn); + + boolean started = false; + try { + boolean firewallOpened = true; + if (openFirewall) { + firewallOpened = _firewallMgr.applyIngressFirewallRules(vpn.getServerAddressId(), caller); + } + + if (firewallOpened) { + for (RemoteAccessVPNServiceProvider element : _vpnServiceProviders) { + if (element.startVpn(vpn)) { + started = true; + break; + } + } + } + + return vpn; + } finally { + if (started) { + Transaction.execute(new TransactionCallbackNoReturn() { + @Override + public void doInTransactionWithoutResult(TransactionStatus status) { + vpn.setState(RemoteAccessVpn.State.Running); + _remoteAccessVpnDao.update(vpn.getId(), vpn); + + List vpnUsers = _vpnUsersDao.listByAccount(vpn.getAccountId()); + for (VpnUserVO user : vpnUsers) { + if (user.getState() != VpnUser.State.Revoke) { + UsageEventUtils.publishUsageEvent( + EventTypes.EVENT_VPN_USER_ADD, + user.getAccountId(), + 0, + user.getId(), + user.getUsername(), + user.getClass().getName(), + user.getUuid() + ); + } + } + } + }); + } + } + } + + @Override + public boolean destroyRemoteAccessVpnForIp(long ipId, Account caller, boolean forceCleanup) throws ResourceUnavailableException { + return false; + } + + @Override + public VpnUser addVpnUser(long vpnOwnerId, String userName, String password) { + return null; + } + @Override + public boolean removeVpnUser(long vpnOwnerId, String userName, Account caller) { + return false; + } + @Override + public List listVpnUsers(long vpnOwnerId, String userName) { + return List.of(); + } + @Override + public boolean applyVpnUsers(long vpnOwnerId, String userName, boolean forRemove) throws ResourceUnavailableException { + return false; + } + @Override + public boolean applyVpnUsers(long vpnOwnerId, String userName) throws ResourceUnavailableException { + return false; + } + @Override + public Pair, Integer> searchForRemoteAccessVpns(ListRemoteAccessVpnsCmd cmd) { + return null; + } + @Override + public Pair, Integer> searchForVpnUsers(ListVpnUsersCmd cmd) { + return null; + } + @Override + public List listRemoteAccessVpns(long networkId) { + return List.of(); + } + @Override + public RemoteAccessVpn getRemoteAccessVpn(long vpnAddrId) { + return null; + } + @Override + public RemoteAccessVpn getRemoteAccessVpnById(long vpnId) { + return null; + } + @Override + public RemoteAccessVpn updateRemoteAccessVpn(long id, String customId, Boolean forDisplay) { + return null; + } + public String getConfigComponentName() { + return RemoteAccessVpnProtocolBase.class.getSimpleName(); + } + public ConfigKey[] getConfigKeys() { + return new ConfigKey[] {RemoteAccessVpnClientIpRange}; + } + +} diff --git a/plugins/remote-access-vpn/l2tp/src/main/resources/META-INF/com.cloud.network/module.properties b/plugins/remote-access-vpn/l2tp/src/main/resources/META-INF/com.cloud.network/module.properties new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/plugins/remote-access-vpn/l2tp/src/main/resources/META-INF/com.cloud.network/spring-l2tp-context.xml b/plugins/remote-access-vpn/l2tp/src/main/resources/META-INF/com.cloud.network/spring-l2tp-context.xml new file mode 100644 index 000000000000..300bfb400d88 --- /dev/null +++ b/plugins/remote-access-vpn/l2tp/src/main/resources/META-INF/com.cloud.network/spring-l2tp-context.xml @@ -0,0 +1,18 @@ + diff --git a/server/src/main/java/com/cloud/network/router/VirtualNetworkApplianceManagerImpl.java b/server/src/main/java/com/cloud/network/router/VirtualNetworkApplianceManagerImpl.java index 702f614d7763..b463a49b01c0 100644 --- a/server/src/main/java/com/cloud/network/router/VirtualNetworkApplianceManagerImpl.java +++ b/server/src/main/java/com/cloud/network/router/VirtualNetworkApplianceManagerImpl.java @@ -2489,7 +2489,7 @@ protected void finalizeNetworkRulesForNetwork(final Commands cmds, final DomainR } if (_networkModel.isProviderSupportServiceInNetwork(guestNetworkId, Service.Vpn, provider)) { - final RemoteAccessVpn vpn = _vpnDao.findByPublicIpAddress(ip.getId()); + final RemoteAccessVpn vpn = _vpnDao.findByPublicIpAddressAndPort(ip.getId()); if (vpn != null) { vpns.add(vpn); } diff --git a/server/src/main/java/com/cloud/network/router/VpcVirtualNetworkApplianceManagerImpl.java b/server/src/main/java/com/cloud/network/router/VpcVirtualNetworkApplianceManagerImpl.java index 9ce26bc213cf..a4ccb2e513fd 100644 --- a/server/src/main/java/com/cloud/network/router/VpcVirtualNetworkApplianceManagerImpl.java +++ b/server/src/main/java/com/cloud/network/router/VpcVirtualNetworkApplianceManagerImpl.java @@ -534,7 +534,7 @@ public boolean finalizeCommandsOnStart(final Commands cmds, final VirtualMachine } // 5) RE-APPLY ALL REMOTE ACCESS VPNs - final RemoteAccessVpnVO vpn = _vpnDao.findByAccountAndVpc(domainRouterVO.getAccountId(), domainRouterVO.getVpcId()); + final RemoteAccessVpnVO vpn = _vpnDao.findByAccountVpcAndPort(domainRouterVO.getAccountId(), domainRouterVO.getVpcId()); if (vpn != null) { _commandSetupHelper.createApplyVpnCommands(true, vpn, domainRouterVO, cmds); } diff --git a/server/src/main/java/com/cloud/network/vpn/RemoteAccessVpnManagerImpl.java b/server/src/main/java/com/cloud/network/vpn/RemoteAccessVpnManagerImpl.java index 9ff599b47c6f..48504945e066 100644 --- a/server/src/main/java/com/cloud/network/vpn/RemoteAccessVpnManagerImpl.java +++ b/server/src/main/java/com/cloud/network/vpn/RemoteAccessVpnManagerImpl.java @@ -94,6 +94,9 @@ import com.cloud.utils.exception.CloudRuntimeException; import com.cloud.utils.net.NetUtils; +/** + * @deprecated + */ public class RemoteAccessVpnManagerImpl extends ManagerBase implements RemoteAccessVpnService, Configurable { private final static Logger s_logger = Logger.getLogger(RemoteAccessVpnManagerImpl.class); @@ -199,7 +202,7 @@ public RemoteAccessVpn createRemoteAccessVpn(final long publicIpId, String ipRan " as ip is not associated with any network or VPC"); } - RemoteAccessVpnVO vpnVO = _remoteAccessVpnDao.findByPublicIpAddress(publicIpId); + RemoteAccessVpnVO vpnVO = _remoteAccessVpnDao.findByPublicIpAddressAndPort(publicIpId); if (vpnVO != null) { if (vpnVO.getState() == RemoteAccessVpn.State.Added) { @@ -220,8 +223,10 @@ public RemoteAccessVpn createRemoteAccessVpn(final long publicIpId, String ipRan Pair cidr = null; if (networkId != null) { + long ipAddressOwner = ipAddr.getAccountId(); - vpnVO = _remoteAccessVpnDao.findByAccountAndNetwork(ipAddressOwner, networkId); + + vpnVO = _remoteAccessVpnDao.findByAccountNetworkAndPort(ipAddressOwner, networkId); if (vpnVO != null) { if (vpnVO.getState() == RemoteAccessVpn.State.Added) { return vpnVO; @@ -229,6 +234,7 @@ public RemoteAccessVpn createRemoteAccessVpn(final long publicIpId, String ipRan throw new InvalidParameterValueException(String.format("A remote access VPN already exists for the account [%s].", ipAddressOwner)); } + Network network = _networkMgr.getNetwork(networkId); if (!_networkMgr.areServicesSupportedInNetwork(network.getId(), Service.Vpn)) { throw new InvalidParameterValueException("Vpn service is not supported in network id=" + ipAddr.getAssociatedWithNetworkId()); @@ -253,8 +259,15 @@ public RemoteAccessVpn createRemoteAccessVpn(final long publicIpId, String ipRan _rulesMgr.reservePorts(ipAddr, NetUtils.UDP_PROTO, Purpose.Vpn, openFirewallFinal, caller, NetUtils.VPN_PORT, NetUtils.VPN_L2TP_PORT, NetUtils.VPN_NATT_PORT); } - RemoteAccessVpnVO remoteAccessVpnVO = new RemoteAccessVpnVO(ipAddr.getAccountId(), ipAddr.getDomainId(), ipAddr.getAssociatedWithNetworkId(), - publicIpId, vpcId, range[0], newIpRange, sharedSecret); + RemoteAccessVpnVO remoteAccessVpnVO = new RemoteAccessVpnVO( + ipAddr.getAccountId(), + ipAddr.getDomainId(), + ipAddr.getAssociatedWithNetworkId(), + publicIpId, + vpcId, + range[0], + newIpRange //, sharedSecret + ); if (forDisplay != null) { remoteAccessVpnVO.setDisplay(forDisplay); @@ -304,11 +317,21 @@ protected void handleExceptionOnValidateIpRangeError(Class } } + @Override + public String GetName() { + return ""; + } + + @Override + public RemoteAccessVpn createRemoteAccessVpn(long vpnServerAddressId, String ipRange, boolean openFirewall, Boolean forDisplay, Integer listenPort, Map implementationData) throws NetworkRuleConflictException { + return null; + } + @Override @DB @ActionEvent(eventType = EventTypes.EVENT_REMOTE_ACCESS_VPN_DESTROY, eventDescription = "removing remote access vpn", async = true) public boolean destroyRemoteAccessVpnForIp(long ipId, Account caller, final boolean forceCleanup) throws ResourceUnavailableException { - final RemoteAccessVpnVO vpn = _remoteAccessVpnDao.findByPublicIpAddress(ipId); + final RemoteAccessVpnVO vpn = _remoteAccessVpnDao.findByPublicIpAddressAndPort(ipId); if (vpn == null) { s_logger.debug("there are no Remote access vpns for public ip address id=" + ipId); return true; @@ -473,7 +496,7 @@ public List listVpnUsers(long vpnOwnerId, String userName) { public RemoteAccessVpnVO startRemoteAccessVpn(long ipAddressId, boolean openFirewall) throws ResourceUnavailableException { Account caller = CallContext.current().getCallingAccount(); - final RemoteAccessVpnVO vpn = _remoteAccessVpnDao.findByPublicIpAddress(ipAddressId); + final RemoteAccessVpnVO vpn = _remoteAccessVpnDao.findByPublicIpAddressAndPort(ipAddressId); if (vpn == null) { throw new InvalidParameterValueException("Unable to find your vpn: " + ipAddressId); } @@ -774,7 +797,7 @@ public List listRemoteAccessVpns(long networkId) { @Override public RemoteAccessVpn getRemoteAccessVpn(long vpnAddrId) { - return _remoteAccessVpnDao.findByPublicIpAddress(vpnAddrId); + return _remoteAccessVpnDao.findByPublicIpAddressAndPort(vpnAddrId); } @Override diff --git a/server/src/main/java/com/cloud/network/vpn/RemoteAccessVpnProtocolBase.java b/server/src/main/java/com/cloud/network/vpn/RemoteAccessVpnProtocolBase.java new file mode 100644 index 000000000000..30d523d44685 --- /dev/null +++ b/server/src/main/java/com/cloud/network/vpn/RemoteAccessVpnProtocolBase.java @@ -0,0 +1,130 @@ +// 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.domain.dao.DomainDao; +import com.cloud.network.NetworkModel; +import com.cloud.network.dao.IPAddressDao; +import com.cloud.network.element.RemoteAccessVPNServiceProvider; +import com.cloud.network.rules.FirewallManager; +import com.cloud.network.rules.RulesManager; +import com.cloud.user.AccountManager; +import com.cloud.utils.component.ManagerBase; +import com.cloud.utils.exception.CloudRuntimeException; +import com.cloud.utils.net.NetUtils; +import org.apache.cloudstack.framework.config.ConfigKey; +import org.apache.cloudstack.framework.config.Configurable; +import org.apache.cloudstack.framework.config.dao.ConfigurationDao; +import com.cloud.network.vpc.dao.VpcDao; +import org.apache.log4j.Logger; + +import javax.inject.Inject; +import javax.naming.ConfigurationException; +import java.lang.reflect.InvocationTargetException; +import java.util.List; + +/** + * This class describe the behaviour of a single vpn server provider (EG: L2TP, Wireguard, OpenVPN) + */ +public abstract class RemoteAccessVpnProtocolBase extends ManagerBase implements Configurable, RemoteAccessVpnService { + + private final static Logger s_logger = Logger.getLogger(RemoteAccessVpnProtocolBase.class); + + static final String RemoteAccessVpnClientIpRangeCK = "remote.access.vpn.client.iprange"; + + static final ConfigKey RemoteAccessVpnClientIpRange = new ConfigKey( + "" + "Network", + String.class, + RemoteAccessVpnClientIpRangeCK, + "10.1.2.1-10.1.2.8", + "The range of ips to be allocated to remote access vpn clients. The first ip in the range is used by the VPN server", + false, + ConfigKey.Scope.Account + ); + + + @Inject + IPAddressDao _ipAddressDao; + @Inject + AccountManager _accountMgr; + @Inject + NetworkModel _networkMgr; + @Inject + VpcDao _vpcDao; + @Inject + DomainDao _domainDao; + @Inject + ConfigurationDao _configDao; + @Inject + RulesManager _rulesMgr; + @Inject + FirewallManager _firewallMgr; + + int _pskLength; + int _userLimit; + List _vpnServiceProviders; + + + void validateRemoteAccessVpnConfiguration() throws ConfigurationException { + String ipRange = RemoteAccessVpnClientIpRange.value(); + if (ipRange == null) { + s_logger.warn(String.format("Remote access VPN configuration: Global configuration [%s] missing client IP range.", RemoteAccessVpnClientIpRange.key())); + return; + } + + if (_pskLength < 8 || _pskLength > 256) { + throw new ConfigurationException(String.format("Remote access VPN configuration: IPSec preshared key length [%s] should be between 8 and 256.", _pskLength)); + } + + validateIpRange(ipRange, ConfigurationException.class); + } + + protected void validateIpRange(String ipRange, Class exceptionClass) throws T { + String[] range = ipRange.split("-"); + + if (range.length != 2) { + handleExceptionOnValidateIpRangeError(exceptionClass, String.format("IP range [%s] is an invalid IP range.", ipRange)); + } + + if (!NetUtils.isValidIp4(range[0]) || !NetUtils.isValidIp4(range[1])) { + handleExceptionOnValidateIpRangeError(exceptionClass, String.format("One or both IPs sets in the range [%s] are invalid IPs.", ipRange)); + } + + if (!NetUtils.validIpRange(range[0], range[1])) { + handleExceptionOnValidateIpRangeError(exceptionClass, String.format("Range of IPs [%s] is invalid.", ipRange)); + } + } + + protected void handleExceptionOnValidateIpRangeError(Class exceptionClass, String errorMessage) throws T { + try { + throw exceptionClass.getConstructor(String.class).newInstance(errorMessage); + } catch (NoSuchMethodException | SecurityException | InstantiationException | IllegalAccessException | IllegalArgumentException | + InvocationTargetException ex) { + throw new CloudRuntimeException(String.format("Unexpected exception [%s] while throwing error [%s] on validateIpRange.", ex.getMessage(), errorMessage), ex); + } + } + + + public List getVpnServiceProviders() { + return _vpnServiceProviders; + } + + public void setVpnServiceProviders(List vpnServiceProviders) { + _vpnServiceProviders = vpnServiceProviders; + } + +} diff --git a/server/src/main/java/com/cloud/network/vpn/RemoteAccessVpnProviderManagerImpl.java b/server/src/main/java/com/cloud/network/vpn/RemoteAccessVpnProviderManagerImpl.java deleted file mode 100644 index 71ba1213d874..000000000000 --- a/server/src/main/java/com/cloud/network/vpn/RemoteAccessVpnProviderManagerImpl.java +++ /dev/null @@ -1,36 +0,0 @@ -package com.cloud.network.vpn; - -import com.cloud.exception.VPNProviderNotFoundException; - -import java.util.HashMap; -import java.util.Map; -import java.util.ServiceLoader; - -public class RemoteAccessVpnProviderManagerImpl implements RemoteAccessVpnProviderManagerService { - - private static HashMap providers; - - static { - // Discover and register VPN services at boot - ServiceLoader loader = ServiceLoader.load(RemoteAccessVpnProvider.class); - for (RemoteAccessVpnProvider service : loader) { - providers.put(service.GetName().toLowerCase(), service); - System.out.println("Registered VPN Service: " + service.GetName().toLowerCase()); - } - } - - - @Override - public Map GetAvailableProviders() { - return providers; - } - - public RemoteAccessVpnProvider GetProvider(String key) throws VPNProviderNotFoundException { - - if (!providers.containsKey(key)) { - throw new VPNProviderNotFoundException(key + " is not a valid VPN provider"); - } - - return providers.get(key); - } -} diff --git a/server/src/main/java/com/cloud/network/vpn/RemoteAccessVpnServiceManagerImpl.java b/server/src/main/java/com/cloud/network/vpn/RemoteAccessVpnServiceManagerImpl.java new file mode 100644 index 000000000000..518c6a790949 --- /dev/null +++ b/server/src/main/java/com/cloud/network/vpn/RemoteAccessVpnServiceManagerImpl.java @@ -0,0 +1,53 @@ +// 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.VPNProviderNotFoundException; + +import java.util.HashMap; +import java.util.Map; +import java.util.ServiceLoader; + +public class RemoteAccessVpnServiceManagerImpl implements RemoteAccessVpnServiceManager { + + private static HashMap providers; + + static { + // Discover and register VPN services at boot + ServiceLoader loader = ServiceLoader.load(RemoteAccessVpnProtocolBase.class); + for (RemoteAccessVpnProtocolBase service : loader) { + providers.put(service.GetName().toLowerCase(), service); + System.out.println("Registered VPN Service: " + service.GetName().toLowerCase()); + } + } + + + @Override + public Map GetAvailableProviders() { + return providers; + } + + public RemoteAccessVpnService GetProvider(String key) throws VPNProviderNotFoundException { + + if (!providers.containsKey(key)) { + throw new VPNProviderNotFoundException(key + " is not a valid VPN provider"); + } + + return providers.get(key); + } +}