def test_notify_without_Dendrite(self): Dendrite.publish_single = MagicMock() event = Event(event_type='test_event', source='test') event.notify() self.assertEqual(Dendrite.publish_single.call_count, 1)
def test_notify_with_Dendrite(self): Dendrite = MagicMock() event = Event(event_type='test_event', source='test', dendrite=Dendrite()) event.notify() self.assertEqual(event.dendrite.publish.call_count, 1)
def test_Event_timestamp(self): event = Event(event_type='test_event', source='test') self.assertAlmostEqual(datetime.utcfromtimestamp(event.timestamp), datetime.utcnow(), delta=timedelta(seconds=2), msg='timestamp set to current epoch') event = Event(event_type='test_event', source='test', timestamp=1234) self.assertEqual(event.timestamp, 1234)
def checkAuthzOnVlan(self, mac, vlan): authz = nac.checkAuthz(mac) if not authz or vlan not in authz.allow_on: event = Event('device-not-authorized', source='network', level='danger') event.add_data('mac', mac, data_type='mac') event.add_data('vlan', vlan) event.notify()
def AuthenticationGroupFailed(req): try: request = request_as_hash_of_values(req) # Todo: set source depending on requester: radius-dot1x, captive-portal-web or captive-portal-guest-access Event('runtime-failure-authentication', source='radius', level='danger')\ .add_data('authentication_group', request.get('ELAN-Auth-Provider'), data_type='authentication')\ .notify() except: ExceptionEvent(source='radius').notify() raise
def test_EventDataType(self): event = Event(event_type='test_event', source='test') event.add_data(key='test', value='test 1', data_type='str') event.add_data(key='test', value='test 2') self.assertEqual(event.data[0]['type'], 'str') self.assertNotIn('type', event.data[1])
async def poll(self, ip, timeout=10): ''' poll and cache result''' try: device_snmp = await asyncio.wait_for(self._poll(ip), timeout) except asyncio.TimeoutError: device_snmp = None if device_snmp is None: event = Event('runtime-failure', source='snmp', level='warning') event.add_data('ip', ip) event.notify() return # Try to find a cached device device_id = self.get_id_by_ip(ip) if not device_id and 'ports' in device_snmp: # Try to find cached device by mac for port in device_snmp['ports']: device_id = self.get_id_by_mac(port['mac']) if device_id: break if device_id: cached_device = self.get_device_by_id(device_id) else: device_id = self.get_new_device_id() cached_device = None device_snmp['local_id'] = device_id # Update cached device if needed if cached_device != device_snmp: if cached_device is None or self.switch_has_changed(cached_device, device_snmp): # notify if has changed. Only send relevant keys Dendrite.publish_single('snmp', { k:v for k, v in device_snmp.items() if k not in IGNORE_SWITCH_KEYS }) # cache the device, including dynamic fields like fw_mac with self.synapse.pipeline() as pipe: pipe.hset(self.DEVICE_SNMP_CACHE_PATH, device_id, device_snmp) pipe.hset(self.DEVICE_IP_SNMP_CACHE_PATH, ip, device_id) cached_macs = set() if cached_device: for port in cached_device['ports']: if port['mac']: cached_macs.add(port['mac']) device_macs = set() for port in device_snmp['ports']: if port['mac']: device_macs.add(port['mac']) for mac in cached_macs - device_macs: # macs not longer valid pipe.hdel(self.DEVICE_MAC_SNMP_CACHE_PATH, mac) for mac in device_macs - cached_macs: # new macs pipe.hset(self.DEVICE_MAC_SNMP_CACHE_PATH, mac, device_id) pipe.execute() return device_snmp
async def post_auth(req): try: request = request_as_hash_of_values(req) if request.get('ELAN-Auth-Type', None) == 'Reject': return await seen(request) else: return await get_assignments(request) except neuron.RequestTimeout: Event('runtime-failure-authorization', source='radius', level='danger')\ .add_data('error', 'timeout')\ .notify() raise except: ExceptionEvent(source='radius').notify() raise
def AuthenticationProviderFailed(req, level='danger'): # Treat error from auth try: request = request_as_hash_of_values(req) # Todo: set source depending on requester: radius-dot1x, captive-portal-web or captive-portal-guest-access event = Event('runtime-failure-authentication', source='radius', level=level)\ .add_data('authentication_provider', request.get('ELAN-Auth-Failed'), data_type='authentication') if request.get('Module-Failure-Message'): event.add_data('details', request.get('Module-Failure-Message')) event.notify() except: ExceptionEvent(source='radius').notify() raise
async def parse_trap_str(self, trap_str, timeout=5): ''' parse the trap and return - trap type - port (ip???/interface) - mac if present ''' # if creds not known, poll switch # If creds , parse trap,-> smart way, trying each one... and saving the matches..., # -> parse trap... (create a thread just to process trap ?) # -> check if swicth/ switch port known and if not poll splitted_trap_str = trap_str.split('|', 3) trap_time = (datetime.datetime.strptime('{} {}'.format(*splitted_trap_str[0:2]), '%Y-%m-%d %H:%M:%S') - datetime.datetime(1970, 1, 1)).total_seconds() # Epoch snmp_connection_str = splitted_trap_str[2] # example of snmp_connection_str: UDP: [10.30.0.2]:56550->[10.30.0.5] -> grab what is enclosed in first brackets device_ip = snmp_connection_str.split(']', 1)[0].split('[', 1)[1] # Grab SNMP read credentials of device read_params = await self.get_read_params(device_ip) if not read_params: return try: trap = await asyncio.wait_for(self._parse_trap_str(device_ip, trap_str, read_params), timeout) except asyncio.TimeoutError: trap = None if trap and trap['trapType'] != 'unknown': ''' Trap types: - up: - trapIfIndex - down - trapIfIndex - mac: - trapOperation: - learnt - removed - unknown -> what do we do ? -> macsuck ? - trapIfIndex - trapMac - trapVlan - secureMacAddrViolation: - trapIfIndex - trapMac - trapVlan - dot11Deauthentication: - trapMac - wirelessIPS - trapMac - roaming - trapSSID - trapIfIndex - trapVlan - trapMac - trapClientUserName - trapConnectionType ''' if 'trapMac' in trap: if trap['trapType'] in ['secureMacAddrViolation', 'wirelessIPS', 'roaming'] or \ (trap['trapType'] == ['mac'] and trap['trapOperation'] == 'learnt'): port = await self.getPortFromIndex(device_ip, trap.get('trapIfIndex', None)) vlan = trap.get('vlan', None) session.seen(trap['trapMac'], vlan=vlan, port=port, time=trap_time) if port is None: DebugEvent(source='snmp-notification')\ .add_data('details', 'Port not found')\ .add_data('trap', trap)\ .add_data('trap_str', trap_str)\ .notify() elif trap['trapType'] == 'dot11Deauthentication'or \ (trap['trapType'] == ['mac'] and trap['trapOperation'] == 'removed'): session.end(mac=trap['trapMac'], time=trap_time) elif trap['trapType'] in ['up', 'down']: port = await self.getPortFromIndex(device_ip, trap['trapIfIndex']) if port: port['device_ip'] = device_ip if trap['trapType'] == 'up': # remember this port could have new ip. self.port_has_new_macs(port) else: self.port_has_no_new_macs(port) # remove macs that are no longer on port mac_ports = await self.get_macs_on_device(device_ip) for mac in session.port_macs(port): if mac not in mac_ports: session.end(mac) # TODO: Mark Port as potentially containing a new mac -> macksuck when new mac? else: event = Event(event_type='runtime-failure', source='snmp-notification', level='warning') event.add_data('ip', device_ip) event.notify()
async def get_assignments(request): ''' Will create new session for Mac and allow it on VLAN and on the net if authorized''' mac = extract_mac(request.get('Calling-Station-Id', None)) port = await find_port(request) session.seen(mac, port=port) auth_type = request.get('ELAN-Auth-Type', None) if auth_type == 'radius-mac': authz = await asyncio.get_event_loop().run_in_executor( None, nac.checkAuthz, mac) elif auth_type == 'radius-dot1x': authentication_provider = request.get('ELAN-Auth-Provider') login = request.get('ELAN-Login', request.get('User-Name')) authz = await asyncio.get_event_loop().run_in_executor( None, functools.partial(nac.newAuthz, mac, no_duplicate_source=True, source='radius-dot1x', till_disconnect=True, authentication_provider=authentication_provider, login=login)) if not authz: # log no assignment rule matched.... event = Event(event_type='device-not-authorized', source=auth_type, level='warning') event.add_data('mac', mac, 'mac') event.add_data('port', port, 'port') if auth_type == 'radius-dot1x': event.add_data('authentication_provider', authentication_provider, 'authentication') event.add_data('login', login) event.notify() raise NotAuthorized return {'ELAN-Vlan-Id': str(authz.assign_vlan)}
def login(request, context=None): if context is None: context = {} clientIP = request.META['REMOTE_ADDR'] clientMAC = ip4_to_mac(clientIP) if is_authenticated(clientMAC): # VlanID not present, means it has been not been redirected, so MAC is allowed on VLAN (maybe not from web or captive portal) return redirect('status') default_context = {} if 'web_authentication' in request.META: default_context['web_authentication'] = request.META[ 'web_authentication'] if 'guest_access' in request.META: default_context['guest_access'] = GuestAccess.get( id=int(request.META['guest_access'])) if default_context['guest_access']: default_context.update( guest_registration_fields=default_context['guest_access'] ['fields'], guest_access_pending=is_authz_pending(clientMAC)) for key in default_context: if key not in context: context[key] = default_context[key] if request.method != 'POST' or context.get('post_processed', False): return render(request, 'captive-portal/login.html', context) # POST try: username = request.POST['username'] password = request.POST['password'] except (KeyError): # Redisplay the login form. context['error_message'] = _("'username' or 'password' missing.") return render(request, 'captive-portal/login.html', context) context['username'] = username if 'web_authentication' not in request.META: context['error_message'] = _("Invalid username or password.") return render(request, 'captive-portal/login.html', context) try: authenticator_id = pwd_authenticate(request.META['web_authentication'], username, password, source='captive-portal-web') except AuthenticationFailed as e: if not e.args: context['error_message'] = _("Invalid username or password.") elif len(e.args) == 1: context['error_message'] = e.args[0] else: context['error_message'] = e.args return render(request, 'captive-portal/login.html', context) except RequestError: context['error_message'] = _("An error occurred, please retry.") return render(request, 'captive-portal/login.html', context) except RequestTimeout: context['error_message'] = _("request timed out, please retry.") return render(request, 'captive-portal/login.html', context) vlan = '{interface}.{id}'.format(interface=request.META['interface'], id=request.META['vlan_id']) # start session authz = nac.newAuthz(clientMAC, source='captive-portal-web', till_disconnect=True, login=username, authentication_provider=authenticator_id, vlan=vlan) # TODO: if vlan incorrect, try to change it if not authz or vlan not in authz.allow_on: # log no assignment rule matched.... event = Event('device-not-authorized', source='captive-portal-web', level='danger') event.add_data('mac', clientMAC, 'mac') event.add_data('vlan', vlan) event.add_data('authentication_provider', authenticator_id, 'authentication') event.add_data('login', username) event.notify() return redirect('status')
async def process_alert(self, alert): try: #timestamp = time.strftime('%Y-%m-%dT%H:%M:%S.'+ str(event['event-microsecond']) +'Z', time.gmtime(event['event-second'])) event = Event(event_type='device-alert', source='ids', level='danger', timestamp=alert['event-second'], dendrite=self.dendrite) event.add_data('priority', alert['priority']) event.add_data('signature', alert['signature-id'], data_type='signature') event.add_data('revision', alert['signature-revision']) # decode Ethernet packet to get mac addresses and protocol # TODO: This may be nflog wrapped now it is sent via nflog? to be checked... packet = b'' for p in alert['packets']: packet += p packet = Ether(packet) src_mac = packet.src if src_mac != '00:00:00:00:00:00': event.add_data('src_mac', src_mac, 'mac') dst_mac = packet.dst if dst_mac != '00:00:00:00:00:00': event.add_data('dst_mac', dst_mac, 'mac') src_vlan = get_bridge_port(src_mac) if src_vlan: event.add_data('src_vlan', src_vlan) dst_vlan = get_bridge_port(dst_mac) if dst_vlan: event.add_data('dst_vlan', dst_vlan) protocol = None while packet: if packet.name not in ('Raw', 'Padding'): protocol = packet.name packet = packet.payload if protocol: event.add_data('protocol', protocol) event.add_data('src_ip', alert['source-ip']) event.add_data('dst_ip', alert['destination-ip']) event.add_data('src_port', alert['sport-itype']) event.add_data('dst_port', alert['dport-icode']) event.notify() except: ExceptionEvent(source='ids').notify()