[capirca] r244 committed - Porting many changes from Google internal tree to external OSS side....

23 views
Skip to first unread message

cap...@googlecode.com

unread,
Sep 10, 2013, 6:39:55 PM9/10/13
to capir...@googlegroups.com
Revision: 244
Author: wat...@google.com
Date: Tue Sep 10 22:36:59 2013 UTC
Log: Porting many changes from Google internal tree to external OSS
side.
--
Paul (Tony) Watson


http://code.google.com/p/capirca/source/detail?r=244

Modified:
/trunk/aclgen.py
/trunk/lib/aclgenerator.py
/trunk/lib/cisco.py
/trunk/lib/iptables.py
/trunk/lib/juniper.py
/trunk/lib/junipersrx.py
/trunk/lib/naming.py
/trunk/lib/packetfilter.py
/trunk/lib/policy.py
/trunk/lib/silverpeak.py
/trunk/lib/speedway.py
/trunk/policies/sample_multitarget.pol

=======================================
--- /trunk/aclgen.py Tue Mar 26 21:47:24 2013 UTC
+++ /trunk/aclgen.py Tue Sep 10 22:36:59 2013 UTC
@@ -126,8 +126,8 @@
pf = copy.deepcopy(pol)
if 'speedway' in header.platforms:
spd = copy.deepcopy(pol)
- if 'silverpeak' in header.platforms:
- spk = copy.deepcopy(pol)
+ #if 'silverpeak' in header.platforms:
+ # spk = copy.deepcopy(pol)
# SRX needs to be un-optimized for correct building of the address book
# entries.
if 'srx' in header.platforms:
=======================================
--- /trunk/lib/aclgenerator.py Tue Mar 26 22:01:05 2013 UTC
+++ /trunk/lib/aclgenerator.py Tue Sep 10 22:36:59 2013 UTC
@@ -19,6 +19,7 @@

import copy
import re
+from string import Template

import policy

@@ -66,6 +67,12 @@

class UnsupportedFilterError(Error):
"""Raised when we see an inappropriate filter."""
+ pass
+
+
+class TermNameTooLongError(Error):
+ """Raised when term named can not be abbreviated."""
+ pass


class Term(object):
@@ -106,6 +113,11 @@
PROTO_MAP_BY_NUMBER = dict([(v, k) for (k, v) in PROTO_MAP.iteritems()])
AF_MAP_BY_NUMBER = dict([(v, k) for (k, v) in AF_MAP.iteritems()])

+ NO_AF_LOG_FORMAT = Template('Term $term will not be rendered, as it has'
+ ' $direction address match specified but no'
+ ' $direction addresses of $af address family'
+ ' are present.')
+
def NormalizeAddressFamily(self, af):
"""Convert (if necessary) address family name to numeric value.

@@ -187,7 +199,7 @@
# Commonly misspelled protocols that the generator should reject.
_FILTER_BLACKLIST = {}

- # set of required keywords that every generator must support
+ # Set of required keywords that every generator must support.
_REQUIRED_KEYWORDS = set(['action',
'comment',
'destination_address',
@@ -208,6 +220,46 @@
# Generators should redefine this in subclass as optional support is
added
_OPTIONAL_SUPPORTED_KEYWORDS = set([])

+ # Abbreviation table used to automatically abbreviate terms that exceed
+ # specified limit. We use uppercase for abbreviations to distinguish
+ # from lowercase names. This is order list - we try the ones in the
+ # top of the list before the ones later in the list. Prefer clear
+ # or very-space-saving abbreviations by putting them early in the
+ # list. Abbreviations may be regular expressions or fixed terms;
+ # prefer fixed terms unless there's a clear benefit to regular
+ # expressions.
+ _ABBREVIATION_TABLE = [
+ ('bogons', 'BGN'),
+ ('bogon', 'BGN'),
+ ('reserved', 'RSV'),
+ ('rfc1918', 'PRV'),
+ ('rfc-1918', 'PRV'),
+ ('internet', 'EXT'),
+ ('global', 'GBL'),
+ ('internal', 'INT'),
+ ('customer', 'CUST'),
+ ('google', 'GOOG'),
+ ('ballmer', 'ASS'),
+ ('microsoft', 'LOL'),
+ ('china', 'BAN'),
+ ('border', 'BDR'),
+ ('service', 'SVC'),
+ ('router', 'RTR'),
+ ('transit', 'TRNS'),
+ ('experiment', 'EXP'),
+ ('established', 'EST'),
+ ('unreachable', 'UNR'),
+ ('fragment', 'FRG'),
+ ('accept', 'OK'),
+ ('discard', 'DSC'),
+ ('reject', 'REJ'),
+ ('replies', 'ACK'),
+ ('request', 'REQ'),
+ ]
+ # Maximum term length. Can be overriden by generator to enforce
+ # platform specific restrictions.
+ _TERM_MAX_LENGTH = 62
+
def __init__(self, pol, exp_info):
"""Initialise an ACLGenerator. Store policy structure for
processing."""
object.__init__(self)
@@ -289,6 +341,42 @@

return mod

+ def FixTermLength(self, term_name, abbreviate=False, truncate=False):
+ """Return a term name which is equal or shorter than _TERM_MAX_LENGTH.
+
+ New term is obtained in two steps. First, if allowed, automatic
+ abbreviation is performed using hardcoded abbreviation table.
Second,
+ if allowed, term name is truncated to specified limit.
+
+ Args:
+ term_name: Name to abbreviate if necessary.
+ abbreviate: Whether to allow abbreviations to shorten the length.
+ truncate: Whether to allow truncation to shorten the length.
+ Returns:
+ A string based on term_name, that is equal or shorter than
+ _TERM_MAX_LENGTH abbreviated and truncated as necessary.
+ Raises:
+ TermNameTooLongError: term_name cannot be abbreviated
+ to be shorter than _TERM_MAX_LENGTH, or truncation is disabled.
+ """
+ new_term = term_name
+ if abbreviate:
+ for word, abbrev in self._ABBREVIATION_TABLE:
+ if len(new_term) <= self._TERM_MAX_LENGTH:
+ return new_term
+ new_term = re.sub(word, abbrev, new_term)
+ if truncate:
+ new_term = new_term[:self._TERM_MAX_LENGTH]
+ if len(new_term) <= self._TERM_MAX_LENGTH:
+ return new_term
+ raise TermNameTooLongError('Term %s (originally %s) is '
+ 'too long. Limit is %d characters (vs. %d) '
+ 'and no abbreviations remain or
abbreviations '
+ 'disabled.' %
+ (new_term, term_name,
+ self._TERM_MAX_LENGTH,
+ len(new_term)))
+

def AddRepositoryTags(prefix=''):
"""Add repository tagging into the output.
=======================================
--- /trunk/lib/cisco.py Tue Mar 26 21:47:24 2013 UTC
+++ /trunk/lib/cisco.py Tue Sep 10 22:36:59 2013 UTC
@@ -216,7 +216,7 @@
# source address
saddrs = term.GetAddressOfVersion('source_address', 4)
# check to see if we've already seen this address.
- if saddrs and not saddrs[0].parent_token in addresses:
+ if saddrs and saddrs[0].parent_token not in addresses:
addresses[saddrs[0].parent_token] = True
ret_str.append('object-group ip address %s' %
saddrs[0].parent_token)
for addr in saddrs:
@@ -226,7 +226,7 @@
# destination address
daddrs = term.GetAddressOfVersion('destination_address', 4)
# check to see if we've already seen this address
- if daddrs and not daddrs[0].parent_token in addresses:
+ if daddrs and daddrs[0].parent_token not in addresses:
addresses[daddrs[0].parent_token] = True
ret_str.append('object-group ip address %s' %
daddrs[0].parent_token)
for addr in term.GetAddressOfVersion('destination_address', 4):
@@ -238,7 +238,7 @@
if not port:
continue
port_key = '%s-%s' % (port[0], port[1])
- if not port_key in ports.keys():
+ if port_key not in ports.keys():
ports[port_key] = True
ret_str.append('object-group ip port %s' % port_key)
if port[0] != port[1]:
@@ -336,7 +336,7 @@
return '\n'.join(ret_str)

def _TermletToStr(self, action, proto, saddr, sport, daddr, dport):
-
+ """Output a portion of a cisco term/filter only, based on the
5-tuple."""
# fix addreses
if saddr:
saddr = 'addrgroup %s' % saddr
@@ -365,6 +365,7 @@
# Our caller should have already verified the address family.
assert af in (4, 6)
self.af = af
+ self.text_af = self.AF_MAP_BY_NUMBER[self.af]

def __str__(self):
# Verify platform specific terms. Skip whole term if platform does not
@@ -420,6 +421,11 @@
source_address = nacaddr.ExcludeAddrs(
source_address,
source_address_exclude)
+ if not source_address:
+ logging.warn(self.NO_AF_LOG_FORMAT.substitute(term=self.term.name,
+ direction='source',
+ af=self.text_af))
+ return ''
else:
# source address not set
source_address = ['any']
@@ -434,6 +440,11 @@
destination_address = nacaddr.ExcludeAddrs(
destination_address,
destination_address_exclude)
+ if not destination_address:
+ logging.warn(self.NO_AF_LOG_FORMAT.substitute(term=self.term.name,
+
direction='destination',
+ af=self.text_af))
+ return ''
else:
# destination address not set
destination_address = ['any']
@@ -545,7 +556,6 @@
sane_options = list(option)
if proto == self.PROTO_MAP['udp'] and 'established' in sane_options:
sane_options.remove('established')
-
ret_lines = []

# str(icmp_type) is needed to ensure 0 maps to '0' instead of FALSE
@@ -563,7 +573,7 @@
))

# remove any trailing spaces and replace multiple spaces with singles
- stripped_ret_lines = [re.sub('\s+', ' ', x).rstrip() for x in
ret_lines]
+ stripped_ret_lines = [re.sub(r'\s+', ' ', x).rstrip() for x in
ret_lines]
return stripped_ret_lines


@@ -583,6 +593,7 @@
'policer',
'port',
'qos',
+ 'routing_instance',
])

def _TranslatePolicy(self, pol, exp_info):
@@ -595,7 +606,7 @@
'mixed']

for header, terms in pol.filters:
- if not self._PLATFORM in header.platforms:
+ if self._PLATFORM not in header.platforms:
continue

obj_target = ObjectGroup()
@@ -641,6 +652,7 @@

new_terms = []
for term in terms:
+ term.name = self.FixTermLength(term.name)
af = 'inet'
if next_filter == 'inet6':
af = 'inet6'
@@ -719,7 +731,9 @@

# now add the terms
for term in terms:
- target.append(str(term))
+ term_str = str(term)
+ if term_str:
+ target.append(term_str)
target.append('\n')

if obj_target.valid:
=======================================
--- /trunk/lib/iptables.py Tue Mar 26 21:47:24 2013 UTC
+++ /trunk/lib/iptables.py Tue Sep 10 22:36:59 2013 UTC
@@ -19,12 +19,14 @@

__author__ = 'wat...@google.com (Tony Watson)'

-import aclgenerator
import datetime
import logging
import nacaddr
import re
+from string import Template

+import aclgenerator
+

class Term(aclgenerator.Term):
"""Generate Iptables policy terms."""
@@ -34,7 +36,10 @@
# correct in cases where we've omitted fields from term.
_PLATFORM = 'iptables'
_POSTJUMP_FORMAT = None
- _PREJUMP_FORMAT = '-A %s -j %s'
+ _PREJUMP_FORMAT = Template('-A $filter -j $term')
+ _TERM_FORMAT = Template('-N $term')
+ _COMMENT_FORMAT = Template('-A $term -m comment --comment "$comment"')
+ _FILTER_TOP_FORMAT = Template('-A $term')
_ACTION_TABLE = {
'accept': '-j ACCEPT',
'deny': '-j DROP',
@@ -70,8 +75,7 @@
'sample': '',
}

- def __init__(self, term, filter_name, trackstate, filter_action,
af='inet',
- truncate=True):
+ def __init__(self, term, filter_name, trackstate, filter_action,
af='inet'):
"""Setup a new term.

Args:
@@ -80,7 +84,6 @@
trackstate: Specifies if conntrack should be used for new connections
filter_action: The default action of the filter.
af: Which address family ('inet' or 'inet6') to apply the term to.
- truncate: Whether to truncate names to meet iptables limits.

Raises:
UnsupportedFilterError: Filter is not supported.
@@ -92,17 +95,15 @@
self.options = []
self.af = af

- # Iptables enforces 30 char limit, but weirdness happens after 28 or 29
- self.term_name = '%s_%s' % (
- self.filter[:1], self._CheckTermLength(self.term.name, 24,
truncate))
-
if af == 'inet6':
self._all_ips = nacaddr.IPv6('::/0')
self._ACTION_TABLE['reject'] = '-j REJECT --reject-with
adm-prohibited'
else:
self._all_ips = nacaddr.IPv4('0.0.0.0/0')
- self._ACTION_TABLE['reject'] = '-j REJECT --reject-with ' \
- 'icmp-host-prohibited'
+ self._ACTION_TABLE['reject'] = ('-j REJECT --reject-with '
+ 'icmp-host-prohibited')
+
+ self.term_name = '%s_%s' % (self.filter[:1], self.term.name)

def __str__(self):
# Verify platform specific terms. Skip whole term if platform does not
@@ -149,10 +150,12 @@
'- specify source or dest', '\nError in term:', self.term.name))

# Create a new term
- ret_str.append('-N %s' % self.term_name) # New term
+ if self._TERM_FORMAT:
+ ret_str.append(self._TERM_FORMAT.substitute(term=self.term_name))

if self._PREJUMP_FORMAT:
- ret_str.append(self._PREJUMP_FORMAT % (self.filter, self.term_name))
+ ret_str.append(self._PREJUMP_FORMAT.substitute(filter=self.filter,
+ term=self.term_name))

if self.term.owner:
self.term.comment.append('Owner: %s' % self.term.owner)
@@ -171,8 +174,9 @@
if not line:
continue # iptables-restore does not like 0-length comments.
# term comments
- ret_str.append('-A %s -m comment --comment "%s"' %
- (self.term_name, str(line)))
+ ret_str.append(self._COMMENT_FORMAT.substitute(filter=self.filter,
+ term=self.term_name,
+ comment=str(line)))

# if terms does not specify action, use filter default action
if not self.term.action:
@@ -200,65 +204,20 @@
'\n', self.term.name,
'protocol_except logic not currently supported.'))

- # source address
- term_saddr = self.term.source_address
- exclude_saddr = self.term.source_address_exclude
- term_saddr_excluded = []
+ (term_saddr, exclude_saddr,
+ term_daddr, exclude_daddr) = self._CalculateAddresses(
+ self.term.source_address, self.term.source_address_exclude,
+ self.term.destination_address,
self.term.destination_address_exclude)
if not term_saddr:
- term_saddr = [self._all_ips]
- if exclude_saddr:
- term_saddr_excluded.extend(nacaddr.ExcludeAddrs(term_saddr,
- exclude_saddr))
-
- # destination address
- term_daddr = self.term.destination_address
- exclude_daddr = self.term.destination_address_exclude
- term_daddr_excluded = []
+ logging.warn(self.NO_AF_LOG_FORMAT.substitute(term=self.term.name,
+ direction='source',
+ af=self.af))
+ return ''
if not term_daddr:
- term_daddr = [self._all_ips]
- if exclude_daddr:
- term_daddr_excluded.extend(nacaddr.ExcludeAddrs(term_daddr,
- exclude_daddr))
-
- # Just to be safe, always have a result of at least 1 to avoid * by
zero
- # returning incorrect results (10src*10dst=100, but 10src*0dst=0, not
10)
- bailout_count = len(exclude_saddr) + len(exclude_daddr) + (
- (len(self.term.source_address) or 1) *
- (len(self.term.destination_address) or 1))
- exclude_count = ((len(term_saddr_excluded) or 1) *
- (len(term_daddr_excluded) or 1))
-
- # Use bailout jumps for excluded addresses if it results in fewer
output
- # lines than nacaddr.ExcludeAddrs() method.
- if exclude_count < bailout_count:
- exclude_saddr = []
- exclude_daddr = []
- if term_saddr_excluded:
- term_saddr = term_saddr_excluded
- if term_daddr_excluded:
- term_daddr = term_daddr_excluded
-
- # With many sources and destinations, iptables needs to generate the
- # cartesian product of sources and destinations. If there are no
- # exclude rules, this can instead be written as exclude [0/0 -
- # srcs], exclude [0/0 - dsts].
- v4_src_count = len([x for x in term_saddr if x.version == 4])
- v4_dst_count = len([x for x in term_daddr if x.version == 4])
- v6_src_count = len([x for x in term_saddr if x.version == 6])
- v6_dst_count = len([x for x in term_daddr if x.version == 6])
- num_pairs = v4_src_count * v4_dst_count + v6_src_count * v6_dst_count
- if num_pairs > 100:
- new_exclude_source = nacaddr.ExcludeAddrs([self._all_ips],
term_saddr)
- new_exclude_dest = nacaddr.ExcludeAddrs([self._all_ips], term_daddr)
- # Invert the shortest list that does not already have exclude
addresses
- if len(new_exclude_source) < len(new_exclude_dest) and not
exclude_saddr:
- if len(new_exclude_source) + len(term_daddr) < num_pairs:
- exclude_saddr = new_exclude_source
- term_saddr = [self._all_ips]
- elif not exclude_daddr:
- if len(new_exclude_dest) + len(term_saddr) < num_pairs:
- exclude_daddr = new_exclude_dest
- term_daddr = [self._all_ips]
+ logging.warn(self.NO_AF_LOG_FORMAT.substitute(term=self.term.name,
+
direction='destination',
+ af=self.af))
+ return ''

# ports
source_port = []
@@ -330,11 +289,11 @@

for saddr in exclude_saddr:
ret_str.extend(self._FormatPart(
- self.af, '', saddr, '', '', '', '', '', '', '', '', '', '',
+ '', saddr, '', '', '', '', '', '', '', '', '', '',
self._ACTION_TABLE.get('next')))
for daddr in exclude_daddr:
ret_str.extend(self._FormatPart(
- self.af, '', '', '', daddr, '', '', '', '', '', '', '', '',
+ '', '', '', daddr, '', '', '', '', '', '', '', '',
self._ACTION_TABLE.get('next')))

for saddr in term_saddr:
@@ -343,7 +302,6 @@
for proto in protocol:
for tcp_matcher in tcp_track_options or (([], []),):
ret_str.extend(self._FormatPart(
- self.af,
str(proto),
saddr,
source_port,
@@ -360,17 +318,98 @@
))

if self._POSTJUMP_FORMAT:
- ret_str.append(self._POSTJUMP_FORMAT % (self.filter, self.term_name))
+ ret_str.append(self._POSTJUMP_FORMAT.substitute(filter=self.filter,
+ term=self.term_name))

return '\n'.join(str(v) for v in ret_str if v is not '')

- def _FormatPart(self, af, protocol, saddr, sport, daddr, dport, options,
+ def _CalculateAddresses(self, term_saddr, exclude_saddr,
+ term_daddr, exclude_daddr):
+ """Calculate source and destination address list for a term.
+
+ Args:
+ term_saddr: source address list of the term
+ exclude_saddr: source address exclude list of the term
+ term_daddr: destination address list of the term
+ exclude_daddr: destination address exclude list of the term
+
+ Returns:
+ tuple containing source address list, source exclude address list,
+ destination address list, destination exclude address list in
+ that order
+
+ """
+ # source address
+ term_saddr_excluded = []
+ if not term_saddr:
+ term_saddr = [self._all_ips]
+ if exclude_saddr:
+ term_saddr_excluded.extend(nacaddr.ExcludeAddrs(term_saddr,
+ exclude_saddr))
+
+ # destination address
+ term_daddr_excluded = []
+ if not term_daddr:
+ term_daddr = [self._all_ips]
+ if exclude_daddr:
+ term_daddr_excluded.extend(nacaddr.ExcludeAddrs(term_daddr,
+ exclude_daddr))
+
+ # Just to be safe, always have a result of at least 1 to avoid * by
zero
+ # returning incorrect results (10src*10dst=100, but 10src*0dst=0, not
10)
+ bailout_count = len(exclude_saddr) + len(exclude_daddr) + (
+ (len(self.term.source_address) or 1) *
+ (len(self.term.destination_address) or 1))
+ exclude_count = ((len(term_saddr_excluded) or 1) *
+ (len(term_daddr_excluded) or 1))
+
+ # Use bailout jumps for excluded addresses if it results in fewer
output
+ # lines than nacaddr.ExcludeAddrs() method.
+ if exclude_count < bailout_count:
+ exclude_saddr = []
+ exclude_daddr = []
+ if term_saddr_excluded:
+ term_saddr = term_saddr_excluded
+ if term_daddr_excluded:
+ term_daddr = term_daddr_excluded
+
+ # With many sources and destinations, iptables needs to generate the
+ # cartesian product of sources and destinations. If there are no
+ # exclude rules, this can instead be written as exclude [0/0 -
+ # srcs], exclude [0/0 - dsts].
+ v4_src_count = len([x for x in term_saddr if x.version == 4])
+ v4_dst_count = len([x for x in term_daddr if x.version == 4])
+ v6_src_count = len([x for x in term_saddr if x.version == 6])
+ v6_dst_count = len([x for x in term_daddr if x.version == 6])
+ num_pairs = v4_src_count * v4_dst_count + v6_src_count * v6_dst_count
+ if num_pairs > 100:
+ new_exclude_source = nacaddr.ExcludeAddrs([self._all_ips],
term_saddr)
+ new_exclude_dest = nacaddr.ExcludeAddrs([self._all_ips], term_daddr)
+ # Invert the shortest list that does not already have exclude
addresses
+ if len(new_exclude_source) < len(new_exclude_dest) and not
exclude_saddr:
+ if len(new_exclude_source) + len(term_daddr) < num_pairs:
+ exclude_saddr = new_exclude_source
+ term_saddr = [self._all_ips]
+ elif not exclude_daddr:
+ if len(new_exclude_dest) + len(term_saddr) < num_pairs:
+ exclude_daddr = new_exclude_dest
+ term_daddr = [self._all_ips]
+ term_saddr = [x for x in term_saddr
+ if x.version == self.AF_MAP[self.af]]
+ exclude_saddr = [x for x in exclude_saddr
+ if x.version == self.AF_MAP[self.af]]
+ term_daddr = [x for x in term_daddr
+ if x.version == self.AF_MAP[self.af]]
+ exclude_daddr = [x for x in exclude_daddr
+ if x.version == self.AF_MAP[self.af]]
+ return (term_saddr, exclude_saddr, term_daddr, exclude_daddr)
+
+ def _FormatPart(self, protocol, saddr, sport, daddr, dport, options,
tcp_flags, icmp_type, track_flags, sint, dint, log_hits,
action):
"""Compose one iteration of the term parts into a string.

Args:
- af: Address family, inet|inet6
protocol: The network protocol
saddr: Source IP address
sport: Source port numbers
@@ -387,30 +426,10 @@
Returns:
rval: A single iptables argument line
"""
- src = ''
- dst = ''
- # Check that AF matches and is what we want
- if saddr:
- if (af == 'inet') and (saddr.version != 4):
- return ''
- if (af == 'inet6') and (saddr.version != 6):
- return ''
- if daddr:
- if (af == 'inet') and (daddr.version != 4):
- return ''
- if (af == 'inet6') and (daddr.version != 6):
- return ''
- filter_top = '-A ' + self.term_name
- # fix addresses
- if not saddr or saddr == self._all_ips:
- src = ''
- else:
- src = '-s %s/%d' % (saddr.ip, saddr.prefixlen)
+ src, dst = self._GenerateAddressStatement(saddr, daddr)

- if not daddr or daddr == self._all_ips:
- dst = ''
- else:
- dst = '-d %s/%d' % (daddr.ip, daddr.prefixlen)
+ filter_top = self._FILTER_TOP_FORMAT.substitute(filter=self.filter,
+ term=self.term_name)

source_int = ''
if sint:
@@ -490,6 +509,30 @@
ret_lines.append(' '.join(rval+[action]))
return ret_lines

+ def _GenerateAddressStatement(self, saddr, daddr):
+ """Return the address section of an individual iptables rule.
+
+ Args:
+ saddr: source address of the rule
+ daddr: destination address of the rule
+
+ Returns:
+ tuple containing source and destination address statement, in
+ that order
+
+ """
+ src = ''
+ dst = ''
+ if not saddr or saddr == self._all_ips:
+ src = ''
+ else:
+ src = '-s %s/%d' % (saddr.ip, saddr.prefixlen)
+ if not daddr or daddr == self._all_ips:
+ dst = ''
+ else:
+ dst = '-d %s/%d' % (daddr.ip, daddr.prefixlen)
+ return (src, dst)
+
def _GeneratePortStatement(self, ports, source=False, dest=False):
"""Return the 'port' section of an individual iptables rule.

@@ -545,66 +588,6 @@
','.join(norm_ports)))
return portstrings

- def _CheckTermLength(self, term_name, term_max_len, abbreviate):
- """Return a name based on term_name which is shorter than term_max_len.
-
- Args:
- term_name: A name to abbreviate if necessary.
- term_max_len: An int representing the maximum acceptable length.
- abbreviate: whether to allow abbreviations to shorten the length
- Returns:
- A string based on term_name, abbreviated as necessary to fit
term_max_len.
- Raises:
- TermNameTooLongError: term_name cannot be abbreviated below
term_max_len.
- """
- # We use uppercase for abbreviations to distinguish from lowercase
- # names. Ordered list of abbreviations, we try the ones in the
- # top of the list before the ones later in the list. Prefer clear
- # or very-space-saving abbreviations by putting them early in the
- # list. Abbreviations may be regular expressions or fixed terms;
- # prefer fixed terms unless there's a clear benefit to regular
- # expressions.
- abbreviation_table = [
- ('bogons', 'BGN'),
- ('bogon', 'BGN'),
- ('reserved', 'RSV'),
- ('rfc1918', 'PRV'),
- ('rfc-1918', 'PRV'),
- ('internet', 'EXT'),
- ('global', 'GBL'),
- ('internal', 'INT'),
- ('customer', 'CUST'),
- ('google', 'GOOG'),
- ('ballmer', 'ASS'),
- ('microsoft', 'LOL'),
- ('china', 'BAN'),
- ('border', 'BDR'),
- ('service', 'SVC'),
- ('router', 'RTR'),
- ('transit', 'TRNS'),
- ('experiment', 'EXP'),
- ('established', 'EST'),
- ('unreachable', 'UNR'),
- ('fragment', 'FRG'),
- ('accept', 'OK'),
- ('discard', 'DSC'),
- ('reject', 'REJ'),
- ('replies', 'ACK'),
- ('request', 'REQ'),
- ]
- new_term = term_name
- if abbreviate:
- for word, abbrev in abbreviation_table:
- if len(new_term) <= term_max_len:
- return new_term
- new_term = re.sub(word, abbrev, new_term)
- if len(new_term) <= term_max_len:
- return new_term
- raise TermNameTooLongError('%s %s %s %s%s %d %s' % (
- '\nTerm', new_term, '(originally', term_name,
- ') is too long. Limit is 24 characters (vs', len(new_term),
- ') and no abbreviations remain.'))
-

class Iptables(aclgenerator.ACLGenerator):
"""Generates filters and terms from provided policy object."""
@@ -617,7 +600,9 @@
_DEFAULTACTION_FORMAT = '-P %s %s'
_DEFAULT_ACTION = 'DROP'
_TERM = Term
+ _TERM_MAX_LENGTH = 24
_OPTIONAL_SUPPORTED_KEYWORDS = set(['counter',
+ 'destination_interface',
'destination_prefix', # skips these
terms
'expiration',
'fragment_offset',
@@ -626,12 +611,13 @@
'packet_length',
'policer', # safely
ignored
'qos',
+ 'routing_instance', # safe to skip
'source_interface',
- 'destination_interface',
'source_prefix', # skips these
terms
])

def _TranslatePolicy(self, pol, exp_info):
+ """Translate a policy from objects into strings."""
self.iptables_policies = []
current_date = datetime.date.today()
exp_info_date = current_date + datetime.timedelta(weeks=exp_info)
@@ -640,12 +626,12 @@
good_default_actions = ['ACCEPT', 'DROP']
good_filters = ['INPUT', 'OUTPUT', 'FORWARD']
good_afs = ['inet', 'inet6']
- good_options = ['nostate', 'truncatenames']
+ good_options = ['nostate', 'abbreviateterms', 'truncateterms']
all_protocols_stateful = True

for header, terms in pol.filters:
filter_type = None
- if not self._PLATFORM in header.platforms:
+ if self._PLATFORM not in header.platforms:
continue

filter_options = header.FilterOptions(self._PLATFORM)[1:]
@@ -698,6 +684,9 @@
new_terms = []
term_names = set()
for term in terms:
+ term.name = self.FixTermLength(term.name,
+ 'abbreviateterms' in filter_options,
+ 'truncateterms' in filter_options)
if term.name in term_names:
raise aclgenerator.DuplicateTermError(
'You have a duplicate term: %s' % term.name)
@@ -718,8 +707,7 @@
continue

new_terms.append(self._TERM(term, filter_name,
all_protocols_stateful,
- default_action, filter_type,
- 'truncatenames' in filter_options))
+ default_action, filter_type))

self.iptables_policies.append((header, filter_name, filter_type,
default_action, new_terms))
@@ -758,12 +746,14 @@
default_action))
# add the terms
for term in terms:
- target.append(str(term))
- target.append('\n')
+ term_str = str(term)
+ if term_str:
+ target.append(term_str)

if self._RENDER_SUFFIX:
target.append(self._RENDER_SUFFIX)

+ target.append('')
return '\n'.join(target)


@@ -771,10 +761,6 @@
"""Base error class."""


-class TermNameTooLongError(Error):
- """Term name is too long."""
-
-
class BadPortsError(Error):
"""Too many ports for a single iptables statement."""

=======================================
--- /trunk/lib/juniper.py Tue Mar 26 22:01:05 2013 UTC
+++ /trunk/lib/juniper.py Tue Sep 10 22:36:59 2013 UTC
@@ -15,8 +15,8 @@
# limitations under the License.
#

-__author__ = 'pmo...@google.com (Peter Moody)'
-__author__ = 'wat...@google.com (Tony Watson)'
+__author__ = ['pmo...@google.com (Peter Moody)',
+ 'wat...@google.com (Tony Watson)']


import datetime
@@ -51,12 +51,66 @@
pass


+class JuniperIndentationError(Error):
+ pass
+
+
+class Config(object):
+ """Config allows a configuration to be assembled easily.
+
+ Configurations are automatically indented following Juniper's style.
+ A textual representation of the config can be extracted with str().
+
+ Attributes:
+ indent: The number of leading spaces on the current line.
+ tabstop: The number of spaces to indent for a new level.
+ """
+
+ def __init__(self, indent=0, tabstop=4):
+ self.indent = indent
+ self._initial_indent = indent
+ self.tabstop = tabstop
+ self.lines = []
+
+ def __str__(self):
+ if self.indent != self._initial_indent:
+ raise JuniperIndentationError(
+ 'Expected indent %d but got %d' % (self._initial_indent,
self.indent))
+ return '\n'.join(self.lines)
+
+ def Append(self, line, verbatim=False):
+ """Append one line to the configuration.
+
+ Args:
+ line: The string to append to the config.
+ verbatim: append line without adjusting indentation. Default False.
+ Raises:
+ JuniperIndentationError: If the indentation would be further left
+ than the initial indent. e.g. too many close braces.
+ """
+ if verbatim:
+ self.lines.append(line)
+ return
+
+ if line.endswith('}'):
+ self.indent -= self.tabstop
+ if self.indent < self._initial_indent:
+ raise JuniperIndentationError('Too many close braces.')
+ spaces = ' ' * self.indent
+ self.lines.append(spaces + line.strip())
+ if line.endswith(' {'):
+ self.indent += self.tabstop
+
+
class Term(aclgenerator.Term):
"""Representation of an individual Juniper term.

This is mostly useful for the __str__() method.

- Args: term policy.Term object
+ Args:
+ term: policy.Term object
+ term_type: the address family for the term, one of "inet", "inet6",
+ or "bridge"
"""
_DEFAULT_INDENT = 12
_ACTIONS = {'accept': 'accept',
@@ -97,26 +151,9 @@
self.term = term
self.term_type = term_type

- if not term_type in self._TERM_TYPE:
+ if term_type not in self._TERM_TYPE:
raise ValueError('Unknown Filter Type: %s' % term_type)

- if (not self.term.address and
- not self.term.destination_address and
- not self.term.destination_prefix and
- not self.term.destination_port and
- not self.term.precedence and
- not self.term.protocol and
- not self.term.protocol_except and
- not self.term.port and
- not self.term.source_address and
- not self.term.source_prefix and
- not self.term.source_port and
- not self.term.ether_type and
- not self.term.traffic_type):
-
- self.default_action = True
- else:
- self.default_action = False
# some options need to modify the actions
self.extra_actions = []

@@ -133,20 +170,16 @@
if 'juniper' in self.term.platform_exclude:
return ''

- ret_str = []
+ config = Config(indent=self._DEFAULT_INDENT)
from_str = []

- # we need a quick way to generate some number of ' ' chars for lining
up
- # the terms properly.
- indent = lambda n: ' ' * (self._DEFAULT_INDENT + n)
-
# Don't render icmpv6 protocol terms under inet, or icmp under inet6
if ((self.term_type == 'inet6' and 'icmp' in self.term.protocol) or
(self.term_type == 'inet' and 'icmpv6' in self.term.protocol)):
- ret_str.append(indent(0) + '/* Term %s' % self.term.name)
- ret_str.append(indent(0) + '** not rendered due to protocol/AF
mismatch.')
- ret_str.append(indent(0) + '*/')
- return '\n'.join(ret_str)
+ config.Append('/* Term %s' % self.term.name)
+ config.Append('** not rendered due to protocol/AF mismatch.')
+ config.Append('*/')
+ return str(config)

# comment
# this deals just fine with multi line comments, but we could probably
@@ -155,19 +188,22 @@
if self.term.owner:
self.term.comment.append('Owner: %s' % self.term.owner)
if self.term.comment:
- ret_str.append(indent(0) + '/*')
+ config.Append('/*')
for comment in self.term.comment:
for line in comment.split('\n'):
- ret_str.append(indent(0) + '** ' + line)
- ret_str.append(indent(0) + '*/')
+ config.Append('** ' + line)
+ config.Append('*/')

# Term verbatim output - this will skip over normal term creation
# code. Warning generated from policy.py if appropriate.
if self.term.verbatim:
for next_term in self.term.verbatim:
if next_term.value[0] == 'juniper':
- ret_str.append(str(next_term.value[1]))
- return '\n'.join(ret_str)
+ config.Append(str(next_term.value[1]), verbatim=True)
+ return str(config)
+
+ # Helper for per-address-family keywords.
+ family_keywords = self._TERM_TYPE.get(self.term_type)

# option
# this is going to be a little ugly b/c there are a few little messed
@@ -175,169 +211,176 @@
if self.term.option:
for opt in [str(x) for x in self.term.option]:
# there should be a better way to search the array of protocols
- if opt.find('sample') == 0:
+ if opt.startswith('sample'):
self.extra_actions.append('sample')

# only append tcp-established for option established when
# tcp is the only protocol, otherwise other protos break on juniper
- elif opt.find('established') == 0:
+ elif opt.startswith('established'):
if self.term.protocol == ['tcp']:
- if 'tcp-established;' not in [x.strip() for x in from_str]:
- from_str.append(indent(8) + self._TERM_TYPE.get(
- self.term_type).get('tcp-est') + ';')
+ if 'tcp-established;' not in from_str:
+ from_str.append(family_keywords['tcp-est'] + ';')

# if tcp-established specified, but more than just tcp is included
# in the protocols, raise an error
- elif opt.find('tcp-established') == 0:
+ elif opt.startswith('tcp-established'):
+ flag = family_keywords['tcp-est'] + ';'
if self.term.protocol == ['tcp']:
- if 'tcp-established;' not in [x.strip() for x in from_str]:
- term_est = self._TERM_TYPE.get(self.term_type).get('tcp-est')
- from_str.append(indent(8) + term_est + ';')
+ if flag not in from_str:
+ from_str.append(flag)
else:
raise TcpEstablishedWithNonTcp(
'tcp-established can only be used with tcp protocol in
term %s'
% self.term.name)
- elif opt.find('rst') == 0:
- from_str.append(indent(8) + 'tcp-flags "rst";')
- elif opt.find('initial') == 0 and 'tcp' in self.term.protocol:
- from_str.append(indent(8) + 'tcp-initial;')
- elif opt.find('first-fragment') == 0:
- from_str.append(indent(8) + 'first-fragment;')
+ elif opt.startswith('rst'):
+ from_str.append('tcp-flags "rst";')
+ elif opt.startswith('initial') and 'tcp' in self.term.protocol:
+ from_str.append('tcp-initial;')
+ elif opt.startswith('first-fragment'):
+ from_str.append('first-fragment;')

# we don't have a special way of dealing with this, so we output
it and
# hope the user knows what they're doing.
else:
- from_str.append('%s%s;' % (indent(8), opt))
+ from_str.append('%s;' % opt)

# term name
- ret_str.append(indent(0) + 'term ' + self.term.name + ' {')
+ config.Append('term %s {' % self.term.name)

# a default action term doesn't have any from { clause
- if not self.default_action:
- ret_str.append(indent(4) + 'from {')
+ has_match_criteria = (self.term.address or
+ self.term.destination_address or
+ self.term.destination_prefix or
+ self.term.destination_port or
+ self.term.precedence or
+ self.term.protocol or
+ self.term.protocol_except or
+ self.term.port or
+ self.term.source_address or
+ self.term.source_prefix or
+ self.term.source_port or
+ self.term.ether_type or
+ self.term.traffic_type)
+
+ if has_match_criteria:
+ config.Append('from {')
+
+ term_af = self.AF_MAP.get(self.term_type)

# address
- address = self.term.GetAddressOfVersion('address',
-
self.AF_MAP.get(self.term_type))
+ address = self.term.GetAddressOfVersion('address', term_af)
if address:
- ret_str.append(indent(8) +
- self._TERM_TYPE.get(self.term_type).get('addr') + '
{')
+ config.Append('%s {' % family_keywords['addr'])
for addr in address:
- # nacaddr comments may not appear for some optimized addresses
- ret_str.append(indent(12) + str(addr) + ';' +
self._Comment(addr))
- ret_str.append(indent(8) + '}')
+ config.Append('%s;%s' % (addr, self._Comment(addr)))
+ config.Append('}')
+ elif self.term.address:
+ logging.warn(self.NO_AF_LOG_FORMAT.substitute(term=self.term.name,
+ af=self.term_type))
+ return ''

# source address
- source_address = self.term.GetAddressOfVersion(
- 'source_address',
- self.AF_MAP.get(self.term_type))
- source_address_exclude = self.term.GetAddressOfVersion(
- 'source_address_exclude',
- self.AF_MAP.get(self.term_type))
+ source_address, source_address_exclude = self._MinimizePrefixes(
+ self.term.GetAddressOfVersion('source_address', term_af),
+ self.term.GetAddressOfVersion('source_address_exclude', term_af))
+
if source_address:
- ret_str.append(indent(8) +
- self._TERM_TYPE.get(self.term_type).get('saddr')
+ ' {')
- for saddr in source_address:
- # nacaddr comments may not appear for some optimized addresses
- ret_str.append(indent(12) + str(saddr) + ';' +
self._Comment(saddr))
- # source-excludes?
- if source_address_exclude:
- for ex in source_address_exclude:
- # nacaddr comments may not appear for some optimized addresses
- ret_str.append(indent(12) + str(ex) + ' except;' +
- self._Comment(ex, exclude=True))
- ret_str.append(indent(8) + '}')
+ config.Append('%s {' % family_keywords['saddr'])
+ for addr in source_address:
+ config.Append('%s;%s' % (addr, self._Comment(addr)))
+ for addr in source_address_exclude:
+ config.Append('%s except;%s' % (
+ addr, self._Comment(addr, exclude=True)))
+ config.Append('}')
+ elif self.term.source_address:
+ logging.warn(self.NO_AF_LOG_FORMAT.substitute(term=self.term.name,
+ direction='source',
+ af=self.term_type))
+ return ''

# destination address
- destination_address = self.term.GetAddressOfVersion(
- 'destination_address',
- self.AF_MAP.get(self.term_type))
- destination_address_exclude = self.term.GetAddressOfVersion(
- 'destination_address_exclude',
- self.AF_MAP.get(self.term_type))
+ destination_address, destination_address_exclude =
self._MinimizePrefixes(
+ self.term.GetAddressOfVersion('destination_address', term_af),
+ self.term.GetAddressOfVersion('destination_address_exclude',
term_af))

if destination_address:
- ret_str.append(indent(8) +
- self._TERM_TYPE.get(self.term_type).get('daddr')
+ ' {')
- for daddr in destination_address:
- # nacaddr comments may not appear for some optimized addresses
- ret_str.append(indent(12) + str(daddr) + ';' +
self._Comment(daddr))
- # destination-excludes?
- if destination_address_exclude:
- for ex in destination_address_exclude:
- ret_str.append(indent(12) + str(ex) + ' except;' +
- self._Comment(ex, exclude=True))
-
- ret_str.append(indent(8) + '}')
+ config.Append('%s {' % family_keywords['daddr'])
+ for addr in destination_address:
+ config.Append('%s;%s' % (addr, self._Comment(addr)))
+ for addr in destination_address_exclude:
+ config.Append('%s except;%s' % (
+ addr, self._Comment(addr, exclude=True)))
+ config.Append('}')
+ elif self.term.destination_address:
+ logging.warn(self.NO_AF_LOG_FORMAT.substitute(term=self.term.name,
+
direction='destination',
+ af=self.term_type))
+ return ''

# source prefix list
if self.term.source_prefix:
- ret_str.append(indent(8) + 'source-prefix-list {')
+ config.Append('source-prefix-list {')
for pfx in self.term.source_prefix:
- ret_str.append(indent(12) + pfx + ';')
- ret_str.append(indent(8) + '}')
+ config.Append(pfx + ';')
+ config.Append('}')

# destination prefix list
if self.term.destination_prefix:
- ret_str.append(indent(8) + 'destination-prefix-list {')
+ config.Append('destination-prefix-list {')
for pfx in self.term.destination_prefix:
- ret_str.append(indent(12) + pfx + ';')
- ret_str.append(indent(8) + '}')
+ config.Append(pfx + ';')
+ config.Append('}')

# protocol
if self.term.protocol:
- ret_str.append(indent(8) +
- self._TERM_TYPE.get(self.term_type).get('protocol')
+
- ' ' + self._Group(self.term.protocol))
+ config.Append(family_keywords['protocol'] +
+ ' ' + self._Group(self.term.protocol))

# protocol
if self.term.protocol_except:
- term_except =
self._TERM_TYPE.get(self.term_type).get('protocol-except')
- ret_str.append(indent(8) + term_except + ' '
- + self._Group(self.term.protocol_except))
+ config.Append(family_keywords['protocol-except'] + ' '
+ + self._Group(self.term.protocol_except))

# port
if self.term.port:
- ret_str.append(indent(8) + 'port ' + self._Group(self.term.port))
+ config.Append('port %s' % self._Group(self.term.port))

# source port
if self.term.source_port:
- ret_str.append(indent(8) + 'source-port ' +
- self._Group(self.term.source_port))
+ config.Append('source-port %s' %
self._Group(self.term.source_port))

# destination port
if self.term.destination_port:
- ret_str.append(indent(8) + 'destination-port ' +
- self._Group(self.term.destination_port))
+ config.Append('destination-port %s' %
+ self._Group(self.term.destination_port))

# append any options beloging in the from {} section
for next_str in from_str:
- ret_str.append(next_str)
+ config.Append(next_str)

# packet length
if self.term.packet_length:
- ret_str.append(indent(8) + 'packet-length ' +
- str(self.term.packet_length) + ';')
+ config.Append('packet-length %s;' % self.term.packet_length)

# fragment offset
if self.term.fragment_offset:
- ret_str.append(indent(8) + 'fragment-offset ' +
- str(self.term.fragment_offset) + ';')
+ config.Append('fragment-offset %s;' % self.term.fragment_offset)
+
# icmp-types
icmp_types = ['']
if self.term.icmp_type:
icmp_types = self.NormalizeIcmpTypes(self.term.icmp_type,
self.term.protocol,
self.term_type)
if icmp_types != ['']:
- ret_str.append(indent(8) + 'icmp-type ' + self._Group(icmp_types))
+ config.Append('icmp-type %s' % self._Group(icmp_types))

if self.term.ether_type:
- ret_str.append(indent(8) + 'ether-type ' +
- self._Group(self.term.ether_type))
+ config.Append('ether-type %s' %
+ self._Group(self.term.ether_type))

if self.term.traffic_type:
- ret_str.append(indent(8) + 'traffic-type ' +
- self._Group(self.term.traffic_type))
+ config.Append('traffic-type %s' %
+ self._Group(self.term.traffic_type))

if self.term.precedence:
# precedence may be a single integer, or a space separated list
@@ -349,64 +392,83 @@
else:
raise PrecedenceError('Precedence value %s is out of bounds
in %s' %
(precedence, self.term.name))
- if len(policy_precedences) > 1:
- # A list looks like '[ 0 3 4 ]'
- precedence_string = '[ %s
]' % ' '.join(sorted(policy_precedences))
- else:
- precedence_string = policy_precedences.pop()
+ config.Append('precedence %s' %
self._Group(sorted(policy_precedences)))

- ret_str.append(indent(8) + 'precedence %s;' % precedence_string)
+ config.Append('}') # end from { ... }

- # end from { ... }
- ret_str.append(indent(4) + '}')
-
+ ####
+ # ACTIONS go below here
+ ####
+ config.Append('then {')
# logging
if self.term.logging:
for log_target in self.term.logging:
if str(log_target) == 'local':
- self.extra_actions.append('log')
+ config.Append('log;')
else:
- self.extra_actions.append('syslog')
+ config.Append('syslog;')

- # routing instance.
if self.term.routing_instance:
- self.extra_actions.append('routing-instance %s' %
- str(self.term.routing_instance))
- # counter
+ config.Append('routing-instance %s;' % self.term.routing_instance)
+
if self.term.counter:
- self.extra_actions.append('count %s' % str(self.term.counter))
+ config.Append('count %s;' % self.term.counter)

- # policer
if self.term.policer:
- self.extra_actions.append('policer %s' % str(self.term.policer))
+ config.Append('policer %s;' % self.term.policer)

- # quality-of-service
if self.term.qos:
- self.extra_actions.append('forwarding-class %s' % str(self.term.qos))
+ config.Append('forwarding-class %s;' % self.term.qos)

- # loss-priority
if self.term.loss_priority:
- self.extra_actions.append('loss-priority %s' %
- str(self.term.loss_priority))
+ config.Append('loss-priority %s;' % self.term.loss_priority)
+
+ for action in self.extra_actions:
+ config.Append(action + ';')
+
+ # If there is a routing-instance defined, skip reject/accept/etc
actions.
+ if not self.term.routing_instance:
+ for action in self.term.action:
+ config.Append(self._ACTIONS.get(action) + ';')

- ####
- # ACTIONS go below here
- ####
- ret_str.append(indent(4) + 'then {')
+ config.Append('}') # end then{...}
+ config.Append('}') # end term accept-foo-to-bar { ... }

- for action in self.extra_actions:
- ret_str.append(indent(8) + str(action) + ';')
+ return str(config)

- for action in self.term.action:
- ret_str.append(indent(8) + self._ACTIONS.get(str(action)) + ';')
+ def _MinimizePrefixes(self, include, exclude):
+ """Calculate a minimal set of prefixes for Juniper match conditions.

- # end then { ... }
- ret_str.append(indent(4) + '}')
+ Args:
+ include: Iterable of nacaddr objects, prefixes to match.
+ exclude: Iterable of nacaddr objects, prefixes to exclude.
+ Returns:
+ A tuple (I,E) where I and E are lists containing the minimized
+ versions of include and exclude, respectively. The order
+ of each input list is preserved.
+ """
+ # Remove any included prefixes that have EXACT matches in the
+ # excluded list. Excluded prefixes take precedence on the router
+ # regardless of the order in which the include/exclude are applied.
+ exclude_set = set(exclude)
+ include_result = [ip for ip in include if ip not in exclude_set]

- # end term accept-foo-to-bar { ... }
- ret_str.append(indent(0) + '}')
+ # Every address match condition on a Juniper firewall filter
+ # contains an implicit "0/0 except" or "0::0/0 except". If an
+ # excluded prefix is not contained within any less-specific prefix
+ # in the included set, we can elide it. In other words, if the
+ # next-less-specific prefix is the implicit "default except",
+ # there is no need to configure the more specific "except".
+ #
+ # TODO(kbrint): this could be made more efficient with a Patricia trie.
+ exclude_result = []
+ for exclude_prefix in exclude:
+ for include_prefix in include_result:
+ if exclude_prefix in include_prefix:
+ exclude_result.append(exclude_prefix)
+ break

- return '\n'.join(ret_str)
+ return include_result, exclude_result

def _Comment(self, addr, exclude=False, line_length=132):
"""Returns address comment field if it exists.
@@ -578,7 +640,7 @@
exp_info_date = current_date + datetime.timedelta(weeks=exp_info)

for header, terms in pol.filters:
- if not self._PLATFORM in header.platforms:
+ if self._PLATFORM not in header.platforms:
continue

filter_options = header.FilterOptions(self._PLATFORM)
@@ -587,7 +649,8 @@
# Checks if the non-interface-specific option was specified.
# I'm assuming that it will be specified as maximum one time, and
# don't check for more appearances of the word in the options.
- interface_specific = not 'not-interface-specific' in
filter_options[1:]
+ interface_specific = 'not-interface-specific' not in
filter_options[1:]
+
# Remove the option so that it is not confused with a filter type
if not interface_specific:
filter_options.remove('not-interface-specific')
@@ -600,6 +663,7 @@
term_names = set()
new_terms = []
for term in terms:
+ term.name = self.FixTermLength(term.name)
if term.name in term_names:
raise JuniperDuplicateTermError('You have multiple terms
named: %s' %
term.name)
@@ -624,38 +688,40 @@
interface_specific, new_terms))

def __str__(self):
- target = []
+ config = Config()

for (header, filter_name, filter_type, interface_specific, terms
) in self.juniper_policies:
# add the header information
- target.append('firewall {')
- target.append(' ' * 4 + 'family %s {' % filter_type)
- target.append(' ' * 8 + 'replace:')
- target.append(' ' * 8 + '/*')
+ config.Append('firewall {')
+ config.Append('family %s {' % filter_type)
+ config.Append('replace:')
+ config.Append('/*')

# we want the acl to contain id and date tags, but p4 will expand
# the tags here when we submit the generator, so we have to trick
# p4 into not knowing these words. like taking c-a-n-d-y from a
# baby.
- target.extend(aclgenerator.AddRepositoryTags(' ' * 8 + '** '))
- target.append(' ' * 8 + '**')
+ for line in aclgenerator.AddRepositoryTags('** '):
+ config.Append(line)
+ config.Append('**')

for comment in header.comment:
for line in comment.split('\n'):
- target.append(' ' * 8 + '** ' + line)
- target.append(' ' * 8 + '*/')
+ config.Append('** ' + line)
+ config.Append('*/')

- target.append(' ' * 8 + 'filter ' + filter_name + ' {')
+ config.Append('filter %s {' % filter_name)
if interface_specific:
- target.append(' ' * 12 + 'interface-specific;')
+ config.Append('interface-specific;')

for term in terms:
- target.append(str(term))
+ term_str = str(term)
+ if term_str:
+ config.Append(term_str, verbatim=True)
+
+ config.Append('}') # filter { ... }
+ config.Append('}') # family inet { ... }
+ config.Append('}') # firewall { ... }

- target.append(' ' * 8 + '}') # filter { ... }
- target.append(' ' * 4 + '}') # family inet { ... }
- target.append('}') # firewall { ... }
- target.append('\n')
- # end for header, filter_name, filter_type...
- return '\n'.join(target)
+ return str(config) + '\n'
=======================================
--- /trunk/lib/junipersrx.py Tue Mar 26 21:47:24 2013 UTC
+++ /trunk/lib/junipersrx.py Tue Sep 10 22:36:59 2013 UTC
@@ -20,6 +20,7 @@

__author__ = 'roba...@google.com (Robert Ankeny)'

+import collections
import datetime
import logging

@@ -87,7 +88,7 @@
return ''
ret_str = []

- #COMMENTS
+ # COMMENTS
comment_max_width = 68
if self.term.owner:
self.term.comment.append('Owner: %s' % self.term.owner)
@@ -101,12 +102,12 @@
ret_str.append(JuniperSRX.INDENT * 3 + 'policy ' + self.term.name + '
{')
ret_str.append(JuniperSRX.INDENT * 4 + 'match {')

- #SOURCE-ADDRESS
+ # SOURCE-ADDRESS
if self.term.source_address:
- saddr_check = []
+ saddr_check = set()
for saddr in self.term.source_address:
- saddr_check.append(saddr.parent_token)
- saddr_check = set(saddr_check)
+ saddr_check.add(saddr.parent_token)
+ saddr_check = sorted(saddr_check)
source_address_string = ''
for addr in saddr_check:
source_address_string += addr + ' '
@@ -115,12 +116,14 @@
else:
ret_str.append(JuniperSRX.INDENT * 5 + 'source-address any;')

- #DESTINATION-ADDRESS
+ # DESTINATION-ADDRESS
if self.term.destination_address:
daddr_check = []
for daddr in self.term.destination_address:
daddr_check.append(daddr.parent_token)
daddr_check = set(daddr_check)
+ daddr_check = list(daddr_check)
+ daddr_check.sort()
destination_address_string = ''
for addr in daddr_check:
destination_address_string += addr + ' '
@@ -129,7 +132,7 @@
else:
ret_str.append(JuniperSRX.INDENT * 5 + 'destination-address any;')

- #APPLICATION
+ # APPLICATION
if (not self.term.source_port and not self.term.destination_port and
not
self.term.icmp_type and not self.term.protocol):
ret_str.append(JuniperSRX.INDENT * 5 + 'application any;')
@@ -139,13 +142,13 @@

ret_str.append(JuniperSRX.INDENT * 4 + '}')

- #ACTIONS
+ # ACTIONS
for action in self.term.action:
ret_str.append(JuniperSRX.INDENT * 4 + 'then {')
ret_str.append(JuniperSRX.INDENT * 5 + self._ACTIONS.get(
str(action)) + ';')

- #LOGGING
+ # LOGGING
if self.term.logging:
ret_str.append(JuniperSRX.INDENT * 5 + 'log {')
ret_str.append(JuniperSRX.INDENT * 6 + 'session-init;')
@@ -154,12 +157,12 @@

ret_str.append(JuniperSRX.INDENT * 3 + '}')

- #OPTIONS
+ # OPTIONS
if self.term.option:
raise SRXOptionError('Options are not implemented yet, please
remove ' +
'from term %s' % self.term.name)

- #VERBATIM
+ # VERBATIM
if self.term.verbatim:
raise SRXVerbatimError('Verbatim is not implemented, please remove '
+
'the offending term %s.' % self.term.name)
@@ -217,10 +220,10 @@
_PLATFORM = 'srx'
_SUFFIX = '.srx'
_SUPPORTED_AF = set(('inet',))
-
_OPTIONAL_SUPPORTED_KEYWORDS = set(['expiration',
'logging',
'owner',
+ 'routing_instance', # safe to skip
'timeout'
])
INDENT = ' '
@@ -230,6 +233,8 @@

Args:
pol: policy.Policy object
+ exp_info: print a info message when a term is set to expire
+ in that many weeks

Raises:
UnsupportedFilterError: An unsupported filter was specified
@@ -237,7 +242,7 @@
SRXDuplicateTermError: Two terms were found with same name in same
filter
"""
self.srx_policies = []
- self.addressbook = {}
+ self.addressbook = collections.OrderedDict()
self.applications = []
self.ports = []
self.from_zone = ''
@@ -247,10 +252,10 @@
exp_info_date = current_date + datetime.timedelta(weeks=exp_info)

for header, terms in pol.filters:
- if not self._PLATFORM in header.platforms:
+ if self._PLATFORM not in header.platforms:
continue

- filter_options = header.FilterOptions('srx')
+ filter_options = header.FilterOptions(self._PLATFORM)

if (len(filter_options) < 4 or filter_options[0] != 'from-zone' or
filter_options[2] != 'to-zone'):
@@ -271,19 +276,20 @@
term_dup_check = set()
new_terms = []
for term in terms:
+ term.name = self.FixTermLength(term.name)
if term.name in term_dup_check:
raise SRXDuplicateTermError('You have a duplicate term: %s'
% term.name)
term_dup_check.add(term.name)

- if term.expiration and term.expiration <= current_date:
+ if term.expiration:
if term.expiration <= exp_info_date:
- logging.info('INFO: Term %s in policy %s expires '
- 'in less than two weeks.', term.name, filter_name)
+ logging.info('INFO: Term %s in policy %s>%s expires '
+ 'in less than two weeks.', term.name,
self.from_zone,
+ self.to_zone)
if term.expiration <= current_date:
- logging.warn('WARNING: Term %s in policy %s is expired and '
- 'will not be rendered.', term.name, filter_name)
- continue
+ logging.warn('WARNING: Term %s in policy %s>%s is expired.',
+ term.name, self.from_zone, self.to_zone)

for i in term.source_address_exclude:
term.source_address = nacaddr.RemoveAddressFromList(
@@ -301,7 +307,6 @@
new_terms.append(new_term)
tmp_icmptype = new_term.NormalizeIcmpTypes(
term.icmp_type, term.protocol, filter_type)
-
# NormalizeIcmpTypes returns [''] for empty, convert to [] for eval
normalized_icmptype = tmp_icmptype if tmp_icmptype != [''] else []
# rewrites the protocol icmpv6 to icmp6
@@ -327,7 +332,7 @@
address: a naming library address object
"""
if zone not in self.addressbook:
- self.addressbook[zone] = {}
+ self.addressbook[zone] = collections.OrderedDict()
if address.parent_token not in self.addressbook[zone]:
self.addressbook[zone][address.parent_token] = []
name = address.parent_token
@@ -362,7 +367,7 @@
target.append(self.INDENT + 'zones {')
for zone in self.addressbook:
target.append(self.INDENT * 2 + 'security-zone ' + zone + ' {')
- target.append(self.INDENT * 3 + 'address-book {')
+ target.append(self.INDENT * 3 + 'replace: address-book {')
for group in self.addressbook[zone]:
for address, name in self.addressbook[zone][group]:
target.append(self.INDENT * 4 + 'address ' + name + ' ' +
@@ -377,7 +382,12 @@
target.append(self.INDENT * 2 + '}')
target.append(self.INDENT + '}')

- target.append(self.INDENT + 'policies {')
+ target.append(self.INDENT + 'replace: policies {')
+
+ target.append(self.INDENT * 2 + '/*')
+ target.extend(aclgenerator.AddRepositoryTags(self.INDENT * 2))
+ target.append(self.INDENT * 2 + '*/')
+
for (_, terms, filter_options) in self.srx_policies:
target.append(self.INDENT * 2 + 'from-zone ' + filter_options[1] +
' to-zone ' + filter_options[3] + ' {')
@@ -387,8 +397,8 @@
target.append(self.INDENT + '}')
target.append('}')

- #APPLICATIONS
- target.append('applications {')
+ # APPLICATIONS
+ target.append('replace: applications {')
done_apps = []
for app in self.applications:
app_list = []
@@ -410,6 +420,7 @@
i = 1
target.append(self.INDENT +
'application-set ' + app['name'] + '-app {')
+
for proto in (app['protocol'] or ['']):
for sport in (app['sport'] or ['']):
for dport in (app['dport'] or ['']):
=======================================
--- /trunk/lib/naming.py Tue Mar 26 21:47:24 2013 UTC
+++ /trunk/lib/naming.py Tue Sep 10 22:36:59 2013 UTC
@@ -379,7 +379,8 @@
else:
raise NoDefinitionsError('Unknown definitions type.')
if not file_names:
- raise NoDefinitionsError('No definition files found.')
+ raise NoDefinitionsError('No definition files for %s in %s found.' %
+ (def_type, defdirectory))

for current_file in file_names:
try:
=======================================
--- /trunk/lib/packetfilter.py Thu Apr 4 12:34:18 2013 UTC
+++ /trunk/lib/packetfilter.py Tue Sep 10 22:36:59 2013 UTC
@@ -32,6 +32,10 @@
"""Raised when we see an unsupported action."""


+class UnsupportedTargetOption(Error):
+ """Raised when we see an unsupported option."""
+
+
class Term(aclgenerator.Term):
"""Generate PacketFilter policy terms."""

@@ -104,13 +108,21 @@

# source address
term_saddrs = self._CheckAddressAf(self.term.source_address)
- if not term_saddrs: return ''
+ if not term_saddrs:
+ logging.warn(self.NO_AF_LOG_FORMAT.substitute(term=self.term.name,
+ direction='source',
+ af=self.af))
+ return ''
term_saddr = self._GenerateAddrStatement(
term_saddrs, self.term.source_address_exclude)

# destination address
term_daddrs = self._CheckAddressAf(self.term.destination_address)
- if not term_daddrs: return ''
+ if not term_daddrs:
+ logging.warn(self.NO_AF_LOG_FORMAT.substitute(term=self.term.name,
+
direction='destination',
+ af=self.af))
+ return ''
term_daddr = self._GenerateAddrStatement(
term_daddrs, self.term.destination_address_exclude)

@@ -164,6 +176,7 @@
return '\n'.join(str(v) for v in ret_str if v is not '')

def _CheckAddressAf(self, addrs):
+ """Verify that the requested address-family matches the address's
family."""
if not addrs:
return ['any']
if self.af == 'mixed':
@@ -177,6 +190,7 @@

def _FormatPart(self, action, log, af, proto, src_addr, src_port,
dst_addr, dst_port, tcp_flags, icmp_types, options):
+ """Format the string which will become a single PF entry."""
line = ['%s' % action]
if log and 'true' in [str(l) for l in log]:
line.append('log')
@@ -240,6 +254,7 @@
_TERM = Term
_OPTIONAL_SUPPORTED_KEYWORDS = set(['expiration',
'logging',
+ 'routing_instance',
])

def _TranslatePolicy(self, pol, exp_info):
@@ -252,7 +267,7 @@
filter_type = None

for header, terms in pol.filters:
- if not self._PLATFORM in header.platforms:
+ if self._PLATFORM not in header.platforms:
continue

filter_options = header.FilterOptions(self._PLATFORM)[1:]
@@ -281,6 +296,7 @@
new_terms = []
term_names = set()
for term in terms:
+ term.name = self.FixTermLength(term.name)
if term.name in term_names:
raise aclgenerator.DuplicateTermError(
'You have a duplicate term: %s' % term.name)
@@ -327,6 +343,6 @@
term_str = str(term)
if term_str:
target.append(term_str)
- target.append('\n')
+ target.append('')

return '\n'.join(target)
=======================================
--- /trunk/lib/policy.py Tue Mar 26 21:47:24 2013 UTC
+++ /trunk/lib/policy.py Tue Sep 10 22:36:59 2013 UTC
@@ -121,11 +121,11 @@
for port in ports:
service_by_proto = DEFINITIONS.GetServiceByProto(port, proto)
if not service_by_proto:
- logging.warn('%s %s %s %s %s %s%s %s' % (
- 'Term', term_name, 'has service', port,
- 'which is not defined with protocol', proto,
+ logging.warn('%s %s %s %s %s %s%s %s', 'Term', term_name,
+ 'has service', port, 'which is not defined with
protocol',
+ proto,
', but will be permitted. Unless intended, you
should',
- 'consider splitting the protocols into separate
terms!'))
+ 'consider splitting the protocols into separate
terms!')

for p in [x.split('-') for x in service_by_proto]:
if len(p) == 1:
@@ -370,16 +370,28 @@

# check prototols
# either protocol or protocol-except may be used, not both at the same
time.
- # evaluating these separately for superset is reasonable.
if self.protocol:
- if not self.CheckProtocolIsContained(self.protocol, other.protocol):
+ if other.protocol:
+ if not self.CheckProtocolIsContained(other.protocol,
self.protocol):
+ return False
+ # this term has protocol, other has protocol_except.
+ elif other.protocol_except:
return False
- # if the other term's exceptions do not have the same, or more
- # exceptions than our own then this term cannot contain that term.
- if self.protocol_except:
- if not self.CheckProtocolIsContained(other.protocol_except,
- self.protocol_except):
+ else:
+ # other does not have protocol or protocol_except. since we do
other
+ # cannot be contained in self.
return False
+ elif self.protocol_except:
+ if other.protocol_except:
+ if self.CheckProtocolIsContained(
+ self.protocol_except, other.protocol_except):
+ return False
+ elif other.protocol:
+ for proto in other.protocol:
+ if proto in self.protocol_except:
+ return False
+ else:
+ return False

# combine addresses with exclusions for proper contains comparisons.
if not self.flattened:
@@ -419,6 +431,11 @@
self.flattened_daddr, other.flattened_daddr)):
return False

+ if not (
+ self.CheckPrincipalsContained(
+ self.principals, other.principals)):
+ return False
+
# check ports
# like the address directive, the port directive is special in that it
can
# be either source or destination.
@@ -427,13 +444,11 @@
self.CheckPortIsContained(self.port, other.sport) or
self.CheckPortIsContained(self.port, other.dport)):
return False
- elif self.source_port:
- if not self.CheckPortIsContained(self.source_port,
other.source_port):
- return False
- elif self.destination_port:
- if not self.CheckPortIsContained(self.destination_port,
- other.destination_port):
- return False
+ if not self.CheckPortIsContained(self.source_port, other.source_port):
+ return False
+ if not self.CheckPortIsContained(self.destination_port,
+ other.destination_port):
+ return False

# prefix lists
if self.source_prefix:
@@ -444,10 +459,20 @@
other.destination_prefix):
return False

+ # check precedence
+ if self.precedence:
+ if not other.precedence:
+ return False
+ for precedence in other.precedence:
+ if precedence not in self.precedence:
+ return False
# check various options
- for opt in self.option:
- if not opt in other.option:
+ if self.option:
+ if not other.option:
return False
+ for opt in other.option:
+ if opt not in self.option:
+ return False
if self.fragment_offset:
# fragment_offset looks like 'integer-integer' or just, 'integer'
sfo = [int(x) for x in self.fragment_offset.split('-')]
@@ -540,7 +565,7 @@

def __eq__(self, other):
# action
- if not sorted(self.action) == sorted(other.action):
+ if sorted(self.action) != sorted(other.action):
return False

# addresses.
@@ -572,41 +597,41 @@
return False

# option
- if not sorted(self.option) == sorted(other.option):
+ if sorted(self.option) != sorted(other.option):
return False

# qos
- if not self.qos == other.qos:
+ if self.qos != other.qos:
return False

# verbatim
- if not self.verbatim == other.verbatim:
+ if self.verbatim != other.verbatim:
return False

# policer
- if not self.policer == other.policer:
+ if self.policer != other.policer:
return False

# interface
- if not self.source_interface == other.source_interface:
+ if self.source_interface != other.source_interface:
return False

- if not self.destination_interface == other.destination_interface:
+ if self.destination_interface != other.destination_interface:
return False

- if not sorted(self.logging) == sorted(other.logging):
+ if sorted(self.logging) != sorted(other.logging):
return False
- if not self.qos == other.qos:
+ if self.qos != other.qos:
return False
- if not self.packet_length == other.packet_length:
+ if self.packet_length != other.packet_length:
return False
- if not self.fragment_offset == other.fragment_offset:
+ if self.fragment_offset != other.fragment_offset:
return False
- if not sorted(self.icmp_type) == sorted(other.icmp_type):
+ if sorted(self.icmp_type) != sorted(other.icmp_type):
return False
- if not sorted(self.ether_type) == sorted(other.ether_type):
+ if sorted(self.ether_type) != sorted(other.ether_type):
return False
- if not sorted(self.traffic_type) == sorted(other.traffic_type):
+ if sorted(self.traffic_type) != sorted(other.traffic_type):
return False

# platform
@@ -615,7 +640,7 @@
return False

# timeout
- if not self.timeout == other.timeout:
+ if self.timeout != other.timeout:
return False

return True
@@ -665,14 +690,14 @@
if not exclude:
return include

- flat_inclusions = []
- for in_addr in include:
+ for index, in_addr in enumerate(include):
for ex_addr in exclude:
if ex_addr in in_addr:
reduced_list = in_addr.address_exclude(ex_addr)
- flat_inclusions.extend(
+ include.pop(index)
+ include.extend(
Term._FlattenAddresses(reduced_list, exclude[1:]))
- return flat_inclusions
+ return include

def GetAddressOfVersion(self, addr_type, af=None):
"""Returns addresses of the appropriate Address Family.
@@ -842,9 +867,6 @@
else:
if not self.action and not self.routing_instance:
raise TermNoActionError('no action specified for term %s' %
self.name)
- if self.action and self.routing_instance:
- raise InvalidTermActionError('action:: and routing-instance::
can\'t ' +
- 'both be defined for term %s' %
self.name)
# have we specified a port with a protocol that doesn't support
ports?
if self.source_port or self.destination_port or self.port:
if 'tcp' not in self.protocol and 'udp' not in self.protocol:
@@ -960,8 +982,27 @@
"""
return self.CollapsePortListRecursive(sorted(ports))

+ def CheckPrincipalsContained(self, superset, subset):
+ """Check to if the given list of principals is wholly contained.
+
+ Args:
+ superset: list of principals
+ subset: list of principals
+
+ Returns:
+ bool: True if subset is contained in superset. false otherwise.
+ """
+ # Skip set comparison if neither term has principals.
+ if not superset and not subset:
+ return True
+
+ # Convert these lists to sets to use set comparison.
+ sup = set(superset)
+ sub = set(subset)
+ return sub.issubset(sup)
+
def CheckProtocolIsContained(self, superset, subset):
- """Check to if the given list of protocols is wholly contained.
+ """Check if the given list of protocols is wholly contained.

Args:
superset: list of protocols
=======================================
--- /trunk/lib/silverpeak.py Tue Mar 26 21:47:24 2013 UTC
+++ /trunk/lib/silverpeak.py Tue Sep 10 22:36:59 2013 UTC
@@ -191,6 +191,7 @@
_OPTIONAL_SUPPORTED_KEYWORDS = set(['counter',
'expiration',
'policer', # safely ignored
+ 'owner', # safely ignored
'precedence',
'qos',
'routing_instance', # safely ignored
@@ -228,7 +229,7 @@
# 'nikto': 'ends'
# }

- def __init__(self, pol, exp_info, fixed_content_file=None):
+ def __init__(self, pol, fixed_content_file, exp_info):
self.current_date = datetime.date.today()
aclgenerator.ACLGenerator.__init__(self, pol, exp_info)

@@ -306,6 +307,7 @@

new_terms = []
for term in terms:
+ term.name = self.FixTermLength(term.name)
verified = self.VerifyTerm(term, exp_info)
if verified:
new_terms.append(Term(term))
@@ -369,7 +371,6 @@
target.append('lan-qos-dscp %s wan-qos-dscp %s\n\n' %
(qos_value, qos_value))
return ' '.join(target)
- return ''

def GenerateConfString(self):
"""Generate configuration file."""
=======================================
--- /trunk/lib/speedway.py Tue Dec 11 18:44:58 2012 UTC
+++ /trunk/lib/speedway.py Tue Sep 10 22:36:59 2013 UTC
@@ -21,14 +21,19 @@

__author__ = 'wat...@google.com (Tony Watson)'

+from string import Template
import iptables


+class Error(Exception):
+ pass
+
+
class Term(iptables.Term):
"""Generate Iptables policy terms."""
_PLATFORM = 'speedway'
_PREJUMP_FORMAT = None
- _POSTJUMP_FORMAT = '-A %s -j %s'
+ _POSTJUMP_FORMAT = Template('-A $filter -j $term')


class Speedway(iptables.Iptables):
@@ -39,7 +44,7 @@
_SUFFIX = '.ipt'

_RENDER_PREFIX = '*filter'
- _RENDER_SUFFIX = 'COMMIT\n'
+ _RENDER_SUFFIX = 'COMMIT'
_DEFAULTACTION_FORMAT = ':%s %s'

_TERM = Term
=======================================
--- /trunk/policies/sample_multitarget.pol Fri Jan 27 22:10:32 2012 UTC
+++ /trunk/policies/sample_multitarget.pol Tue Sep 10 22:36:59 2013 UTC
@@ -7,7 +7,7 @@
# NOTES: iptables produces filter 'lines' that must be used as args to
the
# '$ iptables' cmd, while Speedway produces stateful iptables filters
# compatible with iptables-restore (most people will prefer speedway)
- target:: juniper edge-inbound
+ target:: juniper edge-inbound inet
target:: cisco edge-inbound mixed
target:: speedway INPUT
target:: ciscoasa asa_in
Reply all
Reply to author
Forward
0 new messages