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# 19try: 20 import ipaddress 21except ImportError: # pragma: no cover 22 pass 23 24import logging 25from socket import AF_INET, AF_INET6 26 27from .mixins import MatchContext 28from .query import PolicyQuery 29 30 31class NodeconQuery(MatchContext, PolicyQuery): 32 33 """ 34 Query nodecon statements. 35 36 Parameter: 37 policy The policy to query. 38 39 Keyword Parameters/Class attributes: 40 network The IPv4/IPv6 address or IPv4/IPv6 network address 41 with netmask, e.g. 192.168.1.0/255.255.255.0 or 42 "192.168.1.0/24". 43 network_overlap If true, the net will match if it overlaps with 44 the nodecon's network instead of equality. 45 ip_version The IP version of the nodecon to match. (socket.AF_INET 46 for IPv4 or socket.AF_INET6 for IPv6) 47 user The criteria to match the context's user. 48 user_regex If true, regular expression matching 49 will be used on the user. 50 role The criteria to match the context's role. 51 role_regex If true, regular expression matching 52 will be used on the role. 53 type_ The criteria to match the context's type. 54 type_regex If true, regular expression matching 55 will be used on the type. 56 range_ The criteria to match the context's range. 57 range_subset If true, the criteria will match if it is a subset 58 of the context's range. 59 range_overlap If true, the criteria will match if it overlaps 60 any of the context's range. 61 range_superset If true, the criteria will match if it is a superset 62 of the context's range. 63 range_proper If true, use proper superset/subset operations. 64 No effect if not using set operations. 65 """ 66 67 _network = None 68 network_overlap = False 69 _ip_version = None 70 71 @property 72 def ip_version(self): 73 return self._ip_version 74 75 @ip_version.setter 76 def ip_version(self, value): 77 if value: 78 if not (value == AF_INET or value == AF_INET6): 79 raise ValueError( 80 "The address family must be {0} for IPv4 or {1} for IPv6.". 81 format(AF_INET, AF_INET6)) 82 83 self._ip_version = value 84 else: 85 self._ip_version = None 86 87 @property 88 def network(self): 89 return self._network 90 91 @network.setter 92 def network(self, value): 93 if value: 94 try: 95 self._network = ipaddress.ip_network(value) 96 except NameError: # pragma: no cover 97 raise RuntimeError("Nodecon IP address/network functions require Python 3.3+.") 98 else: 99 self._network = None 100 101 def __init__(self, policy, **kwargs): 102 super(NodeconQuery, self).__init__(policy, **kwargs) 103 self.log = logging.getLogger(__name__) 104 105 def results(self): 106 """Generator which yields all matching nodecons.""" 107 self.log.info("Generating nodecon results from {0.policy}".format(self)) 108 self.log.debug("Network: {0.network!r}, overlap: {0.network_overlap}".format(self)) 109 self.log.debug("IP Version: {0.ip_version}".format(self)) 110 self._match_context_debug(self.log) 111 112 for nodecon in self.policy.nodecons(): 113 114 if self.network: 115 try: 116 netmask = ipaddress.ip_address(nodecon.netmask) 117 except NameError: # pragma: no cover 118 # Should never actually hit this since the self.network 119 # setter raises the same exception. 120 raise RuntimeError("Nodecon IP address/network functions require Python 3.3+.") 121 122 # Python 3.3's IPv6Network constructor does not support 123 # expanded netmasks, only CIDR numbers. Convert netmask 124 # into CIDR. 125 # This is Brian Kernighan's method for counting set bits. 126 # If the netmask happens to be invalid, this will 127 # not detect it. 128 CIDR = 0 129 int_netmask = int(netmask) 130 while int_netmask: 131 int_netmask &= int_netmask - 1 132 CIDR += 1 133 134 net = ipaddress.ip_network('{0}/{1}'.format(nodecon.address, CIDR)) 135 136 if self.network_overlap: 137 if not self.network.overlaps(net): 138 continue 139 else: 140 if not net == self.network: 141 continue 142 143 if self.ip_version and self.ip_version != nodecon.ip_version: 144 continue 145 146 if not self._match_context(nodecon.context): 147 continue 148 149 yield nodecon 150