diff --git a/doorman/api.py b/doorman/api.py index 4a66889..4e7c90f 100644 --- a/doorman/api.py +++ b/doorman/api.py @@ -14,7 +14,7 @@ DistributedQueryTask, DistributedQueryResult, StatusLog, ) -from doorman.tasks import analyze_result +from doorman.tasks import analyze_result, notify_of_node_enrollment from doorman.utils import process_result @@ -222,6 +222,8 @@ def enroll(): request.remote_addr, node ) + notify_of_node_enrollment.delay(node.to_dict()) + return jsonify(node_key=node.node_key, node_invalid=False) diff --git a/doorman/extensions.py b/doorman/extensions.py index 4b08771..040fa41 100644 --- a/doorman/extensions.py +++ b/doorman/extensions.py @@ -163,6 +163,10 @@ def handle_log_entry(self, entry, node): for alerter, match in to_trigger: self.alerters[alerter].handle_alert(node, match) + def handle_enroll(self, node): + for alerter in self.alerters.values(): + alerter.handle_enroll(node) + def make_celery(app, celery): """ From http://flask.pocoo.org/docs/0.10/patterns/celery/ """ diff --git a/doorman/plugins/alerters/base.py b/doorman/plugins/alerters/base.py index da34c1f..0c557f3 100644 --- a/doorman/plugins/alerters/base.py +++ b/doorman/plugins/alerters/base.py @@ -13,3 +13,7 @@ class AbstractAlerterPlugin(with_metaclass(ABCMeta)): @abstractmethod def handle_alert(self, node, match): raise NotImplementedError() + + @abstractmethod + def handle_enroll(self, node): + raise NotImplementedError() diff --git a/doorman/plugins/alerters/debug.py b/doorman/plugins/alerters/debug.py index 9b1baa4..6ff9b6a 100644 --- a/doorman/plugins/alerters/debug.py +++ b/doorman/plugins/alerters/debug.py @@ -22,3 +22,6 @@ def __init__(self, config): def handle_alert(self, node, match): # TODO(andrew-d): better message? current_app.logger.log(self.level, 'Triggered alert: {0!r}'.format(match)) + + def handle_enroll(self, node): + current_app.logger.log(self.level, 'Node enrolled: {0!r}'.format(node)) \ No newline at end of file diff --git a/doorman/plugins/alerters/emailer.py b/doorman/plugins/alerters/emailer.py index 661f89f..88cfed2 100644 --- a/doorman/plugins/alerters/emailer.py +++ b/doorman/plugins/alerters/emailer.py @@ -12,21 +12,29 @@ class EmailAlerter(AbstractAlerterPlugin): def __init__(self, config): self.recipients = config['recipients'] - self.subject_template = config.get('subject_template', 'email/alert.subject.txt') - self.message_template = config.get('message_template', 'email/alert.body.txt') - self.subject_prefix = config.get('subject_prefix', '[Doorman]') + self.config = config def handle_alert(self, node, match): + subject_template = self.config.setdefault( + 'subject_template', 'email/alert.subject.txt' + ) + message_template = self.config.setdefault( + 'message_template', 'email/alert.body.txt' + ) + subject_prefix = self.config.setdefault( + 'subject_prefix', '[Doorman]' + ) + subject = render_template( - self.subject_template, - prefix=self.subject_prefix, + subject_template, + prefix=subject_prefix, match=match, timestamp=dt.datetime.utcnow(), node=node ) body = render_template( - self.message_template, + message_template, match=match, timestamp=dt.datetime.utcnow(), node=node @@ -40,3 +48,36 @@ def handle_alert(self, node, match): ) return mail.send(message) + + def handle_enroll(self, node): + subject_template = self.config.setdefault( + 'enroll_subject_template', 'email/enroll.subject.txt' + ) + message_template = self.config.setdefault( + 'enroll_message_template', 'email/enroll.body.txt' + ) + subject_prefix = self.config.setdefault( + 'enroll_subject_prefix', '[Doorman]' + ) + + subject = render_template( + subject_template, + prefix=subject_prefix, + timestamp=dt.datetime.utcnow(), + node=node + ) + + body = render_template( + message_template, + timestamp=dt.datetime.utcnow(), + node=node + ) + + message = Message( + subject.strip(), + recipients=self.recipients, + body=body, + charset='utf-8', + ) + + return mail.send(message) diff --git a/doorman/plugins/alerters/pagerduty.py b/doorman/plugins/alerters/pagerduty.py index d89d738..5264288 100644 --- a/doorman/plugins/alerters/pagerduty.py +++ b/doorman/plugins/alerters/pagerduty.py @@ -69,3 +69,6 @@ def handle_alert(self, node, match): self.logger.warn('Could not trigger PagerDuty alert!') self.logger.debug('Response from PagerDuty: %r', resp.content) + + def handle_enroll(self, node): + pass diff --git a/doorman/plugins/alerters/sentry.py b/doorman/plugins/alerters/sentry.py index d662189..6f8accf 100644 --- a/doorman/plugins/alerters/sentry.py +++ b/doorman/plugins/alerters/sentry.py @@ -48,4 +48,7 @@ def handle_alert(self, node, match): 'pack': pack, 'query': query, }, - ) \ No newline at end of file + ) + + def handle_enroll(self, node): + pass diff --git a/doorman/tasks.py b/doorman/tasks.py index 57775e4..b1d467f 100644 --- a/doorman/tasks.py +++ b/doorman/tasks.py @@ -20,6 +20,12 @@ def learn_from_result(result, node): return +@celery.task() +def notify_of_node_enrollment(node): + current_app.rule_manager.handle_enroll(node) + return + + @celery.task() def example_task(one, two): print('Adding {0} and {1}'.format(one, two)) diff --git a/doorman/templates/email/enroll.body.txt b/doorman/templates/email/enroll.body.txt new file mode 100644 index 0000000..09b9082 --- /dev/null +++ b/doorman/templates/email/enroll.body.txt @@ -0,0 +1,9 @@ +A new node was enrolled: + +Host identifier: {{ node.display_name }} +Enrolled on: {{ node.enrolled_on }} +Remote IP address: {{ node.last_ip }} + +Review {{ node.display_name }} at {{ url_for('manage.get_node', node_id=node.id, _external=True) }}. + +---END doorman notification diff --git a/doorman/templates/email/enroll.subject.txt b/doorman/templates/email/enroll.subject.txt new file mode 100644 index 0000000..a4771b6 --- /dev/null +++ b/doorman/templates/email/enroll.subject.txt @@ -0,0 +1 @@ +{{ prefix | trim }} New node enrolled: {{ node.display_name }} diff --git a/tests/test_alerters.py b/tests/test_alerters.py index 0632252..b68c3d9 100644 --- a/tests/test_alerters.py +++ b/tests/test_alerters.py @@ -78,10 +78,11 @@ def setup_method(self, _method): self.recipients = ['test@example.com'] self.config = { 'recipients': self.recipients, - 'subject_prefix': '[Doorman Test] ' + 'subject_prefix': '[Doorman Test]', + 'enroll_subject_prefix': '[Doorman Test]', } - def test_will_email(self, node, rule, testapp): + def test_will_email_on_rule_match(self, node, rule, testapp): from flask_mail import email_dispatched match = RuleMatch( @@ -112,6 +113,22 @@ def verify(message, app): alerter = EmailAlerter(self.config) alerter.handle_alert(node.to_dict(), match) + def test_will_email_on_node_enrollment(self, node, testapp): + from flask_mail import email_dispatched + + expected_subject = '[Doorman Test] New node enrolled: {display_name}'.format( + display_name=node.display_name, + ) + + @email_dispatched.connect + def verify(message, app): + assert message.subject == expected_subject + assert self.recipients == message.recipients + assert 'A new node was enrolled:' in message.body + + alerter = EmailAlerter(self.config) + alerter.handle_enroll(node.to_dict()) + class TestSentryAlerter: diff --git a/tests/test_functional.py b/tests/test_functional.py index 048d567..16c0f3d 100644 --- a/tests/test_functional.py +++ b/tests/test_functional.py @@ -1362,6 +1362,9 @@ def __init__(self, *args, **kwargs): def handle_alert(self, node, match): self.calls.append((node, match)) + def handle_enroll(self, node): + self.calls.append((node, None)) + dummy_alerter = DummyAlerter() # This patches the appropriate config to create the 'dummy' alerter. This is a bit ugly :-(