diff --git a/api/src/com/cloud/agent/api/SecurityEgressRuleAnswer.java b/api/src/com/cloud/agent/api/SecurityEgressRuleAnswer.java new file mode 100644 index 00000000000..1b0d33326c1 --- /dev/null +++ b/api/src/com/cloud/agent/api/SecurityEgressRuleAnswer.java @@ -0,0 +1,47 @@ +/** + * 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; + +public class SecurityEgressRuleAnswer extends Answer { + Long logSequenceNumber = null; + Long vmId = null; + + protected SecurityEgressRuleAnswer() { + } + + public SecurityEgressRuleAnswer(SecurityEgressRulesCmd cmd) { + super(cmd); + this.logSequenceNumber = cmd.getSeqNum(); + this.vmId = cmd.getVmId(); + } + + public SecurityEgressRuleAnswer(SecurityEgressRulesCmd cmd, boolean result, String detail) { + super(cmd, result, detail); + this.logSequenceNumber = cmd.getSeqNum(); + this.vmId = cmd.getVmId(); + } + + public Long getLogSequenceNumber() { + return logSequenceNumber; + } + + public Long getVmId() { + return vmId; + } + +} diff --git a/api/src/com/cloud/agent/api/SecurityEgressRulesCmd.java b/api/src/com/cloud/agent/api/SecurityEgressRulesCmd.java new file mode 100644 index 00000000000..f9c096d7768 --- /dev/null +++ b/api/src/com/cloud/agent/api/SecurityEgressRulesCmd.java @@ -0,0 +1,144 @@ +/** + * 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; + + +public class SecurityEgressRulesCmd extends Command { + public static class EgressIpPortAndProto { + String proto; + int startPort; + int endPort; + String [] allowedCidrs; + + public EgressIpPortAndProto() { } + + public EgressIpPortAndProto(String proto, int startPort, int endPort, + String[] allowedCidrs) { + super(); + this.proto = proto; + this.startPort = startPort; + this.endPort = endPort; + this.allowedCidrs = allowedCidrs; + } + + public String[] getAllowedCidrs() { + return allowedCidrs; + } + + public void setAllowedCidrs(String[] allowedCidrs) { + this.allowedCidrs = allowedCidrs; + } + + public String getProto() { + return proto; + } + + public int getStartPort() { + return startPort; + } + + public int getEndPort() { + return endPort; + } + + } + + + String guestIp; + String vmName; + String guestMac; + String signature; + Long seqNum; + Long vmId; + EgressIpPortAndProto [] ruleSet; + + public SecurityEgressRulesCmd() { + super(); + } + + + public SecurityEgressRulesCmd(String guestIp, String guestMac, String vmName, Long vmId, String signature, Long seqNum, EgressIpPortAndProto[] ruleSet) { + super(); + this.guestIp = guestIp; + this.vmName = vmName; + this.ruleSet = ruleSet; + this.guestMac = guestMac; + this.signature = signature; + this.seqNum = seqNum; + this.vmId = vmId; + } + + + @Override + public boolean executeInSequence() { + return true; + } + + + public EgressIpPortAndProto[] getRuleSet() { + return ruleSet; + } + + + public void setRuleSet(EgressIpPortAndProto[] ruleSet) { + this.ruleSet = ruleSet; + } + + + public String getGuestIp() { + return guestIp; + } + + + public String getVmName() { + return vmName; + } + + public String stringifyRules() { + StringBuilder ruleBuilder = new StringBuilder(); + for (SecurityEgressRulesCmd.EgressIpPortAndProto ipPandP: getRuleSet()) { + ruleBuilder.append(ipPandP.getProto()).append(":").append(ipPandP.getStartPort()).append(":").append(ipPandP.getEndPort()).append(":"); + for (String cidr: ipPandP.getAllowedCidrs()) { + ruleBuilder.append(cidr).append(","); + } + ruleBuilder.append("NEXT"); + ruleBuilder.append(" "); + } + return ruleBuilder.toString(); + } + + public String getSignature() { + return signature; + } + + + public String getGuestMac() { + return guestMac; + } + + + public Long getSeqNum() { + return seqNum; + } + + + public Long getVmId() { + return vmId; + } + +} diff --git a/api/src/com/cloud/api/ResponseGenerator.java b/api/src/com/cloud/api/ResponseGenerator.java index 3122ed217f7..99bdca3eb22 100755 --- a/api/src/com/cloud/api/ResponseGenerator.java +++ b/api/src/com/cloud/api/ResponseGenerator.java @@ -82,6 +82,7 @@ import com.cloud.network.rules.LoadBalancer; import com.cloud.network.rules.PortForwardingRule; import com.cloud.network.rules.StaticNatRule; import com.cloud.network.security.IngressRule; +import com.cloud.network.security.EgressRule; import com.cloud.network.security.SecurityGroup; import com.cloud.network.security.SecurityGroupRules; import com.cloud.offering.DiskOffering; @@ -176,6 +177,8 @@ public interface ResponseGenerator { SecurityGroupResponse createSecurityGroupResponseFromIngressRule(List ingressRules); + SecurityGroupResponse createSecurityGroupResponseFromEgressRule(List egressRules); + SecurityGroupResponse createSecurityGroupResponse(SecurityGroup group); ExtractResponse createExtractResponse(Long uploadId, Long id, Long zoneId, Long accountId, String mode); diff --git a/api/src/com/cloud/api/commands/AuthorizeSecurityGroupEgressCmd.java b/api/src/com/cloud/api/commands/AuthorizeSecurityGroupEgressCmd.java new file mode 100644 index 00000000000..e7c89e6e827 --- /dev/null +++ b/api/src/com/cloud/api/commands/AuthorizeSecurityGroupEgressCmd.java @@ -0,0 +1,233 @@ +/** + * 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.Collection; +import java.util.HashMap; +import java.util.Iterator; +import java.util.List; +import java.util.Map; + +import org.apache.log4j.Logger; + +import com.cloud.api.ApiConstants; +import com.cloud.api.BaseAsyncCmd; +import com.cloud.api.BaseCmd; +import com.cloud.api.Implementation; +import com.cloud.api.Parameter; +import com.cloud.api.ServerApiException; +import com.cloud.api.response.EgressRuleResponse; +import com.cloud.api.response.SecurityGroupResponse; +import com.cloud.async.AsyncJob; +import com.cloud.event.EventTypes; +import com.cloud.exception.InvalidParameterValueException; +import com.cloud.network.security.EgressRule; +import com.cloud.user.Account; +import com.cloud.user.UserContext; +import com.cloud.utils.StringUtils; + +@Implementation(responseObject = EgressRuleResponse.class, description = "Authorizes a particular ingress rule for this security group") +@SuppressWarnings("rawtypes") +public class AuthorizeSecurityGroupEgressCmd extends BaseAsyncCmd { + public static final Logger s_logger = Logger.getLogger(AuthorizeSecurityGroupEgressCmd.class.getName()); + + private static final String s_name = "authorizesecuritygroupingress"; + + // /////////////////////////////////////////////////// + // ////////////// API parameters ///////////////////// + // /////////////////////////////////////////////////// + + @Parameter(name = ApiConstants.PROTOCOL, type = CommandType.STRING, description = "TCP is default. UDP is the other supported protocol") + private String protocol; + + @Parameter(name = ApiConstants.START_PORT, type = CommandType.INTEGER, description = "start port for this ingress rule") + private Integer startPort; + + @Parameter(name = ApiConstants.END_PORT, type = CommandType.INTEGER, description = "end port for this ingress rule") + private Integer endPort; + + @Parameter(name = ApiConstants.ICMP_TYPE, type = CommandType.INTEGER, description = "type of the icmp message being sent") + private Integer icmpType; + + @Parameter(name = ApiConstants.ICMP_CODE, type = CommandType.INTEGER, description = "error code for this icmp message") + private Integer icmpCode; + + @Parameter(name=ApiConstants.CIDR_LIST, type=CommandType.LIST, collectionType=CommandType.STRING, description="the cidr list associated") + private List cidrList; + + @Parameter(name = ApiConstants.USER_SECURITY_GROUP_LIST, type = CommandType.MAP, description = "user to security group mapping") + private Map userSecurityGroupList; + + @Parameter(name=ApiConstants.DOMAIN_ID, type=CommandType.LONG, description="an optional domainId for the security group. If the account parameter is used, domainId must also be used.") + private Long domainId; + + @Parameter(name=ApiConstants.ACCOUNT, type=CommandType.STRING, description="an optional account for the virtual machine. Must be used with domainId.") + private String accountName; + + @Parameter(name=ApiConstants.SECURITY_GROUP_ID, type=CommandType.LONG, description="The ID of the security group. Mutually exclusive with securityGroupName parameter") + private Long securityGroupId; + + @Parameter(name=ApiConstants.SECURITY_GROUP_NAME, type=CommandType.STRING, description="The name of the security group. Mutually exclusive with securityGroupName parameter") + private String securityGroupName; + + ///////////////////////////////////////////////////// + /////////////////// Accessors /////////////////////// + ///////////////////////////////////////////////////// + + public String getAccountName() { + return accountName; + } + + public List getCidrList() { + return cidrList; + } + + public Integer getEndPort() { + return endPort; + } + + public Integer getIcmpCode() { + return icmpCode; + } + + public Integer getIcmpType() { + return icmpType; + } + + public Long getSecurityGroupId() { + if (securityGroupId != null && securityGroupName != null) { + throw new InvalidParameterValueException("securityGroupId and securityGroupName parameters are mutually exclusive"); + } + + if (securityGroupName != null) { + securityGroupId = _responseGenerator.getSecurityGroupId(securityGroupName, getEntityOwnerId()); + if (securityGroupId == null) { + throw new InvalidParameterValueException("Unable to find security group " + securityGroupName + " for account id=" + getEntityOwnerId()); + } + securityGroupName = null; + } + + if (securityGroupId == null) { + throw new InvalidParameterValueException("Either securityGroupId or securityGroupName is required by authorizeSecurityGroupEgress command"); + } + + return securityGroupId; + } + + public String getProtocol() { + if (protocol == null) { + return "all"; + } + return protocol; + } + + public Integer getStartPort() { + return startPort; + } + + public Map getUserSecurityGroupList() { + return userSecurityGroupList; + } + + // /////////////////////////////////////////////////// + // ///////////// API Implementation/////////////////// + // /////////////////////////////////////////////////// + + @Override + public String getCommandName() { + return s_name; + } + + public static String getResultObjectName() { + return "securitygroup"; + } + + @Override + public long getEntityOwnerId() { + Account account = UserContext.current().getCaller(); + if ((account == null) || isAdmin(account.getType())) { + if ((domainId != null) && (accountName != null)) { + Account userAccount = _responseGenerator.findAccountByNameDomain(accountName, domainId); + if (userAccount != null) { + return userAccount.getId(); + } else { + throw new InvalidParameterValueException("Unable to find account by name " + accountName + " in domain " + domainId); + } + } + } + + return account.getId(); + } + + @Override + public String getEventType() { + return EventTypes.EVENT_SECURITY_GROUP_AUTHORIZE_INGRESS; + } + + @Override + public String getEventDescription() { + StringBuilder sb = new StringBuilder(); + if (getUserSecurityGroupList() != null) { + sb.append("group list(group/account): "); + Collection userGroupCollection = getUserSecurityGroupList().values(); + Iterator iter = userGroupCollection.iterator(); + + HashMap userGroup = (HashMap) iter.next(); + String group = (String) userGroup.get("group"); + String authorizedAccountName = (String) userGroup.get("account"); + sb.append(group + "/" + authorizedAccountName); + + while (iter.hasNext()) { + userGroup = (HashMap) iter.next(); + group = (String) userGroup.get("group"); + authorizedAccountName = (String) userGroup.get("account"); + sb.append(", " + group + "/" + authorizedAccountName); + } + } else if (getCidrList() != null) { + sb.append("cidr list: "); + sb.append(StringUtils.join(getCidrList(), ", ")); + } else { + sb.append(""); + } + + return "authorizing ingress to group: " + getSecurityGroupId() + " to " + sb.toString(); + } + + @Override + public void execute() { + List egressRules = _securityGroupService.authorizeSecurityGroupEgress(this); + if (egressRules != null && !egressRules.isEmpty()) { + SecurityGroupResponse response = _responseGenerator.createSecurityGroupResponseFromEgressRule(egressRules); + this.setResponseObject(response); + } else { + throw new ServerApiException(BaseCmd.INTERNAL_ERROR, "Failed to authorize security group ingress rule(s)"); + } + + } + + @Override + public AsyncJob.Type getInstanceType() { + return AsyncJob.Type.SecurityGroup; + } + + @Override + public Long getInstanceId() { + return getSecurityGroupId(); + } +} diff --git a/api/src/com/cloud/api/commands/RevokeSecurityGroupEgressCmd.java b/api/src/com/cloud/api/commands/RevokeSecurityGroupEgressCmd.java new file mode 100644 index 00000000000..bfddc9ebc5b --- /dev/null +++ b/api/src/com/cloud/api/commands/RevokeSecurityGroupEgressCmd.java @@ -0,0 +1,109 @@ +/** + * 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.ApiConstants; +import com.cloud.api.BaseAsyncCmd; +import com.cloud.api.BaseCmd; +import com.cloud.api.Implementation; +import com.cloud.api.Parameter; +import com.cloud.api.ServerApiException; +import com.cloud.api.response.SuccessResponse; +import com.cloud.async.AsyncJob; +import com.cloud.event.EventTypes; +import com.cloud.network.security.SecurityGroup; +import com.cloud.user.Account; + +@Implementation(responseObject = SuccessResponse.class, description = "Deletes a particular ingress rule from this security group") +public class RevokeSecurityGroupEgressCmd extends BaseAsyncCmd { + public static final Logger s_logger = Logger.getLogger(RevokeSecurityGroupEgressCmd.class.getName()); + + private static final String s_name = "revokesecuritygroupingress"; + + // /////////////////////////////////////////////////// + // ////////////// API parameters ///////////////////// + // /////////////////////////////////////////////////// + + @Parameter(name = ApiConstants.ID, type = CommandType.LONG, required = true, description = "The ID of the ingress rule") + private Long id; + + // /////////////////////////////////////////////////// + // ///////////////// Accessors /////////////////////// + // /////////////////////////////////////////////////// + + public Long getId() { + return id; + } + + // /////////////////////////////////////////////////// + // ///////////// API Implementation/////////////////// + // /////////////////////////////////////////////////// + + @Override + public String getCommandName() { + return s_name; + } + + public static String getResultObjectName() { + return "revokesecuritygroupingress"; + } + + @Override + public long getEntityOwnerId() { + SecurityGroup group = _entityMgr.findById(SecurityGroup.class, getId()); + if (group != null) { + return group.getAccountId(); + } + + return Account.ACCOUNT_ID_SYSTEM; // no account info given, parent this command to SYSTEM so ERROR events are tracked + } + + @Override + public String getEventType() { + return EventTypes.EVENT_SECURITY_GROUP_REVOKE_INGRESS; + } + + @Override + public String getEventDescription() { + return "revoking ingress rule id: " + getId(); + } + + @Override + public void execute() { + boolean result = _securityGroupService.revokeSecurityGroupEgress(this); + if (result) { + SuccessResponse response = new SuccessResponse(getCommandName()); + this.setResponseObject(response); + } else { + throw new ServerApiException(BaseCmd.INTERNAL_ERROR, "Failed to revoke security group ingress rule"); + } + } + + @Override + public AsyncJob.Type getInstanceType() { + return AsyncJob.Type.SecurityGroup; + } + + @Override + public Long getInstanceId() { + return getId(); + } +} diff --git a/api/src/com/cloud/api/response/EgressRuleResponse.java b/api/src/com/cloud/api/response/EgressRuleResponse.java new file mode 100644 index 00000000000..ca3b9fba49d --- /dev/null +++ b/api/src/com/cloud/api/response/EgressRuleResponse.java @@ -0,0 +1,123 @@ +/** + * 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.api.ApiConstants; +import com.cloud.serializer.Param; +import com.google.gson.annotations.SerializedName; + +public class EgressRuleResponse extends BaseResponse { + @SerializedName("ruleid") @Param(description="the id of the ingress rule") + private Long ruleId; + + @SerializedName("protocol") @Param(description="the protocol of the ingress rule") + private String protocol; + + @SerializedName(ApiConstants.ICMP_TYPE) @Param(description="the type of the ICMP message response") + private Integer icmpType; + + @SerializedName(ApiConstants.ICMP_CODE) @Param(description="the code for the ICMP message response") + private Integer icmpCode; + + @SerializedName(ApiConstants.START_PORT) @Param(description="the starting IP of the ingress rule") + private Integer startPort; + + @SerializedName(ApiConstants.END_PORT) @Param(description="the ending IP of the ingress rule ") + private Integer endPort; + + @SerializedName(ApiConstants.SECURITY_GROUP_NAME) @Param(description="security group name") + private String securityGroupName; + + @SerializedName(ApiConstants.ACCOUNT) @Param(description="account owning the ingress rule") + private String accountName; + + @SerializedName(ApiConstants.CIDR) @Param(description="the CIDR notation for the base IP address of the ingress rule") + private String cidr; + + public Long getRuleId() { + return ruleId; + } + + public void setRuleId(Long ruleId) { + this.ruleId = ruleId; + } + + public String getProtocol() { + return protocol; + } + + public void setProtocol(String protocol) { + this.protocol = protocol; + } + + public Integer getIcmpType() { + return icmpType; + } + + public void setIcmpType(Integer icmpType) { + this.icmpType = icmpType; + } + + public Integer getIcmpCode() { + return icmpCode; + } + + public void setIcmpCode(Integer icmpCode) { + this.icmpCode = icmpCode; + } + + public Integer getStartPort() { + return startPort; + } + + public void setStartPort(Integer startPort) { + this.startPort = startPort; + } + + public Integer getEndPort() { + return endPort; + } + + public void setEndPort(Integer endPort) { + this.endPort = endPort; + } + + public String getSecurityGroupName() { + return securityGroupName; + } + + public void setSecurityGroupName(String securityGroupName) { + this.securityGroupName = securityGroupName; + } + + public String getAccountName() { + return accountName; + } + + public void setAccountName(String accountName) { + this.accountName = accountName; + } + + public String getCidr() { + return cidr; + } + + public void setCidr(String cidr) { + this.cidr = cidr; + } +} diff --git a/api/src/com/cloud/api/response/SecurityGroupResponse.java b/api/src/com/cloud/api/response/SecurityGroupResponse.java index 48f28fc9b99..b4d67466a64 100644 --- a/api/src/com/cloud/api/response/SecurityGroupResponse.java +++ b/api/src/com/cloud/api/response/SecurityGroupResponse.java @@ -51,6 +51,9 @@ public class SecurityGroupResponse extends BaseResponse { @SerializedName("ingressrule") @Param(description="the list of ingress rules associated with the security group", responseObject = IngressRuleResponse.class) private List ingressRules; + @SerializedName("egressrule") @Param(description="the list of ingress rules associated with the security group", responseObject = EgressRuleResponse.class) + private List egressRules; + public Long getId() { return id; } @@ -102,10 +105,18 @@ public class SecurityGroupResponse extends BaseResponse { public List getIngressRules() { return ingressRules; } + + public List getEgressRules() { + return egressRules; + } public void setIngressRules(List ingressRules) { this.ingressRules = ingressRules; } + + public void setEgressRules(List egressRules) { + this.egressRules = egressRules; + } @Override public Long getObjectId() { diff --git a/api/src/com/cloud/network/security/EgressRule.java b/api/src/com/cloud/network/security/EgressRule.java new file mode 100644 index 00000000000..005e91d61d5 --- /dev/null +++ b/api/src/com/cloud/network/security/EgressRule.java @@ -0,0 +1,39 @@ +/** + * 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.security; + +import com.cloud.async.AsyncInstanceCreateStatus; + +public interface EgressRule { + long getId(); + + long getSecurityGroupId(); + + int getStartPort(); + + int getEndPort(); + + String getProtocol(); + + AsyncInstanceCreateStatus getCreateStatus(); + + Long getAllowedNetworkId(); + + String getAllowedDestinationIpCidr(); + +} diff --git a/api/src/com/cloud/network/security/SecurityGroupService.java b/api/src/com/cloud/network/security/SecurityGroupService.java index a25ba39c423..1642a2e57dc 100644 --- a/api/src/com/cloud/network/security/SecurityGroupService.java +++ b/api/src/com/cloud/network/security/SecurityGroupService.java @@ -20,10 +20,12 @@ package com.cloud.network.security; import java.util.List; import com.cloud.api.commands.AuthorizeSecurityGroupIngressCmd; +import com.cloud.api.commands.AuthorizeSecurityGroupEgressCmd; import com.cloud.api.commands.CreateSecurityGroupCmd; import com.cloud.api.commands.DeleteSecurityGroupCmd; import com.cloud.api.commands.ListSecurityGroupsCmd; import com.cloud.api.commands.RevokeSecurityGroupIngressCmd; +import com.cloud.api.commands.RevokeSecurityGroupEgressCmd; import com.cloud.exception.InvalidParameterValueException; import com.cloud.exception.PermissionDeniedException; import com.cloud.exception.ResourceInUseException; @@ -36,6 +38,7 @@ public interface SecurityGroupService { */ public SecurityGroup createSecurityGroup(CreateSecurityGroupCmd command) throws PermissionDeniedException, InvalidParameterValueException; boolean revokeSecurityGroupIngress(RevokeSecurityGroupIngressCmd cmd); + boolean revokeSecurityGroupEgress(RevokeSecurityGroupEgressCmd cmd); boolean deleteSecurityGroup(DeleteSecurityGroupCmd cmd) throws ResourceInUseException; @@ -47,6 +50,6 @@ public interface SecurityGroupService { public List searchForSecurityGroupRules(ListSecurityGroupsCmd cmd) throws PermissionDeniedException, InvalidParameterValueException; public List authorizeSecurityGroupIngress(AuthorizeSecurityGroupIngressCmd cmd); - + public List authorizeSecurityGroupEgress(AuthorizeSecurityGroupEgressCmd cmd); } diff --git a/client/tomcatconf/commands.properties.in b/client/tomcatconf/commands.properties.in index 67a1af26975..8510f3e804a 100755 --- a/client/tomcatconf/commands.properties.in +++ b/client/tomcatconf/commands.properties.in @@ -221,6 +221,8 @@ createSecurityGroup=com.cloud.api.commands.CreateSecurityGroupCmd;15 deleteSecurityGroup=com.cloud.api.commands.DeleteSecurityGroupCmd;15 authorizeSecurityGroupIngress=com.cloud.api.commands.AuthorizeSecurityGroupIngressCmd;15 revokeSecurityGroupIngress=com.cloud.api.commands.RevokeSecurityGroupIngressCmd;15 +authorizeSecurityGroupEgress=com.cloud.api.commands.AuthorizeSecurityGroupEgressCmd;15 +revokeSecurityGroupEgress=com.cloud.api.commands.RevokeSecurityGroupEgressCmd;15 listSecurityGroups=com.cloud.api.commands.ListSecurityGroupsCmd;15 #### vm group commands diff --git a/core/src/com/cloud/hypervisor/xen/resource/CitrixResourceBase.java b/core/src/com/cloud/hypervisor/xen/resource/CitrixResourceBase.java index 74b494ce947..6a8e9ff519b 100644 --- a/core/src/com/cloud/hypervisor/xen/resource/CitrixResourceBase.java +++ b/core/src/com/cloud/hypervisor/xen/resource/CitrixResourceBase.java @@ -114,6 +114,8 @@ import com.cloud.agent.api.RebootCommand; import com.cloud.agent.api.RebootRouterCommand; import com.cloud.agent.api.SecurityIngressRuleAnswer; import com.cloud.agent.api.SecurityIngressRulesCmd; +import com.cloud.agent.api.SecurityEgressRuleAnswer; +import com.cloud.agent.api.SecurityEgressRulesCmd; import com.cloud.agent.api.SetupAnswer; import com.cloud.agent.api.SetupCommand; import com.cloud.agent.api.StartAnswer; @@ -479,6 +481,8 @@ public abstract class CitrixResourceBase implements ServerResource, HypervisorRe return execute((CheckSshCommand)cmd); } else if (clazz == SecurityIngressRulesCmd.class) { return execute((SecurityIngressRulesCmd) cmd); + } else if (clazz == SecurityEgressRulesCmd.class) { + return execute((SecurityEgressRulesCmd) cmd); } else if (clazz == OvsCreateGreTunnelCommand.class) { return execute((OvsCreateGreTunnelCommand)cmd); } else if (clazz == OvsSetTagAndFlowCommand.class) { @@ -4696,6 +4700,36 @@ public abstract class CitrixResourceBase implements ServerResource, HypervisorRe return new OvsCreateGreTunnelAnswer(cmd, false, "EXCEPTION", _host.ip, bridge); } + private Answer execute(SecurityEgressRulesCmd cmd) { + Connection conn = getConnection(); + if (s_logger.isTraceEnabled()) { + s_logger.trace("Sending network rules command to " + _host.ip); + } + + if (!_canBridgeFirewall) { + s_logger.info("Host " + _host.ip + " cannot do bridge firewalling"); + return new SecurityEgressRuleAnswer(cmd, false, "Host " + _host.ip + " cannot do bridge firewalling"); + } + + String result = callHostPlugin(conn, "vmops", "network_rules", + "vmName", cmd.getVmName(), + "vmIP", cmd.getGuestIp(), + "vmMAC", cmd.getGuestMac(), + "type", "egress", + "vmID", Long.toString(cmd.getVmId()), + "signature", cmd.getSignature(), + "seqno", Long.toString(cmd.getSeqNum()), + "rules", cmd.stringifyRules()); + + if (result == null || result.isEmpty() || !Boolean.parseBoolean(result)) { + s_logger.warn("Failed to program network rules for vm " + cmd.getVmName()); + return new SecurityEgressRuleAnswer(cmd, false, "programming network rules failed"); + } else { + s_logger.info("Programmed network rules for vm " + cmd.getVmName() + " guestIp=" + cmd.getGuestIp() + ", numrules=" + cmd.getRuleSet().length); + return new SecurityEgressRuleAnswer(cmd); + } + } + private Answer execute(SecurityIngressRulesCmd cmd) { Connection conn = getConnection(); if (s_logger.isTraceEnabled()) { @@ -4711,6 +4745,7 @@ public abstract class CitrixResourceBase implements ServerResource, HypervisorRe "vmName", cmd.getVmName(), "vmIP", cmd.getGuestIp(), "vmMAC", cmd.getGuestMac(), + "type", "ingress", "vmID", Long.toString(cmd.getVmId()), "signature", cmd.getSignature(), "seqno", Long.toString(cmd.getSeqNum()), diff --git a/core/src/com/cloud/network/security/EgressRuleVO.java b/core/src/com/cloud/network/security/EgressRuleVO.java new file mode 100644 index 00000000000..01d7791ecb1 --- /dev/null +++ b/core/src/com/cloud/network/security/EgressRuleVO.java @@ -0,0 +1,126 @@ +/** + * 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.security; + +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.EnumType; +import javax.persistence.Enumerated; +import javax.persistence.GeneratedValue; +import javax.persistence.GenerationType; +import javax.persistence.Id; +import javax.persistence.Table; + +import com.cloud.async.AsyncInstanceCreateStatus; +import com.google.gson.annotations.Expose; + +@Entity +@Table(name = ("security_egress_rule")) +public class EgressRuleVO implements EgressRule { + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + @Column(name = "id") + private long id; + + @Column(name = "security_group_id") + private long securityGroupId; + + @Column(name = "start_port") + private int startPort; + + @Column(name = "end_port") + private int endPort; + + @Column(name = "protocol") + private String protocol; + + @Column(name = "allowed_network_id", nullable = true) + private Long allowedNetworkId = null; + + @Column(name = "allowed_ip_cidr", nullable = true) + private String allowedDestinationIpCidr = null; + + @Expose + @Column(name = "create_status", updatable = true, nullable = false) + @Enumerated(value = EnumType.STRING) + private AsyncInstanceCreateStatus createStatus; + + public EgressRuleVO() { + } + + public EgressRuleVO(long securityGroupId, int fromPort, int toPort, String protocol, long allowedNetworkId) { + this.securityGroupId = securityGroupId; + this.startPort = fromPort; + this.endPort = toPort; + this.protocol = protocol; + this.allowedNetworkId = allowedNetworkId; + } + + public EgressRuleVO(long securityGroupId, int fromPort, int toPort, String protocol, String allowedIpCidr) { + this.securityGroupId = securityGroupId; + this.startPort = fromPort; + this.endPort = toPort; + this.protocol = protocol; + this.allowedDestinationIpCidr = allowedIpCidr; + } + + @Override + public long getId() { + return id; + } + + @Override + public long getSecurityGroupId() { + return securityGroupId; + } + + @Override + public int getStartPort() { + return startPort; + } + + @Override + public int getEndPort() { + return endPort; + } + + @Override + public String getProtocol() { + return protocol; + } + + @Override + public AsyncInstanceCreateStatus getCreateStatus() { + return createStatus; + } + + public void setCreateStatus(AsyncInstanceCreateStatus createStatus) { + this.createStatus = createStatus; + } + + @Override + public Long getAllowedNetworkId() { + return allowedNetworkId; + } + + @Override + public String getAllowedDestinationIpCidr() { + return allowedDestinationIpCidr; + } +} diff --git a/scripts/vm/hypervisor/xenserver/vmops b/scripts/vm/hypervisor/xenserver/vmops index 2e3929290ee..affbd932e27 100755 --- a/scripts/vm/hypervisor/xenserver/vmops +++ b/scripts/vm/hypervisor/xenserver/vmops @@ -1,6 +1,6 @@ #!/usr/bin/python -# Version @VERSION@ -# +# Version 2.2.8.2011-08-18T08:15:52Z +# # A plugin for executing script needed by vmops cloud import os, sys, time @@ -431,6 +431,7 @@ def ipset(ipsetname, proto, start, end, ips): def destroy_network_rules_for_vm(session, args): vm_name = args.pop('vmName') vmchain = chain_name(vm_name) + vmchain_egress = chain_name(vm_name) + "-egress" vmchain_default = chain_name_def(vm_name) delete_rules_for_vm_in_bridge_firewall_chain(vm_name) @@ -450,6 +451,11 @@ def destroy_network_rules_for_vm(session, args): util.SMlog("Ignoring failure to delete chain " + vmchain) + try: + util.pread2(['iptables', '-F', vmchain_egress]) + util.pread2(['iptables', '-X', vmchain_egress]) + except: + util.SMlog("Ignoring failure to delete chain " + vmchain_egress) remove_rule_log_for_vm(vm_name) @@ -623,6 +629,7 @@ def default_network_rules(session, args): vmchain = chain_name(vm_name) + vmchain_egress = chain_name(vm_name) +"-egress" vmchain_default = chain_name_def(vm_name) destroy_ebtables_rules(vmchain) @@ -632,6 +639,11 @@ def default_network_rules(session, args): util.pread2(['iptables', '-N', vmchain]) except: util.pread2(['iptables', '-F', vmchain]) + + try: + util.pread2(['iptables', '-N', vmchain_egress]) + except: + util.pread2(['iptables', '-F', vmchain_egress]) try: util.pread2(['iptables', '-N', vmchain_default]) @@ -650,7 +662,7 @@ def default_network_rules(session, args): #don't let vm spoof its ip address for v in vifs: - util.pread2(['iptables', '-A', vmchain_default, '-m', 'physdev', '--physdev-is-bridged', '--physdev-in', v, '--source', vm_ip, '-j', 'RETURN']) + util.pread2(['iptables', '-A', vmchain_default, '-m', 'physdev', '--physdev-is-bridged', '--physdev-in', v, '--source', vm_ip, '-j', vmchain_egress]) util.pread2(['iptables', '-A', vmchain_default, '-j', vmchain]) except: util.SMlog("Failed to program default rules for vm " + vm_name) @@ -944,6 +956,7 @@ def network_rules(session, args): vm_name = args.get('vmName') vm_ip = args.get('vmIP') vm_id = args.get('vmID') + type = args.get('type') signature = args.pop('signature') seqno = args.pop('seqno') try: @@ -968,8 +981,11 @@ def network_rules(session, args): vifs.append(tap) except: pass - - vmchain = chain_name(vm_name) + + if type == 'egress': + vmchain = chain_name(vm_name) + "-egress" + else: + vmchain = chain_name(vm_name) changes = check_rule_log_for_vm (vm_name, vm_id, vm_ip, domid, signature, seqno) diff --git a/server/src/com/cloud/api/ApiResponseHelper.java b/server/src/com/cloud/api/ApiResponseHelper.java index cb7a3a55219..7cc12d90e6e 100755 --- a/server/src/com/cloud/api/ApiResponseHelper.java +++ b/server/src/com/cloud/api/ApiResponseHelper.java @@ -50,6 +50,8 @@ import com.cloud.api.response.HostResponse; import com.cloud.api.response.IPAddressResponse; import com.cloud.api.response.IngressRuleResponse; import com.cloud.api.response.IngressRuleResultObject; +import com.cloud.api.response.EgressRuleResponse; +import com.cloud.api.response.EgressRuleResultObject; import com.cloud.api.response.InstanceGroupResponse; import com.cloud.api.response.IpForwardingRuleResponse; import com.cloud.api.response.ListResponse; @@ -115,6 +117,7 @@ import com.cloud.network.rules.LoadBalancer; import com.cloud.network.rules.PortForwardingRule; import com.cloud.network.rules.StaticNatRule; import com.cloud.network.security.IngressRule; +import com.cloud.network.security.EgressRule; import com.cloud.network.security.SecurityGroup; import com.cloud.network.security.SecurityGroupRules; import com.cloud.offering.DiskOffering; @@ -1988,7 +1991,76 @@ public class ApiResponseHelper implements ResponseGenerator { } return response; } + + @Override + public SecurityGroupResponse createSecurityGroupResponseFromEgressRule(List egressRules) { + SecurityGroupResponse response = new SecurityGroupResponse(); + Map securiytGroupAccounts = new HashMap(); + Map allowedSecurityGroups = new HashMap(); + Map allowedSecuriytGroupAccounts = new HashMap(); + if ((egressRules != null) && !egressRules.isEmpty()) { + SecurityGroup securityGroup = ApiDBUtils.findSecurityGroupById(egressRules.get(0).getSecurityGroupId()); + response.setId(securityGroup.getId()); + response.setName(securityGroup.getName()); + response.setDescription(securityGroup.getDescription()); + + Account account = securiytGroupAccounts.get(securityGroup.getAccountId()); + + if (account == null) { + account = ApiDBUtils.findAccountById(securityGroup.getAccountId()); + securiytGroupAccounts.put(securityGroup.getAccountId(), account); + } + + response.setAccountName(account.getAccountName()); + response.setDomainId(account.getDomainId()); + response.setDomainName(ApiDBUtils.findDomainById(securityGroup.getDomainId()).getName()); + + List responses = new ArrayList(); + for (EgressRule egressRule : egressRules) { + EgressRuleResponse egressData = new EgressRuleResponse(); + + egressData.setRuleId(egressRule.getId()); + egressData.setProtocol(egressRule.getProtocol()); + if ("icmp".equalsIgnoreCase(egressRule.getProtocol())) { + egressData.setIcmpType(egressRule.getStartPort()); + egressData.setIcmpCode(egressRule.getEndPort()); + } else { + egressData.setStartPort(egressRule.getStartPort()); + egressData.setEndPort(egressRule.getEndPort()); + } + + Long allowedSecurityGroupId = egressRule.getAllowedNetworkId(); + if (allowedSecurityGroupId != null) { + SecurityGroup allowedSecurityGroup = allowedSecurityGroups.get(allowedSecurityGroupId); + if (allowedSecurityGroup == null) { + allowedSecurityGroup = ApiDBUtils.findSecurityGroupById(allowedSecurityGroupId); + allowedSecurityGroups.put(allowedSecurityGroupId, allowedSecurityGroup); + } + + egressData.setSecurityGroupName(allowedSecurityGroup.getName()); + + Account allowedAccount = allowedSecuriytGroupAccounts.get(allowedSecurityGroup.getAccountId()); + if (allowedAccount == null) { + allowedAccount = ApiDBUtils.findAccountById(allowedSecurityGroup.getAccountId()); + allowedSecuriytGroupAccounts.put(allowedAccount.getId(), allowedAccount); + } + + egressData.setAccountName(allowedAccount.getAccountName()); + } else { + egressData.setCidr(egressRule.getAllowedDestinationIpCidr()); + } + + egressData.setObjectName("egressrule"); + responses.add(egressData); + } + response.setEgressRules(responses); + response.setObjectName("securitygroup"); + + } + return response; + } + @Override public NetworkOfferingResponse createNetworkOfferingResponse(NetworkOffering offering) { NetworkOfferingResponse response = new NetworkOfferingResponse(); diff --git a/server/src/com/cloud/api/response/EgressRuleResultObject.java b/server/src/com/cloud/api/response/EgressRuleResultObject.java new file mode 100644 index 00000000000..85313c7cad2 --- /dev/null +++ b/server/src/com/cloud/api/response/EgressRuleResultObject.java @@ -0,0 +1,112 @@ +/** + * 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; + +public class EgressRuleResultObject { + @Param(name="id") + private Long id; + + @Param(name="startport") + private int startPort; + + @Param(name="endport") + private int endPort; + + @Param(name="protocol") + private String protocol; + + @Param(name="securitygroup") + private String allowedSecurityGroup = null; + + @Param(name="account") + private String allowedSecGroupAcct = null; + + @Param(name="cidr") + private String allowedDestinationIpCidr = null; + + public EgressRuleResultObject() { } + + public EgressRuleResultObject(Long id, int startPort, int endPort, String protocol, String allowedSecurityGroup, String allowedSecGroupAcct, String allowedSourceIpCidr) { + this.id = id; + this.startPort = startPort; + this.endPort = endPort; + this.protocol = protocol; + this.allowedSecurityGroup = allowedSecurityGroup; + this.allowedSecGroupAcct = allowedSecGroupAcct; + this.allowedDestinationIpCidr = allowedSourceIpCidr; + } + + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + public int getStartPort() { + return startPort; + } + + public void setStartPort(int startPort) { + this.startPort = startPort; + } + + public int getEndPort() { + return endPort; + } + + public void setEndPort(int endPort) { + this.endPort = endPort; + } + + public String getProtocol() { + return protocol; + } + + public void setProtocol(String protocol) { + this.protocol = protocol; + } + + public String getAllowedSecurityGroup() { + return allowedSecurityGroup; + } + + public void setAllowedSecurityGroup(String allowedSecurityGroup) { + this.allowedSecurityGroup = allowedSecurityGroup; + } + + public String getAllowedSecGroupAcct() { + return allowedSecGroupAcct; + } + + public void setAllowedSecGroupAcct(String allowedSecGroupAcct) { + this.allowedSecGroupAcct = allowedSecGroupAcct; + } + + public String getAllowedDestinationIpCidr() { + return allowedDestinationIpCidr; + } + + public void setAllowedDestinationIpCidr(String allowedDestinationIpCidr) { + this.allowedDestinationIpCidr = allowedDestinationIpCidr; + } +} diff --git a/server/src/com/cloud/configuration/DefaultComponentLibrary.java b/server/src/com/cloud/configuration/DefaultComponentLibrary.java index 92e71d8ed9f..903a7523480 100755 --- a/server/src/com/cloud/configuration/DefaultComponentLibrary.java +++ b/server/src/com/cloud/configuration/DefaultComponentLibrary.java @@ -98,6 +98,7 @@ import com.cloud.network.rules.RulesManagerImpl; import com.cloud.network.rules.dao.PortForwardingRulesDaoImpl; import com.cloud.network.security.SecurityGroupManagerImpl; import com.cloud.network.security.dao.IngressRuleDaoImpl; +import com.cloud.network.security.dao.EgressRuleDaoImpl; import com.cloud.network.security.dao.SecurityGroupDaoImpl; import com.cloud.network.security.dao.SecurityGroupRulesDaoImpl; import com.cloud.network.security.dao.SecurityGroupVMMapDaoImpl; @@ -203,6 +204,7 @@ public class DefaultComponentLibrary extends ComponentLibraryBase implements Com addDao("DataCenterIpAddressDao", DataCenterIpAddressDaoImpl.class); addDao("SecurityGroupDao", SecurityGroupDaoImpl.class); addDao("IngressRuleDao", IngressRuleDaoImpl.class); + addDao("EgressRuleDao", EgressRuleDaoImpl.class); addDao("SecurityGroupVMMapDao", SecurityGroupVMMapDaoImpl.class); addDao("SecurityGroupRulesDao", SecurityGroupRulesDaoImpl.class); addDao("SecurityGroupWorkDao", SecurityGroupWorkDaoImpl.class); diff --git a/server/src/com/cloud/network/security/SecurityGroupManagerImpl.java b/server/src/com/cloud/network/security/SecurityGroupManagerImpl.java index 0706b6f6acf..b9e752d08b2 100755 --- a/server/src/com/cloud/network/security/SecurityGroupManagerImpl.java +++ b/server/src/com/cloud/network/security/SecurityGroupManagerImpl.java @@ -43,12 +43,16 @@ import com.cloud.agent.AgentManager; import com.cloud.agent.api.NetworkRulesSystemVmCommand; import com.cloud.agent.api.SecurityIngressRulesCmd; import com.cloud.agent.api.SecurityIngressRulesCmd.IpPortAndProto; +import com.cloud.agent.api.SecurityEgressRulesCmd; +import com.cloud.agent.api.SecurityEgressRulesCmd.EgressIpPortAndProto; import com.cloud.agent.manager.Commands; import com.cloud.api.commands.AuthorizeSecurityGroupIngressCmd; +import com.cloud.api.commands.AuthorizeSecurityGroupEgressCmd; import com.cloud.api.commands.CreateSecurityGroupCmd; import com.cloud.api.commands.DeleteSecurityGroupCmd; import com.cloud.api.commands.ListSecurityGroupsCmd; import com.cloud.api.commands.RevokeSecurityGroupIngressCmd; +import com.cloud.api.commands.RevokeSecurityGroupEgressCmd; import com.cloud.configuration.dao.ConfigurationDao; import com.cloud.domain.Domain; import com.cloud.domain.DomainVO; @@ -64,6 +68,7 @@ import com.cloud.hypervisor.Hypervisor.HypervisorType; import com.cloud.network.NetworkManager; import com.cloud.network.security.SecurityGroupWorkVO.Step; import com.cloud.network.security.dao.IngressRuleDao; +import com.cloud.network.security.dao.EgressRuleDao; import com.cloud.network.security.dao.SecurityGroupDao; import com.cloud.network.security.dao.SecurityGroupRulesDao; import com.cloud.network.security.dao.SecurityGroupVMMapDao; @@ -110,6 +115,8 @@ public class SecurityGroupManagerImpl implements SecurityGroupManager, SecurityG @Inject IngressRuleDao _ingressRuleDao; @Inject + EgressRuleDao _egressRuleDao; + @Inject SecurityGroupVMMapDao _securityGroupVMMapDao; @Inject SecurityGroupRulesDao _securityGroupRulesDao; @@ -281,8 +288,41 @@ public class SecurityGroupManagerImpl implements SecurityGroupManager, SecurityG } } + protected Map> generateEgressRulesForVM(Long userVmId) { - protected Map> generateRulesForVM(Long userVmId) { + Map> allowed = new TreeMap>(); + + List groupsForVm = _securityGroupVMMapDao.listByInstanceId(userVmId); + for (SecurityGroupVMMapVO mapVO : groupsForVm) { + List rules = _egressRuleDao.listBySecurityGroupId(mapVO.getSecurityGroupId()); + for (EgressRuleVO rule : rules) { + PortAndProto portAndProto = new PortAndProto(rule.getProtocol(), rule.getStartPort(), rule.getEndPort()); + Set cidrs = allowed.get(portAndProto); + if (cidrs == null) { + cidrs = new TreeSet(new CidrComparator()); + } + if (rule.getAllowedNetworkId() != null) { + List allowedInstances = _securityGroupVMMapDao.listBySecurityGroup(rule.getAllowedNetworkId(), State.Running); + for (SecurityGroupVMMapVO ngmapVO : allowedInstances) { + Nic defaultNic = _networkMgr.getDefaultNic(ngmapVO.getInstanceId()); + if (defaultNic != null) { + String cidr = defaultNic.getIp4Address(); + cidr = cidr + "/32"; + cidrs.add(cidr); + } + } + } else if (rule.getAllowedDestinationIpCidr() != null) { + cidrs.add(rule.getAllowedDestinationIpCidr()); + } + if (cidrs.size() > 0) { + allowed.put(portAndProto, cidrs); + } + } + } + + return allowed; + } + protected Map> generateIngressRulesForVM(Long userVmId) { Map> allowed = new TreeMap>(); @@ -422,7 +462,7 @@ public class SecurityGroupManagerImpl implements SecurityGroupManager, SecurityG return affectedVms; } - protected SecurityIngressRulesCmd generateRulesetCmd(String vmName, String guestIp, String guestMac, Long vmId, String signature, long seqnum, Map> rules) { + protected SecurityIngressRulesCmd generateIngressRulesetCmd(String vmName, String guestIp, String guestMac, Long vmId, String signature, long seqnum, Map> rules) { List result = new ArrayList(); for (PortAndProto pAp : rules.keySet()) { Set cidrs = rules.get(pAp); @@ -433,7 +473,19 @@ public class SecurityGroupManagerImpl implements SecurityGroupManager, SecurityG } return new SecurityIngressRulesCmd(guestIp, guestMac, vmName, vmId, signature, seqnum, result.toArray(new IpPortAndProto[result.size()])); } - + + protected SecurityEgressRulesCmd generateEgressRulesetCmd(String vmName, String guestIp, String guestMac, Long vmId, String signature, long seqnum, Map> rules) { + List result = new ArrayList(); + for (PortAndProto pAp : rules.keySet()) { + Set cidrs = rules.get(pAp); + if (cidrs.size() > 0) { + EgressIpPortAndProto ipPortAndProto = new SecurityEgressRulesCmd.EgressIpPortAndProto(pAp.getProto(), pAp.getStartPort(), pAp.getEndPort(), cidrs.toArray(new String[cidrs.size()])); + result.add(ipPortAndProto); + } + } + return new SecurityEgressRulesCmd(guestIp, guestMac, vmName, vmId, signature, seqnum, result.toArray(new EgressIpPortAndProto[result.size()])); + } + protected void handleVmStopped(VMInstanceVO vm) { if (vm.getType() != VirtualMachine.Type.User || !isVmSecurityGroupEnabled(vm.getId())) return; @@ -689,7 +741,232 @@ public class SecurityGroupManagerImpl implements SecurityGroupManager, SecurityG } } + @Override + @DB + @SuppressWarnings("rawtypes") + public List authorizeSecurityGroupEgress(AuthorizeSecurityGroupEgressCmd cmd) { + Long securityGroupId = cmd.getSecurityGroupId(); + String protocol = cmd.getProtocol(); + Integer startPort = cmd.getStartPort(); + Integer endPort = cmd.getEndPort(); + Integer icmpType = cmd.getIcmpType(); + Integer icmpCode = cmd.getIcmpCode(); + List cidrList = cmd.getCidrList(); + Map groupList = cmd.getUserSecurityGroupList(); + Integer startPortOrType = null; + Integer endPortOrCode = null; + // Validate parameters + SecurityGroup securityGroup = _securityGroupDao.findById(securityGroupId); + if (securityGroup == null) { + throw new InvalidParameterValueException("Unable to find security group by id " + securityGroupId); + } + + if (cidrList == null && groupList == null) { + throw new InvalidParameterValueException("At least one cidr or at least one security group needs to be specified"); + } + + Account caller = UserContext.current().getCaller(); + Account owner = _accountMgr.getAccount(securityGroup.getAccountId()); + + if (owner == null) { + throw new InvalidParameterValueException("Unable to find security group owner by id=" + securityGroup.getAccountId()); + } + + // Verify permissions + _accountMgr.checkAccess(caller, securityGroup); + Long domainId = owner.getDomainId(); + + if (protocol == null) { + protocol = NetUtils.ALL_PROTO; + } + + if (!NetUtils.isValidSecurityGroupProto(protocol)) { + throw new InvalidParameterValueException("Invalid protocol " + protocol); + } + if ("icmp".equalsIgnoreCase(protocol)) { + if ((icmpType == null) || (icmpCode == null)) { + throw new InvalidParameterValueException("Invalid ICMP type/code specified, icmpType = " + icmpType + ", icmpCode = " + icmpCode); + } + if (icmpType == -1 && icmpCode != -1) { + throw new InvalidParameterValueException("Invalid icmp type range"); + } + if (icmpCode > 255) { + throw new InvalidParameterValueException("Invalid icmp code "); + } + startPortOrType = icmpType; + endPortOrCode = icmpCode; + } else if (protocol.equals(NetUtils.ALL_PROTO)) { + if ((startPort != null) || (endPort != null)) { + throw new InvalidParameterValueException("Cannot specify startPort or endPort without specifying protocol"); + } + startPortOrType = 0; + endPortOrCode = 0; + } else { + if ((startPort == null) || (endPort == null)) { + throw new InvalidParameterValueException("Invalid port range specified, startPort = " + startPort + ", endPort = " + endPort); + } + if (startPort == 0 && endPort == 0) { + endPort = 65535; + } + if (startPort > endPort) { + throw new InvalidParameterValueException("Invalid port range " + startPort + ":" + endPort); + } + if (startPort > 65535 || endPort > 65535 || startPort < -1 || endPort < -1) { + throw new InvalidParameterValueException("Invalid port numbers " + startPort + ":" + endPort); + } + + if (startPort < 0 || endPort < 0) { + throw new InvalidParameterValueException("Invalid port range " + startPort + ":" + endPort); + } + startPortOrType = startPort; + endPortOrCode = endPort; + } + + protocol = protocol.toLowerCase(); + + List authorizedGroups = new ArrayList(); + if (groupList != null) { + Collection userGroupCollection = groupList.values(); + Iterator iter = userGroupCollection.iterator(); + while (iter.hasNext()) { + HashMap userGroup = (HashMap) iter.next(); + String group = (String) userGroup.get("group"); + String authorizedAccountName = (String) userGroup.get("account"); + + if ((group == null) || (authorizedAccountName == null)) { + throw new InvalidParameterValueException( + "Invalid user group specified, fields 'group' and 'account' cannot be null, please specify groups in the form: userGroupList[0].group=XXX&userGroupList[0].account=YYY"); + } + + Account authorizedAccount = _accountDao.findActiveAccount(authorizedAccountName, domainId); + if (authorizedAccount == null) { + throw new InvalidParameterValueException("Nonexistent account: " + authorizedAccountName + " when trying to authorize ingress for " + securityGroupId + ":" + protocol + ":" + + startPortOrType + ":" + endPortOrCode); + } + + SecurityGroupVO groupVO = _securityGroupDao.findByAccountAndName(authorizedAccount.getId(), group); + if (groupVO == null) { + throw new InvalidParameterValueException("Nonexistent group " + group + " for account " + authorizedAccountName + "/" + domainId + " is given, unable to authorize ingress."); + } + + // Check permissions + _accountMgr.checkAccess(caller, groupVO); + + authorizedGroups.add(groupVO); + } + } + + final Transaction txn = Transaction.currentTxn(); + final Set authorizedGroups2 = new TreeSet(new SecurityGroupVOComparator()); + + authorizedGroups2.addAll(authorizedGroups); // Ensure we don't re-lock the same row + txn.start(); + + // Prevents other threads/management servers from creating duplicate ingress rules + securityGroup = _securityGroupDao.acquireInLockTable(securityGroupId); + if (securityGroup == null) { + s_logger.warn("Could not acquire lock on network security group: id= " + securityGroupId); + return null; + } + List newRules = new ArrayList(); + try { + for (final SecurityGroupVO ngVO : authorizedGroups2) { + final Long ngId = ngVO.getId(); + // Don't delete the referenced group from under us + if (ngVO.getId() != securityGroup.getId()) { + final SecurityGroupVO tmpGrp = _securityGroupDao.lockRow(ngId, false); + if (tmpGrp == null) { + s_logger.warn("Failed to acquire lock on security group: " + ngId); + txn.rollback(); + return null; + } + } + EgressRuleVO egressRule = _egressRuleDao.findByProtoPortsAndAllowedGroupId(securityGroup.getId(), protocol, startPortOrType, endPortOrCode, ngVO.getId()); + if (egressRule != null) { + continue; // rule already exists. + } + egressRule = new EgressRuleVO(securityGroup.getId(), startPortOrType, endPortOrCode, protocol, ngVO.getId()); + egressRule = _egressRuleDao.persist(egressRule); + newRules.add(egressRule); + } + if (cidrList != null) { + for (String cidr : cidrList) { + EgressRuleVO egressRule = _egressRuleDao.findByProtoPortsAndCidr(securityGroup.getId(), protocol, startPortOrType, endPortOrCode, cidr); + if (egressRule != null) { + continue; + } + egressRule = new EgressRuleVO(securityGroup.getId(), startPortOrType, endPortOrCode, protocol, cidr); + egressRule = _egressRuleDao.persist(egressRule); + newRules.add(egressRule); + } + } + if (s_logger.isDebugEnabled()) { + s_logger.debug("Added " + newRules.size() + " rules to security group " + securityGroup.getName()); + } + txn.commit(); + final Set affectedVms = new HashSet(); + affectedVms.addAll(_securityGroupVMMapDao.listVmIdsBySecurityGroup(securityGroup.getId())); + scheduleRulesetUpdateToHosts(affectedVms, true, null); + return newRules; + } catch (Exception e) { + s_logger.warn("Exception caught when adding ingress rules ", e); + throw new CloudRuntimeException("Exception caught when adding ingress rules", e); + } finally { + if (securityGroup != null) { + _securityGroupDao.releaseFromLockTable(securityGroup.getId()); + } + } + } + + @Override + @DB + public boolean revokeSecurityGroupEgress(RevokeSecurityGroupEgressCmd cmd) { + // input validation + Account caller = UserContext.current().getCaller(); + Long id = cmd.getId(); + + IngressRuleVO rule = _ingressRuleDao.findById(id); + if (rule == null) { + s_logger.debug("Unable to find ingress rule with id " + id); + throw new InvalidParameterValueException("Unable to find ingress rule with id " + id); + } + + // Check permissions + SecurityGroup securityGroup = _securityGroupDao.findById(rule.getSecurityGroupId()); + _accountMgr.checkAccess(caller, securityGroup); + + SecurityGroupVO groupHandle = null; + final Transaction txn = Transaction.currentTxn(); + + try { + txn.start(); + // acquire lock on parent group (preserving this logic) + groupHandle = _securityGroupDao.acquireInLockTable(rule.getSecurityGroupId()); + if (groupHandle == null) { + s_logger.warn("Could not acquire lock on security group id: " + rule.getSecurityGroupId()); + return false; + } + + _ingressRuleDao.remove(id); + s_logger.debug("revokeSecurityGroupIngress succeeded for ingress rule id: " + id); + + final Set affectedVms = new HashSet(); + affectedVms.addAll(_securityGroupVMMapDao.listVmIdsBySecurityGroup(groupHandle.getId())); + scheduleRulesetUpdateToHosts(affectedVms, true, null); + + return true; + } catch (Exception e) { + s_logger.warn("Exception caught when deleting ingress rules ", e); + throw new CloudRuntimeException("Exception caught when deleting ingress rules", e); + } finally { + if (groupHandle != null) { + _securityGroupDao.releaseFromLockTable(groupHandle.getId()); + } + txn.commit(); + } + + } @Override @ActionEvent(eventType = EventTypes.EVENT_SECURITY_GROUP_CREATE, eventDescription = "creating security group") public SecurityGroupVO createSecurityGroup(CreateSecurityGroupCmd cmd) throws PermissionDeniedException, InvalidParameterValueException { @@ -791,12 +1068,23 @@ public class SecurityGroupManagerImpl implements SecurityGroupManager, SecurityG seqnum = log.getLogsequence(); if (vm != null && vm.getState() == State.Running) { - Map> rules = generateRulesForVM(userVmId); + Map> ingressRules = generateIngressRulesForVM(userVmId); + Map> egressRules = generateEgressRulesForVM(userVmId); agentId = vm.getHostId(); if (agentId != null) { _rulesetLogDao.findByVmId(work.getInstanceId()); - SecurityIngressRulesCmd cmd = generateRulesetCmd(vm.getInstanceName(), vm.getPrivateIpAddress(), vm.getPrivateMacAddress(), vm.getId(), generateRulesetSignature(rules), seqnum, - rules); + SecurityIngressRulesCmd ingressCmd = generateIngressRulesetCmd(vm.getInstanceName(), vm.getPrivateIpAddress(), vm.getPrivateMacAddress(), vm.getId(), generateRulesetSignature(ingressRules), seqnum, + ingressRules); + Commands ingressCmds = new Commands(ingressCmd); + try { + _agentMgr.send(agentId, ingressCmds, _answerListener); + } catch (AgentUnavailableException e) { + s_logger.debug("Unable to send updates for vm: " + userVmId + "(agentid=" + agentId + ")"); + _workDao.updateStep(work.getInstanceId(), seqnum, Step.Done); + } + + SecurityEgressRulesCmd cmd = generateEgressRulesetCmd(vm.getInstanceName(), vm.getPrivateIpAddress(), vm.getPrivateMacAddress(), vm.getId(), generateRulesetSignature(egressRules), seqnum, + egressRules); Commands cmds = new Commands(cmd); try { _agentMgr.send(agentId, cmds, _answerListener); diff --git a/server/src/com/cloud/network/security/dao/EgressRuleDao.java b/server/src/com/cloud/network/security/dao/EgressRuleDao.java new file mode 100644 index 00000000000..9cc514d0a86 --- /dev/null +++ b/server/src/com/cloud/network/security/dao/EgressRuleDao.java @@ -0,0 +1,36 @@ +/** + * 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.security.dao; + +import java.util.List; + +import com.cloud.network.security.EgressRuleVO; +import com.cloud.utils.db.GenericDao; + +public interface EgressRuleDao extends GenericDao { + List listBySecurityGroupId(long networkGroupId); + List listByAllowedSecurityGroupId(long networkGroupId); + EgressRuleVO findByProtoPortsAndCidr(long networkGroupId, String proto, int startPort, int endPort, String cidr); + EgressRuleVO findByProtoPortsAndGroup(String proto, int startPort, int endPort, String networkGroup); + EgressRuleVO findByProtoPortsAndAllowedGroupId(long networkGroupId, String proto, int startPort, int endPort, Long allowedGroupId); + int deleteBySecurityGroup(long securityGroupId); + int deleteByPortProtoAndGroup(long securityGroupId, String protocol, int startPort,int endPort, Long id); + int deleteByPortProtoAndCidr(long securityGroupId, String protocol, int startPort,int endPort, String cidr); + +} diff --git a/server/src/com/cloud/network/security/dao/EgressRuleDaoImpl.java b/server/src/com/cloud/network/security/dao/EgressRuleDaoImpl.java new file mode 100644 index 00000000000..adb11b71e43 --- /dev/null +++ b/server/src/com/cloud/network/security/dao/EgressRuleDaoImpl.java @@ -0,0 +1,167 @@ +/** + * 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.security.dao; + +import java.util.List; +import java.util.Map; + +import javax.ejb.Local; +import javax.naming.ConfigurationException; + +import com.cloud.network.security.EgressRuleVO; +import com.cloud.network.security.SecurityGroupVO; +import com.cloud.utils.component.Inject; +import com.cloud.utils.db.GenericDaoBase; +import com.cloud.utils.db.JoinBuilder; +import com.cloud.utils.db.SearchBuilder; +import com.cloud.utils.db.SearchCriteria; + +@Local(value={EgressRuleDao.class}) +public class EgressRuleDaoImpl extends GenericDaoBase implements EgressRuleDao { + + @Inject SecurityGroupDao _securityGroupDao; + + protected SearchBuilder securityGroupIdSearch; + protected SearchBuilder allowedSecurityGroupIdSearch; + protected SearchBuilder protoPortsAndCidrSearch; + protected SearchBuilder protoPortsAndSecurityGroupNameSearch; + protected SearchBuilder protoPortsAndSecurityGroupIdSearch; + + + + protected EgressRuleDaoImpl() { + securityGroupIdSearch = createSearchBuilder(); + securityGroupIdSearch.and("securityGroupId", securityGroupIdSearch.entity().getSecurityGroupId(), SearchCriteria.Op.EQ); + securityGroupIdSearch.done(); + + allowedSecurityGroupIdSearch = createSearchBuilder(); + allowedSecurityGroupIdSearch.and("allowedNetworkId", allowedSecurityGroupIdSearch.entity().getAllowedNetworkId(), SearchCriteria.Op.EQ); + allowedSecurityGroupIdSearch.done(); + + protoPortsAndCidrSearch = createSearchBuilder(); + protoPortsAndCidrSearch.and("securityGroupId", protoPortsAndCidrSearch.entity().getSecurityGroupId(), SearchCriteria.Op.EQ); + protoPortsAndCidrSearch.and("proto", protoPortsAndCidrSearch.entity().getProtocol(), SearchCriteria.Op.EQ); + protoPortsAndCidrSearch.and("startPort", protoPortsAndCidrSearch.entity().getStartPort(), SearchCriteria.Op.EQ); + protoPortsAndCidrSearch.and("endPort", protoPortsAndCidrSearch.entity().getEndPort(), SearchCriteria.Op.EQ); + protoPortsAndCidrSearch.and("cidr", protoPortsAndCidrSearch.entity().getAllowedDestinationIpCidr(), SearchCriteria.Op.EQ); + protoPortsAndCidrSearch.done(); + + protoPortsAndSecurityGroupIdSearch = createSearchBuilder(); + protoPortsAndSecurityGroupIdSearch.and("securityGroupId", protoPortsAndSecurityGroupIdSearch.entity().getSecurityGroupId(), SearchCriteria.Op.EQ); + protoPortsAndSecurityGroupIdSearch.and("proto", protoPortsAndSecurityGroupIdSearch.entity().getProtocol(), SearchCriteria.Op.EQ); + protoPortsAndSecurityGroupIdSearch.and("startPort", protoPortsAndSecurityGroupIdSearch.entity().getStartPort(), SearchCriteria.Op.EQ); + protoPortsAndSecurityGroupIdSearch.and("endPort", protoPortsAndSecurityGroupIdSearch.entity().getEndPort(), SearchCriteria.Op.EQ); + protoPortsAndSecurityGroupIdSearch.and("allowedNetworkId", protoPortsAndSecurityGroupIdSearch.entity().getAllowedNetworkId(), SearchCriteria.Op.EQ); + + } + + public List listBySecurityGroupId(long securityGroupId) { + SearchCriteria sc = securityGroupIdSearch.create(); + sc.setParameters("securityGroupId", securityGroupId); + return listBy(sc); + } + + public int deleteBySecurityGroup(long securityGroupId) { + SearchCriteria sc = securityGroupIdSearch.create(); + sc.setParameters("securityGroupId", securityGroupId); + return expunge(sc); + } + + @Override + public List listByAllowedSecurityGroupId(long securityGroupId) { + SearchCriteria sc = allowedSecurityGroupIdSearch.create(); + sc.setParameters("allowedNetworkId", securityGroupId); + return listBy(sc); + } + + @Override + public EgressRuleVO findByProtoPortsAndCidr(long securityGroupId, String proto, int startPort, + int endPort, String cidr) { + SearchCriteria sc = protoPortsAndCidrSearch.create(); + sc.setParameters("securityGroupId", securityGroupId); + sc.setParameters("proto", proto); + sc.setParameters("startPort", startPort); + sc.setParameters("endPort", endPort); + sc.setParameters("cidr", cidr); + return findOneIncludingRemovedBy(sc); + } + + @Override + public EgressRuleVO findByProtoPortsAndGroup(String proto, int startPort, + int endPort, String securityGroup) { + SearchCriteria sc = protoPortsAndSecurityGroupNameSearch.create(); + sc.setParameters("proto", proto); + sc.setParameters("startPort", startPort); + sc.setParameters("endPort", endPort); + sc.setJoinParameters("groupName", "groupName", securityGroup); + return findOneIncludingRemovedBy(sc); + } + + @Override + public boolean configure(String name, Map params) + throws ConfigurationException { + protoPortsAndSecurityGroupNameSearch = createSearchBuilder(); + protoPortsAndSecurityGroupNameSearch.and("proto", protoPortsAndSecurityGroupNameSearch.entity().getProtocol(), SearchCriteria.Op.EQ); + protoPortsAndSecurityGroupNameSearch.and("startPort", protoPortsAndSecurityGroupNameSearch.entity().getStartPort(), SearchCriteria.Op.EQ); + protoPortsAndSecurityGroupNameSearch.and("endPort", protoPortsAndSecurityGroupNameSearch.entity().getEndPort(), SearchCriteria.Op.EQ); + SearchBuilder ngSb = _securityGroupDao.createSearchBuilder(); + ngSb.and("groupName", ngSb.entity().getName(), SearchCriteria.Op.EQ); + protoPortsAndSecurityGroupNameSearch.join("groupName", ngSb, protoPortsAndSecurityGroupNameSearch.entity().getAllowedNetworkId(), ngSb.entity().getId(), JoinBuilder.JoinType.INNER); + protoPortsAndSecurityGroupNameSearch.done(); + return super.configure(name, params); + } + + @Override + public int deleteByPortProtoAndGroup(long securityGroupId, String protocol, int startPort, int endPort, Long allowedGroupId) { + SearchCriteria sc = protoPortsAndSecurityGroupIdSearch.create(); + sc.setParameters("securityGroupId", securityGroupId); + sc.setParameters("proto", protocol); + sc.setParameters("startPort", startPort); + sc.setParameters("endPort", endPort); + sc.setParameters("allowedNetworkId", allowedGroupId); + + return expunge(sc); + + } + + @Override + public int deleteByPortProtoAndCidr(long securityGroupId, String protocol, int startPort, int endPort, String cidr) { + SearchCriteria sc = protoPortsAndCidrSearch.create(); + sc.setParameters("securityGroupId", securityGroupId); + sc.setParameters("proto", protocol); + sc.setParameters("startPort", startPort); + sc.setParameters("endPort", endPort); + sc.setParameters("cidr", cidr); + + return expunge(sc); + } + + @Override + public EgressRuleVO findByProtoPortsAndAllowedGroupId(long securityGroupId, String proto, + int startPort, int endPort, Long allowedGroupId) { + SearchCriteria sc = protoPortsAndSecurityGroupIdSearch.create(); + sc.addAnd("securityGroupId", SearchCriteria.Op.EQ, securityGroupId); + sc.setParameters("proto", proto); + sc.setParameters("startPort", startPort); + sc.setParameters("endPort", endPort); + sc.setParameters("allowedNetworkId", allowedGroupId); + + return findOneIncludingRemovedBy(sc); + } +} diff --git a/setup/db/create-schema.sql b/setup/db/create-schema.sql index 90195382a4f..2d88b5d7449 100755 --- a/setup/db/create-schema.sql +++ b/setup/db/create-schema.sql @@ -108,6 +108,7 @@ DROP TABLE IF EXISTS `cloud`.`ovs_work`; DROP TABLE IF EXISTS `cloud`.`remote_access_vpn`; DROP TABLE IF EXISTS `cloud`.`resource_count`; DROP TABLE IF EXISTS `cloud`.`security_ingress_rule`; +DROP TABLE IF EXISTS `cloud`.`security_egress_rule`; DROP TABLE IF EXISTS `cloud`.`stack_maid`; DROP TABLE IF EXISTS `cloud`.`storage_pool_work`; DROP TABLE IF EXISTS `cloud`.`user_vm_details`; @@ -1401,6 +1402,18 @@ CREATE TABLE `cloud`.`security_ingress_rule` ( PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8; +CREATE TABLE `cloud`.`security_egress_rule` ( + `id` bigint unsigned NOT NULL auto_increment, + `security_group_id` bigint unsigned NOT NULL, + `start_port` varchar(10) default NULL, + `end_port` varchar(10) default NULL, + `protocol` varchar(16) NOT NULL default 'TCP', + `allowed_network_id` bigint unsigned, + `allowed_ip_cidr` varchar(44), + `create_status` varchar(32) COMMENT 'rule creation status', + PRIMARY KEY (`id`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8; + CREATE TABLE `cloud`.`security_group_vm_map` ( `id` bigint unsigned NOT NULL auto_increment, `security_group_id` bigint unsigned NOT NULL,