| // 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 java.lang.reflect.InvocationTargetException; | |
| import java.util.ArrayList; | |
| import java.util.Iterator; | |
| import java.util.List; | |
| import java.util.Map; | |
| import java.util.stream.Collectors; | |
| import javax.inject.Inject; | |
| import javax.naming.ConfigurationException; | |
| import org.apache.cloudstack.acl.SecurityChecker.AccessType; | |
| 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.cloudstack.framework.config.Configurable; | |
| import org.apache.cloudstack.framework.config.dao.ConfigurationDao; | |
| import org.apache.commons.collections.CollectionUtils; | |
| import com.cloud.configuration.Config; | |
| import com.cloud.domain.DomainVO; | |
| import com.cloud.domain.dao.DomainDao; | |
| import com.cloud.event.ActionEvent; | |
| import com.cloud.event.EventTypes; | |
| import com.cloud.event.UsageEventUtils; | |
| import com.cloud.event.dao.UsageEventDao; | |
| import com.cloud.exception.AccountLimitException; | |
| import com.cloud.exception.InvalidParameterValueException; | |
| import com.cloud.exception.NetworkRuleConflictException; | |
| import com.cloud.exception.ResourceUnavailableException; | |
| import com.cloud.network.Network; | |
| import com.cloud.network.Network.Service; | |
| import com.cloud.network.NetworkModel; | |
| import com.cloud.network.PublicIpAddress; | |
| import com.cloud.network.RemoteAccessVpn; | |
| import com.cloud.network.VpnUser; | |
| import com.cloud.network.VpnUser.State; | |
| import com.cloud.network.VpnUserVO; | |
| import com.cloud.network.dao.FirewallRulesDao; | |
| import com.cloud.network.dao.IPAddressDao; | |
| import com.cloud.network.dao.IPAddressVO; | |
| import com.cloud.network.dao.RemoteAccessVpnDao; | |
| import com.cloud.network.dao.RemoteAccessVpnVO; | |
| import com.cloud.network.dao.VpnUserDao; | |
| import com.cloud.network.element.RemoteAccessVPNServiceProvider; | |
| import com.cloud.network.rules.FirewallManager; | |
| import com.cloud.network.rules.FirewallRule; | |
| import com.cloud.network.rules.FirewallRule.Purpose; | |
| import com.cloud.network.rules.FirewallRuleVO; | |
| import com.cloud.network.rules.RulesManager; | |
| import com.cloud.network.vpc.Vpc; | |
| import com.cloud.network.vpc.VpcManager; | |
| import com.cloud.network.vpc.dao.VpcDao; | |
| import com.cloud.projects.Project.ListProjectResourcesCriteria; | |
| import com.cloud.server.ConfigurationServer; | |
| import com.cloud.user.Account; | |
| import com.cloud.user.AccountManager; | |
| import com.cloud.user.DomainManager; | |
| import com.cloud.user.dao.AccountDao; | |
| import com.cloud.utils.NumbersUtil; | |
| import com.cloud.utils.Pair; | |
| import com.cloud.utils.PasswordGenerator; | |
| import com.cloud.utils.Ternary; | |
| import com.cloud.utils.component.ManagerBase; | |
| import com.cloud.utils.db.DB; | |
| import com.cloud.utils.db.Filter; | |
| import com.cloud.utils.db.JoinBuilder; | |
| import com.cloud.utils.db.SearchBuilder; | |
| import com.cloud.utils.db.SearchCriteria; | |
| import com.cloud.utils.db.SearchCriteria.Op; | |
| import com.cloud.utils.db.Transaction; | |
| import com.cloud.utils.db.TransactionCallback; | |
| import com.cloud.utils.db.TransactionCallbackNoReturn; | |
| import com.cloud.utils.db.TransactionCallbackWithException; | |
| import com.cloud.utils.db.TransactionStatus; | |
| import com.cloud.utils.exception.CloudRuntimeException; | |
| import com.cloud.utils.net.NetUtils; | |
| public class RemoteAccessVpnManagerImpl extends ManagerBase implements RemoteAccessVpnService, Configurable { | |
| static final ConfigKey<String> RemoteAccessVpnClientIpRange = new ConfigKey<String>("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 | |
| AccountDao _accountDao; | |
| @Inject | |
| VpnUserDao _vpnUsersDao; | |
| @Inject | |
| RemoteAccessVpnDao _remoteAccessVpnDao; | |
| @Inject | |
| IPAddressDao _ipAddressDao; | |
| @Inject | |
| AccountManager _accountMgr; | |
| @Inject | |
| DomainManager _domainMgr; | |
| @Inject | |
| NetworkModel _networkMgr; | |
| @Inject | |
| RulesManager _rulesMgr; | |
| @Inject | |
| DomainDao _domainDao; | |
| @Inject | |
| FirewallRulesDao _rulesDao; | |
| @Inject | |
| FirewallManager _firewallMgr; | |
| @Inject | |
| UsageEventDao _usageEventDao; | |
| @Inject | |
| ConfigurationDao _configDao; | |
| @Inject | |
| VpcManager vpcManager; | |
| List<RemoteAccessVPNServiceProvider> _vpnServiceProviders; | |
| @Inject | |
| ConfigurationServer _configServer; | |
| @Inject | |
| VpcDao _vpcDao; | |
| int _userLimit; | |
| int _pskLength; | |
| SearchBuilder<RemoteAccessVpnVO> VpnSearch; | |
| private List<RemoteAccessVpnVO> getValidRemoteAccessVpnForAccount(long accountId) { | |
| List<RemoteAccessVpnVO> vpns = _remoteAccessVpnDao.findByAccount(accountId); | |
| if (CollectionUtils.isNotEmpty(vpns)) { | |
| List<RemoteAccessVpnVO> validVpns = new ArrayList<>(); | |
| for (RemoteAccessVpnVO vpn : vpns) { | |
| if (vpn.getNetworkId() != null) { | |
| Network network = _networkMgr.getNetwork(vpn.getNetworkId()); | |
| if (!Network.State.Implemented.equals(network.getState())) { | |
| continue; | |
| } | |
| } | |
| validVpns.add(vpn); | |
| } | |
| vpns = validVpns; | |
| } | |
| return vpns; | |
| } | |
| @Override | |
| @DB | |
| public RemoteAccessVpn createRemoteAccessVpn(final long publicIpId, String ipRange, boolean openFirewall, final Boolean forDisplay) 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); | |
| if (ipAddress == null) { | |
| 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) { | |
| if (ipAddress.isForRouter()) { | |
| logger.debug("The IP address is reserved for Domain Router, skipping the check now"); | |
| } else { | |
| _networkMgr.checkIpForService(ipAddress, 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"); | |
| } | |
| RemoteAccessVpnVO vpnVO = _remoteAccessVpnDao.findByPublicIpAddress(publicIpId); | |
| 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].", ipAddr.getAddress().toString())); | |
| } | |
| if (ipRange == null) { | |
| ipRange = RemoteAccessVpnClientIpRange.valueIn(ipAddr.getAccountId()); | |
| } | |
| validateIpRange(ipRange, InvalidParameterValueException.class); | |
| String[] range = ipRange.split("-"); | |
| Pair<String, Integer> cidr = null; | |
| if (networkId != null) { | |
| Network network = _networkMgr.getNetwork(networkId); | |
| long ipAddressOwner = ipAddr.getAccountId(); | |
| vpnVO = _remoteAccessVpnDao.findByAccountAndNetwork(ipAddressOwner, networkId); | |
| if (vpnVO != null) { | |
| if (vpnVO.getState() == RemoteAccessVpn.State.Added) { | |
| return vpnVO; | |
| } | |
| throw new InvalidParameterValueException(String.format("A remote access VPN already exists for the network %s.", network)); | |
| } | |
| if (!_networkMgr.areServicesSupportedInNetwork(network.getId(), Service.Vpn)) { | |
| throw new InvalidParameterValueException("Vpn service is not supported in network id=" + ipAddr.getAssociatedWithNetworkId()); | |
| } | |
| cidr = NetUtils.getCidr(network.getCidr()); | |
| validateIpAddressForVpnServiceOnNetwork(network, ipAddress); | |
| } else { | |
| Vpc vpc = _vpcDao.findById(vpcId); | |
| cidr = NetUtils.getCidr(vpc.getCidr()); | |
| validateIpAddressForVpnServiceOnVpc(vpc, ipAddress); | |
| } | |
| 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 sharedSecret = PasswordGenerator.generatePresharedKey(_pskLength); | |
| return Transaction.execute((TransactionCallbackWithException<RemoteAccessVpn, NetworkRuleConflictException>) status -> { | |
| if (vpcId == null) { | |
| _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); | |
| if (forDisplay != null) { | |
| remoteAccessVpnVO.setDisplay(forDisplay); | |
| } | |
| remoteAccessVpnVO = _remoteAccessVpnDao.persist(remoteAccessVpnVO); | |
| if (vpcId != null) { | |
| try { | |
| vpcManager.applyStaticRouteForVpcVpnIfNeeded(vpcId, false); | |
| } catch (ResourceUnavailableException | CloudRuntimeException e) { | |
| logger.error("Unable to apply static routes for vpc " + vpcId + " due to " + e.getMessage()); | |
| } | |
| } | |
| return remoteAccessVpnVO; | |
| }); | |
| } finally { | |
| _ipAddressDao.releaseFromLockTable(publicIpId); | |
| } | |
| } | |
| private void validateIpAddressForVpnServiceOnNetwork(Network network, IPAddressVO ipAddress) { | |
| Long networkId = network.getId(); | |
| if (_networkMgr.isProviderSupportServiceInNetwork(networkId, Service.Vpn, Network.Provider.VirtualRouter)) { | |
| // if VR is the VPN provider, | |
| // (1) if VR is Source NAT, the IP address must be used as Source NAT | |
| // (2) if VR is not Source NAT, the IP address must not be used as Source NAT | |
| boolean isVRSourceNat = _networkMgr.isProviderSupportServiceInNetwork(networkId, Service.SourceNat, Network.Provider.VirtualRouter); | |
| if (isVRSourceNat && !ipAddress.isSourceNat()) { | |
| throw new InvalidParameterValueException("Vpn service can only be configured on the Source NAT IP of network id=" + ipAddress.getAssociatedWithNetworkId()); | |
| } | |
| if (!isVRSourceNat && ipAddress.isSourceNat()) { | |
| throw new InvalidParameterValueException("Vpn service can not be configured on the Source NAT IP of network id=" + ipAddress.getAssociatedWithNetworkId()); | |
| } | |
| if (!isVRSourceNat) { | |
| throw new InvalidParameterValueException("Currently it is not supported to create Vpn service on a non-Source NAT IP of network id=" + ipAddress.getAssociatedWithNetworkId()); | |
| } | |
| } | |
| } | |
| private void validateIpAddressForVpnServiceOnVpc(Vpc vpc, IPAddressVO ipAddress) { | |
| Long vpcId = vpc.getId(); | |
| if (vpcManager.isProviderSupportServiceInVpc(vpcId, Service.Vpn, Network.Provider.VPCVirtualRouter)) { | |
| // if VPC VR is the VPN provider, | |
| // (1) if VPC VR is Source NAT, the IP address must be used as Source NAT | |
| // (2) if VPC VR is not Source NAT, the IP address must not be used as Source NAT | |
| boolean isVpcVRSourceNat = vpcManager.isProviderSupportServiceInVpc(vpcId, Service.SourceNat, Network.Provider.VPCVirtualRouter); | |
| if (isVpcVRSourceNat && !ipAddress.isSourceNat()) { | |
| throw new InvalidParameterValueException("Vpn service can only be configured on the Source NAT IP of VPC id=" + ipAddress.getVpcId()); | |
| } | |
| if (!isVpcVRSourceNat) { | |
| IPAddressVO ipAddressForVpcVR = vpcManager.getIpAddressForVpcVr(vpc, ipAddress, true); | |
| if (!vpcManager.configStaticNatForVpcVr(vpc, ipAddressForVpcVR)) { | |
| throw new CloudRuntimeException("Failed to enable static nat for VPC VR as part of remote access vpn creation"); | |
| } | |
| } | |
| } | |
| } | |
| private void validateRemoteAccessVpnConfiguration() throws ConfigurationException { | |
| String ipRange = RemoteAccessVpnClientIpRange.value(); | |
| if (ipRange == null) { | |
| 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 <T extends Throwable> void validateIpRange(String ipRange, Class<T> 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 <T extends Throwable> void handleExceptionOnValidateIpRangeError(Class<T> 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); | |
| } | |
| } | |
| @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); | |
| if (vpn == null) { | |
| logger.debug("there are no Remote access vpns for public ip address id=" + ipId); | |
| return true; | |
| } | |
| _accountMgr.checkAccess(caller, AccessType.OperateEntry, true, vpn); | |
| RemoteAccessVpn.State prevState = vpn.getState(); | |
| vpn.setState(RemoteAccessVpn.State.Removed); | |
| _remoteAccessVpnDao.update(vpn.getId(), vpn); | |
| boolean success = false; | |
| try { | |
| for (RemoteAccessVPNServiceProvider element : _vpnServiceProviders) { | |
| if (element.stopVpn(vpn)) { | |
| success = true; | |
| break; | |
| } | |
| } | |
| }catch (ResourceUnavailableException ex) { | |
| vpn.setState(prevState); | |
| _remoteAccessVpnDao.update(vpn.getId(), vpn); | |
| logger.debug("Failed to stop the vpn {}, so reverted state to {}", vpn, RemoteAccessVpn.State.Running); | |
| success = false; | |
| } finally { | |
| if (success|| forceCleanup) { | |
| final List<? extends FirewallRule> vpnFwRules = _rulesDao.listByIpAndPurpose(ipId, Purpose.Vpn); | |
| boolean applyFirewall = false; | |
| final List<FirewallRuleVO> fwRules = new ArrayList<>(); | |
| if (CollectionUtils.isNotEmpty(vpnFwRules) && _rulesDao.findByRelatedId(vpnFwRules.get(0).getId()) != null) { | |
| applyFirewall = true; | |
| } | |
| if (applyFirewall) { | |
| Transaction.execute(new TransactionCallbackNoReturn() { | |
| @Override | |
| public void doInTransactionWithoutResult(TransactionStatus status) { | |
| for (FirewallRule vpnFwRule : vpnFwRules) { | |
| _firewallMgr.revokeRelatedFirewallRule(vpnFwRule.getId(), false); | |
| fwRules.add(_rulesDao.findByRelatedId(vpnFwRule.getId())); | |
| } | |
| logger.debug("Marked " + fwRules.size() + " firewall rules as Revoked as a part of disable remote access vpn"); | |
| } | |
| }); | |
| logger.debug("Reapplying firewall rules for ip id=" + ipId + " as a part of disable remote access vpn"); | |
| success = _firewallMgr.applyIngressFirewallRules(ipId, caller); | |
| } | |
| if (success|| forceCleanup) { | |
| try { | |
| Transaction.execute(new TransactionCallbackNoReturn() { | |
| @Override | |
| public void doInTransactionWithoutResult(TransactionStatus status) { | |
| if (vpn.getVpcId() != null) { | |
| try { | |
| vpcManager.applyStaticRouteForVpcVpnIfNeeded(vpn.getVpcId(), false); | |
| } catch (ResourceUnavailableException | CloudRuntimeException e) { | |
| logger.error("Unable to apply static routes for vpc " + vpn.getVpcId() + " due to " + e.getMessage()); | |
| } | |
| } | |
| _remoteAccessVpnDao.remove(vpn.getId()); | |
| List<VpnUserVO> vpnUsers = _vpnUsersDao.listByAccount(vpn.getAccountId()); | |
| for (VpnUserVO user : vpnUsers) { | |
| if (user.getState() != VpnUser.State.Revoke) { | |
| UsageEventUtils.publishUsageEvent(EventTypes.EVENT_VPN_USER_REMOVE, user.getAccountId(), 0, user.getId(), user.getUsername(), | |
| user.getClass().getName(), user.getUuid()); | |
| } | |
| } | |
| if (vpnFwRules != null) { | |
| for (FirewallRule vpnFwRule : vpnFwRules) { | |
| _rulesDao.remove(vpnFwRule.getId()); | |
| logger.debug("Successfully removed firewall rule with ip id=" + vpnFwRule.getSourceIpAddressId() + " and port " + | |
| vpnFwRule.getSourcePortStart() + " as a part of vpn cleanup"); | |
| } | |
| } | |
| } | |
| }); | |
| } catch (Exception ex) { | |
| logger.warn(String.format("Unable to release the VPN ports from the firewall rules [%s] due to [%s]", fwRules.stream().map(rule -> | |
| String.format("{\"ipId\": %s, \"port\": %s}", rule.getSourceIpAddressId(), rule.getSourcePortStart())).collect(Collectors.joining(", ")), ex.getMessage()), ex); | |
| } | |
| } | |
| } | |
| } | |
| return success; | |
| } | |
| @Override | |
| @DB | |
| public VpnUser addVpnUser(final long vpnOwnerId, final String username, final String password) { | |
| final Account caller = CallContext.current().getCallingAccount(); | |
| if (!username.matches("^[a-zA-Z0-9][a-zA-Z0-9@._-]{2,63}$")) { | |
| throw new InvalidParameterValueException(String.format("Username [%s] is invalid. Username has to begin with an alphabet have 3-64 characters including alphabets, numbers and the set '@.-_'.", username)); | |
| } | |
| if (!password.matches("^[a-zA-Z0-9][a-zA-Z0-9@+=._-]{2,31}$")) { | |
| throw new InvalidParameterValueException("Password has to be 3-32 characters including alphabets, numbers and the set '@+=.-_'"); | |
| } | |
| return Transaction.execute(new TransactionCallback<VpnUser>() { | |
| @Override | |
| public VpnUser doInTransaction(TransactionStatus status) { | |
| Account owner = _accountDao.lockRow(vpnOwnerId, true); | |
| if (owner == null) { | |
| throw new InvalidParameterValueException(String.format("Unable to add VPN user {\"id\": %s, \"username\": \"%s\"}: Another operation is active.", vpnOwnerId, username)); | |
| } | |
| _accountMgr.checkAccess(caller, null, true, owner); | |
| VpnUserVO vpnUser = _vpnUsersDao.findByAccountAndUsername(owner.getId(), username); | |
| if (vpnUser != null) { | |
| throw new InvalidParameterValueException("VPN User with name " + username + " is already added for account " + owner); | |
| } | |
| long userCount = _vpnUsersDao.getVpnUserCount(owner.getId()); | |
| if (userCount >= _userLimit) { | |
| throw new AccountLimitException(String.format("Cannot add more than [%s] remote access VPN users to %s.", _userLimit, owner.toString())); | |
| } | |
| VpnUser user = _vpnUsersDao.persist(new VpnUserVO(vpnOwnerId, owner.getDomainId(), username, password)); | |
| UsageEventUtils.publishUsageEvent(EventTypes.EVENT_VPN_USER_ADD, user.getAccountId(), 0, user.getId(), user.getUsername(), user.getClass().getName(), | |
| user.getUuid()); | |
| return user; | |
| } | |
| }); | |
| } | |
| @DB | |
| @Override | |
| public boolean removeVpnUser(Account vpnOwner, String username, Account caller) { | |
| final VpnUserVO user = _vpnUsersDao.findByAccountAndUsername(vpnOwner.getId(), username); | |
| if (user == null) { | |
| String errorMessage = String.format("Could not find VPN user=[%s]. VPN owner=[%s]", username, vpnOwner); | |
| logger.debug(errorMessage); | |
| throw new InvalidParameterValueException(errorMessage); | |
| } | |
| _accountMgr.checkAccess(caller, null, true, user); | |
| Transaction.execute(new TransactionCallbackNoReturn() { | |
| @Override | |
| public void doInTransactionWithoutResult(TransactionStatus status) { | |
| user.setState(State.Revoke); | |
| _vpnUsersDao.update(user.getId(), user); | |
| UsageEventUtils.publishUsageEvent(EventTypes.EVENT_VPN_USER_REMOVE, user.getAccountId(), 0, user.getId(), user.getUsername(), user.getClass().getName(), | |
| user.getUuid()); | |
| } | |
| }); | |
| return true; | |
| } | |
| @Override | |
| public List<? extends VpnUser> listVpnUsers(long vpnOwnerId, String userName) { | |
| Account caller = CallContext.current().getCallingAccount(); | |
| Account owner = _accountDao.findById(vpnOwnerId); | |
| _accountMgr.checkAccess(caller, null, true, owner); | |
| return _vpnUsersDao.listByAccount(vpnOwnerId); | |
| } | |
| @Override | |
| @DB | |
| @ActionEvent(eventType = EventTypes.EVENT_REMOTE_ACCESS_VPN_CREATE, eventDescription = "creating remote access vpn", async = true) | |
| public RemoteAccessVpnVO startRemoteAccessVpn(long ipAddressId, boolean openFirewall) throws ResourceUnavailableException { | |
| Account caller = CallContext.current().getCallingAccount(); | |
| final RemoteAccessVpnVO vpn = _remoteAccessVpnDao.findByPublicIpAddress(ipAddressId); | |
| if (vpn == null) { | |
| throw new InvalidParameterValueException("Unable to find your vpn: " + ipAddressId); | |
| } | |
| 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<VpnUserVO> 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()); | |
| } | |
| } | |
| } | |
| }); | |
| } | |
| } | |
| } | |
| @DB | |
| private boolean removeVpnUserWithoutRemoteAccessVpn(Account vpnOwner, String userName) { | |
| VpnUserVO vpnUser = _vpnUsersDao.findByAccountAndUsername(vpnOwner.getId(), userName); | |
| if (vpnUser == null) { | |
| logger.error("VPN user not found with owner: {} and username: {}", vpnOwner, userName); | |
| return false; | |
| } | |
| if (!State.Revoke.equals(vpnUser.getState())) { | |
| logger.error("VPN user with owner: {} and username: {} is not in revoked state, current state: {}", vpnOwner, userName, vpnUser.getState()); | |
| return false; | |
| } | |
| return _vpnUsersDao.remove(vpnUser.getId()); | |
| } | |
| @DB | |
| @Override | |
| public boolean applyVpnUsers(long vpnOwnerId, String userName, boolean forRemove) throws ResourceUnavailableException { | |
| Account caller = CallContext.current().getCallingAccount(); | |
| Account owner = _accountDao.findById(vpnOwnerId); | |
| _accountMgr.checkAccess(caller, null, true, owner); | |
| logger.debug(String.format("Applying VPN users for %s.", owner.toString())); | |
| List<RemoteAccessVpnVO> vpns = getValidRemoteAccessVpnForAccount(vpnOwnerId); | |
| if (CollectionUtils.isEmpty(vpns)) { | |
| if (forRemove) { | |
| return removeVpnUserWithoutRemoteAccessVpn(owner, userName); | |
| } | |
| logger.warn(String.format("Unable to apply VPN user due to there are no remote access VPNs configured on %s to apply VPN user.", owner.toString())); | |
| return true; | |
| } | |
| RemoteAccessVpnVO vpnTemp = null; | |
| List<VpnUserVO> users = _vpnUsersDao.listByAccount(vpnOwnerId); | |
| for (VpnUserVO user : users) { | |
| if (user.getState() == State.Active) { | |
| user.setState(State.Add); | |
| _vpnUsersDao.update(user.getId(), user); | |
| } | |
| } | |
| boolean success = true; | |
| Boolean[] finals = new Boolean[users.size()]; | |
| for (RemoteAccessVPNServiceProvider element : _vpnServiceProviders) { | |
| logger.debug("Applying vpn access to " + element.getName()); | |
| for (RemoteAccessVpnVO vpn : vpns) { | |
| try { | |
| String[] results = element.applyVpnUsers(vpn, users); | |
| if (results != null) { | |
| int indexUser = -1; | |
| for (String result : results) { | |
| indexUser ++; | |
| if (indexUser == users.size()) { | |
| indexUser = 0; | |
| } | |
| logger.debug("VPN User {}{}{}", users.get(indexUser), result == null ? " is set on " : (" couldn't be set due to " + result) + " on ", vpn); | |
| if (result == null) { | |
| if (finals[indexUser] == null) { | |
| finals[indexUser] = true; | |
| } | |
| } else { | |
| finals[indexUser] = false; | |
| success = false; | |
| vpnTemp = vpn; | |
| } | |
| } | |
| } | |
| } catch (ResourceUnavailableException e) { | |
| logger.warn(String.format("Unable to apply VPN users [%s] due to [%s].", users.stream().map(user -> user.toString()).collect(Collectors.joining(", ")), e.getMessage()), e); | |
| success = false; | |
| vpnTemp = vpn; | |
| for (int i = 0; i < finals.length; i++) { | |
| finals[i] = false; | |
| } | |
| } | |
| } | |
| } | |
| for (int i = 0; i < finals.length; i++) { | |
| final VpnUserVO user = users.get(i); | |
| if (finals[i]) { | |
| if (user.getState() == State.Add) { | |
| user.setState(State.Active); | |
| _vpnUsersDao.update(user.getId(), user); | |
| } else if (user.getState() == State.Revoke) { | |
| _vpnUsersDao.remove(user.getId()); | |
| } | |
| } else { | |
| if (user.getState() == State.Add && (user.getUsername()).equals(userName)) { | |
| Transaction.execute(new TransactionCallbackNoReturn() { | |
| @Override | |
| public void doInTransactionWithoutResult(TransactionStatus status) { | |
| _vpnUsersDao.remove(user.getId()); | |
| UsageEventUtils.publishUsageEvent(EventTypes.EVENT_VPN_USER_REMOVE, user.getAccountId(), 0, user.getId(), user.getUsername(), user.getClass() | |
| .getName(), user.getUuid()); | |
| } | |
| }); | |
| } | |
| logger.warn(String.format("Failed to apply VPN for %s.", user.toString())); | |
| } | |
| } | |
| if (!success) { | |
| throw new ResourceUnavailableException("Failed add vpn user due to Resource unavailable ", | |
| RemoteAccessVPNServiceProvider.class, vpnTemp.getId()); | |
| } | |
| return success; | |
| } | |
| @DB | |
| @Override | |
| public boolean applyVpnUsers(long vpnOwnerId, String userName) throws ResourceUnavailableException { | |
| return applyVpnUsers(vpnOwnerId, userName, false); | |
| } | |
| @Override | |
| public Pair<List<? extends VpnUser>, Integer> searchForVpnUsers(ListVpnUsersCmd cmd) { | |
| String username = cmd.getUsername(); | |
| Long id = cmd.getId(); | |
| String keyword = cmd.getKeyword(); | |
| Account caller = CallContext.current().getCallingAccount(); | |
| List<Long> permittedAccounts = new ArrayList<>(); | |
| Ternary<Long, Boolean, ListProjectResourcesCriteria> domainIdRecursiveListProject = new Ternary<>(cmd.getDomainId(), cmd.isRecursive(), null); | |
| _accountMgr.buildACLSearchParameters(caller, id, cmd.getAccountName(), cmd.getProjectId(), permittedAccounts, domainIdRecursiveListProject, cmd.listAll(), false); | |
| Long domainId = domainIdRecursiveListProject.first(); | |
| Boolean isRecursive = domainIdRecursiveListProject.second(); | |
| ListProjectResourcesCriteria listProjectResourcesCriteria = domainIdRecursiveListProject.third(); | |
| Filter searchFilter = new Filter(VpnUserVO.class, "username", true, cmd.getStartIndex(), cmd.getPageSizeVal()); | |
| SearchBuilder<VpnUserVO> sb = _vpnUsersDao.createSearchBuilder(); | |
| _accountMgr.buildACLSearchBuilder(sb, domainId, isRecursive, permittedAccounts, listProjectResourcesCriteria); | |
| sb.and("id", sb.entity().getId(), SearchCriteria.Op.EQ); | |
| sb.and("username", sb.entity().getUsername(), SearchCriteria.Op.EQ); | |
| sb.and("keyword", sb.entity().getUsername(), SearchCriteria.Op.LIKE); | |
| sb.and("state", sb.entity().getState(), Op.IN); | |
| SearchCriteria<VpnUserVO> sc = sb.create(); | |
| _accountMgr.buildACLSearchCriteria(sc, domainId, isRecursive, permittedAccounts, listProjectResourcesCriteria); | |
| sc.setParameters("state", State.Active, State.Add); | |
| if(keyword != null){ | |
| sc.setParameters("keyword", "%" + keyword + "%"); | |
| } | |
| if (id != null) { | |
| sc.setParameters("id", id); | |
| } | |
| if (username != null) { | |
| sc.setParameters("username", username); | |
| } | |
| Pair<List<VpnUserVO>, Integer> result = _vpnUsersDao.searchAndCount(sc, searchFilter); | |
| return new Pair<>(result.first(), result.second()); | |
| } | |
| @Override | |
| public Pair<List<? extends RemoteAccessVpn>, Integer> searchForRemoteAccessVpns(ListRemoteAccessVpnsCmd cmd) { | |
| Account caller = CallContext.current().getCallingAccount(); | |
| Long ipAddressId = cmd.getPublicIpId(); | |
| List<Long> permittedAccounts = new ArrayList<>(); | |
| Long vpnId = cmd.getId(); | |
| Long networkId = cmd.getNetworkId(); | |
| if (ipAddressId != null) { | |
| PublicIpAddress publicIp = _networkMgr.getPublicIpAddress(ipAddressId); | |
| if (publicIp == null) { | |
| throw new InvalidParameterValueException("Unable to list remote access vpns, IP address " + ipAddressId + " not found."); | |
| } else { | |
| Long ipAddrAcctId = publicIp.getAccountId(); | |
| if (ipAddrAcctId == null) { | |
| throw new InvalidParameterValueException("Unable to list remote access vpns, IP address " + ipAddressId + " is not associated with an account."); | |
| } | |
| } | |
| _accountMgr.checkAccess(caller, null, true, publicIp); | |
| } | |
| Ternary<Long, Boolean, ListProjectResourcesCriteria> domainIdRecursiveListProject = new Ternary<>(cmd.getDomainId(), cmd.isRecursive(), null); | |
| _accountMgr.buildACLSearchParameters(caller, null, cmd.getAccountName(), cmd.getProjectId(), permittedAccounts, domainIdRecursiveListProject, cmd.listAll(), false); | |
| Long domainId = domainIdRecursiveListProject.first(); | |
| Boolean isRecursive = domainIdRecursiveListProject.second(); | |
| ListProjectResourcesCriteria listProjectResourcesCriteria = domainIdRecursiveListProject.third(); | |
| Filter filter = new Filter(RemoteAccessVpnVO.class, "serverAddressId", false, cmd.getStartIndex(), cmd.getPageSizeVal()); | |
| SearchBuilder<RemoteAccessVpnVO> sb = _remoteAccessVpnDao.createSearchBuilder(); | |
| _accountMgr.buildACLSearchBuilder(sb, domainId, isRecursive, permittedAccounts, listProjectResourcesCriteria); | |
| sb.and("serverAddressId", sb.entity().getServerAddressId(), Op.EQ); | |
| sb.and("id", sb.entity().getId(), Op.EQ); | |
| sb.and("networkId", sb.entity().getNetworkId(), Op.EQ); | |
| sb.and("state", sb.entity().getState(), Op.EQ); | |
| sb.and("display", sb.entity().isDisplay(), Op.EQ); | |
| SearchCriteria<RemoteAccessVpnVO> sc = sb.create(); | |
| _accountMgr.buildACLSearchCriteria(sc, domainId, isRecursive, permittedAccounts, listProjectResourcesCriteria); | |
| sc.setParameters("state", RemoteAccessVpn.State.Running); | |
| if (ipAddressId != null) { | |
| sc.setParameters("serverAddressId", ipAddressId); | |
| } | |
| if (vpnId != null) { | |
| sc.setParameters("id", vpnId); | |
| } | |
| if (networkId != null) { | |
| sc.setParameters("networkId", networkId); | |
| } | |
| Pair<List<RemoteAccessVpnVO>, Integer> result = _remoteAccessVpnDao.searchAndCount(sc, filter); | |
| return new Pair<>(result.first(), result.second()); | |
| } | |
| @Override | |
| public boolean configure(String name, Map<String, Object> params) throws ConfigurationException { | |
| Map<String, String> 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<DomainVO> 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; | |
| } | |
| @Override | |
| public List<? extends RemoteAccessVpn> listRemoteAccessVpns(long networkId) { | |
| return _remoteAccessVpnDao.listByNetworkId(networkId); | |
| } | |
| @Override | |
| public RemoteAccessVpn getRemoteAccessVpn(long vpnAddrId) { | |
| return _remoteAccessVpnDao.findByPublicIpAddress(vpnAddrId); | |
| } | |
| @Override | |
| public RemoteAccessVpn getRemoteAccessVpnById(long vpnId) { | |
| return _remoteAccessVpnDao.findById(vpnId); | |
| } | |
| public List<RemoteAccessVPNServiceProvider> getRemoteAccessVPNServiceProviders() { | |
| List<RemoteAccessVPNServiceProvider> result = new ArrayList<>(); | |
| for (Iterator<RemoteAccessVPNServiceProvider> e = _vpnServiceProviders.iterator(); e.hasNext();) { | |
| result.add(e.next()); | |
| } | |
| return result; | |
| } | |
| @Override | |
| public String getConfigComponentName() { | |
| return RemoteAccessVpnService.class.getSimpleName(); | |
| } | |
| @Override | |
| public ConfigKey<?>[] getConfigKeys() { | |
| return new ConfigKey<?>[] {RemoteAccessVpnClientIpRange}; | |
| } | |
| public List<RemoteAccessVPNServiceProvider> getVpnServiceProviders() { | |
| return _vpnServiceProviders; | |
| } | |
| public void setVpnServiceProviders(List<RemoteAccessVPNServiceProvider> vpnServiceProviders) { | |
| _vpnServiceProviders = vpnServiceProviders; | |
| } | |
| @Override | |
| @ActionEvent(eventType = EventTypes.EVENT_REMOTE_ACCESS_VPN_UPDATE, eventDescription = "updating remote access vpn", async = true) | |
| public RemoteAccessVpn updateRemoteAccessVpn(long id, String customId, Boolean forDisplay) { | |
| final RemoteAccessVpnVO vpn = _remoteAccessVpnDao.findById(id); | |
| if (vpn == null) { | |
| throw new InvalidParameterValueException("Can't find remote access vpn by id " + id); | |
| } | |
| _accountMgr.checkAccess(CallContext.current().getCallingAccount(), null, true, vpn); | |
| if (customId != null) { | |
| vpn.setUuid(customId); | |
| } | |
| if (forDisplay != null) { | |
| vpn.setDisplay(forDisplay); | |
| } | |
| _remoteAccessVpnDao.update(vpn.getId(), vpn); | |
| return _remoteAccessVpnDao.findById(id); | |
| } | |
| } |