From 1e0eb04442cf011b85ad10982331eee6d90835c3 Mon Sep 17 00:00:00 2001 From: Chiradeep Vittal Date: Mon, 1 Nov 2010 10:26:40 -0700 Subject: [PATCH] Add VPN apis, some systemvm changes for vpn --- client/tomcatconf/commands.properties.in | 7 +- client/tomcatconf/components.xml.in | 3 +- .../routing/RemoteAccessVpnCfgCommand.java | 90 +++++++ core/src/com/cloud/event/EventTypes.java | 4 + .../xen/resource/CitrixResourceBase.java | 22 ++ .../com/cloud/network/RemoteAccessVpnVO.java | 135 +++++++++++ .../cloud/network/dao/RemoteAccessVpnDao.java | 27 +++ .../network/dao/RemoteAccessVpnDaoImpl.java | 62 +++++ patches/systemvm/debian/buildsystemvm.sh | 12 +- .../debian/config/etc/profile.d/cloud.sh | 4 + patches/systemvm/debian/vpn/etc/ipsec.conf | 50 ++++ .../systemvm/debian/vpn/etc/ipsec.conf.orig | 48 ++++ .../systemvm/debian/vpn/etc/ipsec.d/l2tp.conf | 33 +++ patches/systemvm/debian/vpn/etc/ipsec.secrets | 12 + .../debian/vpn/etc/ipsec.secrets.orig | 11 + .../debian/vpn/etc/ppp/options.xl2tpd | 14 ++ .../debian/vpn/etc/xl2tpd/xl2tpd.conf | 6 + .../debian/vpn/etc/xl2tpd/xl2tpd.conf.orig | 76 ++++++ .../debian/vpn/opt/cloud/bin/vpn_l2tp.sh | 151 ++++++++++++ scripts/network/domr/l2tp_vpn.sh | 10 + scripts/vm/hypervisor/xenserver/vmops | 17 +- .../vm/hypervisor/xenserver/xenserver56/patch | 1 + .../commands/CreateRemoteAccessVpnCmd.java | 148 ++++++++++++ .../commands/DeleteRemoteAccessVpnCmd.java | 120 ++++++++++ .../api/commands/ListRemoteAccessVpnsCmd.java | 129 ++++++++++ .../api/response/RemoteAccessVpnResponse.java | 104 ++++++++ .../src/com/cloud/network/NetworkManager.java | 28 +++ .../com/cloud/network/NetworkManagerImpl.java | 225 +++++++++++++++++- .../network/router/DomainRouterManager.java | 5 + .../router/DomainRouterManagerImpl.java | 58 +++++ .../cloud/server/ConfigurationServerImpl.java | 2 +- .../com/cloud/server/ManagementServer.java | 6 + .../cloud/server/ManagementServerImpl.java | 105 +++++++- .../src/com/cloud/vm/UserVmManagerImpl.java | 4 +- setup/db/create-schema.sql | 11 + .../com/cloud/utils/PasswordGenerator.java | 4 +- 36 files changed, 1731 insertions(+), 13 deletions(-) create mode 100644 core/src/com/cloud/agent/api/routing/RemoteAccessVpnCfgCommand.java create mode 100644 core/src/com/cloud/network/RemoteAccessVpnVO.java create mode 100644 core/src/com/cloud/network/dao/RemoteAccessVpnDao.java create mode 100644 core/src/com/cloud/network/dao/RemoteAccessVpnDaoImpl.java create mode 100755 patches/systemvm/debian/config/etc/profile.d/cloud.sh create mode 100644 patches/systemvm/debian/vpn/etc/ipsec.conf create mode 100644 patches/systemvm/debian/vpn/etc/ipsec.conf.orig create mode 100644 patches/systemvm/debian/vpn/etc/ipsec.d/l2tp.conf create mode 100644 patches/systemvm/debian/vpn/etc/ipsec.secrets create mode 100644 patches/systemvm/debian/vpn/etc/ipsec.secrets.orig create mode 100644 patches/systemvm/debian/vpn/etc/ppp/options.xl2tpd create mode 100644 patches/systemvm/debian/vpn/etc/xl2tpd/xl2tpd.conf create mode 100644 patches/systemvm/debian/vpn/etc/xl2tpd/xl2tpd.conf.orig create mode 100755 patches/systemvm/debian/vpn/opt/cloud/bin/vpn_l2tp.sh create mode 100755 scripts/network/domr/l2tp_vpn.sh create mode 100644 server/src/com/cloud/api/commands/CreateRemoteAccessVpnCmd.java create mode 100644 server/src/com/cloud/api/commands/DeleteRemoteAccessVpnCmd.java create mode 100644 server/src/com/cloud/api/commands/ListRemoteAccessVpnsCmd.java create mode 100644 server/src/com/cloud/api/response/RemoteAccessVpnResponse.java diff --git a/client/tomcatconf/commands.properties.in b/client/tomcatconf/commands.properties.in index 3afde1e317b..505921f5c36 100755 --- a/client/tomcatconf/commands.properties.in +++ b/client/tomcatconf/commands.properties.in @@ -215,4 +215,9 @@ listInstanceGroups=com.cloud.api.commands.ListVMGroupsCmd;15 uploadCustomCertificate=com.cloud.api.commands.UploadCustomCertificateCmd;15 ### other commands -listHypervisors=com.cloud.api.commands.ListHypervisorsCmd;15 \ No newline at end of file +listHypervisors=com.cloud.api.commands.ListHypervisorsCmd;15 + +### VPN +createRemoteAccessVpn=com.cloud.api.commands.CreateRemoteAccessVpnCmd;15 +deleteRemoteAccessVpn=com.cloud.api.commands.DeleteRemoteAccessVpnCmd;15 +listRemoteAccessVpns=com.cloud.api.commands.ListRemoteAccessVpnsCmd;15 diff --git a/client/tomcatconf/components.xml.in b/client/tomcatconf/components.xml.in index 28a78a0ce79..0f9f44cd2df 100755 --- a/client/tomcatconf/components.xml.in +++ b/client/tomcatconf/components.xml.in @@ -114,7 +114,8 @@ - + + diff --git a/core/src/com/cloud/agent/api/routing/RemoteAccessVpnCfgCommand.java b/core/src/com/cloud/agent/api/routing/RemoteAccessVpnCfgCommand.java new file mode 100644 index 00000000000..669db7a954a --- /dev/null +++ b/core/src/com/cloud/agent/api/routing/RemoteAccessVpnCfgCommand.java @@ -0,0 +1,90 @@ +/** + * 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; + + +public class RemoteAccessVpnCfgCommand extends RoutingCommand { + + String vpnAppliancePrivateIpAddress; //router private ip address typically + boolean create; + String vpnServerIp; + String ipRange; + String presharedKey; + String localIp; + + protected RemoteAccessVpnCfgCommand() { + this.create = false; + } + + public boolean isCreate() { + return create; + } + + @Override + public boolean executeInSequence() { + return true; + } + + + public RemoteAccessVpnCfgCommand(boolean create, String routerPrivateIp, String vpnServerAddress, String localIp, String ipRange, String ipsecPresharedKey) { + this.vpnAppliancePrivateIpAddress = routerPrivateIp; + this.vpnServerIp = vpnServerAddress; + this.ipRange = ipRange; + this.presharedKey = ipsecPresharedKey; + this.localIp = localIp; + this.create = create; + } + + public String getVpnServerIp() { + return vpnServerIp; + } + + public void setVpnServerIp(String vpnServerIp) { + this.vpnServerIp = vpnServerIp; + } + + public String getIpRange() { + return ipRange; + } + + public void setIpRange(String ipRange) { + this.ipRange = ipRange; + } + + public String getPresharedKey() { + return presharedKey; + } + + public void setPresharedKey(String presharedKey) { + this.presharedKey = presharedKey; + } + + public String getLocalIp() { + return localIp; + } + + public String getVpnAppliancePrivateIpAddress() { + return vpnAppliancePrivateIpAddress; + } + + public String getRouterPrivateIpAddress() { + return vpnAppliancePrivateIpAddress; + } + +} diff --git a/core/src/com/cloud/event/EventTypes.java b/core/src/com/cloud/event/EventTypes.java index a0776c8f23d..c8c9cd1af54 100755 --- a/core/src/com/cloud/event/EventTypes.java +++ b/core/src/com/cloud/event/EventTypes.java @@ -162,4 +162,8 @@ public class EventTypes { public static final String EVENT_MAINTENANCE_CANCEL_PRIMARY_STORAGE = "MAINT.CANCEL.PS"; public static final String EVENT_MAINTENANCE_PREPARE = "MAINT.PREPARE"; public static final String EVENT_MAINTENANCE_PREPARE_PRIMARY_STORAGE = "MAINT.PREPARE.PS"; + + //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"; } diff --git a/core/src/com/cloud/hypervisor/xen/resource/CitrixResourceBase.java b/core/src/com/cloud/hypervisor/xen/resource/CitrixResourceBase.java index 7a40bbf7f75..a59f305159d 100644 --- a/core/src/com/cloud/hypervisor/xen/resource/CitrixResourceBase.java +++ b/core/src/com/cloud/hypervisor/xen/resource/CitrixResourceBase.java @@ -131,6 +131,7 @@ import com.cloud.agent.api.routing.IPAssocCommand; import com.cloud.agent.api.routing.LoadBalancerCfgCommand; import com.cloud.agent.api.routing.SavePasswordCommand; import com.cloud.agent.api.routing.SetFirewallRuleCommand; +import com.cloud.agent.api.routing.RemoteAccessVpnCfgCommand; import com.cloud.agent.api.routing.VmDataCommand; import com.cloud.agent.api.storage.CopyVolumeAnswer; import com.cloud.agent.api.storage.CopyVolumeCommand; @@ -642,6 +643,8 @@ public abstract class CitrixResourceBase implements StoragePoolResource, ServerR return execute((PoolEjectCommand) cmd); } else if (cmd instanceof Start2Command) { return execute((Start2Command)cmd); + } else if (cmd instanceof RemoteAccessVpnCfgCommand) { + return execute((RemoteAccessVpnCfgCommand)cmd); } else { return Answer.createUnsupportedCommandAnswer(cmd); } @@ -1204,6 +1207,25 @@ public abstract class CitrixResourceBase implements StoragePoolResource, ServerR } return new Answer(cmd); } + + protected synchronized Answer execute(final RemoteAccessVpnCfgCommand cmd) { + String args = cmd.getRouterPrivateIpAddress(); + if (cmd.isCreate()) { + args += " -r " + cmd.getIpRange(); + args += " -p " + cmd.getPresharedKey(); + args += " -s " + cmd.getVpnServerIp(); + args += " -l " + cmd.getLocalIp(); + args += " -c"; + + } else { + args += " -d"; + } + String result = callHostPlugin("vmops", "lt2p_vpn", "args", args); + if (result == null || result.isEmpty()) { + return new Answer(cmd, false, "Configure VPN failed"); + } + return new Answer(cmd); + } protected Answer execute(final VmDataCommand cmd) { String routerPrivateIpAddress = cmd.getRouterPrivateIpAddress(); diff --git a/core/src/com/cloud/network/RemoteAccessVpnVO.java b/core/src/com/cloud/network/RemoteAccessVpnVO.java new file mode 100644 index 00000000000..40fd4b01b02 --- /dev/null +++ b/core/src/com/cloud/network/RemoteAccessVpnVO.java @@ -0,0 +1,135 @@ +/** + * 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.network; + +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.GeneratedValue; +import javax.persistence.GenerationType; +import javax.persistence.Id; +import javax.persistence.PrimaryKeyJoinColumn; +import javax.persistence.SecondaryTable; +import javax.persistence.Table; + +@Entity +@Table(name=("remote_access_vpn")) +@SecondaryTable(name="account", + pkJoinColumns={@PrimaryKeyJoinColumn(name="account_id", referencedColumnName="id")}) +public class RemoteAccessVpnVO { + @Id + @GeneratedValue(strategy=GenerationType.IDENTITY) + @Column(name="id") + private Long id; + + @Column(name="account_id") + private long accountId; + + @Column(name="zone_id") + private long zoneId; + + @Column(name="account_name", table="account", insertable=false, updatable=false) + private String accountName = null; + + @Column(name="domain_id", table="account", insertable=false, updatable=false) + private long domainId; + + @Column(name="vpn_server_addr") + private String vpnServerAddress; + + @Column(name="local_ip") + private String localIp; + + @Column(name="ip_range") + private String ipRange; + + @Column(name="ipsec_psk") + private String ipsecPresharedKey; + + public RemoteAccessVpnVO() { } + + public RemoteAccessVpnVO(long accountId, long zoneId, String publicIp, String localIp, String ipRange, String presharedKey) { + this.accountId = accountId; + this.vpnServerAddress = publicIp; + this.ipRange = ipRange; + this.ipsecPresharedKey = presharedKey; + this.zoneId = zoneId; + this.localIp = localIp; + + } + + public Long getId() { + return id; + } + + + + public long getAccountId() { + return accountId; + } + + public String getAccountName() { + return accountName; + } + + public String getVpnServerAddress() { + return vpnServerAddress; + } + + public void setVpnServerAddress(String vpnServerAddress) { + this.vpnServerAddress = vpnServerAddress; + } + + public String getIpRange() { + return ipRange; + } + + public void setIpRange(String ipRange) { + this.ipRange = ipRange; + } + + public String getIpsecPresharedKey() { + return ipsecPresharedKey; + } + + public void setIpsecPresharedKey(String ipsecPresharedKey) { + this.ipsecPresharedKey = ipsecPresharedKey; + } + + public void setId(Long id) { + this.id = id; + } + + public void setZoneId(long zoneId) { + this.zoneId = zoneId; + } + + public long getZoneId() { + return zoneId; + } + + public String getLocalIp() { + return localIp; + } + + public long getDomainId() { + return domainId; + } + + +} diff --git a/core/src/com/cloud/network/dao/RemoteAccessVpnDao.java b/core/src/com/cloud/network/dao/RemoteAccessVpnDao.java new file mode 100644 index 00000000000..fa8fd5165f6 --- /dev/null +++ b/core/src/com/cloud/network/dao/RemoteAccessVpnDao.java @@ -0,0 +1,27 @@ +/** + * 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.network.dao; + +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); +} diff --git a/core/src/com/cloud/network/dao/RemoteAccessVpnDaoImpl.java b/core/src/com/cloud/network/dao/RemoteAccessVpnDaoImpl.java new file mode 100644 index 00000000000..1860d048e6e --- /dev/null +++ b/core/src/com/cloud/network/dao/RemoteAccessVpnDaoImpl.java @@ -0,0 +1,62 @@ +/** + * 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.network.dao; + +import javax.ejb.Local; + +import org.apache.log4j.Logger; + +import com.cloud.network.RemoteAccessVpnVO; +import com.cloud.utils.db.GenericDaoBase; +import com.cloud.utils.db.SearchBuilder; +import com.cloud.utils.db.SearchCriteria; + +@Local(value={RemoteAccessVpnDao.class}) +public class RemoteAccessVpnDaoImpl extends GenericDaoBase implements RemoteAccessVpnDao { + private static final Logger s_logger = Logger.getLogger(RemoteAccessVpnDaoImpl.class); + + private final SearchBuilder ListByIp; + private final SearchBuilder AccountAndZoneSearch; + + protected RemoteAccessVpnDaoImpl() { + ListByIp = createSearchBuilder(); + ListByIp.and("ipAddress", ListByIp.entity().getVpnServerAddress(), SearchCriteria.Op.EQ); + ListByIp.done(); + + AccountAndZoneSearch = createSearchBuilder(); + AccountAndZoneSearch.and("accountId", AccountAndZoneSearch.entity().getAccountId(), SearchCriteria.Op.EQ); + AccountAndZoneSearch.and("zoneId", AccountAndZoneSearch.entity().getZoneId(), SearchCriteria.Op.EQ); + AccountAndZoneSearch.done(); + } + + @Override + public RemoteAccessVpnVO findByPublicIpAddress(String ipAddress) { + SearchCriteria sc = ListByIp.create(); + sc.setParameters("ipAddress", ipAddress); + return findOneBy(sc); + } + + @Override + public RemoteAccessVpnVO findByAccountAndZone(Long accountId, Long zoneId) { + SearchCriteria sc = AccountAndZoneSearch.create(); + sc.setParameters("accountId", accountId); + sc.setParameters("zoneId", zoneId); + return findOneBy(sc); + } +} diff --git a/patches/systemvm/debian/buildsystemvm.sh b/patches/systemvm/debian/buildsystemvm.sh index e999b8b649d..329da8062fd 100755 --- a/patches/systemvm/debian/buildsystemvm.sh +++ b/patches/systemvm/debian/buildsystemvm.sh @@ -335,10 +335,14 @@ xenstore_utils() { for f in $(find ${scriptdir}/xe/ -name xe-*) do cp $f ./usr/sbin/ - chmod a+x /usr/sbin/xe-* + chmod a+x ./usr/sbin/xe-* done } +vpn_config() { + cp -r ${scriptdir}/vpn/* ./ +} + packages() { DEBIAN_FRONTEND=noninteractive DEBIAN_PRIORITY=critical @@ -346,7 +350,7 @@ packages() { export DEBIAN_FRONTEND DEBIAN_PRIORITY DEBCONF_DB_OVERRIDE #basic stuff - chroot . apt-get --no-install-recommends -q -y --force-yes install rsyslog logrotate cron chkconfig insserv net-tools ifupdown vim-tiny netbase iptables openssh-server grub e2fsprogs dhcp3-client dnsmasq tcpdump socat wget python bzip2 sed gawk diff grep gzip less tar telnet traceroute psmisc procps monit inetutils-ping iputils-arping httping dnsutils zip unzip ethtool uuid file iproute acpid iptables-persistent sysstat + chroot . apt-get --no-install-recommends -q -y --force-yes install rsyslog logrotate cron chkconfig insserv net-tools ifupdown vim-tiny netbase iptables openssh-server grub e2fsprogs dhcp3-client dnsmasq tcpdump socat wget python bzip2 sed gawk diff grep gzip less tar telnet traceroute psmisc lsof procps monit inetutils-ping iputils-arping httping dnsutils zip unzip ethtool uuid file iproute acpid iptables-persistent sysstat #apache chroot . apt-get --no-install-recommends -q -y --force-yes install apache2 ssl-cert #haproxy @@ -422,6 +426,7 @@ cleanup() { signature() { (cd ${scriptdir}/config; tar czf ${MOUNTPOINT}/usr/share/cloud/cloud-scripts.tgz *) md5sum ${MOUNTPOINT}/usr/share/cloud/cloud-scripts.tgz |awk '{print $1}' > ${MOUNTPOINT}/var/cache/cloud/cloud-scripts-signature + echo "Cloudstack Release 2.2 $(date)" > ${MOUNTPOUNT}/etc/cloudstack-release } mkdir -p $IMAGENAME @@ -487,6 +492,9 @@ services echo "*************CONFIGURING APACHE********************" apache2 +echo "*************CONFIGURING VPN********************" +vpn_config + echo "*************CLEANING UP********************" cleanup diff --git a/patches/systemvm/debian/config/etc/profile.d/cloud.sh b/patches/systemvm/debian/config/etc/profile.d/cloud.sh new file mode 100755 index 00000000000..6d8ef23a399 --- /dev/null +++ b/patches/systemvm/debian/config/etc/profile.d/cloud.sh @@ -0,0 +1,4 @@ +if [ "`id -u`" -eq 0 ]; then + PATH=${PATH}:/opt/cloud/bin +fi +export PATH diff --git a/patches/systemvm/debian/vpn/etc/ipsec.conf b/patches/systemvm/debian/vpn/etc/ipsec.conf new file mode 100644 index 00000000000..a1c4bfb52b8 --- /dev/null +++ b/patches/systemvm/debian/vpn/etc/ipsec.conf @@ -0,0 +1,50 @@ +# /etc/ipsec.conf - Openswan IPsec configuration file + +# This file: /usr/share/doc/openswan/ipsec.conf-sample +# +# Manual: ipsec.conf.5 + + +version 2.0 # conforms to second version of ipsec.conf specification + +# basic configuration +config setup + # Do not set debug options to debug configuration issues! + # plutodebug / klipsdebug = "all", "none" or a combation from below: + # "raw crypt parsing emitting control klips pfkey natt x509 dpd private" + # eg: + # plutodebug="control parsing" + # + # enable to get logs per-peer + # plutoopts="--perpeerlog" + # + # Again: only enable plutodebug or klipsdebug when asked by a developer + # + # NAT-TRAVERSAL support, see README.NAT-Traversal + nat_traversal=yes + # exclude networks used on server side by adding %v4:!a.b.c.0/24 + virtual_private=%v4:10.0.0.0/8,%v4:192.168.0.0/16,%v4:172.16.0.0/12 + # OE is now off by default. Uncomment and change to on, to enable. + oe=off + # which IPsec stack to use. auto will try netkey, then klips then mast + protostack=auto + + +# Add connections here + +# sample VPN connection +# for more examples, see /etc/ipsec.d/examples/ +#conn sample +# # Left security gateway, subnet behind it, nexthop toward right. +# left=10.0.0.1 +# leftsubnet=172.16.0.0/24 +# leftnexthop=10.22.33.44 +# # Right security gateway, subnet behind it, nexthop toward left. +# right=10.12.12.1 +# rightsubnet=192.168.0.0/24 +# rightnexthop=10.101.102.103 +# # To authorize this connection, but not actually start it, +# # at startup, uncomment this. +# #auto=add + +include /etc/ipsec.d/*.conf diff --git a/patches/systemvm/debian/vpn/etc/ipsec.conf.orig b/patches/systemvm/debian/vpn/etc/ipsec.conf.orig new file mode 100644 index 00000000000..d185e6cd502 --- /dev/null +++ b/patches/systemvm/debian/vpn/etc/ipsec.conf.orig @@ -0,0 +1,48 @@ +# /etc/ipsec.conf - Openswan IPsec configuration file + +# This file: /usr/share/doc/openswan/ipsec.conf-sample +# +# Manual: ipsec.conf.5 + + +version 2.0 # conforms to second version of ipsec.conf specification + +# basic configuration +config setup + # Do not set debug options to debug configuration issues! + # plutodebug / klipsdebug = "all", "none" or a combation from below: + # "raw crypt parsing emitting control klips pfkey natt x509 dpd private" + # eg: + # plutodebug="control parsing" + # + # enable to get logs per-peer + # plutoopts="--perpeerlog" + # + # Again: only enable plutodebug or klipsdebug when asked by a developer + # + # NAT-TRAVERSAL support, see README.NAT-Traversal + nat_traversal=yes + # exclude networks used on server side by adding %v4:!a.b.c.0/24 + virtual_private=%v4:10.0.0.0/8,%v4:192.168.0.0/16,%v4:172.16.0.0/12 + # OE is now off by default. Uncomment and change to on, to enable. + oe=off + # which IPsec stack to use. auto will try netkey, then klips then mast + protostack=auto + + +# Add connections here + +# sample VPN connection +# for more examples, see /etc/ipsec.d/examples/ +#conn sample +# # Left security gateway, subnet behind it, nexthop toward right. +# left=10.0.0.1 +# leftsubnet=172.16.0.0/24 +# leftnexthop=10.22.33.44 +# # Right security gateway, subnet behind it, nexthop toward left. +# right=10.12.12.1 +# rightsubnet=192.168.0.0/24 +# rightnexthop=10.101.102.103 +# # To authorize this connection, but not actually start it, +# # at startup, uncomment this. +# #auto=add diff --git a/patches/systemvm/debian/vpn/etc/ipsec.d/l2tp.conf b/patches/systemvm/debian/vpn/etc/ipsec.d/l2tp.conf new file mode 100644 index 00000000000..7459e259a4e --- /dev/null +++ b/patches/systemvm/debian/vpn/etc/ipsec.d/l2tp.conf @@ -0,0 +1,33 @@ +conn L2TP-PSK + authby=secret + pfs=no + rekey=no + keyingtries=3 + # + # ---------------------------------------------------------- + # The VPN server. + # + # Allow incoming connections on the external network interface. + # If you want to use a different interface or if there is no + # defaultroute, you can use: left=your.ip.addr.ess + # + left=172.26.0.151 + # + leftprotoport=17/1701 + # If you insist on supporting non-updated Windows clients, + # you can use: leftprotoport=17/%any + # + # ---------------------------------------------------------- + # The remote user(s). + # + # Allow incoming connections only from this IP address. + right=%any + # If you want to allow multiple connections from any IP address, + # you can use: right=%any + # + rightprotoport=17/%any + # + # ---------------------------------------------------------- + # Change 'ignore' to 'add' to enable this configuration. + # + auto=add diff --git a/patches/systemvm/debian/vpn/etc/ipsec.secrets b/patches/systemvm/debian/vpn/etc/ipsec.secrets new file mode 100644 index 00000000000..67ae69886cb --- /dev/null +++ b/patches/systemvm/debian/vpn/etc/ipsec.secrets @@ -0,0 +1,12 @@ +# RCSID $Id: ipsec.secrets.proto,v 1.3.6.1 2005/09/28 13:59:14 paul Exp $ +# This file holds shared secrets or RSA private keys for inter-Pluto +# authentication. See ipsec_pluto(8) manpage, and HTML documentation. + +# RSA private key for this host, authenticating it to any other host +# which knows the public part. Suitable public keys, for ipsec.conf, DNS, +# or configuration of other implementations, can be extracted conveniently +# with "ipsec showhostkey". + +# this file is managed with debconf and will contain the automatically created RSA keys +include /var/lib/openswan/ipsec.secrets.inc +include /etc/ipsec.d/ipsec.*.secrets diff --git a/patches/systemvm/debian/vpn/etc/ipsec.secrets.orig b/patches/systemvm/debian/vpn/etc/ipsec.secrets.orig new file mode 100644 index 00000000000..6885545e8e8 --- /dev/null +++ b/patches/systemvm/debian/vpn/etc/ipsec.secrets.orig @@ -0,0 +1,11 @@ +# RCSID $Id: ipsec.secrets.proto,v 1.3.6.1 2005/09/28 13:59:14 paul Exp $ +# This file holds shared secrets or RSA private keys for inter-Pluto +# authentication. See ipsec_pluto(8) manpage, and HTML documentation. + +# RSA private key for this host, authenticating it to any other host +# which knows the public part. Suitable public keys, for ipsec.conf, DNS, +# or configuration of other implementations, can be extracted conveniently +# with "ipsec showhostkey". + +# this file is managed with debconf and will contain the automatically created RSA keys +include /var/lib/openswan/ipsec.secrets.inc diff --git a/patches/systemvm/debian/vpn/etc/ppp/options.xl2tpd b/patches/systemvm/debian/vpn/etc/ppp/options.xl2tpd new file mode 100644 index 00000000000..08c301b098f --- /dev/null +++ b/patches/systemvm/debian/vpn/etc/ppp/options.xl2tpd @@ -0,0 +1,14 @@ +proxyarp +ipcp-accept-local +ipcp-accept-remote +noccp +idle 1800 +auth +crtscts +mtu 1410 +mru 1410 +nodefaultroute +debug +lock +connect-delay 5000 +ms-dns 10.1.1.1 diff --git a/patches/systemvm/debian/vpn/etc/xl2tpd/xl2tpd.conf b/patches/systemvm/debian/vpn/etc/xl2tpd/xl2tpd.conf new file mode 100644 index 00000000000..574eab1461e --- /dev/null +++ b/patches/systemvm/debian/vpn/etc/xl2tpd/xl2tpd.conf @@ -0,0 +1,6 @@ +[lns default] +ip range = 10.1.9.2-10.1.9.8 +local ip = 10.1.9.1 +require chap = yes +refuse pap = yes +pppoptfile = /etc/ppp/options.xl2tpd diff --git a/patches/systemvm/debian/vpn/etc/xl2tpd/xl2tpd.conf.orig b/patches/systemvm/debian/vpn/etc/xl2tpd/xl2tpd.conf.orig new file mode 100644 index 00000000000..9f2f03a5048 --- /dev/null +++ b/patches/systemvm/debian/vpn/etc/xl2tpd/xl2tpd.conf.orig @@ -0,0 +1,76 @@ +; +; Sample l2tpd configuration file +; +; This example file should give you some idea of how the options for l2tpd +; should work. The best place to look for a list of all options is in +; the source code itself, until I have the time to write better documetation :) +; Specifically, the file "file.c" contains a list of commands at the end. +; +; You most definitely don't have to spell out everything as it is done here +; +; [global] ; Global parameters: +; port = 1701 ; * Bind to port 1701 +; auth file = /etc/l2tpd/l2tp-secrets ; * Where our challenge secrets are +; access control = yes ; * Refuse connections without IP match +; rand source = dev ; Source for entropy for random +; ; numbers, options are: +; ; dev - reads of /dev/urandom +; ; sys - uses rand() +; ; egd - reads from egd socket +; ; egd is not yet implemented +; +; [lns default] ; Our fallthrough LNS definition +; exclusive = no ; * Only permit one tunnel per host +; ip range = 192.168.0.1-192.168.0.20 ; * Allocate from this IP range +; no ip range = 192.168.0.3-192.168.0.9 ; * Except these hosts +; ip range = 192.168.0.5 ; * But this one is okay +; ip range = lac1-lac2 ; * And anything from lac1 to lac2's IP +; lac = 192.168.1.4 - 192.168.1.8 ; * These can connect as LAC's +; no lac = untrusted.marko.net ; * This guy can't connect +; hidden bit = no ; * Use hidden AVP's? +; local ip = 192.168.1.2 ; * Our local IP to use +; length bit = yes ; * Use length bit in payload? +; require chap = yes ; * Require CHAP auth. by peer +; refuse pap = yes ; * Refuse PAP authentication +; refuse chap = no ; * Refuse CHAP authentication +; refuse authentication = no ; * Refuse authentication altogether +; require authentication = yes ; * Require peer to authenticate +; unix authentication = no ; * Use /etc/passwd for auth. +; name = myhostname ; * Report this as our hostname +; ppp debug = no ; * Turn on PPP debugging +; pppoptfile = /etc/ppp/options.l2tpd.lns ; * ppp options file +; call rws = 10 ; * RWS for call (-1 is valid) +; tunnel rws = 4 ; * RWS for tunnel (must be > 0) +; flow bit = yes ; * Include sequence numbers +; challenge = yes ; * Challenge authenticate peer ; +; rx bps = 10000000 ; Receive tunnel speed +; tx bps = 10000000 ; Transmit tunnel speed +; bps = 100000 ; Define both receive and transmit speed in one option + +; [lac marko] ; Example VPN LAC definition +; lns = lns.marko.net ; * Who is our LNS? +; lns = lns2.marko.net ; * A backup LNS (not yet used) +; redial = yes ; * Redial if disconnected? +; redial timeout = 15 ; * Wait n seconds between redials +; max redials = 5 ; * Give up after n consecutive failures +; hidden bit = yes ; * User hidden AVP's? +; local ip = 192.168.1.1 ; * Force peer to use this IP for us +; remote ip = 192.168.1.2 ; * Force peer to use this as their IP +; length bit = no ; * Use length bit in payload? +; require pap = no ; * Require PAP auth. by peer +; require chap = yes ; * Require CHAP auth. by peer +; refuse pap = yes ; * Refuse PAP authentication +; refuse chap = no ; * Refuse CHAP authentication +; refuse authentication = no ; * Refuse authentication altogether +; require authentication = yes ; * Require peer to authenticate +; name = marko ; * Report this as our hostname +; ppp debug = no ; * Turn on PPP debugging +; pppoptfile = /etc/ppp/options.l2tpd.marko ; * ppp options file for this lac +; call rws = 10 ; * RWS for call (-1 is valid) +; tunnel rws = 4 ; * RWS for tunnel (must be > 0) +; flow bit = yes ; * Include sequence numbers +; challenge = yes ; * Challenge authenticate peer +; +; [lac cisco] ; Another quick LAC +; lns = cisco.marko.net ; * Required, but can take from default +; require authentication = yes diff --git a/patches/systemvm/debian/vpn/opt/cloud/bin/vpn_l2tp.sh b/patches/systemvm/debian/vpn/opt/cloud/bin/vpn_l2tp.sh new file mode 100755 index 00000000000..fbb6659ae40 --- /dev/null +++ b/patches/systemvm/debian/vpn/opt/cloud/bin/vpn_l2tp.sh @@ -0,0 +1,151 @@ +#!/bin/bash + +set -x +usage() { + printf "Usage: %s: \n" $(basename $0) +} + +get_intf_ip() { + ip addr show $1 | grep -w inet | awk '{print $2}' | awk -F'/' '{print $1}' +} + + +iptables_() { + local op=$1 + local public_if="eth2" + local subnet_if="eth0" + local subnet_ip=$(get_intf_ip $subnet_if) + + iptables $op INPUT -i $public_if -p udp -m udp --dport 1701 -j ACCEPT + iptables $op INPUT -i $public_if -p udp -m udp --dport 500 -j ACCEPT + iptables $op INPUT -i $public_if -p udp -m udp --dport 4500 -j ACCEPT + iptables $op INPUT -i eth2 -p ah -j ACCEPT + iptables $op INPUT -i eth2 -p esp -j ACCEPT + iptables $op FORWARD -i ppp+ -o $subnet_if -j ACCEPT + iptables $op FORWARD -i $subnet_if -o ppp+ -j ACCEPT + iptables $op FORWARD -i ppp+ -o ppp+ -j ACCEPT + iptables $op INPUT -i ppp+ -m udp -p udp --dport 53 -j ACCEPT + iptables -t nat $op PREROUTING -i ppp+ -p udp -m udp --dport 53 -j DNAT --to-destination $subnet_ip + +} + +ipsec_server() { + local op=$1 + if [ "$op" == "restart" ]; then + service ipsec stop + service xl2tpd stop + service ipsec start + service xl2tpd start + return $? + fi + service ipsec $op + service xl2tpd $op +} + +create_l2tp_ipsec_vpn_server() { + local ipsec_psk=$1 + local server_ip=$2 + local client_range=$3 + local local_ip=$4 + + sed -i -e "s/left=.*$/left=$server_ip/" /etc/ipsec.d/l2tp.conf + echo ": PSK \"$ipsec_psk\"" > /etc/ipsec.d/ipsec.any.secrets + sed -i -e "s/^ip range = .*$/ip range = $client_range/" /etc/xl2tpd/xl2tpd.conf + sed -i -e "s/^local ip = .*$/local ip = $local_ip/" /etc/xl2tpd/xl2tpd.conf + + sed -i -e "s/^ms-dns.*$/ms-dns $local_ip/" /etc/ppp/options.xl2tpd + + iptables_ "-D" + iptables_ "-I" + + ipsec_server "restart" + + ipsec auto --rereadsecrets + ipsec auto --replace L2TP-PSK +} + +destroy_l2tp_ipsec_vpn_server() { + + ipsec auto --down L2TP-PSK + + iptables_ "-D" + + ipsec_server "stop" +} + +remove_l2tp_ipsec_user() { + local u=$1 + sed -i -e "/^$u .*$/d" /etc/ppp/chap-secrets +} + +add_l2tp_ipsec_user() { + local u=$1 + local passwd=$2 + + remove_l2tp_ipsec_user $u + echo "$u * $passwd *" >> /etc/ppp/chap-secrets +} + +rflag= +pflag= +lflag= +sflag= +create= +destroy= +useradd= +userdel= + +while getopts 'cdl:p:r:s:u:U:' OPTION +do + case $OPTION in + c) create=1 + ;; + d) destroy=1 + ;; + u) useradd=1 + user_pwd="$OPTARG" + ;; + U) userdel=1 + user="$OPTARG" + ;; + r) rflag=1 + client_range="$OPTARG" + ;; + p) pflag=1 + ipsec_psk="$OPTARG" + ;; + l) lflag=1 + local_ip="$OPTARG" + ;; + s) sflag=1 + server_ip="$OPTARG" + ;; + ?) usage + exit 2 + ;; + esac +done + +[ "$create$destroy" == "11" ] || [ "$create$destroy$useradd$userdel" == "" ] && usage && exit 2 +[ "$create" == "1" ] && [ "$lflag$pflag$rflag$sflag" != "1111" ] && usage && exit 2 + +if [ "$create" == "1" ]; then + create_l2tp_ipsec_vpn_server $ipsec_psk $server_ip $client_range $local_ip + exit $? +fi + +if [ "$destroy" == "1" ]; then + destroy_l2tp_ipsec_vpn_server + exit $? +fi + +if [ "$useradd" == "1" ]; then + u=$(echo $user_pwd | awk -F',' '{print $1}') + pwd=$(echo $user_pwd | awk -F',' '{print $2}') + add_l2tp_ipsec_user $u $pwd + exit $? +fi +if [ "$userdel" == "1" ]; then + remove_l2tp_ipsec_user $user + exit $? +fi diff --git a/scripts/network/domr/l2tp_vpn.sh b/scripts/network/domr/l2tp_vpn.sh new file mode 100755 index 00000000000..d562dd61c93 --- /dev/null +++ b/scripts/network/domr/l2tp_vpn.sh @@ -0,0 +1,10 @@ +#!/bin/bash +# +# @VERSION@ + +cert="/root/.ssh/id_rsa.cloud" +domr=$1 +shift +ssh -p 3922 -o StrictHostKeyChecking=no -i $cert root@$domr "/opt/cloud/bin/vpn_l2tp.sh $*" >/dev/null + +exit $? diff --git a/scripts/vm/hypervisor/xenserver/vmops b/scripts/vm/hypervisor/xenserver/vmops index 2c45006d2f7..52aceb78250 100755 --- a/scripts/vm/hypervisor/xenserver/vmops +++ b/scripts/vm/hypervisor/xenserver/vmops @@ -271,6 +271,21 @@ def saveDhcpEntry(session, args): txt = '' return txt + +@echo +def lt2p_vpn(session, args): + sargs = args['args'] + cmd = sargs.split(' ') + cmd.insert(0, "/opt/xensource/bin/l2tp_vpn.sh") + cmd.insert(0, "/bin/bash") + try: + txt = util.pread2(cmd) + txt = 'success' + except: + util.SMlog("l2tp vpn failed " ) + txt = '' + + return txt @echo def setLinkLocalIP(session, args): @@ -1088,5 +1103,5 @@ def network_rules(session, args): if __name__ == "__main__": - XenAPIPlugin.dispatch({"pingtest": pingtest, "setup_iscsi":setup_iscsi, "gethostvmstats": gethostvmstats, "getvncport": getvncport, "getgateway": getgateway, "getnetwork": getnetwork, "preparemigration": preparemigration, "setIptables": setIptables, "patchdomr": patchdomr, "pingdomr": pingdomr, "pingxenserver": pingxenserver, "ipassoc": ipassoc, "vm_data": vm_data, "savePassword": savePassword, "saveDhcpEntry": saveDhcpEntry, "setFirewallRule": setFirewallRule, "setLoadBalancerRule": setLoadBalancerRule, "createFile": createFile, "deleteFile": deleteFile, "checkMount": checkMount, "checkIscsi": checkIscsi, "networkUsage": networkUsage, "network_rules":network_rules, "can_bridge_firewall":can_bridge_firewall, "default_network_rules":default_network_rules, "destroy_network_rules_for_vm":destroy_network_rules_for_vm, "default_network_rules_systemvm":default_network_rules_systemvm, "get_rule_logs_for_vms":get_rule_logs_for_vms, "setLinkLocalIP":setLinkLocalIP}) + XenAPIPlugin.dispatch({"pingtest": pingtest, "setup_iscsi":setup_iscsi, "gethostvmstats": gethostvmstats, "getvncport": getvncport, "getgateway": getgateway, "getnetwork": getnetwork, "preparemigration": preparemigration, "setIptables": setIptables, "patchdomr": patchdomr, "pingdomr": pingdomr, "pingxenserver": pingxenserver, "ipassoc": ipassoc, "vm_data": vm_data, "savePassword": savePassword, "saveDhcpEntry": saveDhcpEntry, "setFirewallRule": setFirewallRule, "setLoadBalancerRule": setLoadBalancerRule, "createFile": createFile, "deleteFile": deleteFile, "checkMount": checkMount, "checkIscsi": checkIscsi, "networkUsage": networkUsage, "network_rules":network_rules, "can_bridge_firewall":can_bridge_firewall, "default_network_rules":default_network_rules, "destroy_network_rules_for_vm":destroy_network_rules_for_vm, "default_network_rules_systemvm":default_network_rules_systemvm, "get_rule_logs_for_vms":get_rule_logs_for_vms, "setLinkLocalIP":setLinkLocalIP, "lt2p_vpn":lt2p_vpn}) diff --git a/scripts/vm/hypervisor/xenserver/xenserver56/patch b/scripts/vm/hypervisor/xenserver/xenserver56/patch index 29cbcd95d05..0fb3ed9cfd2 100644 --- a/scripts/vm/hypervisor/xenserver/xenserver56/patch +++ b/scripts/vm/hypervisor/xenserver/xenserver56/patch @@ -36,3 +36,4 @@ save_password_to_domr.sh=../../../../network/domr/,0755,/opt/xensource/bin networkUsage.sh=../../../../network/domr/,0755,/opt/xensource/bin call_firewall.sh=../../../../network/domr/,0755,/opt/xensource/bin call_loadbalancer.sh=../../../../network/domr/,0755,/opt/xensource/bin +l2tp_vpn.sh=../../../../network/domr/,0755,/opt/xensource/bin diff --git a/server/src/com/cloud/api/commands/CreateRemoteAccessVpnCmd.java b/server/src/com/cloud/api/commands/CreateRemoteAccessVpnCmd.java new file mode 100644 index 00000000000..4f0f86a69ea --- /dev/null +++ b/server/src/com/cloud/api/commands/CreateRemoteAccessVpnCmd.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.BaseAsyncCreateCmd; +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(createMethod="createRemoteAccessVpn", method="startRemoteAccessVpn", manager=NetworkManager.class, description="Creates a l2tp/ipsec remote access vpn") +public class CreateRemoteAccessVpnCmd extends BaseAsyncCreateCmd { + public static final Logger s_logger = Logger.getLogger(CreateRemoteAccessVpnCmd.class.getName()); + + private static final String s_name = "createremoteaccessvpnresponse"; + + ///////////////////////////////////////////////////// + //////////////// 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/api/commands/DeleteRemoteAccessVpnCmd.java b/server/src/com/cloud/api/commands/DeleteRemoteAccessVpnCmd.java new file mode 100644 index 00000000000..9730191384c --- /dev/null +++ b/server/src/com/cloud/api/commands/DeleteRemoteAccessVpnCmd.java @@ -0,0 +1,120 @@ +/** + * 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.SuccessResponse; +import com.cloud.event.EventTypes; +import com.cloud.network.NetworkManager; +import com.cloud.user.Account; +import com.cloud.user.UserContext; + +@Implementation(method="destroyRemoteAccessVpn", manager=NetworkManager.class, description="Destroys a l2tp/ipsec remote access vpn") +public class DeleteRemoteAccessVpnCmd extends BaseAsyncCmd { + public static final Logger s_logger = Logger.getLogger(DeleteRemoteAccessVpnCmd.class.getName()); + + private static final String s_name = "deleteremoteaccessvpnresponse"; + + ///////////////////////////////////////////////////// + //////////////// 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="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 void setZoneId(Long zoneId) { + this.zoneId = zoneId; + } + + public Long getZoneId() { + return zoneId; + } + + + public String getAccountName() { + return accountName; + } + + public Long getDomainId() { + return domainId; + } + ///////////////////////////////////////////////////// + /////////////// API Implementation/////////////////// + ///////////////////////////////////////////////////// + + + public String getName() { + return s_name; + } + + @Override @SuppressWarnings("unchecked") + public SuccessResponse getResponse() { + Boolean success = (Boolean)getResponseObject(); + SuccessResponse response = new SuccessResponse(); + response.setSuccess(success); + 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 "Delete Remote Access VPN for account " + getAccountId() + " in zone " + getZoneId(); + } + + @Override + public String getEventType() { + return EventTypes.EVENT_REMOTE_ACCESS_VPN_DESTROY; + } + + + +} diff --git a/server/src/com/cloud/api/commands/ListRemoteAccessVpnsCmd.java b/server/src/com/cloud/api/commands/ListRemoteAccessVpnsCmd.java new file mode 100644 index 00000000000..fe01cdf098b --- /dev/null +++ b/server/src/com/cloud/api/commands/ListRemoteAccessVpnsCmd.java @@ -0,0 +1,129 @@ +/** + * 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 java.util.ArrayList; +import java.util.List; + +import org.apache.log4j.Logger; + +import com.cloud.api.ApiDBUtils; +import com.cloud.api.BaseListCmd; +import com.cloud.api.Implementation; +import com.cloud.api.Parameter; +import com.cloud.api.response.RemoteAccessVpnResponse; +import com.cloud.api.response.ListResponse; +import com.cloud.api.response.LoadBalancerResponse; +import com.cloud.network.LoadBalancerVO; +import com.cloud.network.RemoteAccessVpnVO; +import com.cloud.user.Account; + +@Implementation(method="searchForRemoteAccessVpns", description="Lists remote access vpns") +public class ListRemoteAccessVpnsCmd extends BaseListCmd { + public static final Logger s_logger = Logger.getLogger (ListRemoteAccessVpnsCmd.class.getName()); + + private static final String s_name = "listremoteaccessvpnsresponse"; + + ///////////////////////////////////////////////////// + //////////////// API parameters ///////////////////// + ///////////////////////////////////////////////////// + + @Parameter(name="account", type=CommandType.STRING, description="the account of the remote access vpn. Must be used with the domainId parameter.") + private String accountName; + + @Parameter(name="domainid", type=CommandType.LONG, description="the domain ID of the remote access vpn rule. If used with the account parameter, lists remote access vpns for the account in the specified domain.") + private Long domainId; + + @Parameter(name="id", type=CommandType.LONG, description="the ID of the remote access vpn") + private Long id; + + @Parameter(name="zoneid", type=CommandType.LONG, description="the zone ID of the remote access vpn rule") + private Long zoneId; + + @Parameter(name="publicip", type=CommandType.STRING, description="the public IP address of the remote access vpn ") + private String publicIp; + + + ///////////////////////////////////////////////////// + /////////////////// Accessors /////////////////////// + ///////////////////////////////////////////////////// + + public String getAccountName() { + return accountName; + } + + public Long getDomainId() { + return domainId; + } + + public Long getId() { + return id; + } + + public void setZoneId(Long zoneId) { + this.zoneId = zoneId; + } + + public Long getZoneId() { + return zoneId; + } + + public String getPublicIp() { + return publicIp; + } + + + ///////////////////////////////////////////////////// + /////////////// API Implementation/////////////////// + ///////////////////////////////////////////////////// + + @Override + public String getName() { + return s_name; + } + + @Override @SuppressWarnings("unchecked") + public ListResponse getResponse() { + List vpns = (List)getResponseObject(); + + ListResponse response = new ListResponse(); + List vpnResponses = new ArrayList(); + for (RemoteAccessVpnVO vpn : vpns) { + RemoteAccessVpnResponse vpnResponse = new RemoteAccessVpnResponse(); + vpnResponse.setId(vpn.getId()); + vpnResponse.setPublicIp(vpn.getVpnServerAddress()); + vpnResponse.setIpRange(vpn.getIpRange()); + vpnResponse.setPresharedKey(vpn.getIpsecPresharedKey()); + vpnResponse.setAccountName(vpn.getAccountName()); + + Account accountTemp = ApiDBUtils.findAccountById(vpn.getAccountId()); + if (accountTemp != null) { + vpnResponse.setDomainId(accountTemp.getDomainId()); + vpnResponse.setDomainName(ApiDBUtils.findDomainById(accountTemp.getDomainId()).getName()); + } + + vpnResponse.setResponseName("remoteaccessvpn"); + vpnResponses.add(vpnResponse); + } + + response.setResponses(vpnResponses); + response.setResponseName(getName()); + return response; + } +} diff --git a/server/src/com/cloud/api/response/RemoteAccessVpnResponse.java b/server/src/com/cloud/api/response/RemoteAccessVpnResponse.java new file mode 100644 index 00000000000..41de0de2e5d --- /dev/null +++ b/server/src/com/cloud/api/response/RemoteAccessVpnResponse.java @@ -0,0 +1,104 @@ +/** + * 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.response; + +import com.cloud.serializer.Param; +import com.google.gson.annotations.SerializedName; + +public class RemoteAccessVpnResponse extends BaseResponse { + @SerializedName("id") @Param(description="the vpn ID") + private Long id; + + @SerializedName("publicip") @Param(description="the public ip address of the vpn server") + private String publicIp; + + @SerializedName("ipRange") @Param(description="the range of ips to allocate to the clients") + private String ipRange; + + @SerializedName("presharedkey") @Param(description="the ipsec preshared key") + private String presharedKey; + + @SerializedName("account") @Param(description="the account of the remote access vpn") + private String accountName; + + @SerializedName("domainid") @Param(description="the domain id of the account of the remote access vpn") + private long domainId; + + @SerializedName("domainname") @Param(description="the domain name of the account of the remote access vpn") + private String domainName; + + public String getAccountName() { + return accountName; + } + + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + public String getPublicIp() { + return publicIp; + } + + public void setPublicIp(String publicIp) { + this.publicIp = publicIp; + } + + public String getIpRange() { + return ipRange; + } + + public void setIpRange(String ipRange) { + this.ipRange = ipRange; + } + + public String getPresharedKey() { + return presharedKey; + } + + public void setPresharedKey(String presharedKey) { + this.presharedKey = presharedKey; + } + + public void setAccountName(String accountName) { + this.accountName = accountName; + + } + + public void setDomainId(long domainId) { + this.domainId = domainId; + + } + + public void setDomainName(String name) { + this.domainName = name; + } + + public long getDomainId() { + return domainId; + } + + public String getDomainName() { + return domainName; + } + + +} diff --git a/server/src/com/cloud/network/NetworkManager.java b/server/src/com/cloud/network/NetworkManager.java index 937dd020fa3..d44c60d483e 100644 --- a/server/src/com/cloud/network/NetworkManager.java +++ b/server/src/com/cloud/network/NetworkManager.java @@ -25,8 +25,11 @@ import com.cloud.api.commands.AssignToLoadBalancerRuleCmd; import com.cloud.api.commands.AssociateIPAddrCmd; import com.cloud.api.commands.CreateIPForwardingRuleCmd; import com.cloud.api.commands.CreateLoadBalancerRuleCmd; +import com.cloud.api.commands.CreateRemoteAccessVpnCmd; import com.cloud.api.commands.DeleteIPForwardingRuleCmd; import com.cloud.api.commands.DeleteLoadBalancerRuleCmd; +import com.cloud.api.commands.DeletePortForwardingServiceRuleCmd; +import com.cloud.api.commands.DeleteRemoteAccessVpnCmd; import com.cloud.api.commands.DisassociateIPAddrCmd; import com.cloud.api.commands.ListPortForwardingRulesCmd; import com.cloud.api.commands.RebootRouterCmd; @@ -318,4 +321,29 @@ public interface NetworkManager extends Manager { List setupNetworkConfiguration(Account owner, ServiceOfferingVO offering, DeploymentPlan plan); String assignSourceNatIpAddress(Account account, DataCenter dc) throws InsufficientAddressCapacityException; + /** + * Create a remote access vpn from the given public ip address and client ip range + * @param cmd the command specifying the ip address, ip range + * @return the newly created RemoteAccessVpnVO if successful, null otherwise + * @throws InvalidParameterValueException + * @throws PermissionDeniedException + * @throws ConcurrentOperationException + */ + public RemoteAccessVpnVO createRemoteAccessVpn(CreateRemoteAccessVpnCmd cmd) throws InvalidParameterValueException, PermissionDeniedException, ConcurrentOperationException; + + /** + * Start a remote access vpn for the given public ip address and client ip range + * @param cmd the command specifying the ip address, ip range + * @return the RemoteAccessVpnVO if successful, null otherwise + * @throws ConcurrentOperationException + */ + public RemoteAccessVpnVO startRemoteAccessVpn(CreateRemoteAccessVpnCmd cmd) throws ConcurrentOperationException; + + /** + * Destroy a previously created remote access VPN + * @param cmd the command specifying the account and zone + * @return success if successful, false otherwise + * @throws ConcurrentOperationException + */ + public boolean destroyRemoteAccessVpn(DeleteRemoteAccessVpnCmd cmd) throws ConcurrentOperationException; } diff --git a/server/src/com/cloud/network/NetworkManagerImpl.java b/server/src/com/cloud/network/NetworkManagerImpl.java index 05487302d82..9116bb0fc3f 100755 --- a/server/src/com/cloud/network/NetworkManagerImpl.java +++ b/server/src/com/cloud/network/NetworkManagerImpl.java @@ -47,8 +47,11 @@ import com.cloud.api.commands.AssignToLoadBalancerRuleCmd; import com.cloud.api.commands.AssociateIPAddrCmd; import com.cloud.api.commands.CreateIPForwardingRuleCmd; import com.cloud.api.commands.CreateLoadBalancerRuleCmd; +import com.cloud.api.commands.CreateRemoteAccessVpnCmd; import com.cloud.api.commands.DeleteIPForwardingRuleCmd; import com.cloud.api.commands.DeleteLoadBalancerRuleCmd; +import com.cloud.api.commands.DeletePortForwardingServiceRuleCmd; +import com.cloud.api.commands.DeleteRemoteAccessVpnCmd; import com.cloud.api.commands.DisassociateIPAddrCmd; import com.cloud.api.commands.ListPortForwardingRulesCmd; import com.cloud.api.commands.RebootRouterCmd; @@ -66,8 +69,8 @@ import com.cloud.configuration.dao.ResourceLimitDao; import com.cloud.dc.DataCenter; import com.cloud.dc.DataCenterVO; import com.cloud.dc.HostPodVO; -import com.cloud.dc.Vlan.VlanType; import com.cloud.dc.VlanVO; +import com.cloud.dc.Vlan.VlanType; import com.cloud.dc.dao.AccountVlanMapDao; import com.cloud.dc.dao.DataCenterDao; import com.cloud.dc.dao.HostPodDao; @@ -103,6 +106,9 @@ import com.cloud.network.dao.LoadBalancerDao; 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.SecurityGroupDao; +import com.cloud.network.dao.SecurityGroupVMMapDao; import com.cloud.network.element.NetworkElement; import com.cloud.network.router.DomainRouterManager; import com.cloud.offering.NetworkOffering; @@ -131,16 +137,17 @@ import com.cloud.user.dao.UserDao; import com.cloud.user.dao.UserStatisticsDao; import com.cloud.uservm.UserVm; import com.cloud.utils.Pair; +import com.cloud.utils.PasswordGenerator; import com.cloud.utils.StringUtils; import com.cloud.utils.Ternary; import com.cloud.utils.component.Adapters; import com.cloud.utils.component.Inject; import com.cloud.utils.db.DB; import com.cloud.utils.db.JoinBuilder; -import com.cloud.utils.db.JoinBuilder.JoinType; import com.cloud.utils.db.SearchBuilder; import com.cloud.utils.db.SearchCriteria; import com.cloud.utils.db.Transaction; +import com.cloud.utils.db.JoinBuilder.JoinType; import com.cloud.utils.exception.CloudRuntimeException; import com.cloud.utils.net.NetUtils; import com.cloud.vm.DomainRouter; @@ -200,6 +207,7 @@ public class NetworkManagerImpl implements NetworkManager, DomainRouterService { @Inject NetworkConfigurationDao _networkConfigDao = null; @Inject NicDao _nicDao; @Inject GuestOSDao _guestOSDao = null; + @Inject RemoteAccessVpnDao _remoteAccessVpnDao = null; @Inject DomainRouterManager _routerMgr; @Inject(adapter=NetworkGuru.class) @@ -588,6 +596,37 @@ public class NetworkManagerImpl implements NetworkManager, DomainRouterService { return true; } + /** Returns the target account for an api command + * @param accountName - non-null if the account name was passed in in the command + * @param domainId - non-null if the domainId was passed in in the command. + * @return + */ + protected Account getAccountForApiCommand(String accountName, Long domainId) throws InvalidParameterValueException, PermissionDeniedException{ + Account account = UserContext.current().getAccount(); + + if ((account == null) || isAdmin(account.getType())) { + //The admin is making the call, determine if it is for someone else or for himself + if (domainId != null) { + if ((account != null) && !_domainDao.isChildDomain(account.getDomainId(), domainId)) { + throw new PermissionDeniedException("Invalid domain id (" + domainId + ") given, , permission denied"); + } + if (accountName != null) { + Account userAccount = _accountDao.findActiveAccount(accountName, domainId); + if (userAccount != null) { + account = userAccount; + } else { + throw new PermissionDeniedException("Unable to find account " + accountName + " in domain " + domainId + ", permission denied"); + } + } + } else if (account != null) { + // the admin is calling the api on his own behalf + return account; + } else { + throw new InvalidParameterValueException("Account information is not specified."); + } + } + return account; + } @Override @DB public IPAddressVO associateIP(AssociateIPAddrCmd cmd) throws ResourceAllocationException, InsufficientAddressCapacityException, InvalidParameterValueException, InternalErrorException, PermissionDeniedException { @@ -2547,5 +2586,187 @@ public class NetworkManagerImpl implements NetworkManager, DomainRouterService { NetworkOfferingVO networkOffering = _networkOfferingDao.findByServiceOffering(offering); return setupNetworkConfiguration(owner, networkOffering, plan); } + + @Override + @DB + public RemoteAccessVpnVO createRemoteAccessVpn(CreateRemoteAccessVpnCmd cmd) + throws InvalidParameterValueException, PermissionDeniedException, ConcurrentOperationException { + String publicIp = cmd.getPublicIp(); + IPAddressVO ipAddr = null; + Account account = getAccountForApiCommand(cmd.getAccountName(), cmd.getDomainId()); + if (publicIp == null) { + List accountAddrs = _ipAddressDao.listByAccount(account.getId()); + for (IPAddressVO addr: accountAddrs){ + if (addr.getSourceNat() && addr.getDataCenterId() == cmd.getZoneId()){ + ipAddr = addr; + publicIp = ipAddr.getAddress(); + break; + } + } + if (ipAddr == null) { + throw new InvalidParameterValueException("Account " + account.getAccountName() + " does not have any public ip addresses in zone " + cmd.getZoneId()); + } + } + + // make sure ip address exists + ipAddr = _ipAddressDao.findById(publicIp); + if (ipAddr == null) { + throw new InvalidParameterValueException("Unable to create remote access vpn, invalid public IP address " + publicIp); + } + + VlanVO vlan = _vlanDao.findById(ipAddr.getVlanDbId()); + if (vlan != null) { + if (!VlanType.VirtualNetwork.equals(vlan.getVlanType())) { + throw new InvalidParameterValueException("Unable to create VPN for IP address " + publicIp + ", only VirtualNetwork type IP addresses can be used for VPN."); + } + } + assert vlan != null:"Inconsistent DB state -- ip address does not belong to any vlan?"; + + if ((ipAddr.getAccountId() == null) || (ipAddr.getAllocated() == null)) { + throw new PermissionDeniedException("Unable to create VPN, permission denied for ip " + publicIp); + } + + if (account != null) { + if ((account.getType() == Account.ACCOUNT_TYPE_ADMIN) || (account.getType() == Account.ACCOUNT_TYPE_DOMAIN_ADMIN)) { + if (!_domainDao.isChildDomain(account.getDomainId(), ipAddr.getDomainId())) { + throw new PermissionDeniedException("Unable to create VPN with public IP address " + publicIp + ", permission denied."); + } + } else if (account.getId() != ipAddr.getAccountId().longValue()) { + throw new PermissionDeniedException("Unable to create VPN for account " + account.getAccountName() + " doesn't own ip address " + publicIp); + } + } + + RemoteAccessVpnVO vpnVO = _remoteAccessVpnDao.findByPublicIpAddress(publicIp); + if (vpnVO != null) { + throw new InvalidParameterValueException("A Remote Access VPN already exists for this public Ip address"); + } + //TODO: assumes one virtual network / domr per account per zone + vpnVO = _remoteAccessVpnDao.findByAccountAndZone(account.getId(), cmd.getZoneId()); + if (vpnVO != null) { + throw new InvalidParameterValueException("A Remote Access VPN already exists for this account"); + } + String ipRange = cmd.getIpRange(); + if (ipRange == null) { + //TODO: get default range from database + ipRange = "10.1.2.1-10.1.2.8"; + } + String [] range = ipRange.split("-"); + if (range.length != 2) { + throw new InvalidParameterValueException("Invalid ip range"); + } + if (!NetUtils.isValidIp(range[0]) || !NetUtils.isValidIp(range[1])){ + throw new InvalidParameterValueException("Invalid ip range"); + } + if (!NetUtils.validIpRange(range[0], range[1])){ + throw new InvalidParameterValueException("Invalid ip range"); + } + if (NetUtils.ipRangesOverlap(range[0], range[1], "10.1.1.1", "10.1.1.255")) { + throw new InvalidParameterValueException("Invalid ip range --- overlaps with guest ip range"); + //TODO: get actual guest ip range from config db + } + //TODO: check sufficient range + //TODO: check overlap with private and public ip ranges in datacenter + //TODO: check overlap with port forwarding rules on this ip (udp ports 500, 4500, 1701) + long startIp = NetUtils.ip2Long(range[0]); + String newIpRange = NetUtils.long2Ip(++startIp) + "-" + range[1]; + String sharedSecret = PasswordGenerator.generateRandomPassword(24); + //TODO: use SecureRandom in password generator + Transaction txn = Transaction.currentTxn(); + txn.start(); + boolean locked = false; + try { + ipAddr = _ipAddressDao.acquire(publicIp); + if (ipAddr == null) { + throw new ConcurrentOperationException("Another operation active, unable to create vpn"); + } + locked = true; + vpnVO = new RemoteAccessVpnVO(account.getId(), cmd.getZoneId(), publicIp, range[0], newIpRange, sharedSecret); + _remoteAccessVpnDao.persist(vpnVO); + txn.commit(); + return vpnVO; + } finally { + if (locked) { + _ipAddressDao.release(publicIp); + } + } + } + + @Override + @DB + public RemoteAccessVpnVO startRemoteAccessVpn(CreateRemoteAccessVpnCmd cmd) throws ConcurrentOperationException { + Long userId = UserContext.current().getUserId(); + Account account = getAccountForApiCommand(cmd.getAccountName(), cmd.getDomainId()); + EventUtils.saveStartedEvent(userId, account.getId(), EventTypes.EVENT_REMOTE_ACCESS_VPN_CREATE, "Creating a Remote Access VPN for account: " + account.getAccountName() + " in zone " + cmd.getZoneId(), cmd.getStartEventId()); + RemoteAccessVpnVO vpnVO = _remoteAccessVpnDao.findById(cmd.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_REMOTE_ACCESS_VPN_CREATE, "Created a Remote Access VPN for account: " + account.getAccountName() + " in zone " + cmd.getZoneId()); + } else { + EventUtils.saveEvent(userId, account.getId(), EventVO.LEVEL_ERROR, EventTypes.EVENT_REMOTE_ACCESS_VPN_CREATE, "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 destroyRemoteAccessVpn(DeleteRemoteAccessVpnCmd cmd) throws ConcurrentOperationException { + Long userId = UserContext.current().getUserId(); + Account account = getAccountForApiCommand(cmd.getAccountName(), cmd.getDomainId()); + //TODO: assumes one virtual network / domr per account per zone + RemoteAccessVpnVO vpnVO = _remoteAccessVpnDao.findByAccountAndZone(account.getId(), cmd.getZoneId()); + if (vpnVO == null) { + throw new InvalidParameterValueException("No VPN found for account " + account.getAccountName() + " in zone " + cmd.getZoneId()); + } + EventUtils.saveStartedEvent(userId, account.getId(), EventTypes.EVENT_REMOTE_ACCESS_VPN_DESTROY, "Deleting Remote Access VPN for account: " + account.getAccountName() + " in zone " + cmd.getZoneId(), cmd.getStartEventId()); + String publicIp = vpnVO.getVpnServerAddress(); + Long vpnId = vpnVO.getId(); + Transaction txn = Transaction.currentTxn(); + txn.start(); + boolean locked = false; + boolean deleted = false; + try { + IPAddressVO ipAddr = _ipAddressDao.acquire(publicIp); + if (ipAddr == null) { + throw new ConcurrentOperationException("Another operation active, unable to create vpn"); + } + locked = true; + + deleted = _routerMgr.deleteRemoteAccessVpn(vpnVO); + return deleted; + } finally { + if (deleted) { + _remoteAccessVpnDao.remove(vpnId); + EventUtils.saveEvent(userId, account.getId(), EventTypes.EVENT_REMOTE_ACCESS_VPN_DESTROY, "Deleted Remote Access VPN for account: " + account.getAccountName() + " in zone " + cmd.getZoneId()); + } else { + EventUtils.saveEvent(userId, account.getId(), EventVO.LEVEL_ERROR, EventTypes.EVENT_REMOTE_ACCESS_VPN_DESTROY, "Unable to delete Remote Access VPN ", account.getAccountName() + " in zone " + cmd.getZoneId()); + } + 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 299122356fd..5581d9741f0 100644 --- a/server/src/com/cloud/network/router/DomainRouterManager.java +++ b/server/src/com/cloud/network/router/DomainRouterManager.java @@ -34,6 +34,7 @@ import com.cloud.exception.InvalidParameterValueException; import com.cloud.exception.PermissionDeniedException; import com.cloud.exception.ResourceUnavailableException; import com.cloud.network.NetworkConfiguration; +import com.cloud.network.RemoteAccessVpnVO; import com.cloud.offering.NetworkOffering; import com.cloud.service.ServiceOfferingVO; import com.cloud.user.Account; @@ -169,4 +170,8 @@ public interface DomainRouterManager extends Manager { DomainRouterVO getRouter(String publicIpAddress); DomainRouterVO deploy(NetworkConfiguration guestConfig, NetworkOffering offering, DeployDestination dest, Account owner) throws InsufficientCapacityException, ResourceUnavailableException, ConcurrentOperationException; + + RemoteAccessVpnVO startRemoteAccessVpn(RemoteAccessVpnVO vpnVO); + + 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 02465beac3c..91b02fbdaa9 100644 --- a/server/src/com/cloud/network/router/DomainRouterManagerImpl.java +++ b/server/src/com/cloud/network/router/DomainRouterManagerImpl.java @@ -52,6 +52,7 @@ import com.cloud.agent.api.StartRouterCommand; import com.cloud.agent.api.StopCommand; import com.cloud.agent.api.routing.DhcpEntryCommand; import com.cloud.agent.api.routing.SavePasswordCommand; +import com.cloud.agent.api.routing.RemoteAccessVpnCfgCommand; import com.cloud.agent.api.routing.VmDataCommand; import com.cloud.agent.api.to.NicTO; import com.cloud.agent.api.to.VirtualMachineTO; @@ -109,6 +110,7 @@ import com.cloud.network.Network.TrafficType; import com.cloud.network.NetworkConfiguration; import com.cloud.network.NetworkConfigurationVO; import com.cloud.network.NetworkManager; +import com.cloud.network.RemoteAccessVpnVO; import com.cloud.network.SshKeysDistriMonitor; import com.cloud.network.dao.FirewallRulesDao; import com.cloud.network.dao.IPAddressDao; @@ -2105,4 +2107,60 @@ public class DomainRouterManagerImpl implements DomainRouterManager, VirtualMach return true; } + @Override + public RemoteAccessVpnVO startRemoteAccessVpn(RemoteAccessVpnVO vpnVO) { + DomainRouterVO router = getRouter(vpnVO.getAccountId(), vpnVO.getZoneId()); + if (router == null) { + s_logger.warn("Failed to start remote access VPN: no router found for account and zone"); + return null; + } + if (router.getState() != State.Running) { + s_logger.warn("Failed to start remote access VPN: router not in running state"); + return null; + } + try { + Answer answer = _agentMgr.send(router.getHostId(), new RemoteAccessVpnCfgCommand(true, router.getPrivateIpAddress(), vpnVO.getVpnServerAddress(), vpnVO.getLocalIp(), vpnVO.getIpRange(), vpnVO.getIpsecPresharedKey())); + if (answer != null && answer.getResult()) { + return vpnVO; + } else { + s_logger.debug("Failed to start remote access VPN: " + answer.getDetails()); + return null; + } + } catch (AgentUnavailableException e) { + s_logger.debug("Failed to start remote access VPN: ", e); + return null; + } catch (OperationTimedoutException e) { + s_logger.debug("Failed to start remote access VPN: ", e); + return null; + } + } + + @Override + public boolean deleteRemoteAccessVpn(RemoteAccessVpnVO vpnVO) { + DomainRouterVO router = getRouter(vpnVO.getAccountId(), vpnVO.getZoneId()); + if (router == null) { + s_logger.warn("Failed to delete remote access VPN: no router found for account and zone"); + return false; + } + if (router.getState() != State.Running) { + s_logger.warn("Failed to delete remote access VPN: router not in running state"); + return false; + } + try { + Answer answer = _agentMgr.send(router.getHostId(), new RemoteAccessVpnCfgCommand(false, router.getPrivateIpAddress(), vpnVO.getVpnServerAddress(), vpnVO.getLocalIp(), vpnVO.getIpRange(), vpnVO.getIpsecPresharedKey())); + if (answer != null && answer.getResult()) { + return true; + } else { + s_logger.debug("Failed to delete remote access VPN: " + answer.getDetails()); + return false; + } + } catch (AgentUnavailableException e) { + s_logger.debug("Failed to delete remote access VPN: ", e); + return false; + } catch (OperationTimedoutException e) { + s_logger.debug("Failed to delete remote access VPN: ", e); + return false; + } + } + } diff --git a/server/src/com/cloud/server/ConfigurationServerImpl.java b/server/src/com/cloud/server/ConfigurationServerImpl.java index 072dca0ef09..26ad4d94628 100644 --- a/server/src/com/cloud/server/ConfigurationServerImpl.java +++ b/server/src/com/cloud/server/ConfigurationServerImpl.java @@ -499,7 +499,7 @@ public class ConfigurationServerImpl implements ConfigurationServer { if (already == null) { s_logger.info("Need to store secondary storage vm copy password in the database"); - String password = PasswordGenerator.generateRandomPassword(); + String password = PasswordGenerator.generateRandomPassword(12); String insertSql1 = "INSERT INTO `cloud`.`configuration` (category, instance, component, name, value, description) " + "VALUES ('Hidden','DEFAULT', 'management-server','secstorage.copy.password', '" + password + "','Password used to authenticate zone-to-zone template copy requests')"; diff --git a/server/src/com/cloud/server/ManagementServer.java b/server/src/com/cloud/server/ManagementServer.java index 63b2bfbcbd1..a4ab586dadb 100755 --- a/server/src/com/cloud/server/ManagementServer.java +++ b/server/src/com/cloud/server/ManagementServer.java @@ -58,6 +58,7 @@ import com.cloud.api.commands.ListLoadBalancerRulesCmd; import com.cloud.api.commands.ListPodsByCmd; import com.cloud.api.commands.ListPreallocatedLunsCmd; import com.cloud.api.commands.ListPublicIpAddressesCmd; +import com.cloud.api.commands.ListRemoteAccessVpnsCmd; import com.cloud.api.commands.ListRoutersCmd; import com.cloud.api.commands.ListServiceOfferingsCmd; import com.cloud.api.commands.ListSnapshotsCmd; @@ -113,6 +114,9 @@ import com.cloud.info.ConsoleProxyInfo; import com.cloud.network.FirewallRuleVO; import com.cloud.network.IPAddressVO; import com.cloud.network.LoadBalancerVO; +import com.cloud.network.NetworkRuleConfigVO; +import com.cloud.network.RemoteAccessVpnVO; +import com.cloud.network.SecurityGroupVO; import com.cloud.network.security.NetworkGroupVO; import com.cloud.service.ServiceOfferingVO; import com.cloud.storage.DiskOfferingVO; @@ -1125,4 +1129,6 @@ public interface ManagementServer { * @throws ServerApiException -- even if one of the console proxy patching fails, we throw back this exception */ String uploadCertificate(UploadCustomCertificateCmd cmd) throws ServerApiException; + + public List searchForRemoteAccessVpns(ListRemoteAccessVpnsCmd cmd) throws InvalidParameterValueException, PermissionDeniedException; } diff --git a/server/src/com/cloud/server/ManagementServerImpl.java b/server/src/com/cloud/server/ManagementServerImpl.java index c80846e5c6f..ad91d66e8ee 100755 --- a/server/src/com/cloud/server/ManagementServerImpl.java +++ b/server/src/com/cloud/server/ManagementServerImpl.java @@ -104,6 +104,7 @@ import com.cloud.api.commands.ListLoadBalancerRulesCmd; import com.cloud.api.commands.ListPodsByCmd; import com.cloud.api.commands.ListPreallocatedLunsCmd; import com.cloud.api.commands.ListPublicIpAddressesCmd; +import com.cloud.api.commands.ListRemoteAccessVpnsCmd; import com.cloud.api.commands.ListRoutersCmd; import com.cloud.api.commands.ListServiceOfferingsCmd; import com.cloud.api.commands.ListSnapshotsCmd; @@ -202,10 +203,18 @@ import com.cloud.network.IPAddressVO; import com.cloud.network.LoadBalancerVMMapVO; import com.cloud.network.LoadBalancerVO; import com.cloud.network.NetworkManager; +import com.cloud.network.NetworkRuleConfigVO; +import com.cloud.network.RemoteAccessVpnVO; +import com.cloud.network.SecurityGroupVMMapVO; +import com.cloud.network.SecurityGroupVO; import com.cloud.network.dao.FirewallRulesDao; import com.cloud.network.dao.IPAddressDao; import com.cloud.network.dao.LoadBalancerDao; import com.cloud.network.dao.LoadBalancerVMMapDao; +import com.cloud.network.dao.NetworkRuleConfigDao; +import com.cloud.network.dao.RemoteAccessVpnDao; +import com.cloud.network.dao.SecurityGroupDao; +import com.cloud.network.dao.SecurityGroupVMMapDao; import com.cloud.network.security.NetworkGroupManager; import com.cloud.network.security.NetworkGroupVO; import com.cloud.network.security.dao.NetworkGroupDao; @@ -375,6 +384,7 @@ public class ManagementServerImpl implements ManagementServer { private final UploadMonitor _uploadMonitor; private final UploadDao _uploadDao; private final CertificateDao _certDao; + private final RemoteAccessVpnDao _remoteAccessVpnDao; private final ScheduledExecutorService _executor = Executors.newScheduledThreadPool(1, new NamedThreadFactory("AccountChecker")); private final ScheduledExecutorService _eventExecutor = Executors.newScheduledThreadPool(1, new NamedThreadFactory("EventChecker")); @@ -449,6 +459,7 @@ public class ManagementServerImpl implements ManagementServer { _groupVMMapDao = locator.getDao(InstanceGroupVMMapDao.class); _uploadDao = locator.getDao(UploadDao.class); _certDao = locator.getDao(CertificateDao.class); + _remoteAccessVpnDao = locator.getDao(RemoteAccessVpnDao.class); _configs = _configDao.getConfiguration(); _userStatsDao = locator.getDao(UserStatisticsDao.class); _vmInstanceDao = locator.getDao(VMInstanceDao.class); @@ -1432,7 +1443,7 @@ public class ManagementServerImpl implements ManagementServer { @Override public String generateRandomPassword() { - return PasswordGenerator.generateRandomPassword(); + return PasswordGenerator.generateRandomPassword(6); } @Override @@ -6048,4 +6059,96 @@ public class ManagementServerImpl implements ManagementServer { return accountId; } + + @Override + public List searchForRemoteAccessVpns(ListRemoteAccessVpnsCmd cmd) throws InvalidParameterValueException, + PermissionDeniedException { + // do some parameter validation + Account account = UserContext.current().getAccount(); + String accountName = cmd.getAccountName(); + Long domainId = cmd.getDomainId(); + Long accountId = null; + Account ipAddressOwner = null; + String ipAddress = cmd.getPublicIp(); + + if (ipAddress != null) { + IPAddressVO ipAddressVO = _publicIpAddressDao.findById(ipAddress); + if (ipAddressVO == null) { + throw new InvalidParameterValueException("Unable to list remote access vpns, IP address " + ipAddress + " not found."); + } else { + Long ipAddrAcctId = ipAddressVO.getAccountId(); + if (ipAddrAcctId == null) { + throw new InvalidParameterValueException("Unable to list remote access vpns, IP address " + ipAddress + " is not associated with an account."); + } + ipAddressOwner = _accountDao.findById(ipAddrAcctId); + } + } + + if ((account == null) || isAdmin(account.getType())) { + // validate domainId before proceeding + if (domainId != null) { + if ((account != null) && !_domainDao.isChildDomain(account.getDomainId(), domainId)) { + throw new PermissionDeniedException("Unable to list remote access vpns for domain id " + domainId + ", permission denied."); + } + if (accountName != null) { + Account userAccount = _accountDao.findActiveAccount(accountName, domainId); + if (userAccount != null) { + accountId = userAccount.getId(); + } else { + throw new InvalidParameterValueException("Unable to find account " + accountName + " in domain " + domainId); + } + } + } else if (ipAddressOwner != null) { + if ((account != null) && !_domainDao.isChildDomain(account.getDomainId(), ipAddressOwner.getDomainId())) { + throw new PermissionDeniedException("Unable to list remote access vpn for IP address " + ipAddress + ", permission denied."); + } + } else { + domainId = ((account == null) ? DomainVO.ROOT_DOMAIN : account.getDomainId()); + } + } else { + accountId = account.getId(); + } + + Filter searchFilter = new Filter(RemoteAccessVpnVO.class, "vpnServerAddress", true, cmd.getStartIndex(), cmd.getPageSizeVal()); + + Object id = cmd.getId(); + Object zoneId = cmd.getZoneId(); + + + SearchBuilder sb = _remoteAccessVpnDao.createSearchBuilder(); + sb.and("id", sb.entity().getId(), SearchCriteria.Op.EQ); + sb.and("zoneId", sb.entity().getZoneId(), SearchCriteria.Op.EQ); + sb.and("accountId", sb.entity().getAccountId(), SearchCriteria.Op.EQ); + sb.and("ipAddress", sb.entity().getVpnServerAddress(), SearchCriteria.Op.EQ); + + if ((accountId == null) && (domainId != null)) { + // if accountId isn't specified, we can do a domain match for the admin case + SearchBuilder domainSearch = _domainDao.createSearchBuilder(); + domainSearch.and("path", domainSearch.entity().getPath(), SearchCriteria.Op.LIKE); + sb.join("domainSearch", domainSearch, sb.entity().getDomainId(), domainSearch.entity().getId(), JoinBuilder.JoinType.INNER); + } + + SearchCriteria sc = sb.create(); + + if (id != null) { + sc.setParameters("id", id); + } + + if (ipAddress != null) { + sc.setParameters("ipAddress", ipAddress); + } + + if (zoneId != null) { + sc.setParameters("zoneId", zoneId); + } + + if (accountId != null) { + sc.setParameters("accountId", accountId); + } else if (domainId != null) { + DomainVO domain = _domainDao.findById(domainId); + sc.setJoinParameters("domainSearch", "path", domain.getPath() + "%"); + } + + return _remoteAccessVpnDao.search(sc, searchFilter); + } } diff --git a/server/src/com/cloud/vm/UserVmManagerImpl.java b/server/src/com/cloud/vm/UserVmManagerImpl.java index 5e3dc79de85..9f55814cdd8 100755 --- a/server/src/com/cloud/vm/UserVmManagerImpl.java +++ b/server/src/com/cloud/vm/UserVmManagerImpl.java @@ -322,7 +322,7 @@ public class UserVmManagerImpl implements UserVmManager, UserVmService, VirtualM VMTemplateVO template = _templateDao.findById(vmInstance.getTemplateId()); if (template.getEnablePassword()) { - password = PasswordGenerator.generateRandomPassword();; + password = PasswordGenerator.generateRandomPassword(6);; } else { password = "saved_password"; } @@ -3824,7 +3824,7 @@ public class UserVmManagerImpl implements UserVmManager, UserVmService, VirtualM } // Check that the password was passed in and is valid - String password = PasswordGenerator.generateRandomPassword(); + String password = PasswordGenerator.generateRandomPassword(6); if (!template.getEnablePassword()) { password = "saved_password"; } diff --git a/setup/db/create-schema.sql b/setup/db/create-schema.sql index 14def07e731..bde6a7e097d 100755 --- a/setup/db/create-schema.sql +++ b/setup/db/create-schema.sql @@ -952,6 +952,17 @@ CREATE TABLE `cloud`.`load_balancer` ( PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8; +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, + `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`.`storage_pool` ( `id` bigint unsigned UNIQUE NOT NULL, `name` varchar(255) COMMENT 'should be NOT NULL', diff --git a/utils/src/com/cloud/utils/PasswordGenerator.java b/utils/src/com/cloud/utils/PasswordGenerator.java index 37b3d258706..567a8a24158 100644 --- a/utils/src/com/cloud/utils/PasswordGenerator.java +++ b/utils/src/com/cloud/utils/PasswordGenerator.java @@ -25,7 +25,7 @@ import java.util.Random; * */ public class PasswordGenerator { - public static String generateRandomPassword() { + public static String generateRandomPassword(int num) { Random r = new Random(); StringBuffer password = new StringBuffer(); @@ -46,7 +46,7 @@ public class PasswordGenerator { // Generate a random 6-character string with only lowercase // characters - for (int i = 0; i < 6; i++) { + for (int i = 0; i < num; i++) { // Generate a random lowercase character (don't allow lowercase // "l" or lowercase "o") lowercase = generateLowercaseChar(r);