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
try:
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:
result.add(key.key.split('/')[-1])
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,
backupCount=5)
logging.getLogger("").addHandler(handler)
self.range = [150, 200]
self.prefix = "158.38.213."
def output(self, line):
self.output_file.write(line)
self.output_file.write("\n")
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],
suffix))
if member in self.addresses_v6 and (net is None or ":" in net):
self.output("ipset add {}_inet6 {}{}".format(name, self.addresses_v6[member],
suffix))
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"],
rule["source_cidr"])
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"],
rule["destination_port"])
else:
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:
next
mac = entry.key.split("/")[-1]
try:
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("output.sh", "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()
sys.exit(0)
while True:
data = self.etcd_client.watch("/nova/iaas/instances", index+1)
logging.debug("new config index %d", data.index)
time.sleep(1)
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/nova_lb_configuration.pid", 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__':
logging.basicConfig(level=logging.DEBUG)
logging.getLogger("requests").setLevel(logging.WARNING)
args = parse_args()
if args.daemonize:
logfile='/var/log/nova_loadbalancer_configurator.log'
daemon_context = daemon.DaemonContext(pidfile=TimeoutPIDLockFile(args.pidfile))
with daemon_context:
Generator(args.cert, args.key, args.cacert, logfile=logfile).main()
else:
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