def create_subscription(apps, subscription_dict): """ Saves a subscription to the database :param apps: Django apps instance. :param subscription_dict: arguments to set up a subscription, as a dict. """ subscription_model = apps.get_model('p_soc_auto_base', 'Subscription') user = get_or_create_user() # TODO can I have alternate_email_subject='', do we even need the alt? subscription_defaults = { 'emails_list': TO_EMAILS, 'from_email': '*****@*****.**', 'template_dir': 'p_soc_auto_base/template/', 'template_prefix': 'email/', 'created_by_id': user.id, 'updated_by_id': user.id, 'enabled': True, } subscription_dict.update(subscription_defaults) subscription = subscription_model(**subscription_dict) subscription.save()
def add_default_ssl_port(apps, schema_editor): port = 443 model = apps.get_model('ssl_cert_tracker', 'SslProbePort') user = get_or_create_user() ssl_port = model(port=port, created_by_id=user.id, updated_by_id=user.id) ssl_port.save()
def test_getorcreateuser(self): """ Tests that getorcreateuser does create a user. :return: """ test_user = get_or_create_user('testuser') self.assertTrue( get_user_model().objects.filter(username='******').exists()) test_user.delete()
def get_or_create_from_borg(cls, borg): """ maintain the host information based on data in the `Windows` log event If a :class:`WinlogbeatHost` instance matching the information in the `borg` argument doesn't exist, one will be created. This `class method <https://docs.python.org/3.6/library/functions.html?highlight=classmethod#classmethod>`__ is invoked from the :func:`citrus_borg.tasks.store_borg_data` task. :arg borg: a :func:`collections.namedtuple` with the processed `Windows` log event data See the functions in the :mod:`citrus_borg.locutus.assimilation` module for the structure of the :func:`namedtuple <collections.namedtuple>`. :returns: the :class:`WinlogbeatHost` instance """ if borg.event_source == 'ControlUp Logon Monitor': last_seen = now() else: last_seen = None if borg.event_source == 'BorgExchangeMonitor': exch_last_seen = now() else: exch_last_seen = None winloghost = cls.objects.filter( host_name__iexact=borg.source_host.host_name) if winloghost.exists(): winloghost = winloghost.get() if last_seen: winloghost.last_seen = last_seen if exch_last_seen: winloghost.exchange_last_seen = exch_last_seen else: user = get_or_create_user(settings.CITRUS_BORG_SERVICE_USER) winloghost = cls(host_name=borg.source_host.host_name, last_seen=last_seen, ip_address=borg.source_host.ip_address, created_by=user, exchange_last_seen=exch_last_seen, updated_by=user) winloghost.save() return winloghost
def failure_cluster_check(sender, instance, *args, **kwargs): """ Send an alert if there has been a cluster of failed winlogevents, as defined by the appropriate preferences: citrusborgux__cluster_event_ids., citrusborgux__cluster_length, citrusborgux__cluster_size. See preference definitions for more details. """ failure_ids = get_int_list_preference('citrusborgux__cluster_event_ids') if instance.event_id not in failure_ids: return recent_failures = WinlogEvent.active.filter( timestamp__gte=instance.timestamp - get_preference('citrusborgux__cluster_length'), timestamp__lte=instance.timestamp, event_id__in=failure_ids, cluster__isnull=True) recent_failures_count = recent_failures.count() LOG.debug('there have been %d failures recently', recent_failures_count) if recent_failures_count >= get_preference('citrusborgux__cluster_size'): default_user = get_or_create_user() new_cluster = EventCluster(created_by=default_user, updated_by=default_user) new_cluster.save() new_cluster.winlogevent_set.add(*list(recent_failures)) # TODO could this be done on the server side? # Note that this count includes the cluster we just created, hence <= if (len([ cluster for cluster in EventCluster.active.all() if cluster.end_time > timezone.now() - get_preference('citrusborgux__backoff_time') ]) <= get_preference('citrusborgux__backoff_limit')): Email.send_email( None, Subscription.get_subscription('Citrix Cluster Alert'), False, start_time=new_cluster.start_time, end_time=new_cluster.end_time.astimezone( timezone.get_current_timezone()).time(), bots=new_cluster.winlogevent_set.all()) LOG.debug('sent cluster email') else: new_cluster.enabled = False new_cluster.save() LOG.info('Cluster created, but alert skipped due to frequency.')
def save_citrix_login_event(borg): """ Save data received from a citrix bot. :param borg: Message received from bot. :raises: Any exception raised during """ def reraise(msg): LOG.exception('%s %s.', msg, borg) # This function should only be called from inside an except block # If an Exception is not being handled it will cause a RuntimeError raise # pylint: disable=misplaced-bare-raise event_host = WinlogbeatHost.get_or_create_from_borg(borg) event_broker = KnownBrokeringDevice.get_or_create_from_borg(borg) try: event_source = AllowedEventSource.objects.get( source_name=borg.event_source) except AllowedEventSource.DoesNotExist: reraise('Cannot match event source for event') try: windows_log = WindowsLog.objects.get(log_name=borg.windows_log) except WindowsLog.DoesNotExist: reraise('Cannot match windows log info for event') user = get_or_create_user(get_preference('citrusborgcommon__service_user')) winlogevent = WinlogEvent( source_host=event_host, record_number=borg.record_number, event_source=event_source, windows_log=windows_log, event_state=borg.borg_message.state, xml_broker=event_broker, event_test_result=borg.borg_message.test_result, storefront_connection_duration =borg.borg_message.storefront_connection_duration, receiver_startup_duration=borg.borg_message.receiver_startup_duration, connection_achieved_duration =borg.borg_message.connection_achieved_duration, logon_achieved_duration=borg.borg_message.logon_achieved_duration, logoff_achieved_duration=borg.borg_message.logoff_achieved_duration, failure_reason=borg.borg_message.failure_reason, failure_details=borg.borg_message.failure_details, raw_message=borg.borg_message.raw_message, event_id=borg.event_id, timestamp=borg.timestamp, created_by=user, updated_by=user) winlogevent.save() LOG.info('saved event: %s', winlogevent.uuid)
def add_new_perf_buckets(apps, schema_editor): user = get_or_create_user() user_dict = { 'created_by_id': user.id, 'updated_by_id': user.id, } perf_buckets = [ { 'name': 'Alert at 10 ms', 'avg_warn_threshold': .0025, 'avg_err_threshold': .005, 'alert_threshold': .01, 'notes': '', 'is_default': False, }, { 'name': 'Alert at 100 ms', 'avg_warn_threshold': .025, 'avg_err_threshold': .05, 'alert_threshold': .1, 'notes': '', 'is_default': False, }, { 'name': 'Alert at 1 s', 'avg_warn_threshold': .25, 'avg_err_threshold': .5, 'alert_threshold': 1, 'notes': '', 'is_default': False, }, { 'name': 'Alert at 10 s', 'avg_warn_threshold': 2.5, 'avg_err_threshold': 5, 'alert_threshold': 10, 'notes': '', 'is_default': True, }, ] perf_bucket_model = apps.get_model('ldap_probe', 'ADNodePerfBucket') for perf_bucket_dict in perf_buckets: perf_bucket_dict.update(user_dict) perf_bucket = perf_bucket_model(**perf_bucket_dict) perf_bucket.save()
def populate_ac_orion_nodes(apps, schema_editor): user = get_or_create_user() ldap_bind_cred = apps.get_model( 'ldap_probe', 'LDAPBindCred').objects.filter(is_default=True).get() orion_nodes = apps.get_model( 'orion_integration', 'OrionDomainControllerNode').objects.\ filter(program_application_type='DomainController').all() ac_nodes_model = apps.get_model('ldap_probe', 'OrionADNode') for node in orion_nodes: ac_node = ac_nodes_model(node=node, ldap_bind_cred=ldap_bind_cred, created_by_id=user.id, updated_by_id=user.id) ac_node.save()
def populate_ldap_cred_default(apps, schema_editor): user = get_or_create_user() default_cred_dict = { 'domain': 'VCH', 'username': '******', 'password': '******', 'is_default': True, 'created_by_id': user.id, 'updated_by_id': user.id, } ldap_cred_model = apps.get_model('ldap_probe', 'LDAPBindCred') if ldap_cred_model.objects.filter( domain__iexact=default_cred_dict['domain'], username__iexact=default_cred_dict['username']).exists(): return default_ldap_cred = ldap_cred_model(**default_cred_dict) default_ldap_cred.save()
def get_or_create_from_borg(cls, borg): """ maintain the `Citrix` application servers information based on data in the `Windows` log event If a :class:`KnownBrokeringDevice` instance matching the information in the `borg` argument doesn't exist, one will be created. This `class method <https://docs.python.org/3.6/library/functions.html#classmethod>`__ is invoked from the :func:`citrus_borg.tasks.store_borg_data` task. :arg borg: a :func:`collections.namedtuple` with the processed `Windows` log event data See the functions in the :mod:`citrus_borg.locutus.assimilation` module for the structure of the :func:`namedtuple <collections.namedtuple>`. :returns: the :class:`KnownBrokeringDevice` instance """ if borg.borg_message.broker is None: return None broker = cls.objects.filter( broker_name__iexact=borg.borg_message.broker) if broker.exists(): broker = broker.get() broker.last_seen = now() else: user = get_or_create_user(settings.CITRUS_BORG_SERVICE_USER) broker = cls(broker_name=borg.borg_message.broker, created_by=user, updated_by=user, last_seen=now()) broker.save() return broker
def get_or_create( cls, ssl_issuer, username=settings.NMAP_SERVICE_USER): """ create (if it doesn't exist already) and retrieve a :class:`SslCertificateIssuer` instance :arg dict ssl_issuer: data about the `Certificate authority <https://en.wikipedia.org/wiki/Public_key_certificate#Certificate_authorities>`__ :arg str username: the key for the :class:`django.contrib.auth.models.User` (or its `replacement <https://docs.djangoproject.com/en/2.2/topics/auth/customizing/#substituting-a-custom-user-model>`__) instance representing the user that is maintaining the :class:`SslCertificateIssuer` instance By default, this value is picked from :attr:`p_soc_auto.settings.NMAP_SERVICE_USER` and if that user doesn't exist, it will be created. :returns: a :class:`SslCertificateIssuer` instance """ ssl_certificate_issuer = cls._meta.model.objects.\ filter(common_name__iexact=ssl_issuer.get('commonName')) if ssl_certificate_issuer.exists(): return ssl_certificate_issuer.get() user = get_or_create_user(username) ssl_certificate_issuer = cls( common_name=ssl_issuer.get('commonName'), organization_name=ssl_issuer.get('organizationName'), country_name=ssl_issuer.get('countryName'), created_by=user, updated_by=user) ssl_certificate_issuer.save() return ssl_certificate_issuer
def maintain_ad_orion_nodes(): """ synchronize :class:`ldap_probe.models.OrionADNode` with :class:`orion_integration.models.OrionDomainControllerNode` This is needed because `AD` `Orion` nodes may change outside this application """ known_nodes_model = apps.get_model('ldap_probe.orionadnode') new_nodes_model = apps.get_model( 'orion_integration.oriondomaincontrollernode') known_node_ids = known_nodes_model.objects.values_list( 'node_id', flat=True) new_nodes = new_nodes_model.objects.exclude(id__in=known_node_ids) if not new_nodes.exists(): LOG.debug('did not find any unknown AD nodes in Orion') return service_user = get_or_create_user( name=get_preference('ldapprobe__service_user')) ldap_bind_cred = apps.get_model('ldap_probe.ldapbindcred').default() for node in new_nodes: new_ad_orion_node = known_nodes_model( node=node, ldap_bind_cred=ldap_bind_cred, created_by=service_user, updated_by=service_user) try: new_ad_orion_node.save() except Exception as error: LOG.exception(error) raise error LOG.info('created %s AD nodes from Orion', new_nodes.count())
def populate_ac_nodes(apps, schema_editor): user = get_or_create_user() ldap_bind_cred = apps.get_model( 'ldap_probe', 'LDAPBindCred').objects.filter(is_default=True).get() ac_nodes = [ 'alpha.fraserhealth.org', 'atlas.fraserhealth.org', 'blackcomb.fraserhealth.org', 'cypress.fraserhealth.org', 'dcns0002.fraserhealth.org', 'dcns0004.fraserhealth.org', 'denman.fraserhealth.org ', 'everest.fraserhealth.org', 'fuji.fraserhealth.org ', 'janus.fraserhealth.org', 'laila.fraserhealth.org ', 'olympus.fraserhealth.org', 'phcdc3.phcnet.ca', 'phcdc5.phcnet.ca', 'phcdc7.phcnet.ca', 'phcdc21.phcnet.ca', 'phcdc41.phcnet.ca', 'phcdc42.phcnet.ca', 'phsacdcldcphc3.phcnet.ca', 'phsacdcldcphc4.phcnet.ca', 'phsacdcldcphsa3.phsabc.ehcnet.ca', 'phsacdcldcphsa4.phsabc.ehcnet.ca', 'phsacdcldcvch3.vch.ca', 'phsacdcldcvch4.vch.ca', 'phsacdcldcvrhb3.main.vrhb.local', 'phsacdcldcvrhb4.main.vrhb.local', 'proteus.fraserhealth.org', 'rainier.fraserhealth.org', 'rhsdc2.main.vrhb.local', 'robson.fraserhealth.org', 'rootdc4.vrhb.local', 'rootdc5.vrhb.local', 'rootdc6.vrhb.local', 'spdc0001.healthbc.org', 'spdc0002.healthbc.org', 'spdc0003.healthbc.org', 'spdc0004.healthbc.org', 'spdc0005.healthbc.org', 'spdc0006.healthbc.org', 'spdc0007.healthbc.org', 'spdc0008.healthbc.org', 'spdc0009.healthbc.org', 'spdc0010.healthbc.org', 'spdc0011.healthbc.org', 'spdc0012.healthbc.org', 'spdc0013.healthbc.org', 'spdc0014.healthbc.org', 'spdc0015.healthbc.org', 'spdc0016.healthbc.org', 'spdc0017.healthbc.org', 'spdcphc001.phcnet.ca', 'spdcphsa001.phsabc.ehcnet.ca', 'spdcvrhb006.main.vrhb.local', 'srvcs01.phsabc.ehcnet.ca', 'srvcs02.phsabc.ehcnet.ca', 'srvcs03.phsabc.ehcnet.ca', 'srvcs04.phsabc.ehcnet.ca', 'srvcs41.phsabc.ehcnet.ca', 'srvfr01.ehcnet.ca', 'srvfr02.ehcnet.ca', 'srvpngcs01.phsapng.ca', 'srvpngcs02.phsapng.ca', 'svmcs06.phsabc.ehcnet.ca', 'svmcs10.phsabc.ehcnet.ca', 'svmcs15-new.phsabc.ehcnet.ca', 'svmcs15.phsabc.ehcnet.ca', 'svmcs16-new.phsabc.ehcnet.ca', 'svmcs16.phsabc.ehcnet.ca', 'svmfr03.ehcnet.ca', 'vchdc1.vch.ca', 'vchdc2.vch.ca', 'vchdc3.vch.ca', 'vchdc4.vch.ca', 'vchdc5.vch.ca', 'vchdc6.vch.ca', 'vchdc7.vch.ca', 'vchdc8.vch.ca', 'vchdc9.vch.ca', 'vchdc12.vch.ca', 'vchdc13.vch.ca', 'vchdc14.vch.ca', 'vchdc16.vch.ca', 'vchdc41.vch.ca', 'vesuvius.fraserhealth.org ', 'vrhbdc5.main.vrhb.local', 'vrhbdc6.main.vrhb.local', 'vrhbdc7.main.vrhb.local', 'vrhbdc41.main.vrhb.local', 'whistler.fraserhealth.org', 'whitney.fraserhealth.org', ] ac_nodes_model = apps.get_model('ldap_probe', 'NonOrionADNode') for node_dns in ac_nodes: ac_node = ac_nodes_model(node_dns=node_dns, ldap_bind_cred=ldap_bind_cred, created_by_id=user.id, updated_by_id=user.id) ac_node.save()
def populate_ldap_errors(apps, schema_editor): user = get_or_create_user() ldap_errors = [ { 'error_unique_identifier': '525', 'short_description': 'LDAP_NO_SUCH_OBJECT', 'notes': 'Entry does not exist', 'comments': None, }, { 'error_unique_identifier': '52e', 'short_description': 'ERROR_LOGON_FAILURE', 'notes': ('Returns when username is valid but password/credential' ' is invalid.'), 'comments': ('Will prevent most other errors from being displayed' ' as noted.'), }, { 'error_unique_identifier': '52f', 'short_description': 'ERROR_ACCOUNT_RESTRICTION', 'notes': ('Account Restrictions are preventing this user from signing in.'), 'comments': ('For example: blank passwords are not allowed,' ' sign-in times are limited, or a policy restriction' ' has been enforced.'), }, { 'error_unique_identifier': '530', 'short_description': 'ERROR_INVALID_LOGON_HOURS', 'notes': 'Time Restriction:Entry logon time restriction violation', 'comments': None, }, { 'error_unique_identifier': '531', 'short_description': 'ERROR_INVALID_WORKSTATION', 'notes': 'Device Restriction:Entry not allowed to log on to this computer.', 'comments': None, }, { 'error_unique_identifier': '532', 'short_description': 'ERROR_PASSWORD_EXPIRED', 'notes': ('Password Expiration: Entry password has expired LDAP' ' User-Account-Control Attribute - ERROR_PASSWORD_EXPIRED'), 'comments': 'Returns only when presented with valid username and password/credential. ', }, { 'error_unique_identifier': '533', 'short_description': 'ERROR_ACCOUNT_DISABLED', 'notes': 'Administratively Disabled: LDAP User-Account-Control Attribute - ACCOUNTDISABLE', 'comments': 'Returns only when presented with valid username and password/credential.', }, { 'error_unique_identifier': '568', 'short_description': 'ERROR_TOO_MANY_CONTEXT_IDS', 'notes': ("During a logon attempt, the user's security context" " accumulated too many security Identifiers. (ie Group-AD)"), 'comments': None, }, { 'error_unique_identifier': '701', 'short_description': 'ERROR_ACCOUNT_EXPIRED', 'notes': 'LDAP Password Expiration: User-Account-Control Attribute - ACCOUNTEXPIRED', 'comments': 'Returns only when presented with valid username and password/credential.', }, { 'error_unique_identifier': '773', 'short_description': 'ERROR_PASSWORD_MUST_CHANGE', 'notes': ("Password Expiration: Entry's password must be changed" " before logging on LDAP pwdLastSet: value of 0 indicates" " admin-required password change - MUST_CHANGE_PASSWD"), 'comments': 'Returns only when presented with valid username and password/credential. ', }, { 'error_unique_identifier': '775', 'short_description': 'ERROR_ACCOUNT_LOCKED_OUT', 'notes': ('Intruder Detection:Entry is currently locked out and may' ' not be logged on to LDAP User-Account-Control Attribute - LOCKOUT' ), 'comments': 'Returns even if invalid password is presented', }, ] ldap_error_model = apps.get_model('ldap_probe', 'LdapCredError') for ldap_error in ldap_errors: ldap_error.update(created_by_id=user.id, updated_by_id=user.id) ldap_error_entry = ldap_error_model(**ldap_error) ldap_error_entry.save()
def add_ssl_ports(apps, schema_editor): user = get_or_create_user() ports = [ { 'port': 261, 'enabled': False, 'notes': 'Nsiiops', }, { 'port': 443, 'enabled': True, 'notes': 'HTTPS', }, { 'port': 446, 'enabled': False, 'notes': 'Openfiler management interface', }, { 'port': 448, 'enabled': False, 'notes': 'ddm-ssl', }, { 'port': 465, 'enabled': False, 'notes': 'SMTPS', }, { 'port': 563, 'enabled': False, 'notes': 'NNTPS', }, { 'port': 585, 'enabled': False, 'notes': 'imap4-ssl', }, { 'port': 614, 'enabled': False, 'notes': 'SSLshell', }, { 'port': 636, 'enabled': False, 'notes': 'LDAPS', }, { 'port': 684, 'enabled': False, 'notes': 'Corba IIOP SSL', }, { 'port': 695, 'enabled': False, 'notes': 'IEEE-MMS-SSL', }, { 'port': 902, 'enabled': False, 'notes': 'VMWare Auth Daemon', }, { 'port': 989, 'enabled': False, 'notes': 'FTPS data', }, ] port_model = apps.get_model('ssl_cert_tracker', 'SslProbePort') for port_dict in ports: port_dict.update({'created_by_id': user.id, 'updated_by_id': user.id}) port_model.objects.get_or_create(**port_dict)
def update_or_create_from_orion(cls): """ update or create instances of :class:`django.db.models.Model` classes inheriting from this class using data from the `Orion` server :arg str username: the :attr:`username` of the :class:`django.contrib.auth.models.User` instance when creating the model instance .. todo:: We are picking the default value for this argument from the `Django` `settings` file. We need to use a dynamic preference instead. :raises: :exc:`OrionQueryError` if the :attr:`orion_query` attribute is not defined in the model :exc:`OrionMappingsError` if the :attr:`orion_mappings` attribute is not defined in the model :returns: a :class:`dictionary <dict>` with these keys * status * model: the `model` that executed this method * orion_rows: the number of rows returned by the `Orion` `REST` call * updated_records: the number of objects that have been updated during the `Orion` `REST` call * created_records: the number of objects that have been created during the `Orion` `REST` call * errored_records: the number of objects that threw an error during the `Orion` `REST` call """ return_dict = dict(model=cls._meta.verbose_name, orion_rows=0, updated_records=0, created_records=0, errored_records=0) if cls.orion_query is None: raise OrionQueryError( '%s is not providing a value for the Orion query' % cls._meta.label) if not cls.orion_mappings: raise OrionMappingsError( '%s is not providing an Orion value mapping' % cls._meta.label) user = get_or_create_user(settings.ORION_USER) data = OrionClient.query(orion_query=cls.orion_query) return_dict['orion_rows'] = len(data) for data_item in data: orion_mapping = dict() for mapping in cls.orion_mappings: if mapping[2] is None: orion_mapping[mapping[0]] = data_item[mapping[1]] else: app, model = mapping[2].split('.') model = apps.get_model(app, model) orion_mapping[mapping[0]] = model.objects.filter( orion_id__exact=data_item[mapping[1]]).get() orion_mapping.update(updated_by=user, created_by=user) try: qs = cls.objects.filter( orion_id__exact=orion_mapping['orion_id']) if qs.exists(): orion_mapping.pop('orion_id') orion_mapping.pop('created_by', None) return_dict['updated_records'] += 1 # we do not want to use qs.update(88orion_mapping) because # it bypasses the pre- and post-save signals # thus, setattr in a loop instance = qs.get() for attr, value in orion_mapping.items(): setattr(instance, attr, value) else: # it is a new instance return_dict['created_records'] += 1 instance = cls(**orion_mapping) instance.save() except Exception as error: return_dict['errored_records'] += 1 LOG.error('%s when acquiring Orion object %s', str(error), orion_mapping) return return_dict
def setUpClass(cls): super().setUpClass() user = get_or_create_user() cls.USER_ARGS = {'created_by': user, 'updated_by': user}
def add_ssl_ports(apps, schema_editor): ports = [ ( 261, False, 'Nsiiops', ), ( 443, True, 'HTTPS', ), ( 446, False, 'Openfiler management interface', ), ( 448, False, 'ddm-ssl', ), ( 465, False, 'SMTPS', ), ( 563, False, 'NNTPS', ), ( 585, False, 'imap4-ssl', ), ( 614, False, 'SSLshell', ), ( 636, False, 'LDAPS', ), ( 684, False, 'Corba IIOP SSL', ), ( 695, False, 'IEEE-MMS-SSL', ), ( 902, False, 'VMWare Auth Daemon', ), ( 989, False, 'FTPS data', ), ] model = apps.get_model('ssl_cert_tracker', 'SslProbePort') user = get_or_create_user() for port in ports: ssl_port = model.objects.filter(port=port[0]) if ssl_port.exists(): ssl_port = ssl_port.get() else: ssl_port = model(port=port[0], created_by_id=user.id, updated_by_id=user.id) ssl_port.enabled = port[1] ssl_port.notes = port[2] ssl_port.save()
def create_or_update(cls, ssl_certificate, orion_id=None, external_id=None, username=settings.NMAP_SERVICE_USER): """ create or update a :class:`SslCertificate` instance Currently, we are uniquely identifying an `SSL` certificate by the ('network address', 'network port') tuple where the certificate is being served. This approach is fully justified from an operational perspective; it is not possible to serve more than one certificate per the network tuple. However, a (valid) SSL certificate is an ephemeral construct. When it expires, it cannot be extended. It can only be replaced by a new `SSL` certificate. This method needs to be able to detect such a change and handle the instance update accordingly. In this method, we look for the network tuple in the underlying table: * if not found, this is a new `SSL` certificate, the method creates a new :class:`SslCertificate` instance * if found, compare the `MD5 <https://en.wikipedia.org/wiki/MD5>`_ checksum already present in the :class:`SslCertificate` instance as :attr:`pk_md5` with the one present in the :attr:`ssl_md5` attribute of the `ssl_certificate` argument * if the `MD5` values match, the whole `SSL` certificate matches; the method will only update the :attr:`SslCertificate.last_seen` field * if the `MD5` values don't match, this is a new `SSL` certificate; the method will update all the fields in the :class:`SslCertificate` instance :Note: In :class:`SslCetificate`, the network address is hiding behind the :attr:`SslCertificate.orion_id` which is a :class:`django.db.models.ForeignKey` to :class:`orion_integration.models.OrionNode`. :arg int orion_id: the reference to the :class:`orion_integration.models.OrionNode` instance for the network node from where the `SSL` certificate is served :arg ssl_certificate: all the data collected by the `NMAP <https://nmap.org/>`_ scan :type ssl_certificate: :class:`ssl_cert_tracker.nmap.SslProbe` :arg str username: :attr:`django.contrib.auth.models.User.username` of the user (or its `replacement <https://docs.djangoproject.com/en/2.2/topics/auth/customizing/#substituting-a-custom-user-model>`__) maintaining the :class:`SslCertificate` instance The default value is picked from :attr:`p_soc_auto.settings.NMAP_SERVICE_USER`. If a matching :class:`django.contrib.auth.models.User` instance doesn't exist, one will be created. """ user = get_or_create_user(username) issuer = SslCertificateIssuer.get_or_create( ssl_certificate.ssl_issuer, username) if orion_id: node_id = {'orion_id': orion_id} elif external_id: node_id = {'external_node_id': external_id} else: raise ValueError( "Id for the node the Certificate came from was not supplied.") ssl_obj = cls._meta.model.objects.filter(**node_id, port__port=ssl_certificate.port) if ssl_obj.exists(): ssl_obj = ssl_obj.get() if ssl_obj.pk_md5 != ssl_certificate.ssl_md5: # host and port are the same but the checksum has changed, # ergo the certificate has been replaced. we need to save # the new data ssl_obj.common_name = ssl_certificate.ssl_subject.\ get('commonName') ssl_obj.organization_name = ssl_certificate.ssl_subject.\ get('organizationName') ssl_obj.country_name = ssl_certificate.ssl_subject.\ get('countryName') ssl_obj.issuer = issuer ssl_obj.hostnames = ssl_certificate.hostnames ssl_obj.not_before = ssl_certificate.ssl_not_before ssl_obj.not_after = ssl_certificate.ssl_not_after ssl_obj.pem = ssl_certificate.ssl_pem ssl_obj.pk_bits = ssl_certificate.ssl_pk_bits ssl_obj.pk_type = ssl_certificate.ssl_pk_type ssl_obj.pk_md5 = ssl_certificate.ssl_md5 ssl_obj.pk_sha1 = ssl_certificate.ssl_sha1 ssl_obj.updated_by = user ssl_obj.last_seen = timezone.now() ssl_obj.save() return False, ssl_obj port = SslProbePort.objects.get(port=int(ssl_certificate.port)) ssl_obj = cls( common_name=ssl_certificate.ssl_subject.get('commonName'), organization_name=ssl_certificate.ssl_subject.get( 'organizationName'), country_name=ssl_certificate.ssl_subject.get('countryName'), orion_id=orion_id, external_node_id=external_id, port=port, issuer=issuer, hostnames=ssl_certificate.hostnames, not_before=ssl_certificate.ssl_not_before, not_after=ssl_certificate.ssl_not_after, pem=ssl_certificate.ssl_pem, pk_bits=ssl_certificate.ssl_pk_bits, pk_type=ssl_certificate.ssl_pk_type, pk_md5=ssl_certificate.ssl_md5, pk_sha1=ssl_certificate.ssl_sha1, created_by=user, updated_by=user, last_seen=timezone.now()) ssl_obj.save() return True, ssl_obj