Home | History | Annotate | Download | only in setools
      1 # Copyright 2014-2015, Tresys Technology, LLC
      2 #
      3 # This file is part of SETools.
      4 #
      5 # SETools is free software: you can redistribute it and/or modify
      6 # it under the terms of the GNU Lesser General Public License as
      7 # published by the Free Software Foundation, either version 2.1 of
      8 # the License, or (at your option) any later version.
      9 #
     10 # SETools is distributed in the hope that it will be useful,
     11 # but WITHOUT ANY WARRANTY; without even the implied warranty of
     12 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
     13 # GNU Lesser General Public License for more details.
     14 #
     15 # You should have received a copy of the GNU Lesser General Public
     16 # License along with SETools.  If not, see
     17 # <http://www.gnu.org/licenses/>.
     18 #
     19 try:
     20     import ipaddress
     21 except ImportError:  # pragma: no cover
     22     pass
     23 
     24 import logging
     25 from socket import AF_INET, AF_INET6
     26 
     27 from . import contextquery
     28 
     29 
     30 class NodeconQuery(contextquery.ContextQuery):
     31 
     32     """
     33     Query nodecon statements.
     34 
     35     Parameter:
     36     policy          The policy to query.
     37 
     38     Keyword Parameters/Class attributes:
     39     network         The IPv4/IPv6 address or IPv4/IPv6 network address
     40                     with netmask, e.g. 192.168.1.0/255.255.255.0 or
     41                     "192.168.1.0/24".
     42     network_overlap If true, the net will match if it overlaps with
     43                     the nodecon's network instead of equality.
     44     ip_version      The IP version of the nodecon to match. (socket.AF_INET
     45                     for IPv4 or socket.AF_INET6 for IPv6)
     46     user            The criteria to match the context's user.
     47     user_regex      If true, regular expression matching
     48                     will be used on the user.
     49     role            The criteria to match the context's role.
     50     role_regex      If true, regular expression matching
     51                     will be used on the role.
     52     type_           The criteria to match the context's type.
     53     type_regex      If true, regular expression matching
     54                     will be used on the type.
     55     range_          The criteria to match the context's range.
     56     range_subset    If true, the criteria will match if it is a subset
     57                     of the context's range.
     58     range_overlap   If true, the criteria will match if it overlaps
     59                     any of the context's range.
     60     range_superset  If true, the criteria will match if it is a superset
     61                     of the context's range.
     62     range_proper    If true, use proper superset/subset operations.
     63                     No effect if not using set operations.
     64     """
     65 
     66     _network = None
     67     network_overlap = False
     68     _ip_version = None
     69 
     70     @property
     71     def ip_version(self):
     72         return self._ip_version
     73 
     74     @ip_version.setter
     75     def ip_version(self, value):
     76         if value:
     77             if not (value == AF_INET or value == AF_INET6):
     78                 raise ValueError(
     79                     "The address family must be {0} for IPv4 or {1} for IPv6.".
     80                     format(AF_INET, AF_INET6))
     81 
     82             self._ip_version = value
     83         else:
     84             self._ip_version = None
     85 
     86     @property
     87     def network(self):
     88         return self._network
     89 
     90     @network.setter
     91     def network(self, value):
     92         if value:
     93             try:
     94                 self._network = ipaddress.ip_network(value)
     95             except NameError:  # pragma: no cover
     96                 raise RuntimeError("Nodecon IP address/network functions require Python 3.3+.")
     97         else:
     98             self._network = None
     99 
    100     def results(self):
    101         """Generator which yields all matching nodecons."""
    102         self.log.info("Generating results from {0.policy}".format(self))
    103         self.log.debug("Network: {0.network!r}, overlap: {0.network_overlap}".format(self))
    104         self.log.debug("IP Version: {0.ip_version}".format(self))
    105         self.log.debug("User: {0.user!r}, regex: {0.user_regex}".format(self))
    106         self.log.debug("Role: {0.role!r}, regex: {0.role_regex}".format(self))
    107         self.log.debug("Type: {0.type_!r}, regex: {0.type_regex}".format(self))
    108         self.log.debug("Range: {0.range_!r}, subset: {0.range_subset}, overlap: {0.range_overlap}, "
    109                        "superset: {0.range_superset}, proper: {0.range_proper}".format(self))
    110 
    111         for nodecon in self.policy.nodecons():
    112 
    113             if self.network:
    114                 try:
    115                     netmask = ipaddress.ip_address(nodecon.netmask)
    116                 except NameError:  # pragma: no cover
    117                     # Should never actually hit this since the self.network
    118                     # setter raises the same exception.
    119                     raise RuntimeError("Nodecon IP address/network functions require Python 3.3+.")
    120 
    121                 # Python 3.3's IPv6Network constructor does not support
    122                 # expanded netmasks, only CIDR numbers. Convert netmask
    123                 # into CIDR.
    124                 # This is Brian Kernighan's method for counting set bits.
    125                 # If the netmask happens to be invalid, this will
    126                 # not detect it.
    127                 CIDR = 0
    128                 int_netmask = int(netmask)
    129                 while int_netmask:
    130                     int_netmask &= int_netmask - 1
    131                     CIDR += 1
    132 
    133                 net = ipaddress.ip_network('{0}/{1}'.format(nodecon.address, CIDR))
    134 
    135                 if self.network_overlap:
    136                     if not self.network.overlaps(net):
    137                         continue
    138                 else:
    139                     if not net == self.network:
    140                         continue
    141 
    142             if self.ip_version and self.ip_version != nodecon.ip_version:
    143                 continue
    144 
    145             if not self._match_context(nodecon.context):
    146                 continue
    147 
    148             yield nodecon
    149