package io.fathom.cloud.compute.actions;
import io.fathom.cloud.CloudException;
import io.fathom.cloud.compute.networks.IpRange;
import io.fathom.cloud.compute.scheduler.SchedulerHost;
import io.fathom.cloud.protobuf.CloudModel.InstanceData;
import io.fathom.cloud.protobuf.CloudModel.NetworkAddressData;
import io.fathom.cloud.protobuf.CloudModel.Protocols;
import io.fathom.cloud.protobuf.CloudModel.SecurityGroupData;
import io.fathom.cloud.protobuf.CloudModel.SecurityGroupRuleData;
import java.net.Inet6Address;
import java.net.InetAddress;
import java.util.List;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.google.common.collect.Lists;
import com.google.common.net.InetAddresses;
public class ConfigureFirewall {
private static final Logger log = LoggerFactory.getLogger(ConfigureFirewall.class);
private final ApplydContext applyd;
private final SchedulerHost host;
public ConfigureFirewall(SchedulerHost host, ApplydContext applyd) {
super();
this.host = host;
this.applyd = applyd;
}
public boolean updateConfig(InstanceData instance) throws CloudException {
boolean dirty = false;
dirty |= applyd.updateConfig("iptables/50-" + "os-compute-inst-" + instance.getId(), build(instance, false));
dirty |= applyd.updateConfig("ip6tables/50-" + "os-compute-inst-" + instance.getId(), build(instance, true));
dirty |= applyd.updateConfig("ip6neigh/os-compute-inst-" + instance.getId(), buildIpNeighProxy(instance, true));
// Tunnel names are of limited length
dirty |= applyd.updateConfig("tunnel/i-" + Long.toHexString(instance.getId()), buildTunnel(instance));
return dirty;
}
public boolean removeConfig(InstanceData instance) throws CloudException {
boolean dirty = false;
dirty |= applyd.removeConfig("iptables/50-" + "os-compute-inst-" + instance.getId());
dirty |= applyd.removeConfig("ip6tables/50-" + "os-compute-inst-" + instance.getId());
dirty |= applyd.removeConfig("ip6neigh/os-compute-inst-" + instance.getId());
dirty |= applyd.removeConfig("tunnel/os-compute-inst-" + instance.getId());
return dirty;
}
public boolean updateConfig(SecurityGroupData sg) throws CloudException {
boolean dirty = false;
dirty |= applyd.updateConfig("iptables/50-" + "os-compute-sg-" + sg.getId(), build(sg, false));
dirty |= applyd.updateConfig("ip6tables/50-" + "os-compute-sg-" + sg.getId(), build(sg, true));
return dirty;
}
private String build(SecurityGroupData sg, boolean ipv6) {
StringBuilder conf = new StringBuilder();
// e.g.
// *filter
// -A INPUT -m state --state ESTABLISHED -j ACCEPT
// COMMIT
conf.append("*filter\n");
conf.append(":" + "os-compute-sg-" + sg.getId() + " -\n");
for (SecurityGroupRuleData rule : sg.getRulesList()) {
String prefix = "-A os-compute-sg-" + sg.getId();
String filt = "";
if (rule.hasFromCidr()) {
IpRange range = Instance.toIpRange(rule.getFromCidr());
if (range.isIpv6() != ipv6) {
continue;
}
filt += " -s " + InetAddresses.toAddrString(range.getAddress()) + "/" + range.getNetmaskLength();
}
if (rule.hasFromSecurityGroup()) {
String setKey = "sg-" + rule.getFromSecurityGroup();
filt += "-m set --match-set " + setKey + " src";
}
if (rule.getIpProtocolCount() != 0) {
if (rule.getIpProtocolCount() != 1) {
throw new UnsupportedOperationException();
}
for (int ipProtocol : rule.getIpProtocolList()) {
switch (ipProtocol) {
case Protocols.ICMP_VALUE:
filt += " -p icmp";
break;
case Protocols.UDP_VALUE:
filt += " -p udp";
break;
case Protocols.TCP_VALUE:
filt += " -p tcp -m tcp";
break;
default:
throw new IllegalStateException();
}
}
}
if (rule.hasFromPortLow()) {
if (!rule.hasFromPortHigh() || rule.getFromPortHigh() == rule.getFromPortLow()) {
if (rule.getFromPortLow() > 0) {
filt += " --dport " + rule.getFromPortLow();
} else {
log.warn("Ignoring invalid port range: {}", rule);
}
} else {
filt += " --dport " + rule.getFromPortLow() + ":" + rule.getFromPortHigh();
}
}
conf.append(prefix + " " + filt + " -j ACCEPT\n");
}
conf.append("COMMIT\n");
return conf.toString();
}
private String build(InstanceData instance, boolean ipv6) {
StringBuilder conf = new StringBuilder();
List<String> ips = findIps(instance, ipv6);
// *filter
// -A INPUT -m state --state ESTABLISHED -j ACCEPT
// COMMIT
conf.append("*filter\n");
{
// os-compute-local chain
String prefix = "-A os-compute-local ";
// Jump to a per-instance rule if the target IP matches
for (String ip : ips) {
conf.append(prefix + "-d " + ip + " -j os-compute-inst-" + instance.getId() + "\n");
}
}
{
// os-compute-inst-<id>
String prefix = "-A os-compute-inst-" + instance.getId() + " ";
conf.append(prefix + "-m state --state INVALID -j DROP\n");
conf.append(prefix + "-m state --state RELATED,ESTABLISHED -j ACCEPT\n");
for (long securityGroupId : instance.getSecurityGroupIdList()) {
String sgRuleName = "os-compute-sg-" + securityGroupId;
conf.append(prefix + "-j " + sgRuleName + "\n");
}
conf.append(prefix + "-j os-compute-sg-fallback\n");
}
conf.append("COMMIT\n");
return conf.toString();
}
private String buildIpNeighProxy(InstanceData instance, boolean ipv6) {
StringBuilder conf = new StringBuilder();
List<String> ips = findIps(instance, ipv6);
{
String prefix = "ip -6 neigh add proxy ";
String suffix = " dev " + host.getNetworkDevice();
for (String ip : ips) {
conf.append(prefix + ip + suffix + "\n");
}
}
return conf.toString();
}
private List<String> findIps(InstanceData instance, boolean ipv6) {
List<String> ips = Lists.newArrayList();
for (NetworkAddressData addressInfo : instance.getNetwork().getAddressesList()) {
InetAddress address = InetAddresses.forString(addressInfo.getIp());
boolean isIpv6 = address instanceof Inet6Address;
if (isIpv6 != ipv6) {
continue;
}
ips.add(InetAddresses.toAddrString(address));
}
return ips;
}
private String buildTunnel(InstanceData instance) {
StringBuilder conf = new StringBuilder();
boolean ipv6 = true;
List<String> ips = findIps(instance, ipv6);
if (ips.isEmpty()) {
throw new IllegalStateException();
}
String remote = ips.get(0);
InetAddress localAddress = host.getIpAddress();
if (!(localAddress instanceof Inet6Address)) {
throw new IllegalStateException();
}
conf.append("ip6ip6 remote " + remote + " local " + InetAddresses.toAddrString(localAddress));
return conf.toString();
}
}