package tw.com.pictures;
import java.io.IOException;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import tw.com.AwsFacade;
import tw.com.entity.Cidr;
import tw.com.exceptions.CfnAssistException;
import tw.com.pictures.dot.Recorder;
import com.amazonaws.services.ec2.model.Address;
import com.amazonaws.services.ec2.model.IpPermission;
import com.amazonaws.services.ec2.model.NetworkAcl;
import com.amazonaws.services.ec2.model.NetworkAclEntry;
import com.amazonaws.services.ec2.model.PortRange;
import com.amazonaws.services.ec2.model.Route;
import com.amazonaws.services.ec2.model.RouteState;
import com.amazonaws.services.ec2.model.RouteTable;
import com.amazonaws.services.ec2.model.RuleAction;
import com.amazonaws.services.ec2.model.SecurityGroup;
import com.amazonaws.services.ec2.model.Subnet;
import com.amazonaws.services.ec2.model.Tag;
import com.amazonaws.services.ec2.model.Vpc;
import com.amazonaws.services.elasticloadbalancing.model.LoadBalancerDescription;
import com.amazonaws.services.rds.model.DBInstance;
public class VPCDiagramBuilder extends CommonBuilder {
private static final Logger logger = LoggerFactory.getLogger(VPCDiagramBuilder.class);
private static final String ROUTE_BLACKHOLE = RouteState.Blackhole.toString();
private static final String ROUTE_ACTIVE = RouteState.Active.toString();
private static final String CIDR_ANY = "any";
private Diagram networkDiagram;
private Diagram securityDiagram;
private Vpc vpc;
private Map<String, SubnetDiagramBuilder> subnetDiagramBuilders; // id -> diagram
public VPCDiagramBuilder(Vpc vpc, Diagram networkDiagram, Diagram securityDiagram) {
this.vpc = vpc;
this.networkDiagram = networkDiagram;
this.securityDiagram = securityDiagram;
subnetDiagramBuilders = new HashMap<String, SubnetDiagramBuilder>();
}
private void addTitle(Vpc vpc, Diagram diagram) {
String title = vpc.getVpcId();
List<Tag> tags = vpc.getTags();
String name = AmazonVPCFacade.getNameFromTags(tags);
if (!name.isEmpty()) {
title = title + String.format(" (%s)", name);
}
String project = AmazonVPCFacade.getValueFromTag(tags, AwsFacade.PROJECT_TAG);
if (!project.isEmpty()) {
title = title + String.format(" PROJECT=%s", project);
}
String env = AmazonVPCFacade.getValueFromTag(tags, AwsFacade.ENVIRONMENT_TAG);
if (!env.isEmpty()) {
title = title + String.format(" ENV=%s", env);
}
diagram.addTitle(title);
}
public void add(String unique, SubnetDiagramBuilder subnetDiagram) {
subnetDiagramBuilders.put(unique, subnetDiagram);
}
public void render(Recorder recorder) throws IOException {
renderDiagramWithPrefix(recorder, networkDiagram, "network_diagram");
renderDiagramWithPrefix(recorder, securityDiagram, "security_diagram");
}
private void renderDiagramWithPrefix(Recorder recorder, Diagram diagram,
String prefix) throws IOException {
addTitle(vpc, diagram);
recorder.beginFor(vpc, prefix);
diagram.render(recorder);
recorder.end();
}
public NetworkChildDiagram createNetworkDiagramForSubnet(Subnet subnet) throws CfnAssistException {
String subnetName = AmazonVPCFacade.getNameFromTags(subnet.getTags());
String label = SubnetDiagramBuilder.formSubnetLabel(subnet, subnetName);
return new NetworkChildDiagram(networkDiagram.createSubDiagram(subnet.getSubnetId(), label));
}
public SecurityChildDiagram createSecurityDiagramForSubnet(Subnet subnet) throws CfnAssistException {
String subnetName = AmazonVPCFacade.getNameFromTags(subnet.getTags());
String label = SubnetDiagramBuilder.formSubnetLabel(subnet, subnetName);
return new SecurityChildDiagram(securityDiagram.createSubDiagram(subnet.getSubnetId(), label));
}
public void addAsssociatedRouteTable(RouteTable routeTable, String subnetId) throws CfnAssistException {
SubnetDiagramBuilder subnetDiagram = subnetDiagramBuilders.get(subnetId);
subnetDiagram.addRouteTable(routeTable);
}
public void addEIP(Address eip) throws CfnAssistException {
String label = AmazonVPCFacade.createLabelFromNameAndID(eip.getAllocationId() ,eip.getPublicIp());
networkDiagram.addPublicIPAddress(eip.getPublicIp(), label);
}
public void linkEIPToInstance(String publicIp, String instanceId) {
networkDiagram.addConnectionBetween(publicIp, instanceId);
}
public void addELB(LoadBalancerDescription elb) throws CfnAssistException {
String label = elb.getLoadBalancerName();
String id = elb.getDNSName();
networkDiagram.addLoadBalancer(id, label);
securityDiagram.addLoadBalancer(id, label);
}
public void associateELBToInstance(LoadBalancerDescription elb,
String instanceId) {
networkDiagram.addConnectionBetween(elb.getDNSName(), instanceId);
}
public void associateELBToSubnet(LoadBalancerDescription elb,
String subnetId) {
networkDiagram.associateWithSubDiagram(elb.getDNSName(), subnetId, subnetDiagramBuilders.get(subnetId));
securityDiagram.associateWithSubDiagram(elb.getDNSName(), subnetId, subnetDiagramBuilders.get(subnetId));
}
public void addDBInstance(DBInstance rds) throws CfnAssistException {
String rdsId = rds.getDBInstanceIdentifier();
String label = AmazonVPCFacade.createLabelFromNameAndID(rdsId, rds.getDBName());
networkDiagram.addDBInstance(rdsId, label);
securityDiagram.addDBInstance(rdsId, label);
}
public void addAcl(NetworkAcl acl) throws CfnAssistException {
String aclId = acl.getNetworkAclId();
String name = AmazonVPCFacade.getNameFromTags(acl.getTags());
String label = AmazonVPCFacade.createLabelFromNameAndID(aclId,name);
securityDiagram.addACL(aclId, label);
}
// this is relying on the ID being the same on both diagrams (network & security)
public void associateDBWithSubnet(DBInstance rds, String subnetId) throws CfnAssistException {
SubnetDiagramBuilder childDiagramBuilder = subnetDiagramBuilders.get(subnetId);
if (childDiagramBuilder==null) {
throw new CfnAssistException("Unable to find child diagram for subnet Id:" + subnetId);
}
networkDiagram.associateWithSubDiagram(rds.getDBInstanceIdentifier(), subnetId, childDiagramBuilder);
securityDiagram.associateWithSubDiagram(rds.getDBInstanceIdentifier(), subnetId, childDiagramBuilder);
}
public void addSecurityGroup(SecurityGroup group, String subnetId) throws CfnAssistException {
subnetDiagramBuilders.get(subnetId).addSecurityGroup(group);
}
public void addSecGroupInboundPerms(String groupId, IpPermission ipPermsInbound, String subnetId) throws CfnAssistException {
subnetDiagramBuilders.get(subnetId).addSecGroupInboundPerms(groupId, ipPermsInbound);
}
public void addSecGroupOutboundPerms(String groupId ,IpPermission ipPermsOutbound, String subnetId) throws CfnAssistException {
subnetDiagramBuilders.get(subnetId).addSecGroupOutboundPerms(groupId, ipPermsOutbound);
}
public void addSecurityGroup(SecurityGroup dbSecurityGroup) throws CfnAssistException {
String groupId = dbSecurityGroup.getGroupId();
String label = AmazonVPCFacade.labelForSecGroup(dbSecurityGroup);
securityDiagram.addSecurityGroup(groupId, label);
}
public void addSecGroupInboundPerms(String groupId, IpPermission dbIpPermsInbound) throws CfnAssistException {
addSecGroupInboundPerms(securityDiagram, groupId, dbIpPermsInbound);
}
public void addSecGroupOutboundPerms(String groupId, IpPermission dbIpPermsOutbound) throws CfnAssistException {
addSecGroupOutboundPerms(securityDiagram, groupId, dbIpPermsOutbound);
}
public void associateInstanceWithSecGroup(String instanceId, SecurityGroup securityGroup) {
securityDiagram.associate(instanceId, securityGroup.getGroupId());
}
// this is relying on the subnet ID being the same on both diagrams (network & security)
public void addRoute(String routeTableId, String subnetId, Route route) throws CfnAssistException {
String string = route.getDestinationCidrBlock();
Cidr subnet = parseCidr(string);
String state = route.getState();
if (ROUTE_ACTIVE.equals(state)) {
addActiveRoute(routeTableId, subnetId, route, subnet);
} else if (ROUTE_BLACKHOLE.equals(state)){
logger.warn("Route state is not active, cidr block is " + route.getDestinationCidrBlock());
networkDiagram.addConnectionFromSubDiagram(ROUTE_BLACKHOLE, subnetId, subnetDiagramBuilders.get(subnetId), string);
} else {
throw new CfnAssistException(String.format("Unexpected state for route with cidr %s, state was %s", string, state));
}
}
private Cidr parseCidr(String string) {
if (string==null) {
return null;
}
try {
return Cidr.parse(string);
} catch (CfnAssistException e) {
return null;
}
}
private void addActiveRoute(String routeTableId, String subnetId, Route route, Cidr subnet) throws CfnAssistException {
String destination = getDestination(route);
String cidr = subnetAsCidrString(subnet);
if (!destination.equals("local")) {
String diagramId = formRouteTableIdForDiagram(subnetId, routeTableId);
networkDiagram.addRouteToInstance(destination, diagramId, subnetDiagramBuilders.get(subnetId), cidr);
} else {
// this associates the cidr block with the current subnet
networkDiagram.associateWithSubDiagram(cidr, subnetId, subnetDiagramBuilders.get(subnetId));
}
}
private String subnetAsCidrString(Cidr subnet) {
String result = "no cidr";
if (subnet!=null) {
result = subnet.toString();
}
return result;
}
private String getDestination(Route route) {
String dest = route.getGatewayId();
if (dest==null) {
dest = route.getInstanceId(); // api docs say this is a NAT instance, but could it be any instance?
}
return dest;
}
// this is relying on the ID being the same on both diagrams (network & security)
public void associateAclWithSubnet(NetworkAcl acl, String subnetId) {
securityDiagram.associateWithSubDiagram(acl.getNetworkAclId(), subnetId, subnetDiagramBuilders.get(subnetId));
}
public void addACLOutbound(String aclId, NetworkAclEntry entry, String subnetId) throws CfnAssistException {
String cidrUniqueId = createCidrUniqueId("out", aclId, entry);
String labelForEdge = labelFromEntry(entry);
securityDiagram.addCidr(cidrUniqueId, getLabelFromCidr(entry));
if (entry.getRuleAction().equals(RuleAction.Allow.toString())) {
securityDiagram.addConnectionFromSubDiagram(cidrUniqueId, subnetId, subnetDiagramBuilders.get(subnetId), labelForEdge);
} else {
securityDiagram.addBlockedConnectionFromSubDiagram(cidrUniqueId, subnetId, subnetDiagramBuilders.get(subnetId), labelForEdge);
}
}
public void addACLInbound(String aclId, NetworkAclEntry entry, String subnetId) throws CfnAssistException {
String cidrUniqueId = createCidrUniqueId("in", aclId, entry);
String labelForEdge = labelFromEntry(entry);
securityDiagram.addCidr(cidrUniqueId, getLabelFromCidr(entry));
// associate subnet with port range and port range with cidr
if (entry.getRuleAction().equals(RuleAction.Allow.toString())) {
securityDiagram.addConnectionToSubDiagram(cidrUniqueId, subnetId, subnetDiagramBuilders.get(subnetId), labelForEdge);
} else {
securityDiagram.addBlockedConnectionToSubDiagram(cidrUniqueId, subnetId, subnetDiagramBuilders.get(subnetId), labelForEdge);
}
}
private String labelFromEntry(NetworkAclEntry entry) {
String proto = getProtoFrom(entry);
String range = getRangeFrom(entry);
return String.format("%s:[%s]\n(rule:%s)",proto, range, getRuleName(entry));
}
private String getRuleName(NetworkAclEntry entry) {
Integer number = entry.getRuleNumber();
if (number==32767) {
return "default";
}
return number.toString();
}
private String getRangeFrom(NetworkAclEntry entry) {
PortRange portRange = entry.getPortRange();
if (portRange==null) {
return("all");
}
if (portRange.getFrom().toString().equals(portRange.getTo().toString())) {
return String.format("%s", portRange.getFrom());
}
return String.format("%s-%s", portRange.getFrom(), portRange.getTo());
}
private String getProtoFrom(NetworkAclEntry entry) {
Integer protoNum = Integer.parseInt(entry.getProtocol());
switch(protoNum) {
case -1: return "all";
case 1: return "icmp";
case 6: return "tcp";
case 17: return "udp";
}
return protoNum.toString();
}
private String getLabelFromCidr(NetworkAclEntry entry) {
String cidrBlock = entry.getCidrBlock();
if (cidrBlock.equals("0.0.0.0/0")) {
return CIDR_ANY;
}
return cidrBlock;
}
private String createCidrUniqueId(String direction, String aclId, NetworkAclEntry entry) {
String uniqueId = String.format("%s_%s_%s", direction, entry.getCidrBlock(), aclId);
return uniqueId;
}
}