Commit 166dae9c authored by Sigmund Augdal's avatar Sigmund Augdal

First shot at code to generate ipset/iptables commands out of security groups

parent 9ce0f34c
#!/usr/bin/env python
import etcd
import os
import os.path
import subprocess
import logging
import logging.handlers
import time
import argparse
import daemon
import sys
from daemon.pidfile import TimeoutPIDLockFile
except ImportError:
from daemon.pidlockfile import TimeoutPIDLockFile
from nova_router import security_groups
CONF_FILE = "/etc/haproxy/haproxy.cfg"
def etcd_get_dict(etcd_client, prefix):
result = dict()
for key in etcd_client.list(prefix):
if not key.dir:
result[key.key.split('/')[-1]] = key.value
return result
def etcd_get_dirs(etcd_client, prefix):
result = set()
for key in etcd_client.list(prefix):
if key.dir:
return result
def escape_group_name(name):
name = name.replace(" ", "_")
name = name.replace("\t", "_")
return name
class Generator(object):
def __init__(self, cert, key, cacert, logfile=None):
self.etcd_client = etcd.Etcd(ssl_key=key, ssl_cert=cert, verify=cacert)
if logfile:
handler = logging.handlers.RotatingFileHandler(logfile, maxBytes=10*1024**3,
self.range = [150, 200]
self.prefix = "158.38.213."
def output(self, line):
def create_ipset(self, name, set_type):
name = escape_group_name(name)
for family in ("inet", "inet6"):
self.output("ipset create {}_{} {} family {}".format(name, family,
set_type, family))
def add_ipset_member(self, name, member, protocol="tcp", port=None, net=None):
name = escape_group_name(name)
suffix = ""
if port is not None:
suffix += ",{}:{}".format(protocol, port)
if net is not None:
suffix += ",{}".format(net)
member = member.lower()
if member in self.addresses_v4 and (net is None or "." in net):
self.output("ipset add {}_inet {}{}".format(name, self.addresses_v4[member],
if member in self.addresses_v6 and (net is None or ":" in net):
self.output("ipset add {}_inet6 {}{}".format(name, self.addresses_v6[member],
def process_security_group(self, group_id, name):
rules = security_groups.get_group_rules(self.etcd_client, group_id)
_, members = security_groups.get_group_members(self.etcd_client, group_id)
source_group = "source_{}".format(group_id)
self.create_ipset(source_group, "hash:ip")
for member in members:
self.add_ipset_member(source_group, member)
for rule in rules:
for member in members:
if rule["source_type"] == "any":
self.add_ipset_member("rules_from_any", member,
rule["protocol"], rule["destination_port"])
elif rule["source_type"] == "cidr":
self.add_ipset_member("rule_from_cidr", member,
rule["protocol"], rule["destination_port"],
elif rule["source_type"] == "security_group":
source_group = rule["source_security_group"]
set_name = "rule_from_group_{}".format(escape_group_name(source_group))
if source_group not in self.source_sets:
self.create_ipset(set_name, "hash:ip,port")
self.source_sets[source_group] = set_name
self.add_ipset_member(set_name, member, rule["protocol"],
logging.warning("Unhandled source type: %s", rule["source_type"])
def get_addresses(self, addrtype):
addresses = {}
for entry in self.etcd_client.list("/nova/iaas/instances"):
if not entry.dir:
mac = entry.key.split("/")[-1]
ipaddr = self.etcd_client.get(entry.key + "/" + addrtype).value
addresses[mac] = ipaddr
except etcd.EtcdError as ex:
if ex.args[0] != 100:
raise ex
return addresses
def generate_all(self):
index = None
self.addresses_v4 = self.get_addresses("ipv4")
self.addresses_v6 = self.get_addresses("ipv6_public")
self.source_sets = {}
self.output_file = open("", "w")
self.create_ipset("rules_from_any", "hash:ip,port")
self.create_ipset("rules_from_cidr", "hash:ip,port,net")
groups = security_groups.get_security_groups(self.etcd_client)
for group_id, name in groups.items():
self.process_security_group(group_id, name)
self.output("iptables -A FORWARD -m set --match-set rules_from_any_inet dst,dst -j ACCEPT")
self.output("ip6tables -A FORWARD -m set --match-set rules_from_any_inet6 dst,dst -j ACCEPT")
self.output("iptables -A FORWARD -m set --match-set rules_from_cidr_inet dst,dst,src -j ACCEPT")
self.output("ip6tables -A FORWARD -m set --match-set rules_from_cidr_inet6 dst,dst,src -j ACCEPT")
for source_group, destination_set in self.source_sets.items():
self.output("iptables -A FORWARD -m set --match-set {}_inet dst,dst --match-set source_{}_inet src -j ACCEPT".format(destination_set, source_group))
self.output("ip6tables -A FORWARD -m set --match-set {}_inet6 dst,dst --match-set source_{}_inet6 src -j ACCEPT".format(destination_set, source_group))
return index
def main(self):
index = self.generate_all()
while True:
data ="/nova/iaas/instances", index+1)
logging.debug("new config index %d", data.index)
index = self.generate_all()
def parse_args():
parser = argparse.ArgumentParser(description="Configure haproxy based on data from etcd")
parser.add_argument('-d', '--daemonize', default=False, action='store_true', help="Run as daemon")
parser.add_argument('--pidfile', type=str, default="/var/run/", help="pidfile when run as daemon")
parser.add_argument('--cert', default="client.crt", help="client certificate to use")
parser.add_argument('--key', default="client.key", help="private key to use for client certificate")
parser.add_argument('--cacert', default="etcd_ca.crt", help="ca certificate to use")
return parser.parse_args()
if __name__ == '__main__':
args = parse_args()
if args.daemonize:
daemon_context = daemon.DaemonContext(pidfile=TimeoutPIDLockFile(args.pidfile))
with daemon_context:
Generator(args.cert, args.key, args.cacert, logfile=logfile).main()
Generator(args.cert, args.key, args.cacert).main()
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment