This commit is contained in:
@ -1,3 +1,4 @@
|
||||
from .destination import DestinationPlugin # noqa
|
||||
from .issuer import IssuerPlugin # noqa
|
||||
from .source import SourcePlugin # noqa
|
||||
from .notification import NotificationPlugin, ExpirationNotificationPlugin # noqa
|
||||
|
52
lemur/plugins/bases/notification.py
Normal file
52
lemur/plugins/bases/notification.py
Normal file
@ -0,0 +1,52 @@
|
||||
"""
|
||||
.. module: lemur.bases.notification
|
||||
:platform: Unix
|
||||
:copyright: (c) 2015 by Netflix Inc., see AUTHORS for more
|
||||
:license: Apache, see LICENSE for more details.
|
||||
|
||||
.. moduleauthor:: Kevin Glisson <kglisson@netflix.com>
|
||||
"""
|
||||
from lemur.plugins.base import Plugin
|
||||
|
||||
|
||||
class NotificationPlugin(Plugin):
|
||||
"""
|
||||
This is the base class from which all of the supported
|
||||
issuers will inherit from.
|
||||
"""
|
||||
type = 'notification'
|
||||
|
||||
def send(self):
|
||||
raise NotImplementedError
|
||||
|
||||
|
||||
class ExpirationNotificationPlugin(NotificationPlugin):
|
||||
"""
|
||||
This is the base class for all expiration notification plugins.
|
||||
It contains some default options that are needed for all expiration
|
||||
notification plugins.
|
||||
"""
|
||||
default_options = [
|
||||
{
|
||||
'name': 'interval',
|
||||
'type': 'int',
|
||||
'required': True,
|
||||
'validation': '^\d+$',
|
||||
'helpMessage': 'Number of days to be alert before expiration.',
|
||||
},
|
||||
{
|
||||
'name': 'unit',
|
||||
'type': 'select',
|
||||
'required': True,
|
||||
'validation': '',
|
||||
'available': ['days', 'weeks', 'months'],
|
||||
'helpMessage': 'Interval unit',
|
||||
}
|
||||
]
|
||||
|
||||
@property
|
||||
def options(self):
|
||||
return list(self.default_options) + self.additional_options
|
||||
|
||||
def send(self):
|
||||
raise NotImplementedError
|
@ -61,7 +61,7 @@ class AWSSourcePlugin(SourcePlugin):
|
||||
options = [
|
||||
{
|
||||
'name': 'accountNumber',
|
||||
'type': 'int',
|
||||
'type': 'str',
|
||||
'required': True,
|
||||
'validation': '/^[0-9]{12,12}$/',
|
||||
'helpMessage': 'Must be a valid AWS account number!',
|
||||
|
5
lemur/plugins/lemur_email/__init__.py
Normal file
5
lemur/plugins/lemur_email/__init__.py
Normal file
@ -0,0 +1,5 @@
|
||||
try:
|
||||
VERSION = __import__('pkg_resources') \
|
||||
.get_distribution(__name__).version
|
||||
except Exception, e:
|
||||
VERSION = 'unknown'
|
76
lemur/plugins/lemur_email/plugin.py
Normal file
76
lemur/plugins/lemur_email/plugin.py
Normal file
@ -0,0 +1,76 @@
|
||||
"""
|
||||
.. module: lemur.plugins.lemur_aws.aws
|
||||
:platform: Unix
|
||||
:copyright: (c) 2015 by Netflix Inc., see AUTHORS for more
|
||||
:license: Apache, see LICENSE for more details.
|
||||
|
||||
.. moduleauthor:: Kevin Glisson <kglisson@netflix.com>
|
||||
"""
|
||||
import boto.ses
|
||||
from flask import current_app
|
||||
from flask_mail import Message
|
||||
|
||||
from lemur.extensions import smtp_mail
|
||||
|
||||
from lemur.plugins.bases import ExpirationNotificationPlugin
|
||||
from lemur.plugins import lemur_email as email
|
||||
|
||||
|
||||
from lemur.plugins.lemur_email.templates.config import env
|
||||
|
||||
|
||||
def find_value(name, options):
|
||||
for o in options:
|
||||
if o.get(name):
|
||||
return o['value']
|
||||
|
||||
|
||||
class EmailNotificationPlugin(ExpirationNotificationPlugin):
|
||||
title = 'Email'
|
||||
slug = 'email-notification'
|
||||
description = 'Sends expiration email notifications'
|
||||
version = email.VERSION
|
||||
|
||||
author = 'Kevin Glisson'
|
||||
author_url = 'https://github.com/netflix/lemur'
|
||||
|
||||
additional_options = [
|
||||
{
|
||||
'name': 'recipients',
|
||||
'type': 'str',
|
||||
'required': True,
|
||||
'validation': '^([\w+-.%]+@[\w-.]+\.[A-Za-z]{2,4},?)+$',
|
||||
'helpMessage': 'Comma delimited list of email addresses',
|
||||
},
|
||||
]
|
||||
|
||||
@staticmethod
|
||||
def send(event_type, message, targets, options, **kwargs):
|
||||
"""
|
||||
Configures all Lemur email messaging
|
||||
|
||||
:param event_type:
|
||||
:param options:
|
||||
"""
|
||||
subject = 'Notification: Lemur'
|
||||
|
||||
if event_type == 'expiration':
|
||||
subject = 'Notification: SSL Certificate Expiration '
|
||||
|
||||
# jinja template depending on type
|
||||
template = env.get_template('{}.html'.format(event_type))
|
||||
body = template.render(**kwargs)
|
||||
|
||||
s_type = current_app.config.get("LEMUR_EMAIL_SENDER").lower()
|
||||
if s_type == 'ses':
|
||||
conn = boto.connect_ses()
|
||||
conn.send_email(current_app.config.get("LEMUR_EMAIL"), subject, body, targets, format='html')
|
||||
|
||||
elif s_type == 'smtp':
|
||||
msg = Message(subject, recipients=targets)
|
||||
msg.body = "" # kinda a weird api for sending html emails
|
||||
msg.html = body
|
||||
smtp_mail.send(msg)
|
||||
|
||||
else:
|
||||
current_app.logger.error("No mail carrier specified, notification emails were not able to be sent!")
|
0
lemur/plugins/lemur_email/templates/__init__.py
Normal file
0
lemur/plugins/lemur_email/templates/__init__.py
Normal file
4
lemur/plugins/lemur_email/templates/config.py
Normal file
4
lemur/plugins/lemur_email/templates/config.py
Normal file
@ -0,0 +1,4 @@
|
||||
from jinja2 import Environment, PackageLoader
|
||||
|
||||
loader = PackageLoader('lemur')
|
||||
env = Environment(loader=loader)
|
150
lemur/plugins/lemur_email/templates/expiration.html
Normal file
150
lemur/plugins/lemur_email/templates/expiration.html
Normal file
@ -0,0 +1,150 @@
|
||||
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
|
||||
"http://www.w3.org/TR/html4/loose.dtd">
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
|
||||
<meta name="viewport" content="initial-scale=1.0"> <!-- So that mobile webkit will display zoomed in -->
|
||||
<meta name="format-detection" content="telephone=no"> <!-- disable auto telephone linking in iOS -->
|
||||
|
||||
<title>Lemur</title>
|
||||
<style type="text/css">
|
||||
|
||||
/* Resets: see reset.css for details */
|
||||
.ReadMsgBody { width: 100%; background-color: #ebebeb;}
|
||||
.ExternalClass {width: 100%; background-color: #ebebeb;}
|
||||
.ExternalClass, .ExternalClass p, .ExternalClass span, .ExternalClass font, .ExternalClass td, .ExternalClass div {line-height:100%;}
|
||||
body {-webkit-text-size-adjust:none; -ms-text-size-adjust:none;}
|
||||
body {margin:0; padding:0;}
|
||||
table {border-spacing:0;}
|
||||
table td {border-collapse:collapse;}
|
||||
.yshortcuts a {border-bottom: none !important;}
|
||||
|
||||
|
||||
/* Constrain email width for small screens */
|
||||
@media screen and (max-width: 600px) {
|
||||
table[class="container"] {
|
||||
width: 95% !important;
|
||||
}
|
||||
}
|
||||
|
||||
/* Give content more room on mobile */
|
||||
@media screen and (max-width: 480px) {
|
||||
td[class="container-padding"] {
|
||||
padding-left: 12px !important;
|
||||
padding-right: 12px !important;
|
||||
}
|
||||
}
|
||||
|
||||
</style>
|
||||
</head>
|
||||
<body style="margin:0; padding:10px 0;" bgcolor="#ebebeb" leftmargin="0" topmargin="0" marginwidth="0" marginheight="0">
|
||||
<br>
|
||||
<!-- 100% wrapper (grey background) -->
|
||||
<table border="0" width="100%" height="100%" cellpadding="50" cellspacing="0" bgcolor="#ebebeb">
|
||||
<tr>
|
||||
<td align="center" valign="top" bgcolor="#ebebeb" style="background-color: #ebebeb;">
|
||||
<!-- 600px container (white background) -->
|
||||
<table border="0" width="600" cellpadding="0" cellspacing="0" class="container" bgcolor="#ffffff">
|
||||
<tr>
|
||||
<td class="container-padding" bgcolor="#ffffff" style="background-color: #ffffff; padding-left: 30px; padding-right: 30px; font-size: 14px; line-height: 20px; font-family: Helvetica, sans-serif; color: #333;">
|
||||
<br />
|
||||
<div style="font-weight: bold; font-size: 18px; line-height: 24px; color: #202d3b">
|
||||
<span style="color: #29abe0">Notice: Your SSL certificates are expiring!</span>
|
||||
<hr />
|
||||
</div>
|
||||
Lemur, Netflix's SSL management portal has noticed that the following certificates are expiring soon, if you rely on these certificates
|
||||
you should create new certificates to replace the certificates that are expiring. Visit https://lemur.netflix.com/#/certificates/create to reissue them.
|
||||
</td>
|
||||
</tr>
|
||||
{% for message in messages %}
|
||||
<tr>
|
||||
<td class="container-padding" bgcolor="#ffffff" style="background-color: #ffffff; padding-left: 30px; padding-right: 30px; font-size: 14px; line-height: 20px; font-family: Helvetica, sans-serif; color: #333;">
|
||||
<hr />
|
||||
<table width="540">
|
||||
<tr>
|
||||
<td><strong>Name</strong></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>{{ message.name }}</td>
|
||||
<tr>
|
||||
<td><strong>Owner</strong></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>{{ message.owner }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><strong>Creator</strong></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>{{ message.creator }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><strong>Not Before</strong></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>{{ message.not_before }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><strong>Not After</strong></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>{{ message.not_after }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><strong>Associated Domains</strong></td>
|
||||
</tr>
|
||||
{% if message.domains %}
|
||||
{% for name in message.domains %}
|
||||
<tr>
|
||||
<td>{{ name }}</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
{% else %}
|
||||
<tr>
|
||||
<td>Unknown</td>
|
||||
</tr>
|
||||
{% endif %}
|
||||
<tr>
|
||||
<td><strong>Associated ELBs</strong></td>
|
||||
</tr>
|
||||
{% if message.listeners %}
|
||||
{% for name in message.listeners %}
|
||||
<tr>
|
||||
<td>{{ name }}</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
{% else %}
|
||||
<tr>
|
||||
<td>None</td>
|
||||
</tr>
|
||||
{% endif %}
|
||||
<tr>
|
||||
<td><strong>Potentially Superseded by</strong> (Lemur's best guess)</td>
|
||||
</tr>
|
||||
{% if message.superseded %}
|
||||
{% for name in message.superseded %}
|
||||
<tr><td>{{ name }}</td></tr>
|
||||
{% endfor %}
|
||||
{% else %}
|
||||
<tr><td>Unknown</td></tr>
|
||||
{% endif %}
|
||||
<tr><td></td></tr>
|
||||
</table>
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</table>
|
||||
<!--/600px container -->
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td style="padding-top: 0px" align="center" valign="top">
|
||||
<em style="font-style:italic; font-size: 12px; color: #aaa;">Lemur is broken regularly by <a style="color: #29abe0; text-decoration: none;" href="mailto:secops@netflix.com">Security Operations</a></em>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
<!--/100% wrapper-->
|
||||
<br>
|
||||
<br>
|
||||
</body>
|
||||
</html>
|
@ -65,13 +65,13 @@ class PluginsList(AuthenticatedResource):
|
||||
"id": 2,
|
||||
"accountNumber": 222222222,
|
||||
"label": "account2",
|
||||
"comments": "this is a thing"
|
||||
"description": "this is a thing"
|
||||
},
|
||||
{
|
||||
"id": 1,
|
||||
"accountNumber": 11111111111,
|
||||
"label": "account1",
|
||||
"comments": "this is a thing"
|
||||
"description": "this is a thing"
|
||||
},
|
||||
]
|
||||
"total": 2
|
||||
@ -80,19 +80,24 @@ class PluginsList(AuthenticatedResource):
|
||||
:reqheader Authorization: OAuth token to authenticate
|
||||
:statuscode 200: no error
|
||||
"""
|
||||
self.reqparse.add_argument('type', type=str, location='args')
|
||||
args = self.reqparse.parse_args()
|
||||
|
||||
if args['type']:
|
||||
return list(plugins.all(plugin_type=args['type']))
|
||||
|
||||
return plugins.all()
|
||||
|
||||
|
||||
class PluginsTypeList(AuthenticatedResource):
|
||||
""" Defines the 'plugins' endpoint """
|
||||
class Plugins(AuthenticatedResource):
|
||||
""" Defines the the 'plugins' endpoint """
|
||||
def __init__(self):
|
||||
self.reqparse = reqparse.RequestParser()
|
||||
super(PluginsTypeList, self).__init__()
|
||||
super(Plugins, self).__init__()
|
||||
|
||||
@marshal_items(FIELDS)
|
||||
def get(self, plugin_type):
|
||||
def get(self, name):
|
||||
"""
|
||||
.. http:get:: /plugins/issuer
|
||||
.. http:get:: /plugins/<name>
|
||||
|
||||
The current plugin list
|
||||
|
||||
@ -100,7 +105,7 @@ class PluginsTypeList(AuthenticatedResource):
|
||||
|
||||
.. sourcecode:: http
|
||||
|
||||
GET /plugins/issuer HTTP/1.1
|
||||
GET /plugins HTTP/1.1
|
||||
Host: example.com
|
||||
Accept: application/json, text/javascript
|
||||
|
||||
@ -113,27 +118,16 @@ class PluginsTypeList(AuthenticatedResource):
|
||||
Content-Type: text/javascript
|
||||
|
||||
{
|
||||
"items": [
|
||||
{
|
||||
"id": 2,
|
||||
"accountNumber": 222222222,
|
||||
"label": "account2",
|
||||
"comments": "this is a thing"
|
||||
},
|
||||
{
|
||||
"id": 1,
|
||||
"accountNumber": 11111111111,
|
||||
"label": "account1",
|
||||
"comments": "this is a thing"
|
||||
},
|
||||
]
|
||||
"total": 2
|
||||
"accountNumber": 222222222,
|
||||
"label": "account2",
|
||||
"description": "this is a thing"
|
||||
}
|
||||
|
||||
:reqheader Authorization: OAuth token to authenticate
|
||||
:statuscode 200: no error
|
||||
"""
|
||||
return list(plugins.all(plugin_type=plugin_type))
|
||||
return plugins.get(name)
|
||||
|
||||
|
||||
api.add_resource(PluginsList, '/plugins', endpoint='plugins')
|
||||
api.add_resource(PluginsTypeList, '/plugins/<plugin_type>', endpoint='pluginType')
|
||||
api.add_resource(Plugins, '/plugins/<name>', endpoint='pluginName')
|
||||
|
Reference in New Issue
Block a user