fixing merge conflict
This commit is contained in:
commit
25f652c1eb
1
AUTHORS
1
AUTHORS
|
@ -1 +1,2 @@
|
||||||
- Kevin Glisson (kglisson@netflix.com)
|
- Kevin Glisson (kglisson@netflix.com)
|
||||||
|
- Jeremy Heffner <jheffner@netflix.com>
|
||||||
|
|
|
@ -0,0 +1,4 @@
|
||||||
|
include setup.py package.json bower.json gulpfile.js README.rst MANIFEST.in LICENSE AUTHORS
|
||||||
|
recursive-include lemur/plugins/lemur_email/templates *
|
||||||
|
recursive-include lemur/static *
|
||||||
|
global-exclude *~
|
2
Makefile
2
Makefile
|
@ -6,8 +6,6 @@ develop: update-submodules setup-git
|
||||||
npm install
|
npm install
|
||||||
pip install "setuptools>=0.9.8"
|
pip install "setuptools>=0.9.8"
|
||||||
# order matters here, base package must install first
|
# order matters here, base package must install first
|
||||||
# this is temporary until the version we need is released
|
|
||||||
pip install -e 'git+https://git@github.com/pyca/cryptography.git#egg=cryptography-1.0.dev1'
|
|
||||||
pip install -e .
|
pip install -e .
|
||||||
pip install "file://`pwd`#egg=lemur[dev]"
|
pip install "file://`pwd`#egg=lemur[dev]"
|
||||||
pip install "file://`pwd`#egg=lemur[tests]"
|
pip install "file://`pwd`#egg=lemur[tests]"
|
||||||
|
|
|
@ -17,7 +17,7 @@ from lemur.domains.views import mod as domains_bp
|
||||||
from lemur.destinations.views import mod as destinations_bp
|
from lemur.destinations.views import mod as destinations_bp
|
||||||
from lemur.authorities.views import mod as authorities_bp
|
from lemur.authorities.views import mod as authorities_bp
|
||||||
from lemur.certificates.views import mod as certificates_bp
|
from lemur.certificates.views import mod as certificates_bp
|
||||||
from lemur.status.views import mod as status_bp
|
from lemur.defaults.views import mod as defaults_bp
|
||||||
from lemur.plugins.views import mod as plugins_bp
|
from lemur.plugins.views import mod as plugins_bp
|
||||||
from lemur.notifications.views import mod as notifications_bp
|
from lemur.notifications.views import mod as notifications_bp
|
||||||
from lemur.sources.views import mod as sources_bp
|
from lemur.sources.views import mod as sources_bp
|
||||||
|
@ -31,7 +31,7 @@ LEMUR_BLUEPRINTS = (
|
||||||
destinations_bp,
|
destinations_bp,
|
||||||
authorities_bp,
|
authorities_bp,
|
||||||
certificates_bp,
|
certificates_bp,
|
||||||
status_bp,
|
defaults_bp,
|
||||||
plugins_bp,
|
plugins_bp,
|
||||||
notifications_bp,
|
notifications_bp,
|
||||||
sources_bp
|
sources_bp
|
||||||
|
|
|
@ -37,7 +37,7 @@ ViewRoleCredentialsNeed = partial(RoleUser, 'roleView')
|
||||||
|
|
||||||
class ViewRoleCredentialsPermission(Permission):
|
class ViewRoleCredentialsPermission(Permission):
|
||||||
def __init__(self, role_id):
|
def __init__(self, role_id):
|
||||||
need = ViewRoleCredentialsNeed(str(role_id))
|
need = ViewRoleCredentialsNeed(role_id)
|
||||||
super(ViewRoleCredentialsPermission, self).__init__(need, RoleNeed('admin'))
|
super(ViewRoleCredentialsPermission, self).__init__(need, RoleNeed('admin'))
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -22,7 +22,7 @@ from lemur.certificates.models import Certificate
|
||||||
from lemur.plugins.base import plugins
|
from lemur.plugins.base import plugins
|
||||||
|
|
||||||
|
|
||||||
def update(authority_id, active=None, roles=None):
|
def update(authority_id, description=None, owner=None, active=None, roles=None):
|
||||||
"""
|
"""
|
||||||
Update a an authority with new values.
|
Update a an authority with new values.
|
||||||
|
|
||||||
|
@ -37,6 +37,9 @@ def update(authority_id, active=None, roles=None):
|
||||||
|
|
||||||
if active:
|
if active:
|
||||||
authority.active = active
|
authority.active = active
|
||||||
|
|
||||||
|
authority.description = description
|
||||||
|
authority.owner = owner
|
||||||
return database.update(authority)
|
return database.update(authority)
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -20,6 +20,7 @@ from lemur.common.utils import paginated_parser, marshal_items
|
||||||
|
|
||||||
FIELDS = {
|
FIELDS = {
|
||||||
'name': fields.String,
|
'name': fields.String,
|
||||||
|
'owner': fields.String,
|
||||||
'description': fields.String,
|
'description': fields.String,
|
||||||
'options': fields.Raw,
|
'options': fields.Raw,
|
||||||
'pluginName': fields.String,
|
'pluginName': fields.String,
|
||||||
|
@ -264,7 +265,9 @@ class Authorities(AuthenticatedResource):
|
||||||
|
|
||||||
{
|
{
|
||||||
"roles": [],
|
"roles": [],
|
||||||
"active": false
|
"active": false,
|
||||||
|
"owner": "bob@example.com",
|
||||||
|
"description": "this is authority1"
|
||||||
}
|
}
|
||||||
|
|
||||||
**Example response**:
|
**Example response**:
|
||||||
|
@ -279,12 +282,12 @@ class Authorities(AuthenticatedResource):
|
||||||
"id": 1,
|
"id": 1,
|
||||||
"name": "authority1",
|
"name": "authority1",
|
||||||
"description": "this is authority1",
|
"description": "this is authority1",
|
||||||
"pluginname": null,
|
"pluginName": null,
|
||||||
"chain": "-----begin ...",
|
"chain": "-----begin ...",
|
||||||
"body": "-----begin ...",
|
"body": "-----begin ...",
|
||||||
"active": false,
|
"active": false,
|
||||||
"notbefore": "2015-06-05t17:09:39",
|
"notBefore": "2015-06-05t17:09:39",
|
||||||
"notafter": "2015-06-10t17:09:39"
|
"notAfter": "2015-06-10t17:09:39"
|
||||||
"options": null
|
"options": null
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -292,8 +295,10 @@ class Authorities(AuthenticatedResource):
|
||||||
:statuscode 200: no error
|
:statuscode 200: no error
|
||||||
:statuscode 403: unauthenticated
|
:statuscode 403: unauthenticated
|
||||||
"""
|
"""
|
||||||
self.reqparse.add_argument('roles', type=list, location='json')
|
self.reqparse.add_argument('roles', type=list, default=[], location='json')
|
||||||
self.reqparse.add_argument('active', type=str, location='json')
|
self.reqparse.add_argument('active', type=str, location='json', required=True)
|
||||||
|
self.reqparse.add_argument('owner', type=str, location='json', required=True)
|
||||||
|
self.reqparse.add_argument('description', type=str, location='json', required=True)
|
||||||
args = self.reqparse.parse_args()
|
args = self.reqparse.parse_args()
|
||||||
|
|
||||||
authority = service.get(authority_id)
|
authority = service.get(authority_id)
|
||||||
|
@ -315,7 +320,13 @@ class Authorities(AuthenticatedResource):
|
||||||
return dict(message="You are not allowed to associate a role which you are not a member of"), 400
|
return dict(message="You are not allowed to associate a role which you are not a member of"), 400
|
||||||
|
|
||||||
if permission.can():
|
if permission.can():
|
||||||
return service.update(authority_id, active=args['active'], roles=args['roles'])
|
return service.update(
|
||||||
|
authority_id,
|
||||||
|
owner=args['owner'],
|
||||||
|
description=args['description'],
|
||||||
|
active=args['active'],
|
||||||
|
roles=args['roles']
|
||||||
|
)
|
||||||
|
|
||||||
return dict(message="You are not authorized to update this authority"), 403
|
return dict(message="You are not authorized to update this authority"), 403
|
||||||
|
|
||||||
|
|
|
@ -6,14 +6,28 @@
|
||||||
.. moduleauthor:: Kevin Glisson <kglisson@netflix.com>
|
.. moduleauthor:: Kevin Glisson <kglisson@netflix.com>
|
||||||
"""
|
"""
|
||||||
import os
|
import os
|
||||||
import re
|
|
||||||
import hashlib
|
|
||||||
import requests
|
import requests
|
||||||
import subprocess
|
import subprocess
|
||||||
from OpenSSL import crypto
|
from OpenSSL import crypto
|
||||||
|
from cryptography import x509
|
||||||
|
from cryptography.hazmat.backends import default_backend
|
||||||
|
|
||||||
from flask import current_app
|
from flask import current_app
|
||||||
|
|
||||||
|
from contextlib import contextmanager
|
||||||
|
from tempfile import NamedTemporaryFile
|
||||||
|
|
||||||
|
|
||||||
|
@contextmanager
|
||||||
|
def mktempfile():
|
||||||
|
with NamedTemporaryFile(delete=False) as f:
|
||||||
|
name = f.name
|
||||||
|
|
||||||
|
try:
|
||||||
|
yield name
|
||||||
|
finally:
|
||||||
|
os.unlink(name)
|
||||||
|
|
||||||
|
|
||||||
def ocsp_verify(cert_path, issuer_chain_path):
|
def ocsp_verify(cert_path, issuer_chain_path):
|
||||||
"""
|
"""
|
||||||
|
@ -53,27 +67,18 @@ def crl_verify(cert_path):
|
||||||
:return: True if certificate is valid, False otherwise
|
:return: True if certificate is valid, False otherwise
|
||||||
:raise Exception: If certificate does not have CRL
|
:raise Exception: If certificate does not have CRL
|
||||||
"""
|
"""
|
||||||
s = "(http(s)?\://[a-zA-Z0-9\-\.]+\.[a-zA-Z]{2,3}/\S*?$)"
|
with open(cert_path, 'rt') as c:
|
||||||
regex = re.compile(s, re.MULTILINE)
|
cert = x509.load_pem_x509_certificate(c.read(), default_backend())
|
||||||
|
|
||||||
x509 = crypto.load_certificate(crypto.FILETYPE_PEM, open(cert_path, 'rt').read())
|
distribution_points = cert.extensions.get_extension_for_oid(x509.OID_CRL_DISTRIBUTION_POINTS).value
|
||||||
for x in range(x509.get_extension_count()):
|
for p in distribution_points:
|
||||||
ext = x509.get_extension(x)
|
point = p.full_name[0].value
|
||||||
if ext.get_short_name() == 'crlDistributionPoints':
|
response = requests.get(point)
|
||||||
r = regex.search(ext.get_data())
|
crl = crypto.load_crl(crypto.FILETYPE_ASN1, response.content) # TODO this should be switched to cryptography when support exists
|
||||||
points = r.groups()
|
revoked = crl.get_revoked()
|
||||||
break
|
for r in revoked:
|
||||||
else:
|
if cert.serial == r.get_serial():
|
||||||
raise Exception("Certificate does not have a CRL distribution point")
|
return
|
||||||
|
|
||||||
for point in points:
|
|
||||||
if point:
|
|
||||||
response = requests.get(point)
|
|
||||||
crl = crypto.load_crl(crypto.FILETYPE_ASN1, response.content)
|
|
||||||
revoked = crl.get_revoked()
|
|
||||||
for r in revoked:
|
|
||||||
if x509.get_serial_number() == r.get_serial():
|
|
||||||
return
|
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
|
||||||
|
@ -99,22 +104,6 @@ def verify(cert_path, issuer_chain_path):
|
||||||
raise Exception("Failed to verify")
|
raise Exception("Failed to verify")
|
||||||
|
|
||||||
|
|
||||||
def make_tmp_file(string):
|
|
||||||
"""
|
|
||||||
Creates a temporary file for a given string
|
|
||||||
|
|
||||||
:param string:
|
|
||||||
:return: Full file path to created file
|
|
||||||
"""
|
|
||||||
m = hashlib.md5()
|
|
||||||
m.update(string)
|
|
||||||
hexdigest = m.hexdigest()
|
|
||||||
path = os.path.join(os.path.dirname(os.path.abspath(__file__)), hexdigest)
|
|
||||||
with open(path, 'w') as f:
|
|
||||||
f.write(string)
|
|
||||||
return path
|
|
||||||
|
|
||||||
|
|
||||||
def verify_string(cert_string, issuer_string):
|
def verify_string(cert_string, issuer_string):
|
||||||
"""
|
"""
|
||||||
Verify a certificate given only it's string value
|
Verify a certificate given only it's string value
|
||||||
|
@ -123,13 +112,11 @@ def verify_string(cert_string, issuer_string):
|
||||||
:param issuer_string:
|
:param issuer_string:
|
||||||
:return: True if valid, False otherwise
|
:return: True if valid, False otherwise
|
||||||
"""
|
"""
|
||||||
cert_path = make_tmp_file(cert_string)
|
with mktempfile() as cert_tmp:
|
||||||
issuer_path = make_tmp_file(issuer_string)
|
with open(cert_tmp, 'w') as f:
|
||||||
status = verify(cert_path, issuer_path)
|
f.write(cert_string)
|
||||||
remove_tmp_file(cert_path)
|
with mktempfile() as issuer_tmp:
|
||||||
remove_tmp_file(issuer_path)
|
with open(issuer_tmp, 'w') as f:
|
||||||
|
f.write(issuer_string)
|
||||||
|
status = verify(cert_tmp, issuer_tmp)
|
||||||
return status
|
return status
|
||||||
|
|
||||||
|
|
||||||
def remove_tmp_file(file_path):
|
|
||||||
os.remove(file_path)
|
|
||||||
|
|
|
@ -7,7 +7,7 @@
|
||||||
"""
|
"""
|
||||||
from builtins import str
|
from builtins import str
|
||||||
|
|
||||||
from flask import Blueprint, current_app, make_response, jsonify
|
from flask import Blueprint, make_response, jsonify
|
||||||
from flask.ext.restful import reqparse, Api, fields
|
from flask.ext.restful import reqparse, Api, fields
|
||||||
|
|
||||||
from cryptography import x509
|
from cryptography import x509
|
||||||
|
@ -668,58 +668,9 @@ class NotificationCertificatesList(AuthenticatedResource):
|
||||||
return service.render(args)
|
return service.render(args)
|
||||||
|
|
||||||
|
|
||||||
class CertificatesDefaults(AuthenticatedResource):
|
|
||||||
""" Defineds the 'certificates' defaults endpoint """
|
|
||||||
def __init__(self):
|
|
||||||
super(CertificatesDefaults)
|
|
||||||
|
|
||||||
def get(self):
|
|
||||||
"""
|
|
||||||
.. http:get:: /certificates/defaults
|
|
||||||
|
|
||||||
Returns defaults needed to generate CSRs
|
|
||||||
|
|
||||||
**Example request**:
|
|
||||||
|
|
||||||
.. sourcecode:: http
|
|
||||||
|
|
||||||
GET /certificates/defaults 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
|
|
||||||
|
|
||||||
{
|
|
||||||
"country": "US",
|
|
||||||
"state": "CA",
|
|
||||||
"location": "Los Gatos",
|
|
||||||
"organization": "Netflix",
|
|
||||||
"organizationalUnit": "Operations"
|
|
||||||
}
|
|
||||||
|
|
||||||
:reqheader Authorization: OAuth token to authenticate
|
|
||||||
:statuscode 200: no error
|
|
||||||
:statuscode 403: unauthenticated
|
|
||||||
"""
|
|
||||||
return dict(
|
|
||||||
country=current_app.config.get('LEMUR_DEFAULT_COUNTRY'),
|
|
||||||
state=current_app.config.get('LEMUR_DEFAULT_STATE'),
|
|
||||||
location=current_app.config.get('LEMUR_DEFAULT_LOCATION'),
|
|
||||||
organization=current_app.config.get('LEMUR_DEFAULT_ORGANIZATION'),
|
|
||||||
organizationalUnit=current_app.config.get('LEMUR_DEFAULT_ORGANIZATIONAL_UNIT')
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
api.add_resource(CertificatesList, '/certificates', endpoint='certificates')
|
api.add_resource(CertificatesList, '/certificates', endpoint='certificates')
|
||||||
api.add_resource(Certificates, '/certificates/<int:certificate_id>', endpoint='certificate')
|
api.add_resource(Certificates, '/certificates/<int:certificate_id>', endpoint='certificate')
|
||||||
api.add_resource(CertificatesStats, '/certificates/stats', endpoint='certificateStats')
|
api.add_resource(CertificatesStats, '/certificates/stats', endpoint='certificateStats')
|
||||||
api.add_resource(CertificatesUpload, '/certificates/upload', endpoint='certificateUpload')
|
api.add_resource(CertificatesUpload, '/certificates/upload', endpoint='certificateUpload')
|
||||||
api.add_resource(CertificatePrivateKey, '/certificates/<int:certificate_id>/key', endpoint='privateKeyCertificates')
|
api.add_resource(CertificatePrivateKey, '/certificates/<int:certificate_id>/key', endpoint='privateKeyCertificates')
|
||||||
api.add_resource(NotificationCertificatesList, '/notifications/<int:notification_id>/certificates', endpoint='notificationCertificates')
|
api.add_resource(NotificationCertificatesList, '/notifications/<int:notification_id>/certificates', endpoint='notificationCertificates')
|
||||||
api.add_resource(CertificatesDefaults, '/certificates/defaults', endpoint='certificatesDefault')
|
|
||||||
|
|
|
@ -0,0 +1,63 @@
|
||||||
|
"""
|
||||||
|
.. module: lemur.status.views
|
||||||
|
:copyright: (c) 2015 by Netflix Inc., see AUTHORS for more
|
||||||
|
:license: Apache, see LICENSE for more details.
|
||||||
|
"""
|
||||||
|
from flask import current_app, Blueprint
|
||||||
|
from flask.ext.restful import Api
|
||||||
|
|
||||||
|
from lemur.auth.service import AuthenticatedResource
|
||||||
|
|
||||||
|
|
||||||
|
mod = Blueprint('default', __name__)
|
||||||
|
api = Api(mod)
|
||||||
|
|
||||||
|
|
||||||
|
class LemurDefaults(AuthenticatedResource):
|
||||||
|
""" Defines the 'defaults' endpoint """
|
||||||
|
def __init__(self):
|
||||||
|
super(LemurDefaults)
|
||||||
|
|
||||||
|
def get(self):
|
||||||
|
"""
|
||||||
|
.. http:get:: /defaults
|
||||||
|
|
||||||
|
Returns defaults needed to generate CSRs
|
||||||
|
|
||||||
|
**Example request**:
|
||||||
|
|
||||||
|
.. sourcecode:: http
|
||||||
|
|
||||||
|
GET /defaults 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
|
||||||
|
|
||||||
|
{
|
||||||
|
"country": "US",
|
||||||
|
"state": "CA",
|
||||||
|
"location": "Los Gatos",
|
||||||
|
"organization": "Netflix",
|
||||||
|
"organizationalUnit": "Operations"
|
||||||
|
}
|
||||||
|
|
||||||
|
:reqheader Authorization: OAuth token to authenticate
|
||||||
|
:statuscode 200: no error
|
||||||
|
:statuscode 403: unauthenticated
|
||||||
|
"""
|
||||||
|
return dict(
|
||||||
|
country=current_app.config.get('LEMUR_DEFAULT_COUNTRY'),
|
||||||
|
state=current_app.config.get('LEMUR_DEFAULT_STATE'),
|
||||||
|
location=current_app.config.get('LEMUR_DEFAULT_LOCATION'),
|
||||||
|
organization=current_app.config.get('LEMUR_DEFAULT_ORGANIZATION'),
|
||||||
|
organizationalUnit=current_app.config.get('LEMUR_DEFAULT_ORGANIZATIONAL_UNIT')
|
||||||
|
)
|
||||||
|
|
||||||
|
api.add_resource(LemurDefaults, '/defaults', endpoint='default')
|
|
@ -4,6 +4,8 @@ import os
|
||||||
import sys
|
import sys
|
||||||
import base64
|
import base64
|
||||||
import time
|
import time
|
||||||
|
import requests
|
||||||
|
import json
|
||||||
from gunicorn.config import make_settings
|
from gunicorn.config import make_settings
|
||||||
|
|
||||||
from cryptography.fernet import Fernet
|
from cryptography.fernet import Fernet
|
||||||
|
@ -146,12 +148,15 @@ def check_revoked():
|
||||||
as `unknown`.
|
as `unknown`.
|
||||||
"""
|
"""
|
||||||
for cert in cert_service.get_all_certs():
|
for cert in cert_service.get_all_certs():
|
||||||
if cert.chain:
|
try:
|
||||||
status = verify_string(cert.body, cert.chain)
|
if cert.chain:
|
||||||
else:
|
status = verify_string(cert.body, cert.chain)
|
||||||
status = verify_string(cert.body, "")
|
else:
|
||||||
|
status = verify_string(cert.body, "")
|
||||||
|
|
||||||
cert.status = 'valid' if status else "invalid"
|
cert.status = 'valid' if status else 'invalid'
|
||||||
|
except Exception as e:
|
||||||
|
cert.status = 'unknown'
|
||||||
database.update(cert)
|
database.update(cert)
|
||||||
|
|
||||||
|
|
||||||
|
@ -181,7 +186,7 @@ def generate_settings():
|
||||||
return output
|
return output
|
||||||
|
|
||||||
|
|
||||||
@manager.option('-s', '--sources', dest='labels', default='', required=False)
|
@manager.option('-s', '--sources', dest='labels')
|
||||||
def sync_sources(labels):
|
def sync_sources(labels):
|
||||||
"""
|
"""
|
||||||
Attempts to run several methods Certificate discovery. This is
|
Attempts to run several methods Certificate discovery. This is
|
||||||
|
@ -207,13 +212,14 @@ def sync_sources(labels):
|
||||||
try:
|
try:
|
||||||
sync_lock.acquire(timeout=10) # wait up to 10 seconds
|
sync_lock.acquire(timeout=10) # wait up to 10 seconds
|
||||||
|
|
||||||
if labels:
|
sys.stdout.write("[+] Staring to sync sources: {labels}!\n".format(labels=labels))
|
||||||
sys.stdout.write("[+] Staring to sync sources: {labels}!\n".format(labels=labels))
|
labels = labels.split(",")
|
||||||
labels = labels.split(",")
|
|
||||||
else:
|
if labels[0] == 'all':
|
||||||
sys.stdout.write("[+] Starting to sync ALL sources!\n")
|
sync()
|
||||||
|
else:
|
||||||
|
sync(labels=labels)
|
||||||
|
|
||||||
sync(labels=labels)
|
|
||||||
sys.stdout.write(
|
sys.stdout.write(
|
||||||
"[+] Finished syncing sources. Run Time: {time}\n".format(
|
"[+] Finished syncing sources. Run Time: {time}\n".format(
|
||||||
time=(time.time() - start_time)
|
time=(time.time() - start_time)
|
||||||
|
@ -563,11 +569,11 @@ class ProvisionELB(Command):
|
||||||
'authority': authority,
|
'authority': authority,
|
||||||
'owner': owner,
|
'owner': owner,
|
||||||
# defaults:
|
# defaults:
|
||||||
'organization': u'Netflix, Inc.',
|
'organization': current_app.config.get('LEMUR_DEFAULT_ORGANIZATION'),
|
||||||
'organizationalUnit': u'Operations',
|
'organizationalUnit': current_app.config.get('LEMUR_DEFAULT_ORGANIZATIONAL_UNIT'),
|
||||||
'country': u'US',
|
'country': current_app.config.get('LEMUR_DEFAULT_COUNTRY'),
|
||||||
'state': u'California',
|
'state': current_app.config.get('LEMUR_DEFAULT_STATE'),
|
||||||
'location': u'Los Gatos'
|
'location': current_app.config.get('LEMUR_DEFAULT_LOCATION')
|
||||||
}
|
}
|
||||||
|
|
||||||
return options
|
return options
|
||||||
|
@ -680,6 +686,38 @@ class ProvisionELB(Command):
|
||||||
done = True
|
done = True
|
||||||
|
|
||||||
|
|
||||||
|
@manager.command
|
||||||
|
def publish_verisign_units():
|
||||||
|
"""
|
||||||
|
Simple function that queries verisign for API units and posts the mertics to
|
||||||
|
Atlas API for other teams to consume.
|
||||||
|
:return:
|
||||||
|
"""
|
||||||
|
from lemur.plugins import plugins
|
||||||
|
v = plugins.get('verisign-issuer')
|
||||||
|
units = v.get_available_units()
|
||||||
|
|
||||||
|
metrics = {}
|
||||||
|
for item in units:
|
||||||
|
if item['@type'] in metrics.keys():
|
||||||
|
metrics[item['@type']] += int(item['@remaining'])
|
||||||
|
else:
|
||||||
|
metrics.update({item['@type']: int(item['@remaining'])})
|
||||||
|
|
||||||
|
for name, value in metrics.items():
|
||||||
|
metric = [
|
||||||
|
{
|
||||||
|
"timestamp": 1321351651,
|
||||||
|
"type": "GAUGE",
|
||||||
|
"name": "Symantec {0} Unit Count".format(name),
|
||||||
|
"tags": {},
|
||||||
|
"value": value
|
||||||
|
}
|
||||||
|
]
|
||||||
|
|
||||||
|
requests.post('http://localhost:8078/metrics', data=json.dumps(metric))
|
||||||
|
|
||||||
|
|
||||||
def main():
|
def main():
|
||||||
manager.add_command("start", LemurServer())
|
manager.add_command("start", LemurServer())
|
||||||
manager.add_command("runserver", Server(host='127.0.0.1'))
|
manager.add_command("runserver", Server(host='127.0.0.1'))
|
||||||
|
|
|
@ -9,7 +9,6 @@
|
||||||
|
|
||||||
"""
|
"""
|
||||||
import ssl
|
import ssl
|
||||||
import socket
|
|
||||||
|
|
||||||
import arrow
|
import arrow
|
||||||
|
|
||||||
|
@ -114,8 +113,9 @@ def _get_domain_certificate(name):
|
||||||
try:
|
try:
|
||||||
pub_key = ssl.get_server_certificate((name, 443))
|
pub_key = ssl.get_server_certificate((name, 443))
|
||||||
return cert_service.find_duplicates(pub_key.strip())
|
return cert_service.find_duplicates(pub_key.strip())
|
||||||
except socket.gaierror as e:
|
except Exception as e:
|
||||||
current_app.logger.info(str(e))
|
current_app.logger.info(str(e))
|
||||||
|
return []
|
||||||
|
|
||||||
|
|
||||||
def _find_superseded(cert):
|
def _find_superseded(cert):
|
||||||
|
|
|
@ -53,7 +53,7 @@
|
||||||
<hr />
|
<hr />
|
||||||
</div>
|
</div>
|
||||||
<p>
|
<p>
|
||||||
Lemur, Netflix's SSL management portal has noticed that the following certificates are expiring soon, if you rely on these certificates
|
Lemur, 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.
|
you should create new certificates to replace the certificates that are expiring.
|
||||||
</p>
|
</p>
|
||||||
<p>
|
<p>
|
||||||
|
|
|
@ -146,7 +146,7 @@ class VerisignIssuerPlugin(IssuerPlugin):
|
||||||
:param issuer_options:
|
:param issuer_options:
|
||||||
:return: :raise Exception:
|
:return: :raise Exception:
|
||||||
"""
|
"""
|
||||||
url = current_app.config.get('VERISIGN_URL') + 'rest/services/enroll'
|
url = current_app.config.get("VERISIGN_URL") + '/rest/services/enroll'
|
||||||
|
|
||||||
data = process_options(issuer_options)
|
data = process_options(issuer_options)
|
||||||
data['csr'] = csr
|
data['csr'] = csr
|
||||||
|
@ -176,7 +176,7 @@ class VerisignIssuerPlugin(IssuerPlugin):
|
||||||
|
|
||||||
:return:
|
:return:
|
||||||
"""
|
"""
|
||||||
url = current_app.config.get("VERISIGN_URL") + 'rest/services/getTokens'
|
url = current_app.config.get("VERISIGN_URL") + '/rest/services/getTokens'
|
||||||
response = self.session.post(url, headers={'content-type': 'application/x-www-form-urlencoded'})
|
response = self.session.post(url, headers={'content-type': 'application/x-www-form-urlencoded'})
|
||||||
return handle_response(response.content)['Response']['Order']
|
return handle_response(response.content)['Response']['Order']
|
||||||
|
|
||||||
|
|
|
@ -60,6 +60,15 @@ lemur.controller('datePickerController', function ($scope, $timeout){
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
|
lemur.service('DefaultService', function (LemurRestangular) {
|
||||||
|
var DefaultService = this;
|
||||||
|
DefaultService.get = function () {
|
||||||
|
return LemurRestangular.all('defaults').customGET().then(function (defaults) {
|
||||||
|
return defaults;
|
||||||
|
});
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
lemur.factory('LemurRestangular', function (Restangular, $location, $auth) {
|
lemur.factory('LemurRestangular', function (Restangular, $location, $auth) {
|
||||||
return Restangular.withConfig(function (RestangularConfigurer) {
|
return Restangular.withConfig(function (RestangularConfigurer) {
|
||||||
RestangularConfigurer.setBaseUrl('http://localhost:5000/api/1');
|
RestangularConfigurer.setBaseUrl('http://localhost:5000/api/1');
|
||||||
|
|
|
@ -30,6 +30,9 @@ angular.module('lemur')
|
||||||
.controller('AuthorityCreateController', function ($scope, $modalInstance, AuthorityService, LemurRestangular, RoleService, PluginService, WizardHandler) {
|
.controller('AuthorityCreateController', function ($scope, $modalInstance, AuthorityService, LemurRestangular, RoleService, PluginService, WizardHandler) {
|
||||||
$scope.authority = LemurRestangular.restangularizeElement(null, {}, 'authorities');
|
$scope.authority = LemurRestangular.restangularizeElement(null, {}, 'authorities');
|
||||||
|
|
||||||
|
// set the defaults
|
||||||
|
AuthorityService.getDefaults($scope.authority);
|
||||||
|
|
||||||
$scope.loading = false;
|
$scope.loading = false;
|
||||||
$scope.create = function (authority) {
|
$scope.create = function (authority) {
|
||||||
WizardHandler.wizard().context.loading = true;
|
WizardHandler.wizard().context.loading = true;
|
||||||
|
|
|
@ -1,9 +1,32 @@
|
||||||
<div class="modal-header">
|
<div class="modal-header">
|
||||||
<div class="modal-title">
|
<div class="modal-title">
|
||||||
<div class="modal-header">Edit Authority <span class="text-muted"><small>chain of command!</small></span></div>
|
<h3 class="modal-header">Edit <span class="text-muted"><small>{{ authority.name }}</small></span></h3>
|
||||||
</div>
|
</div>
|
||||||
<div class="modal-body">
|
<div class="modal-body">
|
||||||
<form name="createForm" class="form-horizontal" role="form" novalidate>
|
<form name="createForm" class="form-horizontal" role="form" novalidate>
|
||||||
|
<div class="form-group"
|
||||||
|
ng-class="{'has-error': editForm.owner.$invalid, 'has-success': !editForm.owner.$invalid&&editForm.owner.$dirty}">
|
||||||
|
<label class="control-label col-sm-2">
|
||||||
|
Owner
|
||||||
|
</label>
|
||||||
|
<div class="col-sm-10">
|
||||||
|
<input type="email" name="owner" ng-model="authority.owner" placeholder="owner@example.com"
|
||||||
|
class="form-control" required/>
|
||||||
|
|
||||||
|
<p ng-show="editForm.owner.$invalid && !editForm.owner.$pristine" class="help-block">Enter a valid
|
||||||
|
email.</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="form-group"
|
||||||
|
ng-class="{'has-error': editForm.description.$invalid, 'has-success': !editForm.$invalid&&editForm.description.$dirty}">
|
||||||
|
<label class="control-label col-sm-2">
|
||||||
|
Description
|
||||||
|
</label>
|
||||||
|
<div class="col-sm-10">
|
||||||
|
<textarea name="description" ng-model="authority.description" placeholder="Something elegant" class="form-control" required></textarea>
|
||||||
|
<p ng-show="editForm.description.$invalid && !editForm.description.$pristine" class="help-block">You must give a short description about this authority will be used for, this description should only include alphanumeric characters</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label class="control-label col-sm-2">
|
<label class="control-label col-sm-2">
|
||||||
Roles
|
Roles
|
|
@ -16,7 +16,7 @@
|
||||||
Owner
|
Owner
|
||||||
</label>
|
</label>
|
||||||
<div class="col-sm-10">
|
<div class="col-sm-10">
|
||||||
<input type="email" name="ownerEmail" ng-model="authority.ownerEmail" placeholder="TeamDL@netflix.com" tooltip="This is the authorities team distribution list or the main point of contact for this authority" class="form-control" required/>
|
<input type="email" name="ownerEmail" ng-model="authority.ownerEmail" placeholder="TeamDL@example.com" tooltip="This is the authorities team distribution list or the main point of contact for this authority" class="form-control" required/>
|
||||||
<p ng-show="trackingForm.ownerEmail.$invalid && !trackingForm.ownerEmail.$pristine" class="help-block">You must enter an Certificate Authority owner</p>
|
<p ng-show="trackingForm.ownerEmail.$invalid && !trackingForm.ownerEmail.$pristine" class="help-block">You must enter an Certificate Authority owner</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -56,7 +56,7 @@ angular.module('lemur')
|
||||||
});
|
});
|
||||||
return LemurRestangular.all('authorities');
|
return LemurRestangular.all('authorities');
|
||||||
})
|
})
|
||||||
.service('AuthorityService', function ($location, AuthorityApi, toaster) {
|
.service('AuthorityService', function ($location, AuthorityApi, DefaultService, toaster) {
|
||||||
var AuthorityService = this;
|
var AuthorityService = this;
|
||||||
AuthorityService.findAuthorityByName = function (filterValue) {
|
AuthorityService.findAuthorityByName = function (filterValue) {
|
||||||
return AuthorityApi.getList({'filter[name]': filterValue})
|
return AuthorityApi.getList({'filter[name]': filterValue})
|
||||||
|
@ -117,6 +117,16 @@ angular.module('lemur')
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
AuthorityService.getDefaults = function (authority) {
|
||||||
|
return DefaultService.get().then(function (defaults) {
|
||||||
|
authority.caDN.country = defaults.country;
|
||||||
|
authority.caDN.state = defaults.state;
|
||||||
|
authority.caDN.location = defaults.location;
|
||||||
|
authority.caDN.organization = defaults.organization;
|
||||||
|
authority.caDN.organizationalUnit = defaults.organizationalUnit;
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
AuthorityService.getRoles = function (authority) {
|
AuthorityService.getRoles = function (authority) {
|
||||||
return authority.getList('roles').then(function (roles) {
|
return authority.getList('roles').then(function (roles) {
|
||||||
authority.roles = roles;
|
authority.roles = roles;
|
||||||
|
|
|
@ -46,7 +46,7 @@ angular.module('lemur')
|
||||||
$scope.edit = function (authorityId) {
|
$scope.edit = function (authorityId) {
|
||||||
var modalInstance = $modal.open({
|
var modalInstance = $modal.open({
|
||||||
animation: true,
|
animation: true,
|
||||||
templateUrl: '/angular/authorities/authority/authorityEdit.tpl.html',
|
templateUrl: '/angular/authorities/authority/edit.tpl.html',
|
||||||
controller: 'AuthorityEditController',
|
controller: 'AuthorityEditController',
|
||||||
size: 'lg',
|
size: 'lg',
|
||||||
resolve: {
|
resolve: {
|
||||||
|
@ -62,6 +62,25 @@ angular.module('lemur')
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
$scope.editRole = function (roleId) {
|
||||||
|
var modalInstance = $modal.open({
|
||||||
|
animation: true,
|
||||||
|
templateUrl: '/angular/roles/role/role.tpl.html',
|
||||||
|
controller: 'RolesEditController',
|
||||||
|
size: 'lg',
|
||||||
|
resolve: {
|
||||||
|
editId: function () {
|
||||||
|
return roleId;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
modalInstance.result.then(function () {
|
||||||
|
$scope.authoritiesTable.reload();
|
||||||
|
});
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
$scope.create = function () {
|
$scope.create = function () {
|
||||||
var modalInstance = $modal.open({
|
var modalInstance = $modal.open({
|
||||||
animation: true,
|
animation: true,
|
||||||
|
|
|
@ -29,7 +29,7 @@
|
||||||
</td>
|
</td>
|
||||||
<td data-title="'Roles'"> <!--filter="{ 'select': 'role' }" filter-data="roleService.getRoleDropDown()">-->
|
<td data-title="'Roles'"> <!--filter="{ 'select': 'role' }" filter-data="roleService.getRoleDropDown()">-->
|
||||||
<div class="btn-group">
|
<div class="btn-group">
|
||||||
<a href="#/roles/{{ role.id }}/edit" ng-repeat="role in authority.roles" class="btn btn-sm btn-danger">
|
<a ng-click="editRole(role.id)" ng-repeat="role in authority.roles" class="btn btn-sm btn-danger">
|
||||||
{{ role.name }}
|
{{ role.name }}
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -4,7 +4,7 @@
|
||||||
</label>
|
</label>
|
||||||
<div class="col-sm-10">
|
<div class="col-sm-10">
|
||||||
<div class="input-group">
|
<div class="input-group">
|
||||||
<input type="text" ng-model="certificate.selectedDestination" placeholder="AWS, TheSecret..."
|
<input type="text" ng-model="certificate.selectedDestination" placeholder="AWS..."
|
||||||
typeahead="destination.label for destination in destinationService.findDestinationsByName($viewValue)" typeahead-loading="loadingDestinations"
|
typeahead="destination.label for destination in destinationService.findDestinationsByName($viewValue)" typeahead-loading="loadingDestinations"
|
||||||
class="form-control input-md" typeahead-on-select="certificate.attachDestination($item)" typeahead-min-wait="50"
|
class="form-control input-md" typeahead-on-select="certificate.attachDestination($item)" typeahead-min-wait="50"
|
||||||
tooltip="Lemur can upload certificates to any pre-defined destination"
|
tooltip="Lemur can upload certificates to any pre-defined destination"
|
||||||
|
|
|
@ -6,7 +6,7 @@
|
||||||
Owner
|
Owner
|
||||||
</label>
|
</label>
|
||||||
<div class="col-sm-10">
|
<div class="col-sm-10">
|
||||||
<input type="email" name="ownerEmail" ng-model="certificate.owner" placeholder="TeamDL@netflix.com" tooltip="This is the certificates team distribution list or main point of contact" class="form-control" required/>
|
<input type="email" name="ownerEmail" ng-model="certificate.owner" placeholder="TeamDL@example.com" tooltip="This is the certificates team distribution list or main point of contact" class="form-control" required/>
|
||||||
<p ng-show="trackingForm.ownerEmail.$invalid && !trackingForm.ownerEmail.$pristine" class="help-block">You must enter an Certificate owner</p>
|
<p ng-show="trackingForm.ownerEmail.$invalid && !trackingForm.ownerEmail.$pristine" class="help-block">You must enter an Certificate owner</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -11,7 +11,7 @@
|
||||||
</label>
|
</label>
|
||||||
|
|
||||||
<div class="col-sm-10">
|
<div class="col-sm-10">
|
||||||
<input type="email" name="owner" ng-model="certificate.owner" placeholder="owner@netflix.com"
|
<input type="email" name="owner" ng-model="certificate.owner" placeholder="owner@example.com"
|
||||||
class="form-control" required/>
|
class="form-control" required/>
|
||||||
|
|
||||||
<p ng-show="uploadForm.owner.$invalid && !uploadForm.owner.$pristine" class="help-block">Enter a valid
|
<p ng-show="uploadForm.owner.$invalid && !uploadForm.owner.$pristine" class="help-block">Enter a valid
|
||||||
|
@ -24,7 +24,7 @@
|
||||||
Custom Name <span class="glyphicon glyphicon-question-sign"></span>
|
Custom Name <span class="glyphicon glyphicon-question-sign"></span>
|
||||||
</label>
|
</label>
|
||||||
<div class="col-sm-10">
|
<div class="col-sm-10">
|
||||||
<input name="name" ng-model="certificate.name" placeholder="example.netflix.net-SymantecCorporation-20150828-20160830" class="form-control"/>
|
<input name="name" ng-model="certificate.name" placeholder="the.example.net-SymantecCorporation-20150828-20160830" class="form-control"/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="form-group"
|
<div class="form-group"
|
||||||
|
|
|
@ -89,7 +89,7 @@ angular.module('lemur')
|
||||||
});
|
});
|
||||||
return LemurRestangular.all('certificates');
|
return LemurRestangular.all('certificates');
|
||||||
})
|
})
|
||||||
.service('CertificateService', function ($location, CertificateApi, LemurRestangular, toaster) {
|
.service('CertificateService', function ($location, CertificateApi, LemurRestangular, DefaultService, toaster) {
|
||||||
var CertificateService = this;
|
var CertificateService = this;
|
||||||
CertificateService.findCertificatesByName = function (filterValue) {
|
CertificateService.findCertificatesByName = function (filterValue) {
|
||||||
return CertificateApi.getList({'filter[name]': filterValue})
|
return CertificateApi.getList({'filter[name]': filterValue})
|
||||||
|
@ -207,7 +207,7 @@ angular.module('lemur')
|
||||||
};
|
};
|
||||||
|
|
||||||
CertificateService.getDefaults = function (certificate) {
|
CertificateService.getDefaults = function (certificate) {
|
||||||
return certificate.customGET('defaults').then(function (defaults) {
|
return DefaultService.get().then(function (defaults) {
|
||||||
certificate.country = defaults.country;
|
certificate.country = defaults.country;
|
||||||
certificate.state = defaults.state;
|
certificate.state = defaults.state;
|
||||||
certificate.location = defaults.location;
|
certificate.location = defaults.location;
|
||||||
|
|
|
@ -55,7 +55,6 @@ angular.module('lemur')
|
||||||
title: role.name,
|
title: role.name,
|
||||||
body: 'Has been successfully created!'
|
body: 'Has been successfully created!'
|
||||||
});
|
});
|
||||||
$location.path('roles');
|
|
||||||
},
|
},
|
||||||
function (response) {
|
function (response) {
|
||||||
toaster.pop({
|
toaster.pop({
|
||||||
|
@ -74,7 +73,6 @@ angular.module('lemur')
|
||||||
title: role.name,
|
title: role.name,
|
||||||
body: 'Successfully updated!'
|
body: 'Successfully updated!'
|
||||||
});
|
});
|
||||||
$location.path('roles');
|
|
||||||
},
|
},
|
||||||
function (response) {
|
function (response) {
|
||||||
toaster.pop({
|
toaster.pop({
|
||||||
|
|
|
@ -8,12 +8,5 @@
|
||||||
<div class="row featurette">
|
<div class="row featurette">
|
||||||
<div class="col-md-10">
|
<div class="col-md-10">
|
||||||
<h2 class="featurette-heading">SSL In The Cloud <span class="text-muted">Encrypt it all </span></h2>
|
<h2 class="featurette-heading">SSL In The Cloud <span class="text-muted">Encrypt it all </span></h2>
|
||||||
|
|
||||||
<p class="lead">The Security Operations team manages all of the SSL certificate generation at Netflix. This
|
|
||||||
portal was created to serve as both a self service application so that application owners can provision
|
|
||||||
their own certificates and to help enforce some key naming and security conventions, in order provide
|
|
||||||
Netflix with scalable and manageable SSL security.</p>
|
|
||||||
|
|
||||||
<p>See <a href="http://go/ssl">go/ssl</a> for more info.</p>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -1,33 +0,0 @@
|
||||||
"""
|
|
||||||
.. module: lemur.status.views
|
|
||||||
:copyright: (c) 2015 by Netflix Inc., see AUTHORS for more
|
|
||||||
:license: Apache, see LICENSE for more details.
|
|
||||||
"""
|
|
||||||
import os
|
|
||||||
|
|
||||||
from flask import app, Blueprint, jsonify
|
|
||||||
from flask.ext.restful import Api
|
|
||||||
|
|
||||||
from lemur.auth.service import AuthenticatedResource
|
|
||||||
|
|
||||||
|
|
||||||
mod = Blueprint('status', __name__)
|
|
||||||
api = Api(mod)
|
|
||||||
|
|
||||||
|
|
||||||
class Status(AuthenticatedResource):
|
|
||||||
""" Defines the 'accounts' endpoint """
|
|
||||||
def __init__(self):
|
|
||||||
super(Status, self).__init__()
|
|
||||||
|
|
||||||
def get(self):
|
|
||||||
if not os.path.isdir(os.path.join(app.config.get("KEY_PATH"), "decrypted")):
|
|
||||||
return jsonify({
|
|
||||||
'environment': app.config.get('ENVIRONMENT'),
|
|
||||||
'status': 'degraded',
|
|
||||||
'message': "This Lemur instance is in a degraded state and is unable to issue certificates, please alert secops@netflix.com"})
|
|
||||||
else:
|
|
||||||
return jsonify({
|
|
||||||
'environment': app.config.get('ENVIRONMENT'),
|
|
||||||
'status': 'healthy',
|
|
||||||
'message': "This Lemur instance is healthy"})
|
|
9
setup.py
9
setup.py
|
@ -16,7 +16,7 @@ from distutils.core import Command
|
||||||
from setuptools.command.develop import develop
|
from setuptools.command.develop import develop
|
||||||
from setuptools.command.install import install
|
from setuptools.command.install import install
|
||||||
from setuptools.command.sdist import sdist
|
from setuptools.command.sdist import sdist
|
||||||
from setuptools import setup
|
from setuptools import setup, find_packages
|
||||||
from subprocess import check_output
|
from subprocess import check_output
|
||||||
|
|
||||||
ROOT = os.path.realpath(os.path.join(os.path.dirname(__file__)))
|
ROOT = os.path.realpath(os.path.join(os.path.dirname(__file__)))
|
||||||
|
@ -39,7 +39,7 @@ install_requires = [
|
||||||
'six==1.9.0',
|
'six==1.9.0',
|
||||||
'gunicorn==19.3.0',
|
'gunicorn==19.3.0',
|
||||||
'pycrypto==2.6.1',
|
'pycrypto==2.6.1',
|
||||||
'cryptography>=1.0dev',
|
'cryptography==1.0.1',
|
||||||
'pyopenssl==0.15.1',
|
'pyopenssl==0.15.1',
|
||||||
'pyjwt==1.0.1',
|
'pyjwt==1.0.1',
|
||||||
'xmltodict==0.9.2',
|
'xmltodict==0.9.2',
|
||||||
|
@ -110,11 +110,11 @@ class BuildStatic(Command):
|
||||||
|
|
||||||
setup(
|
setup(
|
||||||
name='lemur',
|
name='lemur',
|
||||||
version='0.1',
|
version='0.1.3',
|
||||||
author='Kevin Glisson',
|
author='Kevin Glisson',
|
||||||
author_email='kglisson@netflix.com',
|
author_email='kglisson@netflix.com',
|
||||||
long_description=open(os.path.join(ROOT, 'README.rst')).read(),
|
long_description=open(os.path.join(ROOT, 'README.rst')).read(),
|
||||||
packages=['lemur'],
|
packages=find_packages(),
|
||||||
include_package_data=True,
|
include_package_data=True,
|
||||||
zip_safe=False,
|
zip_safe=False,
|
||||||
install_requires=install_requires,
|
install_requires=install_requires,
|
||||||
|
@ -127,7 +127,6 @@ setup(
|
||||||
'build_static': BuildStatic,
|
'build_static': BuildStatic,
|
||||||
'sdist': SdistWithBuildStatic,
|
'sdist': SdistWithBuildStatic,
|
||||||
'install': SmartInstall
|
'install': SmartInstall
|
||||||
|
|
||||||
},
|
},
|
||||||
entry_points={
|
entry_points={
|
||||||
'console_scripts': [
|
'console_scripts': [
|
||||||
|
|
Loading…
Reference in New Issue