Adding backend code for sources models

This commit is contained in:
kevgliss
2015-08-01 15:29:34 -07:00
parent 46c6b8f8a4
commit e247d635fc
26 changed files with 1096 additions and 588 deletions

View File

29
lemur/sources/models.py Normal file
View File

@ -0,0 +1,29 @@
"""
.. module: lemur.sources.models
: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 copy
from sqlalchemy import Column, Integer, String, Text
from sqlalchemy_utils import JSONType
from lemur.database import db
from lemur.plugins.base import plugins
class Source(db.Model):
__tablename__ = 'sources'
id = Column(Integer, primary_key=True)
label = Column(String(32))
options = Column(JSONType)
description = Column(Text())
plugin_name = Column(String(32))
@property
def plugin(self):
p = plugins.get(self.plugin_name)
c = copy.deepcopy(p)
c.options = self.options
return c

107
lemur/sources/service.py Normal file
View File

@ -0,0 +1,107 @@
"""
.. module: lemur.sources.service
: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 import database
from lemur.sources.models import Source
from lemur.certificates.models import Certificate
def create(label, plugin_name, options, description=None):
"""
Creates a new source, that can then be used as a source for certificates.
:param label: Source common name
:param description:
:rtype : Source
:return: New source
"""
source = Source(label=label, options=options, plugin_name=plugin_name, description=description)
return database.create(source)
def update(source_id, label, options, description):
"""
Updates an existing source.
:param source_id: Lemur assigned ID
:param label: Source common name
:rtype : Source
:return:
"""
source = get(source_id)
source.label = label
source.options = options
source.description = description
return database.update(source)
def delete(source_id):
"""
Deletes an source.
:param source_id: Lemur assigned ID
"""
database.delete(get(source_id))
def get(source_id):
"""
Retrieves an source by it's lemur assigned ID.
:param source_id: Lemur assigned ID
:rtype : Source
:return:
"""
return database.get(Source, source_id)
def get_by_label(label):
"""
Retrieves a source by it's label
:param label:
:return:
"""
return database.get(Source, label, field='label')
def get_all():
"""
Retrieves all source currently known by Lemur.
:return:
"""
query = database.session_query(Source)
return database.find_all(query, Source, {}).all()
def render(args):
sort_by = args.pop('sort_by')
sort_dir = args.pop('sort_dir')
page = args.pop('page')
count = args.pop('count')
filt = args.pop('filter')
certificate_id = args.pop('certificate_id', None)
if certificate_id:
query = database.session_query(Source).join(Certificate, Source.certificate)
query = query.filter(Certificate.id == certificate_id)
else:
query = database.session_query(Source)
if filt:
terms = filt.split(';')
query = database.filter(query, Source, terms)
query = database.find_all(query, Source, args)
if sort_by and sort_dir:
query = database.sort(query, Source, sort_by, sort_dir)
return database.paginate(query, page, count)

46
lemur/sources/sync.py Normal file
View File

@ -0,0 +1,46 @@
"""
.. module: lemur.sources.sync
:platform: Unix
:synopsis: This module contains various certificate syncing operations.
Because of the nature of the SSL environment there are multiple ways
a certificate could be created without Lemur's knowledge. Lemur attempts
to 'sync' with as many different datasources as possible to try and track
any certificate that may be in use.
These operations are typically run on a periodic basis from either the command
line or a cron job.
:copyright: (c) 2015 by Netflix Inc., see AUTHORS for more
:license: Apache, see LICENSE for more details.
.. moduleauthor:: Kevin Glisson <kglisson@netflix.com>
"""
from flask import current_app
from lemur.certificates import service as cert_service
from lemur.plugins.base import plugins
from lemur.plugins.bases.source import SourcePlugin
def sync():
for plugin in plugins:
new = 0
updated = 0
if isinstance(plugin, SourcePlugin):
if plugin.is_enabled():
current_app.logger.error("Retrieving certificates from {0}".format(plugin.title))
certificates = plugin.get_certificates()
for certificate in certificates:
exists = cert_service.find_duplicates(certificate)
if not exists:
cert_service.import_certificate(**certificate)
new += 1
if len(exists) == 1:
updated += 1
# TODO associated cert with source
# TODO update cert if found from different source
# TODO disassociate source if missing

359
lemur/sources/views.py Normal file
View File

@ -0,0 +1,359 @@
"""
.. module: lemur.sources.views
:platform: Unix
:synopsis: This module contains all of the accounts view code.
:copyright: (c) 2015 by Netflix Inc., see AUTHORS for more
:license: Apache, see LICENSE for more details.
.. moduleauthor:: Kevin Glisson <kglisson@netflix.com>
"""
from flask import Blueprint
from flask.ext.restful import Api, reqparse, fields
from lemur.sources import service
from lemur.auth.service import AuthenticatedResource
from lemur.auth.permissions import admin_permission
from lemur.common.utils import paginated_parser, marshal_items
mod = Blueprint('sources', __name__)
api = Api(mod)
FIELDS = {
'description': fields.String,
'sourceOptions': fields.Raw(attribute='options'),
'pluginName': fields.String(attribute='plugin_name'),
'label': fields.String,
'id': fields.Integer,
}
class SourcesList(AuthenticatedResource):
""" Defines the 'sources' endpoint """
def __init__(self):
self.reqparse = reqparse.RequestParser()
super(SourcesList, self).__init__()
@marshal_items(FIELDS)
def get(self):
"""
.. http:get:: /sources
The current account list
**Example request**:
.. sourcecode:: http
GET /sources HTTP/1.1
Host: example.com
Accept: application/json, text/javascript
**Example response**:
.. sourcecode:: http
HTTP/1.1 200 OK
Vary: Accept
Content-Type: text/javascript
{
"items": [
{
"sourceOptions": [
{
"name": "accountNumber",
"required": true,
"value": 111111111112,
"helpMessage": "Must be a valid AWS account number!",
"validation": "/^[0-9]{12,12}$/",
"type": "int"
}
],
"pluginName": "aws-source",
"id": 3,
"description": "test",
"label": "test"
}
],
"total": 1
}
:query sortBy: field to sort on
:query sortDir: acs or desc
:query page: int. default is 1
:query filter: key value pair. format is k=v;
:query limit: limit number. default is 10
:reqheader Authorization: OAuth token to authenticate
:statuscode 200: no error
"""
parser = paginated_parser.copy()
args = parser.parse_args()
return service.render(args)
@admin_permission.require(http_exception=403)
@marshal_items(FIELDS)
def post(self):
"""
.. http:post:: /sources
Creates a new account
**Example request**:
.. sourcecode:: http
POST /sources HTTP/1.1
Host: example.com
Accept: application/json, text/javascript
{
"sourceOptions": [
{
"name": "accountNumber",
"required": true,
"value": 111111111112,
"helpMessage": "Must be a valid AWS account number!",
"validation": "/^[0-9]{12,12}$/",
"type": "int"
}
],
"pluginName": "aws-source",
"id": 3,
"description": "test",
"label": "test"
}
**Example response**:
.. sourcecode:: http
HTTP/1.1 200 OK
Vary: Accept
Content-Type: text/javascript
{
"sourceOptions": [
{
"name": "accountNumber",
"required": true,
"value": 111111111112,
"helpMessage": "Must be a valid AWS account number!",
"validation": "/^[0-9]{12,12}$/",
"type": "int"
}
],
"pluginName": "aws-source",
"id": 3,
"description": "test",
"label": "test"
}
:arg label: human readable account label
:arg description: some description about the account
:reqheader Authorization: OAuth token to authenticate
:statuscode 200: no error
"""
self.reqparse.add_argument('label', type=str, location='json', required=True)
self.reqparse.add_argument('plugin', type=dict, location='json', required=True)
self.reqparse.add_argument('description', type=str, location='json')
args = self.reqparse.parse_args()
return service.create(args['label'], args['plugin']['slug'], args['plugin']['pluginOptions'], args['description'])
class Sources(AuthenticatedResource):
def __init__(self):
self.reqparse = reqparse.RequestParser()
super(Sources, self).__init__()
@marshal_items(FIELDS)
def get(self, source_id):
"""
.. http:get:: /sources/1
Get a specific account
**Example request**:
.. sourcecode:: http
GET /sources/1 HTTP/1.1
Host: example.com
Accept: application/json, text/javascript
**Example response**:
.. sourcecode:: http
HTTP/1.1 200 OK
Vary: Accept
Content-Type: text/javascript
{
"sourceOptions": [
{
"name": "accountNumber",
"required": true,
"value": 111111111112,
"helpMessage": "Must be a valid AWS account number!",
"validation": "/^[0-9]{12,12}$/",
"type": "int"
}
],
"pluginName": "aws-source",
"id": 3,
"description": "test",
"label": "test"
}
:reqheader Authorization: OAuth token to authenticate
:statuscode 200: no error
"""
return service.get(source_id)
@admin_permission.require(http_exception=403)
@marshal_items(FIELDS)
def put(self, source_id):
"""
.. http:put:: /sources/1
Updates an account
**Example request**:
.. sourcecode:: http
POST /sources/1 HTTP/1.1
Host: example.com
Accept: application/json, text/javascript
{
"sourceOptions": [
{
"name": "accountNumber",
"required": true,
"value": 111111111112,
"helpMessage": "Must be a valid AWS account number!",
"validation": "/^[0-9]{12,12}$/",
"type": "int"
}
],
"pluginName": "aws-source",
"id": 3,
"description": "test",
"label": "test"
}
**Example response**:
.. sourcecode:: http
HTTP/1.1 200 OK
Vary: Accept
Content-Type: text/javascript
{
"sourceOptions": [
{
"name": "accountNumber",
"required": true,
"value": 111111111112,
"helpMessage": "Must be a valid AWS account number!",
"validation": "/^[0-9]{12,12}$/",
"type": "int"
}
],
"pluginName": "aws-source",
"id": 3,
"description": "test",
"label": "test"
}
:arg accountNumber: aws account number
:arg label: human readable account label
:arg description: some description about the account
:reqheader Authorization: OAuth token to authenticate
:statuscode 200: no error
"""
self.reqparse.add_argument('label', type=str, location='json', required=True)
self.reqparse.add_argument('plugin', type=dict, location='json', required=True)
self.reqparse.add_argument('description', type=str, location='json')
args = self.reqparse.parse_args()
return service.update(source_id, args['label'], args['plugin']['pluginOptions'], args['description'])
@admin_permission.require(http_exception=403)
def delete(self, source_id):
service.delete(source_id)
return {'result': True}
class CertificateSources(AuthenticatedResource):
""" Defines the 'certificate/<int:certificate_id/sources'' endpoint """
def __init__(self):
super(CertificateSources, self).__init__()
@marshal_items(FIELDS)
def get(self, certificate_id):
"""
.. http:get:: /certificates/1/sources
The current account list for a given certificates
**Example request**:
.. sourcecode:: http
GET /certificates/1/sources HTTP/1.1
Host: example.com
Accept: application/json, text/javascript
**Example response**:
.. sourcecode:: http
HTTP/1.1 200 OK
Vary: Accept
Content-Type: text/javascript
{
"items": [
{
"sourceOptions": [
{
"name": "accountNumber",
"required": true,
"value": 111111111112,
"helpMessage": "Must be a valid AWS account number!",
"validation": "/^[0-9]{12,12}$/",
"type": "int"
}
],
"pluginName": "aws-source",
"id": 3,
"description": "test",
"label": "test"
}
],
"total": 1
}
:query sortBy: field to sort on
:query sortDir: acs or desc
:query page: int. default is 1
:query filter: key value pair. format is k=v;
:query limit: limit number. default is 10
:reqheader Authorization: OAuth token to authenticate
:statuscode 200: no error
"""
parser = paginated_parser.copy()
args = parser.parse_args()
args['certificate_id'] = certificate_id
return service.render(args)
api.add_resource(SourcesList, '/sources', endpoint='sources')
api.add_resource(Sources, '/sources/<int:source_id>', endpoint='account')
api.add_resource(CertificateSources, '/certificates/<int:certificate_id>/sources',
endpoint='certificateSources')