#!/usr/bin/python # # Copyright(C) 2005 INL # Written by Jean Gillaux <jean@inl.fr> # # This program 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, version 3 of the License. # # 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 <http://www.gnu.org/licenses/>. # # """ Generates iptables commands and ldap nufw acls from xml file. Nupyf takes an nufw acls xml file and an nufw firewall xml file and can * output iptables commands for both nufw and rescue (standard) modes * inserts nufw acls into an ldap tree usage: nupyf [options] acls.xml firewall.xml """ __version__ = '1.2' __author__ = 'Jean Gillaux' __copyright__ = 'Copyright 2005, INL' POLICY_VERSION = u"2.0" import xml.dom.minidom from nupyf.nuelt import EltGrpList, EltGrp from nupyf.nuacl import Acl from nupyf.nuacl_func import xml_new_eltgrp, parse_groups from nupyf import nuldap from nupyf.nuldap import LDAPConn, LDAPfw from nupyf.fw import parse as parseFW from nupyf.nubackend import NURule from nupyf.nuxml import parse_periodicities, parse_durations, parse_descsort from nupyf.nunat import nats_from_xml from nupyf.ipt import FWipt from nupyf import ipt from os import linesep from optparse import OptionParser from getpass import getpass from ldap import LDAPError, NO_SUCH_OBJECT import cPickle import IPy from sys import stderr, exit, stdout, argv from datetime import datetime # Do not check networks netmasks IPy.check_addr_prefixlen = False def check_ldap(options): if not options.ldap_server and not options.ldap_basedn: # LDAP is not used: exit return if not (options.ldap_server and options.ldap_basedn): print >>stderr, 'Error: --server and --basedn options must be provided together' exit(1) def parse_config_file(config_file, options): """Parse config_file and put directives into options. """ try: config = eval(open(config_file).read()) except IOError, ioe: print ioe exit(1) for key, value in config.iteritems(): if key == 'ldap_password': options.ldap_pwd = value if key == 'ldap_user': options.ldap_user = value if key == 'ldap_server': options.ldap_server = value if key == 'ldap_basedn': options.ldap_basedn = value def try_write_file(filename, *args): """write args in file with name = filename - value for filename make function print to stdout """ if filename == '-': fd = stdout else: fd = open(filename, 'w') for arg in args: if type(arg) == list: fd.writelines(arg) if type(arg) in [str, unicode]: fd.write(arg) def parseOptions(): usage = "usage: %prog [options] firewall_file.xml acls_file.xml" parser = OptionParser(usage, version = '%prog '+ __version__) # command line options parser.add_option('-d', '--dispatch', dest='dispatch', help='file to write dispatch commands in', metavar='FILE', default='') parser.add_option('-e', '--default_estrel_invalid', dest='default_estrel_invalid', help='file to write default_estrel_invalid commands in', metavar='FILE', default='') parser.add_option('-t', '--dispatch_targets', dest='dispatch_targets', help='file to write dispatch targets commands in', metavar='FILE', default='') parser.add_option('-f', '--forward', dest='forward', help='file to write forward iptables rules in', metavar='FILE') parser.add_option('-i', '--input', dest='input', help='file to ouput input rules in', metavar='FILE', default='') parser.add_option('-o', '--output', dest='output', help='file to write output rules in', metavar='FILE', default='') parser.add_option('-m', '--mangle', dest='mangle', help='file to write mangle rules in', metavar='FILE', default='') parser.add_option('-v', '--vpn', dest='vpn_rules', help='file to output vpn rules for packet marks, special value - means stdout', metavar='FILE', default='') parser.add_option('-n', '--nat', dest='nat_rules', metavar='FILE', help='file to output nat rules in, special value - means stdout') parser.add_option('-r', '--rescue', dest='rescue', action='store_true', help='activate rescue mode for firewall rules generation') parser.add_option('', '--ulog', dest='ulog', action="store_true", help='use ULOG system in log commands, default is to use LOG.') parser.add_option('', '--iptables', dest='ipt', help='path to iptables, can be a variable.', metavar='VALUE', default='') parser.add_option('-s', '--server', dest='ldap_server', help='ldap server address.', metavar='ADDR', default='') parser.add_option('-u', '--user', dest='ldap_user', help='ldap user used to connect.', metavar='USER', default='') parser.add_option('-p', '--pwd', dest='ldap_pwd', help='password used to connect on ldap server (WARNING: visible on comand line).', metavar='PWD', default='') parser.add_option('-b', '--basedn', dest='ldap_basedn', help='basedn used to insert acls in (ex: ou=acls, dc=domain, dc=com).', metavar='DN', default='') parser.add_option('-a', '--askpwd', dest='ask_ldappwd', help='ask user for ldap passwd.', action='store_true', default=False) parser.add_option('-c', '--config', dest='config_file', help='configuration file', default='') parser.add_option('', '--dumpldap', dest='dump_ldap', help='file to save ldap informations', default='') parser.add_option('', '--loadldap', dest='load_ldap', help='file to load ldap informations from', default=None) parser.add_option('', '--auth_ext', dest='auth_ext', action='store_true', help='Generate netfilter rules to autenticate internet') parser.add_option('', '--sortid', dest='sortid', help='Use this ID for acl order') parser.add_option('', '--same-iface', dest='same_iface', action='store_true', help='Generate Netfilter rules with same input and output interfaces', default=False) parser.add_option('', '--no-check-net', dest='no_check_net', action='store_true', help='Do not check inclusion and overlapping on enterprise networks', default=False) parser.add_option('', '--ipv6', action='store_true', help='Enable mode IPv6 (for NuFW 2.2)', default=False) parser.add_option('', '--nulayer7', dest='enable_nulayer7', action='store_true', help='Enable nulayer7 feature (generate rules which set connmarks)', default=False) parser.add_option('', '--short-description', dest='disable_logprefix', action='store_true', help='Only save ACL id in the description field of ACL objects in LDAP.', default=False) parser.add_option('--transparent-proxy-port', type='str', help='Port number of the transparent proxy (eg. 3128 for default Squid port)', default=None) parser.add_option('--debug', help='Enable debug mode', action='store_true', default=False) parser.add_option('--nufw', help='Enable authenticated rules', action='store_true', default=False) parser.set_defaults(rescue=False, forward='', nat_rules='', auth_ext=False) parser.set_defaults(sortid=0) return parser.parse_args(argv) class Nupyf: def __init__(self): self.acls = [] self.resources = EltGrpList() self.protocols = EltGrpList() self.applications = EltGrpList() self.operating_systems = EltGrpList() self.authentications = EltGrpList() (self.options, args) = parseOptions() if self.options.config_file: parse_config_file(self.options.config_file, self.options) if self.options.ipv6: nuldap.USE_IPV6 = True if self.options.ask_ldappwd: self.options.ldap_pwd = getpass('LDAP Password: ') check_ldap(self.options) if self.options.load_ldap: self.mainLDAP() if self.options.transparent_proxy_port: ipt.TRANSPARENT_PROXY_PORT = self.options.transparent_proxy_port NURule.disable_logprefix = self.options.disable_logprefix self.desc_filename = args[1] self.acl_filename = args[2] self.doc = xml.dom.minidom.parse(self.acl_filename) def run(self): self.parseAll() self.process() self.createIptablesCommands() if self.options.ldap_server and self.options.ldap_basedn: self.ldapStuff() def parseAll(self): root = self.doc.documentElement version = root.getAttribute('version') if version != POLICY_VERSION: raise RuntimeError('Document version is not "%s": "%s"' % (POLICY_VERSION, version)) # Parse network description file, and take first firewall fwlist = parseFW(self.desc_filename) try: (fwid, self.myfw) = fwlist.popitem() except KeyError: print "unable to find a firewall" exit(1) # Parse groups self.groups_list = parse_groups(self.doc) if 1 not in self.groups_list: # group 1 is enabled by default self.groups_list[1] = 1 # Parse NAT self.lsnat, self.ldnat, self.lpnat = nats_from_xml(self.doc, self.groups_list) # Parse resources for node in self.doc.getElementsByTagName("resource"): grp = xml_new_eltgrp(int(node.getAttribute('ID')), node.getAttribute('name'), node.getElementsByTagName("elt")) self.resources.add(grp) # Parse authentications objects if not self.options.rescue: for node in self.doc.getElementsByTagName("auth"): grp = xml_new_eltgrp(int(node.getAttribute('ID')), node.getAttribute('name'), node.getElementsByTagName("elt")) self.authentications.add(grp) # Layer7 support l7connmark = {} if self.options.enable_nulayer7: from nulayer7 import l7xml l7rulelist = l7xml.load(open(self.acl_filename)) l7connmark['mask'] = l7rulelist.mask for l7rule in l7rulelist: l7connmark[str(l7rule.ID)] = l7rule.connmark # Parse protocols for node in self.doc.getElementsByTagName("protocol"): grp = xml_new_eltgrp(int(node.getAttribute('ID')), node.getAttribute('name'), node.getElementsByTagName("elt"), l7connmark=l7connmark) self.protocols.add(grp) # Parse applications for node in self.doc.getElementsByTagName("application"): grp = xml_new_eltgrp(int(node.getAttribute('ID')), node.getAttribute('name'), node.getElementsByTagName("elt")) self.applications.add(grp) # Parse operating systems for node in self.doc.getElementsByTagName("os"): grp = xml_new_eltgrp( int(node.getAttribute('ID')), node.getAttribute('name'), node.getElementsByTagName("elt")) self.operating_systems.add(grp) # Parse periodicities self.periodicities = parse_periodicities(self.doc) self.durations = parse_durations(self.doc) def processAcl(self, acl, acl_type): # Check that ACL group is enabled try: acl_group = int(acl.getAttribute("group")) if acl_group \ and (acl_group not in self.groups_list or self.groups_list[acl_group]!=1): # Group disabled: exit return except ValueError: pass acl_id = int(acl.getAttribute('ID')) if not self.options.rescue: auth_id = acl.getAttribute("auth") else: auth_id = None log = acl.getAttribute("log") if not log: log = 'no' src = acl.getAttribute("from") dst = acl.getAttribute("to") idappli = acl.getAttribute("with") idos = acl.getAttribute("on") proto = acl.getAttribute("proto") if proto: proto = self.protocols.find(int(proto)) else: proto = None transparent_proxy = acl.getAttribute("transparent_proxy") if acl_type == 'FORWARD' and transparent_proxy: transparent_proxy = int(transparent_proxy) else: transparent_proxy = None if idos: os = self.operating_systems.find(int(idos)) else: os = None if idappli: appli = self.applications.find(int(idappli)) else: appli = None idperiod = acl.getAttribute("periodicity") if idperiod: period = self.periodicities.find(int(idperiod)).name else: idduration = acl.getAttribute("duration") if idduration: period = self.durations.find(int(idduration)).name else: period = '' #get acl only if its group is enabled if src: subject = self.resources.find(int(src)) else: subject = None if dst: resource = self.resources.find(int(dst)) else: resource = None if self.options.nufw and auth_id: auth = self.authentications.find(int(auth_id)) else: auth = None if acl_type == 'FORWARD': if not subject: raise ValueError("Unable to find subject of ACL #%s" % acl_id) if not resource: raise ValueError("Unable to find resource of ACL #%s" % acl_id) # FIXME: Support descsort descsort = parse_descsort(acl) order = descsort[self.options.sortid] else: order = None if not subject: subject = EltGrp(-1, 'all', 'or') if not resource: resource = EltGrp(-1, 'all', 'or') aclobj = Acl(subject, resource, self.myfw, id=acl_id, proto=proto, name=acl.getAttribute("name"), decision=acl.getAttribute('decision'), ulog_prefix=acl.getAttribute('prefix'), appli=appli, os=os, period_name=period, sort_order=order, auth=auth, log=log, acl_type=acl_type, transparent_proxy=transparent_proxy) self.acls.append(aclobj) def process(self): self.resources.normall() self.protocols.normall() self.applications.normall() self.operating_systems.normall() self.periodicities.normall() self.durations.normall() self.authentications.normall() # do we have to authenticate packets from internet? self.myfw.auth_ext = self.options.auth_ext # don't generate rules for same output and input intefaces ? if self.myfw.nb_ifaces() == 1: self.options.same_iface = True self.myfw.same_iface = self.options.same_iface # don't check inclusion and overlaping on enterprise networks ? self.myfw.no_check_net = self.options.no_check_net # to check descsort unicity by (descsort.ID, elt.from, elt.to, elt.order) for acl in self.doc.getElementsByTagName("acl"): self.processAcl(acl, 'FORWARD') for acl in self.doc.getElementsByTagName("localin"): self.processAcl(acl, 'INPUT') for acl in self.doc.getElementsByTagName("localout"): self.processAcl(acl, 'OUTPUT') #net_ent = self.myfw.entreprise_nets() self.myfw.chains(all = 1, inout = 1) for x in self.acls: for myress in x.rules(): self.myfw.add_rule(myress) self.myfw.set_nat_rules(self.lsnat, self.ldnat, self.lpnat) def createIptablesCommands(self): if self.options.ulog: log_type = 'ulog' else: log_type = 'log' fwp = FWipt(self.myfw, logtype = log_type) rules_create = fwp.create_ipt_chains() rules_connect = fwp.connect_chains() default_estrel_invalid = rules_connect.pop(0) + rules_connect.pop(0) dispatch_rules = rules_connect.pop(0) + rules_connect.pop(0) + rules_connect.pop(0) + rules_connect.pop(0) + rules_connect.pop(0) + rules_connect.pop(0) + rules_connect.pop(0) + rules_connect.pop(0) + rules_connect.pop(0) input_rules, output_rules, srules = fwp.gen_rules(rescue = self.options.rescue) intro = "#Generated by nupyf on %s from %s\n\n" % ( datetime.now(), self.acl_filename) if self.options.dispatch: try_write_file(self.options.dispatch, intro, '#DISPATCH Rules%s'%(linesep), dispatch_rules) if self.options.default_estrel_invalid: try_write_file(self.options.default_estrel_invalid, intro, '#DEFAULT Established and Related Rules%s'%(linesep), default_estrel_invalid) if self.options.dispatch_targets: try_write_file(self.options.dispatch_targets, intro, '#DISPATCH Target Rules%s'%(linesep), rules_create) if self.options.forward: try_write_file(self.options.forward, intro, '#Rules for FORWARD%s'%linesep, srules) if self.options.input: try_write_file(self.options.input, intro, '#Rules for INPUT%s'%linesep, input_rules) if self.options.output: try_write_file(self.options.output, intro, '#Rules for OUTPUT%s'%linesep, output_rules) if self.options.vpn_rules: try_write_file(self.options.vpn_rules, intro, '#VPN Rules%s'%linesep, fwp.gen_vpn_rules()) if self.options.nat_rules: try_write_file(self.options.nat_rules, intro, '#NAT Rules%s'%linesep, fwp.gen_nat_rules()) if self.options.mangle: try_write_file(self.options.mangle, intro, '#MANGLE Rules%s'%linesep, fwp.mangle_rules()) def ldapStuff(self): ldapfw = LDAPfw(self.myfw) if self.options.dump_ldap: cPickle.dump(ldapfw, open(self.options.dump_ldap, 'w+')) else: conn = LDAPConn(self.options.ldap_server, self.options.ldap_user, self.options.ldap_pwd, self.options.ldap_basedn) ldapfw.process(conn) def mainLDAP(self): filename = self.options.load_ldap server = self.options.ldap_server user = self.options.ldap_user password = self.options.ldap_pwd basedn = self.options.ldap_basedn try: dapfw = cPickle.load(open(filename)) ldapconn = LDAPConn(server, user, password, basedn) dapfw.process(ldapconn) exit(0) except NO_SUCH_OBJECT, err: print "LDAP error: no such object!" print "Check that the top domain does exist." except LDAPError, err: print "LDAP error: %s" % err if self.options.debug: raise print "(user: %s, server: %s, basedn: %s)" % (user, server, basedn) print print "Please check LDAP parameters of your nupyf configuration" exit(1) def main(): #use psyco if available try: import psyco psyco.full() except ImportError: pass # Call main code nupyf = Nupyf() if not nupyf.options.debug: try: nupyf.run() exit(0) except LDAPError, ldaperr: print >>stderr, 'LDAP error: %s' % ldaperr except Exception, err: if err.__class__ is SystemExit: # exit(0) is not an error! raise print >>stderr, 'Error: [%s] %s' % (err.__class__.__name__, err) exit(1) else: nupyf.run() exit(0) if __name__ == "__main__": main()