diff --git a/core/src/com/cloud/agent/api/routing/VpnUsersCfgCommand.java b/core/src/com/cloud/agent/api/routing/VpnUsersCfgCommand.java new file mode 100644 index 00000000000..a47d58e99cb --- /dev/null +++ b/core/src/com/cloud/agent/api/routing/VpnUsersCfgCommand.java @@ -0,0 +1,87 @@ +/** + * Copyright (C) 2010 Cloud.com, Inc. All rights reserved. + * + * This software is licensed under the GNU General Public License v3 or later. + * + * It is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or any later version. + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + */ + +package com.cloud.agent.api.routing; +import java.util.List; + +import com.cloud.network.VpnUserVO; + + +public class VpnUsersCfgCommand extends RoutingCommand { + public static class UsernamePassword{ + private String username; + private String password; + boolean add = true; + + public boolean isAdd() { + return add; + } + public void setAdd(boolean add) { + this.add = add; + } + public String getUsername() { + return username; + } + public void setUsername(String username) { + this.username = username; + } + public String getPassword() { + return password; + } + public void setPassword(String password) { + this.password = password; + } + public UsernamePassword(String username, String password) { + super(); + this.username = username; + this.password = password; + } + public UsernamePassword(String username, String password, boolean add) { + super(); + this.username = username; + this.password = password; + this.add = add; + } + protected UsernamePassword() { + //for Gson + } + } + + UsernamePassword [] userpwds; + + protected VpnUsersCfgCommand() { + + } + + public VpnUsersCfgCommand(List addUsers, List removeUsers) { + userpwds = new UsernamePassword[addUsers.size() + removeUsers.size()]; + int i = 0; + for (VpnUserVO vpnUser: removeUsers) { + userpwds[i++] = new UsernamePassword(vpnUser.getUserName(), vpnUser.getPassword(), false); + } + for (VpnUserVO vpnUser: addUsers) { + userpwds[i++] = new UsernamePassword(vpnUser.getUserName(), vpnUser.getPassword(), true); + } + } + + @Override + public boolean executeInSequence() { + return true; + } + +} diff --git a/core/src/com/cloud/event/EventTypes.java b/core/src/com/cloud/event/EventTypes.java index c8c9cd1af54..24de0d57749 100755 --- a/core/src/com/cloud/event/EventTypes.java +++ b/core/src/com/cloud/event/EventTypes.java @@ -166,4 +166,5 @@ public class EventTypes { //VPN public static final String EVENT_REMOTE_ACCESS_VPN_CREATE = "VPN.REMOTE.ACCESS.CREATE"; public static final String EVENT_REMOTE_ACCESS_VPN_DESTROY = "VPN.REMOTE.ACCESS.DESTROY"; + public static final String EVENT_VPN_USERS_ADD_OR_DELETE = "VPN.USERS.ADD.OR.DELETE"; } diff --git a/core/src/com/cloud/network/dao/RemoteAccessVpnDao.java b/core/src/com/cloud/network/dao/RemoteAccessVpnDao.java index fa8fd5165f6..071f54f2890 100644 --- a/core/src/com/cloud/network/dao/RemoteAccessVpnDao.java +++ b/core/src/com/cloud/network/dao/RemoteAccessVpnDao.java @@ -18,10 +18,13 @@ package com.cloud.network.dao; +import java.util.List; + import com.cloud.network.RemoteAccessVpnVO; import com.cloud.utils.db.GenericDao; public interface RemoteAccessVpnDao extends GenericDao { RemoteAccessVpnVO findByPublicIpAddress(String ipAddress); - RemoteAccessVpnVO findByAccountAndZone(Long accountId, Long zoneId); + RemoteAccessVpnVO findByAccountAndZone(Long accountId, Long zoneId); + List findByAccount(Long accountId); } diff --git a/core/src/com/cloud/network/dao/RemoteAccessVpnDaoImpl.java b/core/src/com/cloud/network/dao/RemoteAccessVpnDaoImpl.java index 1860d048e6e..50b717357c1 100644 --- a/core/src/com/cloud/network/dao/RemoteAccessVpnDaoImpl.java +++ b/core/src/com/cloud/network/dao/RemoteAccessVpnDaoImpl.java @@ -18,6 +18,8 @@ package com.cloud.network.dao; +import java.util.List; + import javax.ejb.Local; import org.apache.log4j.Logger; @@ -33,6 +35,8 @@ public class RemoteAccessVpnDaoImpl extends GenericDaoBase ListByIp; private final SearchBuilder AccountAndZoneSearch; + private final SearchBuilder AccountSearch; + protected RemoteAccessVpnDaoImpl() { ListByIp = createSearchBuilder(); @@ -43,6 +47,10 @@ public class RemoteAccessVpnDaoImpl extends GenericDaoBase findByAccount(Long accountId) { + SearchCriteria sc = AccountSearch.create(); + sc.setParameters("accountId", accountId); + return listBy(sc); + } } diff --git a/server/src/com/cloud/api/commands/VpnUserConfigCmd.java b/server/src/com/cloud/api/commands/VpnUserConfigCmd.java new file mode 100644 index 00000000000..debea8cbae4 --- /dev/null +++ b/server/src/com/cloud/api/commands/VpnUserConfigCmd.java @@ -0,0 +1,148 @@ +/** + * Copyright (C) 2010 Cloud.com, Inc. All rights reserved. + * + * This software is licensed under the GNU General Public License v3 or later. + * + * It is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or any later version. + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + */ + +package com.cloud.api.commands; + +import org.apache.log4j.Logger; + +import com.cloud.api.ApiDBUtils; +import com.cloud.api.BaseAsyncCmd; +import com.cloud.api.Implementation; +import com.cloud.api.Parameter; +import com.cloud.api.response.RemoteAccessVpnResponse; +import com.cloud.event.EventTypes; +import com.cloud.network.NetworkManager; +import com.cloud.network.RemoteAccessVpnVO; +import com.cloud.user.Account; +import com.cloud.user.UserContext; + +@Implementation( method="addRemoveVpnUsers", manager=NetworkManager.class, description="Adds or removes vpn users") +public class VpnUserConfigCmd extends BaseAsyncCmd { + public static final Logger s_logger = Logger.getLogger(VpnUserConfigCmd.class.getName()); + + private static final String s_name = "addremovevpnusersresponse"; + + ///////////////////////////////////////////////////// + //////////////// API parameters ///////////////////// + ///////////////////////////////////////////////////// + @Parameter(name="zoneid", type=CommandType.LONG, required=true, description="zone id where the vpn server needs to be created") + private Long zoneId; + + @Parameter(name="publicip", type=CommandType.STRING, required=false, description="public ip address of the vpn server") + private String publicIp; + + @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; + + @Parameter(name="account", type=CommandType.STRING, description="an optional account for the virtual machine. Must be used with domainId.") + private String accountName; + + @Parameter(name="domainid", type=CommandType.LONG, description="an optional domainId for the virtual machine. If the account parameter is used, domainId must also be used.") + private Long domainId; + + ///////////////////////////////////////////////////// + /////////////////// Accessors /////////////////////// + ///////////////////////////////////////////////////// + public String getPublicIp() { + return publicIp; + } + + public String getAccountName() { + return accountName; + } + + public Long getDomainId() { + return domainId; + } + + public void setPublicIp(String publicIp) { + this.publicIp = publicIp; + } + + public String getIpRange() { + return ipRange; + } + + public void setIpRange(String ipRange) { + this.ipRange = ipRange; + } + + public void setZoneId(Long zoneId) { + this.zoneId = zoneId; + } + + public Long getZoneId() { + return zoneId; + } + ///////////////////////////////////////////////////// + /////////////// API Implementation/////////////////// + ///////////////////////////////////////////////////// + + + + public String getName() { + return s_name; + } + + @Override @SuppressWarnings("unchecked") + public RemoteAccessVpnResponse getResponse() { + RemoteAccessVpnVO responseObj = (RemoteAccessVpnVO)getResponseObject(); + + RemoteAccessVpnResponse response = new RemoteAccessVpnResponse(); + response.setId(responseObj.getId()); + response.setPublicIp(responseObj.getVpnServerAddress()); + response.setIpRange(responseObj.getIpRange()); + response.setAccountName(responseObj.getAccountName()); + response.setDomainId(responseObj.getDomainId()); + response.setDomainName(ApiDBUtils.findDomainById(responseObj.getDomainId()).getName()); + response.setResponseName(getName()); + return response; + } + + @Override + public long getAccountId() { + Account account = (Account)UserContext.current().getAccount(); + if ((account == null) || isAdmin(account.getType())) { + if ((domainId != null) && (accountName != null)) { + Account userAccount = ApiDBUtils.findAccountByNameDomain(accountName, domainId); + if (userAccount != null) { + return userAccount.getId(); + } + } + } + + if (account != null) { + return account.getId(); + } + + return Account.ACCOUNT_ID_SYSTEM; // no account info given, parent this command to SYSTEM so ERROR events are tracked + } + + @Override + public String getEventDescription() { + return "Create Remote Access VPN for account " + getAccountId() + " in zone " + getZoneId(); + } + + @Override + public String getEventType() { + return EventTypes.EVENT_REMOTE_ACCESS_VPN_CREATE; + } + + + +} diff --git a/server/src/com/cloud/network/NetworkManager.java b/server/src/com/cloud/network/NetworkManager.java index 6eeb1f5d76f..2d0fccd6ed7 100644 --- a/server/src/com/cloud/network/NetworkManager.java +++ b/server/src/com/cloud/network/NetworkManager.java @@ -37,6 +37,7 @@ import com.cloud.api.commands.StartRouterCmd; import com.cloud.api.commands.StopRouterCmd; import com.cloud.api.commands.UpdateLoadBalancerRuleCmd; import com.cloud.api.commands.UpgradeRouterCmd; +import com.cloud.api.commands.VpnUserConfigCmd; import com.cloud.dc.DataCenter; import com.cloud.dc.DataCenterVO; import com.cloud.dc.HostPodVO; @@ -341,4 +342,6 @@ public interface NetworkManager extends Manager { * @throws ConcurrentOperationException */ public boolean destroyRemoteAccessVpn(DeleteRemoteAccessVpnCmd cmd) throws ConcurrentOperationException; + + boolean addRemoveVpnUsers(VpnUserConfigCmd cmd) throws ConcurrentOperationException; } diff --git a/server/src/com/cloud/network/NetworkManagerImpl.java b/server/src/com/cloud/network/NetworkManagerImpl.java index 3b86b8d7dca..e07d89d0777 100755 --- a/server/src/com/cloud/network/NetworkManagerImpl.java +++ b/server/src/com/cloud/network/NetworkManagerImpl.java @@ -105,6 +105,7 @@ import com.cloud.network.dao.LoadBalancerVMMapDao; import com.cloud.network.dao.NetworkConfigurationDao; import com.cloud.network.dao.NetworkRuleConfigDao; import com.cloud.network.dao.RemoteAccessVpnDao; +import com.cloud.network.dao.VpnUserDao; import com.cloud.network.element.NetworkElement; import com.cloud.network.router.DomainRouterManager; import com.cloud.offering.NetworkOffering; @@ -204,6 +205,7 @@ public class NetworkManagerImpl implements NetworkManager, DomainRouterService { @Inject NicDao _nicDao; @Inject GuestOSDao _guestOSDao = null; @Inject RemoteAccessVpnDao _remoteAccessVpnDao = null; + @Inject VpnUserDao _vpnUsersDao = null; @Inject DomainRouterManager _routerMgr; @Inject(adapter=NetworkGuru.class) @@ -2740,5 +2742,81 @@ public class NetworkManagerImpl implements NetworkManager, DomainRouterService { } } } + + @Override + @DB + public boolean addRemoveVpnUsers(VpnUserConfigCmd cmd) throws ConcurrentOperationException { + Long userId = UserContext.current().getUserId(); + Account account = getAccountForApiCommand(cmd.getAccountName(), cmd.getDomainId()); + EventUtils.saveStartedEvent(userId, account.getId(), EventTypes.EVENT_VPN_USERS_ADD_OR_DELETE, "Add/remove VPN users for account: " + account.getAccountName(), cmd.getStartEventId()); + List vpnVOList = _remoteAccessVpnDao.findByAccount(account.getId()); + String publicIp = vpnVO.getVpnServerAddress(); + Long vpnId = vpnVO.getId(); + Transaction txn = Transaction.currentTxn(); + txn.start(); + boolean locked = false; + boolean created = false; + try { + IPAddressVO ipAddr = _ipAddressDao.acquire(publicIp); + if (ipAddr == null) { + throw new ConcurrentOperationException("Another operation active, unable to create vpn"); + } + locked = true; + + vpnVO = _routerMgr.startRemoteAccessVpn(vpnVO); + created = (vpnVO != null); + + return vpnVO; + } finally { + if (created) { + EventUtils.saveEvent(userId, account.getId(), EventTypes.EVENT_VPN_USERS_ADD_OR_DELETE, "Created a Remote Access VPN for account: " + account.getAccountName() + " in zone " + cmd.getZoneId()); + } else { + EventUtils.saveEvent(userId, account.getId(), EventVO.LEVEL_ERROR, EventTypes.EVENT_VPN_USERS_ADD_OR_DELETE, "Unable to create Remote Access VPN ", account.getAccountName() + " in zone " + cmd.getZoneId()); + _remoteAccessVpnDao.remove(vpnId); + } + txn.commit(); + if (locked) { + _ipAddressDao.release(publicIp); + } + } + } + + @Override + @DB + public boolean addRemoveVpnUsers(VpnUserConfigCmd cmd) throws ConcurrentOperationException { + Long userId = UserContext.current().getUserId(); + Account account = getAccountForApiCommand(cmd.getAccountName(), cmd.getDomainId()); + EventUtils.saveStartedEvent(userId, account.getId(), EventTypes.EVENT_VPN_USERS_ADD_OR_DELETE, "Add/remove VPN users for account: " + account.getAccountName(), cmd.getStartEventId()); + List vpnVOList = _remoteAccessVpnDao.findByAccount(account.getId()); + String publicIp = vpnVO.getVpnServerAddress(); + Long vpnId = vpnVO.getId(); + Transaction txn = Transaction.currentTxn(); + txn.start(); + boolean locked = false; + boolean created = false; + try { + IPAddressVO ipAddr = _ipAddressDao.acquire(publicIp); + if (ipAddr == null) { + throw new ConcurrentOperationException("Another operation active, unable to create vpn"); + } + locked = true; + + vpnVO = _routerMgr.startRemoteAccessVpn(vpnVO); + created = (vpnVO != null); + + return vpnVO; + } finally { + if (created) { + EventUtils.saveEvent(userId, account.getId(), EventTypes.EVENT_VPN_USERS_ADD_OR_DELETE, "Created a Remote Access VPN for account: " + account.getAccountName() + " in zone " + cmd.getZoneId()); + } else { + EventUtils.saveEvent(userId, account.getId(), EventVO.LEVEL_ERROR, EventTypes.EVENT_VPN_USERS_ADD_OR_DELETE, "Unable to create Remote Access VPN ", account.getAccountName() + " in zone " + cmd.getZoneId()); + _remoteAccessVpnDao.remove(vpnId); + } + txn.commit(); + if (locked) { + _ipAddressDao.release(publicIp); + } + } + } } diff --git a/server/src/com/cloud/network/router/DomainRouterManager.java b/server/src/com/cloud/network/router/DomainRouterManager.java index 7851453c286..c9436119bcf 100644 --- a/server/src/com/cloud/network/router/DomainRouterManager.java +++ b/server/src/com/cloud/network/router/DomainRouterManager.java @@ -36,6 +36,7 @@ import com.cloud.exception.PermissionDeniedException; import com.cloud.exception.ResourceUnavailableException; import com.cloud.network.NetworkConfiguration; import com.cloud.network.RemoteAccessVpnVO; +import com.cloud.network.VpnUserVO; import com.cloud.offering.NetworkOffering; import com.cloud.service.ServiceOfferingVO; import com.cloud.user.Account; @@ -175,6 +176,9 @@ public interface DomainRouterManager extends Manager { DomainRouterVO deploy(NetworkConfiguration guestConfig, NetworkOffering offering, DeployDestination dest, Account owner) throws InsufficientCapacityException, ResourceUnavailableException, ConcurrentOperationException; RemoteAccessVpnVO startRemoteAccessVpn(RemoteAccessVpnVO vpnVO); + + boolean addRemoveVpnUsers(RemoteAccessVpnVO vpnVO, Long accountId, List addUsers, List removeUsers); + boolean deleteRemoteAccessVpn(RemoteAccessVpnVO vpnVO); diff --git a/server/src/com/cloud/network/router/DomainRouterManagerImpl.java b/server/src/com/cloud/network/router/DomainRouterManagerImpl.java index 6ef5004e03d..1d288f481a6 100644 --- a/server/src/com/cloud/network/router/DomainRouterManagerImpl.java +++ b/server/src/com/cloud/network/router/DomainRouterManagerImpl.java @@ -54,6 +54,7 @@ import com.cloud.agent.api.routing.DhcpEntryCommand; import com.cloud.agent.api.routing.RemoteAccessVpnCfgCommand; import com.cloud.agent.api.routing.SavePasswordCommand; import com.cloud.agent.api.routing.VmDataCommand; +import com.cloud.agent.api.routing.VpnUsersCfgCommand; import com.cloud.agent.api.to.NicTO; import com.cloud.agent.api.to.VirtualMachineTO; import com.cloud.agent.api.to.VirtualMachineTO.SshMonitor; @@ -113,6 +114,7 @@ import com.cloud.network.NetworkConfigurationVO; import com.cloud.network.NetworkManager; import com.cloud.network.RemoteAccessVpnVO; import com.cloud.network.SshKeysDistriMonitor; +import com.cloud.network.VpnUserVO; import com.cloud.network.dao.FirewallRulesDao; import com.cloud.network.dao.IPAddressDao; import com.cloud.network.dao.LoadBalancerDao; @@ -2262,4 +2264,32 @@ public class DomainRouterManagerImpl implements DomainRouterManager, VirtualMach public DomainRouterVO persist(DomainRouterVO router) { return _routerDao.persist(router); } + + @Override + public boolean addRemoveVpnUsers(RemoteAccessVpnVO vpnVO, Long accountId, List addUsers, List removeUsers) { + DomainRouterVO router = getRouter(vpnVO.getAccountId(), vpnVO.getZoneId()); + if (router == null) { + s_logger.warn("Failed to add/remove VPN users: no router found for account and zone"); + return false; + } + if (router.getState() != State.Running) { + s_logger.warn("Failed to add/remove VPN users: router not in running state"); + return false; + } + try { + Answer answer = _agentMgr.send(router.getHostId(), new VpnUsersCfgCommand(addUsers, removeUsers)); + if (answer != null && answer.getResult()) { + return true; + } else { + s_logger.debug("Failed to add/remove VPN users: " + answer.getDetails()); + return false; + } + } catch (AgentUnavailableException e) { + s_logger.debug("Failed to add/remove VPN users:: ", e); + return false; + } catch (OperationTimedoutException e) { + s_logger.debug("Failed to add/remove VPN users:: ", e); + return false; + } + } } diff --git a/setup/db/create-index-fk.sql b/setup/db/create-index-fk.sql index 36cded11da6..1898a47a098 100755 --- a/setup/db/create-index-fk.sql +++ b/setup/db/create-index-fk.sql @@ -260,3 +260,10 @@ ALTER TABLE `cloud`.`instance_group` ADD CONSTRAINT `fk_instance_group__account_ ALTER TABLE `cloud`.`instance_group_vm_map` ADD CONSTRAINT `fk_instance_group_vm_map___group_id` FOREIGN KEY `fk_instance_group_vm_map___group_id` (`group_id`) REFERENCES `instance_group` (`id`) ON DELETE CASCADE; ALTER TABLE `cloud`.`instance_group_vm_map` ADD CONSTRAINT `fk_instance_group_vm_map___instance_id` FOREIGN KEY `fk_instance_group_vm_map___instance_id` (`instance_id`) REFERENCES `user_vm` (`id`) ON DELETE CASCADE; +ALTER TABLE `cloud`.`remote_access_vpn` ADD CONSTRAINT `fk_remote_access_vpn___account_id` FOREIGN KEY `fk_remote_access_vpn__account_id` (`account_id`) REFERENCES `account` (`id`) ON DELETE CASCADE; +ALTER TABLE `cloud`.`remote_access_vpn` ADD CONSTRAINT `fk_remote_access_vpn__zone_id` FOREIGN KEY `fk_remote_access_vpn__zone_id` (`zone_id`) REFERENCES `data_center` (`id`); +ALTER TABLE `cloud`.`remote_access_vpn` ADD INDEX `i_remote_access_vpn_addr`(`vpn_server_addr`); + +ALTER TABLE `cloud`.`vpn_users` ADD CONSTRAINT `fk_vpn_users___account_id` FOREIGN KEY `fk_vpn_users__account_id` (`account_id`) REFERENCES `account` (`id`) ON DELETE CASCADE; +ALTER TABLE `cloud`.`vpn_users` ADD INDEX `i_vpn_users_username`(`username`); +ALTER TABLE `cloud`.`vpn_users` ADD UNIQUE `i_vpn_users__account_id__username`(`account_id`, `username`); diff --git a/setup/db/create-schema.sql b/setup/db/create-schema.sql index 81944e6e8e7..f66718bdb66 100755 --- a/setup/db/create-schema.sql +++ b/setup/db/create-schema.sql @@ -955,13 +955,21 @@ CREATE TABLE `cloud`.`remote_access_vpn` ( `id` bigint unsigned NOT NULL auto_increment, `account_id` bigint unsigned NOT NULL, `zone_id` bigint unsigned NOT NULL, - `vpn_server_addr` varchar(15) NOT NULL, + `vpn_server_addr` varchar(15) UNIQUE NOT NULL, `local_ip` varchar(15) NOT NULL, `ip_range` varchar(32) NOT NULL, `ipsec_psk` varchar(255) NOT NULL, PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8; +CREATE TABLE `cloud`.`vpn_users` ( + `id` bigint unsigned NOT NULL auto_increment, + `account_id` bigint unsigned NOT NULL, + `username` varchar(255) NOT NULL, + `password` varchar(255) NOT NULL, + PRIMARY KEY (`id`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8; + CREATE TABLE `cloud`.`storage_pool` ( `id` bigint unsigned UNIQUE NOT NULL, `name` varchar(255) COMMENT 'should be NOT NULL',