Compare commits
36 Commits
Author | SHA1 | Date | |
---|---|---|---|
b8ae8cd452 | |||
ca82b227b9 | |||
862496495f | |||
8bb9a8c5d1 | |||
00cb66484b | |||
cabe2ae18d | |||
665a3f3180 | |||
3b5d7eaab6 | |||
aa2358aa03 | |||
a7decc1948 | |||
38b48604f3 | |||
60856cb7b9 | |||
350d013043 | |||
70c92fea15 | |||
6211b126a9 | |||
54c3fcc72a | |||
27c9088ddb | |||
b8c2d42cad | |||
1f5ddd9530 | |||
2896ce0dad | |||
29bcde145c | |||
11db429bcc | |||
75aea9f885 | |||
c80559005f | |||
9b927cfcc2 | |||
4db7931aa0 | |||
1e67329c64 | |||
6d17e4d538 | |||
350f58ec9d | |||
de9478a992 | |||
70a2c985cf | |||
78037dc9ec | |||
9b11efd1e5 | |||
3c2ee8fbb3 | |||
163cc3f795 | |||
041382b02f |
@ -1,13 +1,23 @@
|
||||
Changelog
|
||||
=========
|
||||
|
||||
|
||||
0.2.1 - `master` _
|
||||
0.2.2 - `master` _
|
||||
~~~~~~~~~~~~~~~~~~
|
||||
|
||||
.. note:: This version not yet released and is under active development
|
||||
|
||||
|
||||
0.2.1 - 2015-12-14
|
||||
~~~~~~~~~~~~~~~~~~
|
||||
|
||||
* Fixed bug with search not refreshing values
|
||||
* Cleaned up documentation, including working supervisor example (thanks rpicard!)
|
||||
* Closed #165 - Fixed an issue with email templates
|
||||
* Closed #188 - Added ability to submit third party CSR
|
||||
* Closed #176 - Java-export should allow user to specify truststore/keystore
|
||||
* Closed #176 - Extended support for exporting certificate in P12 format
|
||||
|
||||
|
||||
0.2.0 - 2015-12-02
|
||||
~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
|
1
OSSMETADATA
Normal file
1
OSSMETADATA
Normal file
@ -0,0 +1 @@
|
||||
osslifecycle=active
|
@ -262,11 +262,18 @@ for those plugins.
|
||||
|
||||
Authentication
|
||||
--------------
|
||||
Lemur currently supports Basic Authentication and Ping OAuth2 out of the box. Additional flows can be added relatively easily.
|
||||
If you are not using Ping you do not need to configure any of these options.
|
||||
Lemur currently supports Basic Authentication, Ping OAuth2, and Google out of the box. Additional flows can be added relatively easily.
|
||||
If you are not using an authentication provider you do not need to configure any of these options.
|
||||
|
||||
For more information about how to use social logins, see: `Satellizer <https://github.com/sahat/satellizer>`_
|
||||
|
||||
.. data:: ACTIVE_PROVIDERS
|
||||
:noindex:
|
||||
|
||||
::
|
||||
|
||||
ACTIVE_PROVIDERS = ["ping", "google"]
|
||||
|
||||
.. data:: PING_SECRET
|
||||
:noindex:
|
||||
|
||||
@ -296,6 +303,33 @@ For more information about how to use social logins, see: `Satellizer <https://g
|
||||
|
||||
PING_JWKS_URL = "https://<yourpingserver>/pf/JWKS"
|
||||
|
||||
.. data:: PING_NAME
|
||||
:noindex:
|
||||
|
||||
::
|
||||
|
||||
PING_NAME = "Example Oauth2 Provider"
|
||||
|
||||
.. data:: PING_CLIENT_ID
|
||||
:noindex:
|
||||
|
||||
::
|
||||
|
||||
PING_CLIENT_ID = "client-id"
|
||||
|
||||
.. data:: GOOGLE_CLIENT_ID
|
||||
:noindex:
|
||||
|
||||
::
|
||||
|
||||
GOOGLE_CLIENT_ID = "client-id"
|
||||
|
||||
.. data:: GOOGLE_SECRET
|
||||
:noindex:
|
||||
|
||||
::
|
||||
|
||||
GOOGLE_SECRET = "somethingsecret"
|
||||
|
||||
|
||||
AWS Plugin Configuration
|
||||
@ -493,6 +527,11 @@ version of Lemur from pypi and then apply any schema changes with the following
|
||||
|
||||
.. note:: Internally, this uses `Alembic <https://alembic.readthedocs.org/en/latest/>`_ to manage database migrations.
|
||||
|
||||
.. note:: By default Alembic looks for the `migrations` folder in the current working directory.
|
||||
The migrations folder is located under `<LEMUR_HOME>/lemur/migrations` if you are running the lemur command from any
|
||||
location besides `<LEMUR_HOME>/lemur` you will need to pass the `-d` flag to specify the absolute file path to the
|
||||
`migrations` folder.
|
||||
|
||||
.. _CommandLineInterface:
|
||||
|
||||
Command Line Interface
|
||||
|
@ -215,7 +215,7 @@ certificate Lemur does not know about and adding the certificate to it's invento
|
||||
The `SourcePlugin` object has one default option of `pollRate`. This controls the number of seconds which to get new certificates.
|
||||
|
||||
.. warning::
|
||||
Lemur currently has a very basic polling system of running a cron job every 15min to see which source plugins need to be run. A lock file is generated to guarantee that
|
||||
Lemur currently has a very basic polling system of running a cron job every 15min to see which source plugins need to be run. A lock file is generated to guarantee that
|
||||
only one sync is running at a time. It also means that the minimum resolution of a source plugin poll rate is effectively 15min. You can always specify a faster cron
|
||||
job if you need a higher resolution sync job.
|
||||
|
||||
@ -227,7 +227,29 @@ The `SourcePlugin` object requires implementation of one function::
|
||||
|
||||
|
||||
.. Note::
|
||||
Often times to facilitate code re-use it makes sense put source and destination plugins into one package.
|
||||
Often times to facilitate code re-use it makes sense put source and destination plugins into one package.
|
||||
|
||||
|
||||
Export
|
||||
------
|
||||
|
||||
Formats, formats and more formats. That's the current PKI landscape. See the always relevant `xkcd <https://xkcd.com/927/>`_.
|
||||
Thankfully Lemur supports the ability to output your certificates into whatever format you want. This integration comes by the way
|
||||
of Export plugins. Support is still new and evolving, the goal of these plugins is to return raw data in a new format that
|
||||
can then be used by any number of applications. Included in Lemur is the `JavaExportPlugin` which currently supports generating
|
||||
a Java Key Store (JKS) file for use in Java based applications.
|
||||
|
||||
|
||||
The `ExportPlugin` object requires the implementation of one function::
|
||||
|
||||
def export(self, body, chain, key, options, **kwargs):
|
||||
# sys.call('openssl hokuspocus')
|
||||
# return "extension", passphrase, raw
|
||||
|
||||
|
||||
.. Note::
|
||||
Support of various formats sometimes relies on external tools system calls. Always be mindful of sanitizing any input to
|
||||
these calls.
|
||||
|
||||
|
||||
Testing
|
||||
|
@ -257,13 +257,12 @@ Create a configuration file named supervisor.ini::
|
||||
nodaemon=false
|
||||
minfds=1024
|
||||
minprocs=200
|
||||
user=lemur
|
||||
|
||||
[program:lemur]
|
||||
command=python /path/to/lemur/manage.py manage.py start
|
||||
|
||||
directory=/path/to/lemur/
|
||||
environment=PYTHONPATH='/path/to/lemur/'
|
||||
environment=PYTHONPATH='/path/to/lemur/',LEMUR_CONF='/home/lemur/.lemur/lemur.conf.py'
|
||||
user=lemur
|
||||
autostart=true
|
||||
autorestart=true
|
||||
|
@ -158,7 +158,6 @@ Additional notifications can be created through the UI or API. See :ref:`Creati
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
$ lemur db init
|
||||
$ lemur init
|
||||
|
||||
.. note:: It is recommended that once the ``lemur`` user is created that you create individual users for every day access. There is currently no way for a user to self enroll for Lemur access, they must have an administrator create an account for them or be enrolled automatically through SSO. This can be done through the CLI or UI. See :ref:`Creating Users <CreatingUsers>` and :ref:`Command Line Interface <CommandLineInterface>` for details.
|
||||
|
@ -9,7 +9,7 @@ __title__ = "lemur"
|
||||
__summary__ = ("Certificate management and orchestration service")
|
||||
__uri__ = "https://github.com/Netflix/lemur"
|
||||
|
||||
__version__ = "0.2"
|
||||
__version__ = "0.2.1"
|
||||
|
||||
__author__ = "The Lemur developers"
|
||||
__email__ = "security@netflix.com"
|
||||
|
@ -230,5 +230,77 @@ class Ping(Resource):
|
||||
return dict(token=create_token(user))
|
||||
|
||||
|
||||
class Google(Resource):
|
||||
def __init__(self):
|
||||
self.reqparse = reqparse.RequestParser()
|
||||
super(Google, self).__init__()
|
||||
|
||||
def post(self):
|
||||
access_token_url = 'https://accounts.google.com/o/oauth2/token'
|
||||
people_api_url = 'https://www.googleapis.com/plus/v1/people/me/openIdConnect'
|
||||
|
||||
self.reqparse.add_argument('clientId', type=str, required=True, location='json')
|
||||
self.reqparse.add_argument('redirectUri', type=str, required=True, location='json')
|
||||
self.reqparse.add_argument('code', type=str, required=True, location='json')
|
||||
|
||||
args = self.reqparse.parse_args()
|
||||
|
||||
# Step 1. Exchange authorization code for access token
|
||||
payload = {
|
||||
'client_id': args['clientId'],
|
||||
'grant_type': 'authorization_code',
|
||||
'redirect_uri': args['redirectUri'],
|
||||
'code': args['code'],
|
||||
'client_secret': current_app.config.get('GOOGLE_SECRET')
|
||||
}
|
||||
|
||||
r = requests.post(access_token_url, data=payload)
|
||||
token = r.json()
|
||||
|
||||
# Step 2. Retrieve information about the current user
|
||||
headers = {'Authorization': 'Bearer {0}'.format(token['access_token'])}
|
||||
|
||||
r = requests.get(people_api_url, headers=headers)
|
||||
profile = r.json()
|
||||
|
||||
user = user_service.get_by_email(profile['email'])
|
||||
|
||||
if user:
|
||||
return dict(token=create_token(user))
|
||||
|
||||
|
||||
class Providers(Resource):
|
||||
def get(self):
|
||||
active_providers = []
|
||||
|
||||
for provider in current_app.config.get("ACTIVE_PROVIDERS"):
|
||||
provider = provider.lower()
|
||||
|
||||
if provider == "google":
|
||||
active_providers.append({
|
||||
'name': 'google',
|
||||
'clientId': current_app.config.get("GOOGLE_CLIENT_ID"),
|
||||
'url': api.url_for(Google)
|
||||
})
|
||||
|
||||
elif provider == "ping":
|
||||
active_providers.append({
|
||||
'name': current_app.config.get("PING_NAME"),
|
||||
'url': current_app.config.get('PING_REDIRECT_URI'),
|
||||
'redirectUri': current_app.config.get("PING_REDIRECT_URI"),
|
||||
'clientId': current_app.config.get("PING_CLIENT_ID"),
|
||||
'responseType': 'code',
|
||||
'scope': ['openid', 'email', 'profile', 'address'],
|
||||
'scopeDelimeter': ' ',
|
||||
'authorizationEndpoint': current_app.config.get("PING_AUTH_ENDPOINT"),
|
||||
'requiredUrlParams': ['scope'],
|
||||
'type': '2.0'
|
||||
})
|
||||
|
||||
return active_providers
|
||||
|
||||
|
||||
api.add_resource(Login, '/auth/login', endpoint='login')
|
||||
api.add_resource(Ping, '/auth/ping', endpoint='ping')
|
||||
api.add_resource(Google, '/auth/google', endpoint='google')
|
||||
api.add_resource(Providers, '/auth/providers', endpoint='providers')
|
||||
|
@ -140,7 +140,12 @@ def mint(issuer_options):
|
||||
|
||||
issuer = plugins.get(authority.plugin_name)
|
||||
|
||||
csr, private_key = create_csr(issuer_options)
|
||||
# allow the CSR to be specified by the user
|
||||
if not issuer_options.get('csr'):
|
||||
csr, private_key = create_csr(issuer_options)
|
||||
else:
|
||||
csr = issuer_options.get('csr')
|
||||
private_key = None
|
||||
|
||||
issuer_options['creator'] = g.user.email
|
||||
cert_body, cert_chain = issuer.create_certificate(csr, issuer_options)
|
||||
|
@ -192,6 +192,7 @@ class CertificatesList(AuthenticatedResource):
|
||||
"owner": "bob@example.com",
|
||||
"description": "test",
|
||||
"selectedAuthority": "timetest2",
|
||||
"csr",
|
||||
"authority": {
|
||||
"body": "-----BEGIN...",
|
||||
"name": "timetest2",
|
||||
@ -325,6 +326,7 @@ class CertificatesList(AuthenticatedResource):
|
||||
self.reqparse.add_argument('organizationalUnit', type=str, location='json', required=True)
|
||||
self.reqparse.add_argument('owner', type=str, location='json', required=True)
|
||||
self.reqparse.add_argument('commonName', type=str, location='json', required=True)
|
||||
self.reqparse.add_argument('csr', type=str, location='json')
|
||||
|
||||
args = self.reqparse.parse_args()
|
||||
|
||||
|
@ -90,6 +90,8 @@ LEMUR_DEFAULT_LOCATION = ''
|
||||
LEMUR_DEFAULT_ORGANIZATION = ''
|
||||
LEMUR_DEFAULT_ORGANIZATIONAL_UNIT = ''
|
||||
|
||||
# Authentication Providers
|
||||
ACTIVE_PROVIDERS = []
|
||||
|
||||
# Logging
|
||||
|
||||
@ -503,11 +505,34 @@ def unicode_(data):
|
||||
return data
|
||||
|
||||
|
||||
class RotateELBs(Command):
|
||||
"""
|
||||
Rotates existing certificates to a new one on an ELB
|
||||
"""
|
||||
option_list = (
|
||||
Option('-c', '--cert-name', dest='cert_name', required=True),
|
||||
Option('-a', '--account-id', dest='account_id', required=True),
|
||||
Option('-e', '--elb-list', dest='elb_list', required=True)
|
||||
)
|
||||
|
||||
def run(self, cert_name, account_id, elb_list):
|
||||
from lemur.plugins.lemur_aws import elb
|
||||
arn = "arn:aws:iam::{0}:server-certificate/{1}".format(account_id, cert_name)
|
||||
|
||||
for e in open(elb_list, 'r').readlines():
|
||||
for region in elb.get_all_regions():
|
||||
if str(region) in e:
|
||||
name = "-".join(e.split('.')[0].split('-')[:-1])
|
||||
if name.startswith("internal"):
|
||||
name = "-".join(name.split("-")[1:])
|
||||
elb.update_listeners(account_id, str(region), name, [(443, 7001, 'https', arn)], [443])
|
||||
sys.out.write("[+] Updated {0} to use {1} on 443\n".format(name, cert_name))
|
||||
|
||||
|
||||
class ProvisionELB(Command):
|
||||
"""
|
||||
Creates and provisions a certificate on an ELB based on command line arguments
|
||||
"""
|
||||
|
||||
option_list = (
|
||||
Option('-d', '--dns', dest='dns', action='append', required=True, type=unicode_),
|
||||
Option('-e', '--elb', dest='elb_name', required=True, type=unicode_),
|
||||
@ -746,6 +771,7 @@ def main():
|
||||
manager.add_command("create_user", CreateUser())
|
||||
manager.add_command("create_role", CreateRole())
|
||||
manager.add_command("provision_elb", ProvisionELB())
|
||||
manager.add_command("rotate_elbs", RotateELBs())
|
||||
manager.run()
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
@ -36,13 +36,16 @@ def _get_message_data(cert):
|
||||
:param cert:
|
||||
:return:
|
||||
"""
|
||||
cert_dict = cert.as_dict()
|
||||
cert_dict = {}
|
||||
|
||||
if cert.user:
|
||||
cert_dict['creator'] = cert.user.email
|
||||
|
||||
cert_dict['domains'] = [x .name for x in cert.domains]
|
||||
cert_dict['superseded'] = list(set([x.name for x in _find_superseded(cert) if cert.name != x]))
|
||||
cert_dict['not_after'] = cert.not_after
|
||||
cert_dict['owner'] = cert.owner
|
||||
cert_dict['name'] = cert.name
|
||||
cert_dict['body'] = cert.body
|
||||
|
||||
return cert_dict
|
||||
|
||||
|
||||
|
File diff suppressed because one or more lines are too long
@ -52,6 +52,74 @@ def split_chain(chain):
|
||||
return certs
|
||||
|
||||
|
||||
def create_truststore(cert, chain, jks_tmp, alias, passphrase):
|
||||
with mktempfile() as cert_tmp:
|
||||
with open(cert_tmp, 'w') as f:
|
||||
f.write(cert)
|
||||
|
||||
run_process([
|
||||
"keytool",
|
||||
"-importcert",
|
||||
"-file", cert_tmp,
|
||||
"-keystore", jks_tmp,
|
||||
"-alias", "{0}_cert".format(alias),
|
||||
"-storepass", passphrase,
|
||||
"-noprompt"
|
||||
])
|
||||
|
||||
# Import the entire chain
|
||||
for idx, cert in enumerate(split_chain(chain)):
|
||||
with mktempfile() as c_tmp:
|
||||
with open(c_tmp, 'w') as f:
|
||||
f.write(cert)
|
||||
|
||||
# Import signed cert in to JKS keystore
|
||||
run_process([
|
||||
"keytool",
|
||||
"-importcert",
|
||||
"-file", c_tmp,
|
||||
"-keystore", jks_tmp,
|
||||
"-alias", "{0}_cert_{1}".format(alias, idx),
|
||||
"-storepass", passphrase,
|
||||
"-noprompt"
|
||||
])
|
||||
|
||||
|
||||
def create_keystore(cert, jks_tmp, key, alias, passphrase):
|
||||
with mktempfile() as key_tmp:
|
||||
with open(key_tmp, 'w') as f:
|
||||
f.write(key)
|
||||
|
||||
# Create PKCS12 keystore from private key and public certificate
|
||||
with mktempfile() as cert_tmp:
|
||||
with open(cert_tmp, 'w') as f:
|
||||
f.write(cert)
|
||||
|
||||
with mktempfile() as p12_tmp:
|
||||
run_process([
|
||||
"openssl",
|
||||
"pkcs12",
|
||||
"-export",
|
||||
"-name", alias,
|
||||
"-in", cert_tmp,
|
||||
"-inkey", key_tmp,
|
||||
"-out", p12_tmp,
|
||||
"-password", "pass:{}".format(passphrase)
|
||||
])
|
||||
|
||||
# Convert PKCS12 keystore into a JKS keystore
|
||||
run_process([
|
||||
"keytool",
|
||||
"-importkeystore",
|
||||
"-destkeystore", jks_tmp,
|
||||
"-srckeystore", p12_tmp,
|
||||
"-srcstoretype", "PKCS12",
|
||||
"-alias", alias,
|
||||
"-srcstorepass", passphrase,
|
||||
"-deststorepass", passphrase
|
||||
])
|
||||
|
||||
|
||||
class JavaExportPlugin(ExportPlugin):
|
||||
title = 'Java'
|
||||
slug = 'java-export'
|
||||
@ -66,7 +134,7 @@ class JavaExportPlugin(ExportPlugin):
|
||||
'name': 'type',
|
||||
'type': 'select',
|
||||
'required': True,
|
||||
'available': ['Java Key Store (JKS)'],
|
||||
'available': ['Truststore (JKS)', 'Keystore (JKS)'],
|
||||
'helpMessage': 'Choose the format you wish to export',
|
||||
},
|
||||
{
|
||||
@ -105,71 +173,23 @@ class JavaExportPlugin(ExportPlugin):
|
||||
else:
|
||||
alias = "blah"
|
||||
|
||||
if not key:
|
||||
raise Exception("Unable to export, no private key found.")
|
||||
type = self.get_option('type', options)
|
||||
|
||||
with mktempfile() as cert_tmp:
|
||||
with open(cert_tmp, 'w') as f:
|
||||
f.write(body)
|
||||
with mktemppath() as jks_tmp:
|
||||
if type == 'Truststore (JKS)':
|
||||
create_truststore(body, chain, jks_tmp, alias, passphrase)
|
||||
|
||||
with mktempfile() as key_tmp:
|
||||
with open(key_tmp, 'w') as f:
|
||||
f.write(key)
|
||||
elif type == 'Keystore (JKS)':
|
||||
if not key:
|
||||
raise Exception("Unable to export, no private key found.")
|
||||
|
||||
# Create PKCS12 keystore from private key and public certificate
|
||||
with mktempfile() as p12_tmp:
|
||||
run_process([
|
||||
"openssl",
|
||||
"pkcs12",
|
||||
"-export",
|
||||
"-name", alias,
|
||||
"-in", cert_tmp,
|
||||
"-inkey", key_tmp,
|
||||
"-out", p12_tmp,
|
||||
"-password", "pass:{}".format(passphrase)
|
||||
])
|
||||
create_truststore(body, chain, jks_tmp, alias, passphrase)
|
||||
create_keystore(body, jks_tmp, key, alias, passphrase)
|
||||
|
||||
# Convert PKCS12 keystore into a JKS keystore
|
||||
with mktemppath() as jks_tmp:
|
||||
run_process([
|
||||
"keytool",
|
||||
"-importkeystore",
|
||||
"-destkeystore", jks_tmp,
|
||||
"-srckeystore", p12_tmp,
|
||||
"-srcstoretype", "PKCS12",
|
||||
"-alias", alias,
|
||||
"-srcstorepass", passphrase,
|
||||
"-deststorepass", passphrase
|
||||
])
|
||||
else:
|
||||
raise Exception("Unable to export, unsupported type: {0}".format(type))
|
||||
|
||||
# Import leaf cert in to JKS keystore
|
||||
run_process([
|
||||
"keytool",
|
||||
"-importcert",
|
||||
"-file", cert_tmp,
|
||||
"-keystore", jks_tmp,
|
||||
"-alias", "{0}_cert".format(alias),
|
||||
"-storepass", passphrase,
|
||||
"-noprompt"
|
||||
])
|
||||
with open(jks_tmp, 'rb') as f:
|
||||
raw = f.read()
|
||||
|
||||
# Import the entire chain
|
||||
for idx, cert in enumerate(split_chain(chain)):
|
||||
with mktempfile() as c_tmp:
|
||||
with open(c_tmp, 'w') as f:
|
||||
f.write(cert)
|
||||
# Import signed cert in to JKS keystore
|
||||
run_process([
|
||||
"keytool",
|
||||
"-importcert",
|
||||
"-file", c_tmp,
|
||||
"-keystore", jks_tmp,
|
||||
"-alias", "{0}_cert_{1}".format(alias, idx),
|
||||
"-storepass", passphrase,
|
||||
"-noprompt"
|
||||
])
|
||||
|
||||
with open(jks_tmp, 'rb') as f:
|
||||
raw = f.read()
|
||||
|
||||
return "jks", passphrase, raw
|
||||
return "jks", passphrase, raw
|
||||
|
5
lemur/plugins/lemur_openssl/__init__.py
Normal file
5
lemur/plugins/lemur_openssl/__init__.py
Normal file
@ -0,0 +1,5 @@
|
||||
try:
|
||||
VERSION = __import__('pkg_resources') \
|
||||
.get_distribution(__name__).version
|
||||
except Exception as e:
|
||||
VERSION = 'unknown'
|
130
lemur/plugins/lemur_openssl/plugin.py
Normal file
130
lemur/plugins/lemur_openssl/plugin.py
Normal file
@ -0,0 +1,130 @@
|
||||
"""
|
||||
.. module: lemur.plugins.lemur_openssl.plugin
|
||||
: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 subprocess
|
||||
|
||||
from flask import current_app
|
||||
|
||||
from lemur.utils import mktempfile, mktemppath
|
||||
from lemur.plugins.bases import ExportPlugin
|
||||
from lemur.plugins import lemur_openssl as openssl
|
||||
from lemur.common.utils import get_psuedo_random_string
|
||||
|
||||
|
||||
def run_process(command):
|
||||
"""
|
||||
Runs a given command with pOpen and wraps some
|
||||
error handling around it.
|
||||
:param command:
|
||||
:return:
|
||||
"""
|
||||
p = subprocess.Popen(command, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
|
||||
current_app.logger.debug(command)
|
||||
stdout, stderr = p.communicate()
|
||||
|
||||
if p.returncode != 0:
|
||||
current_app.logger.debug(" ".join(command))
|
||||
current_app.logger.error(stderr)
|
||||
raise Exception(stderr)
|
||||
|
||||
|
||||
def create_pkcs12(cert, p12_tmp, key, alias, passphrase):
|
||||
"""
|
||||
Creates a pkcs12 formated file.
|
||||
:param cert:
|
||||
:param jks_tmp:
|
||||
:param key:
|
||||
:param alias:
|
||||
:param passphrase:
|
||||
"""
|
||||
with mktempfile() as key_tmp:
|
||||
with open(key_tmp, 'w') as f:
|
||||
f.write(key)
|
||||
|
||||
# Create PKCS12 keystore from private key and public certificate
|
||||
with mktempfile() as cert_tmp:
|
||||
with open(cert_tmp, 'w') as f:
|
||||
f.write(cert)
|
||||
|
||||
run_process([
|
||||
"openssl",
|
||||
"pkcs12",
|
||||
"-export",
|
||||
"-name", alias,
|
||||
"-in", cert_tmp,
|
||||
"-inkey", key_tmp,
|
||||
"-out", p12_tmp,
|
||||
"-password", "pass:{}".format(passphrase)
|
||||
])
|
||||
|
||||
|
||||
class OpenSSLExportPlugin(ExportPlugin):
|
||||
title = 'OpenSSL'
|
||||
slug = 'openssl-export'
|
||||
description = 'Is a loose interface to openssl and support various formats'
|
||||
version = openssl.VERSION
|
||||
|
||||
author = 'Kevin Glisson'
|
||||
author_url = 'https://github.com/netflix/lemur'
|
||||
|
||||
options = [
|
||||
{
|
||||
'name': 'type',
|
||||
'type': 'select',
|
||||
'required': True,
|
||||
'available': ['PKCS12 (.p12)'],
|
||||
'helpMessage': 'Choose the format you wish to export',
|
||||
},
|
||||
{
|
||||
'name': 'passphrase',
|
||||
'type': 'str',
|
||||
'required': False,
|
||||
'helpMessage': 'If no passphrase is given one will be generated for you, we highly recommend this. Minimum length is 8.',
|
||||
'validation': '^(?=.*[A-Za-z])(?=.*\d)(?=.*[$@$!%*#?&])[A-Za-z\d$@$!%*#?&]{8,}$'
|
||||
},
|
||||
{
|
||||
'name': 'alias',
|
||||
'type': 'str',
|
||||
'required': False,
|
||||
'helpMessage': 'Enter the alias you wish to use for the keystore.',
|
||||
}
|
||||
]
|
||||
|
||||
def export(self, body, chain, key, options, **kwargs):
|
||||
"""
|
||||
Generates a Java Keystore or Truststore
|
||||
|
||||
:param key:
|
||||
:param chain:
|
||||
:param body:
|
||||
:param options:
|
||||
:param kwargs:
|
||||
"""
|
||||
if self.get_option('passphrase', options):
|
||||
passphrase = self.get_option('passphrase', options)
|
||||
else:
|
||||
passphrase = get_psuedo_random_string()
|
||||
|
||||
if self.get_option('alias', options):
|
||||
alias = self.get_option('alias', options)
|
||||
else:
|
||||
alias = "blah"
|
||||
|
||||
type = self.get_option('type', options)
|
||||
|
||||
with mktemppath() as output_tmp:
|
||||
if type == 'PKCS12 (.p12)':
|
||||
create_pkcs12(body, output_tmp, key, alias, passphrase)
|
||||
extension = "p12"
|
||||
else:
|
||||
raise Exception("Unable to export, unsupported type: {0}".format(type))
|
||||
|
||||
with open(output_tmp, 'rb') as f:
|
||||
raw = f.read()
|
||||
|
||||
return extension, passphrase, raw
|
1
lemur/plugins/lemur_openssl/tests/conftest.py
Normal file
1
lemur/plugins/lemur_openssl/tests/conftest.py
Normal file
@ -0,0 +1 @@
|
||||
from lemur.tests.conftest import * # noqa
|
63
lemur/plugins/lemur_openssl/tests/test_openssl.py
Normal file
63
lemur/plugins/lemur_openssl/tests/test_openssl.py
Normal file
@ -0,0 +1,63 @@
|
||||
PRIVATE_KEY_STR = b"""
|
||||
-----BEGIN RSA PRIVATE KEY-----
|
||||
MIIEogIBAAKCAQEAsXn+QZRATxryRmGXI4fdI+0a2oBwuVh8fC/9bcqX6c5eDmgc
|
||||
rj6esmc1hpIFxMM3DvkFXX6xISkU6B5fmYDEGZLi7NvcXF3+EoA/SCkP1MFlvqhn
|
||||
EvNhb0t1fBLs0i/0gfTS/FHBZY1ekHisd/sUetCDZ7F11RxMwws0Oc8bl7j1TpRc
|
||||
awXFAsh/aWwQOwFeyWU7TtZeAE7sMyWXInBg37tKk1wlv+mN+27WijI091+amkVy
|
||||
zIV6mA5OHfqbjuqV8uQflN8jE244Qr7shtSk7LpBpWf0M6dC7dXbuUctHFhqcDjy
|
||||
3IRUl+NisKRoMtq+a0uehfmpFNSUD7F4gdUtSwIDAQABAoIBAGITsZ+aBuPwVzzv
|
||||
x286MMoeyL1BR4oVzU1v09Rtpf/uLGo3vMnKDzc19A12+rseynl6wi1FyysxIb2Y
|
||||
s2oID9a2JrOQWLmus66TsuT01CvV6J0xQSzm1MyFXdqANuF84NlEa6hGoeK1+jFK
|
||||
jr0LQukP+9484oovxnfu5CCiRHRWNZmeuekuYhI1SJf343Tr6jwvyr6KZpnIy0Yt
|
||||
axuuIZdCfY9ZV2vFG89GwwgwVQrhf14Kv5vBMZrNh1lRGsr0Sqlx5cGkPRAy90lg
|
||||
HjrRMogrtXr3AR5Pk2qqAYXzZBU2EFhJ3k2njpwOzlSj0r0ZwTmejZ89cco0sW5j
|
||||
+eQ6aRECgYEA1tkNW75fgwU52Va5VETCzG8II/pZdqNygnoc3z8EutN+1w8f6Tr+
|
||||
PdpKSICW0z7Iq4f5k/4wrA5xw1vy5RBMH0ZP29GwHTvCPiTBboR9vWvxQvZn1jb9
|
||||
wvKa0RxE18KcF0YIyTnZMubkA17QTFlvCNyZg0iCqeyFYPyqVE+R4AkCgYEA03h1
|
||||
XrqECZDDbG9HLUdGbkZNk4VzTcF6dQ3GAPY8M/H7rw5BbvH0RZLOrzl46DDVzKTg
|
||||
B1VOReAHsxBKFdkqeq1A99CLDow6vHTIEG8DwxkA7/2QPkt8MybwdApUyYnQh5/v
|
||||
CxwkRt4Mm+EiYfn5iyL8yI+vaQSRToVO/3BND7MCgYAJQSpBJG8qzqPSR9kN1zRo
|
||||
5/N60ULfSGUbV7U8rJNAlPGmw+EFA+SFt4xxmRBmIxMzyFSo2k8waiLeXmyVD2Go
|
||||
CzhPaLXkXHmegajPYOelrCulTcXlRVMi/Z5LmaMhhCGDIyInwNUpSybROllQoJ2W
|
||||
zSHTtODj/usz5U5U+WR4OQKBgHQRosI6t2wUo96peTS18UdnmP7GeZINBuymga5X
|
||||
eJW+VLkxpuKBNOTW/lCYx+8Rlte7CyebP9oEa9VxtGgniTRKUeVy9lAm0bpMkt7K
|
||||
QBNebvBKiVhX0DS3Q7U9UmpIFUfLlcXQTW0ERYFtYZTLQpeGvZ5LlyiaFDM34jM7
|
||||
7WAXAoGANDPJdQLEuimCOAMx/xoecNWeZIP6ieB0hVBrwLNxsaZlkn1KodUMuvla
|
||||
VEowbtPRdc9o3VZRh4q9cEakssTvOD70hgUZCFcMarmc37RgRvvD2fsZmDZF6qd3
|
||||
QfHplREs9F0sW+eiirczG7up4XL+CA162TtZxW+2GAiQhwhE5jA=
|
||||
-----END RSA PRIVATE KEY-----
|
||||
"""
|
||||
|
||||
EXTERNAL_VALID_STR = b"""
|
||||
-----BEGIN CERTIFICATE-----
|
||||
MIID2zCCAsOgAwIBAgICA+0wDQYJKoZIhvcNAQELBQAwgZcxCzAJBgNVBAYTAlVT
|
||||
MRMwEQYDVQQIDApDYWxpZm9ybmlhMRIwEAYDVQQHDAlMb3MgR2F0b3MxDTALBgNV
|
||||
BAMMBHRlc3QxFjAUBgNVBAoMDU5ldGZsaXgsIEluYy4xEzARBgNVBAsMCk9wZXJh
|
||||
dGlvbnMxIzAhBgkqhkiG9w0BCQEWFGtnbGlzc29uQG5ldGZsaXguY29tMB4XDTE1
|
||||
MTEyMzIxNDIxMFoXDTE1MTEyNjIxNDIxMFowcjENMAsGA1UEAwwEdGVzdDEWMBQG
|
||||
A1UECgwNTmV0ZmxpeCwgSW5jLjETMBEGA1UECwwKT3BlcmF0aW9uczELMAkGA1UE
|
||||
BhMCVVMxEzARBgNVBAgMCkNhbGlmb3JuaWExEjAQBgNVBAcMCUxvcyBHYXRvczCC
|
||||
ASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBALF5/kGUQE8a8kZhlyOH3SPt
|
||||
GtqAcLlYfHwv/W3Kl+nOXg5oHK4+nrJnNYaSBcTDNw75BV1+sSEpFOgeX5mAxBmS
|
||||
4uzb3Fxd/hKAP0gpD9TBZb6oZxLzYW9LdXwS7NIv9IH00vxRwWWNXpB4rHf7FHrQ
|
||||
g2exddUcTMMLNDnPG5e49U6UXGsFxQLIf2lsEDsBXsllO07WXgBO7DMllyJwYN+7
|
||||
SpNcJb/pjftu1ooyNPdfmppFcsyFepgOTh36m47qlfLkH5TfIxNuOEK+7IbUpOy6
|
||||
QaVn9DOnQu3V27lHLRxYanA48tyEVJfjYrCkaDLavmtLnoX5qRTUlA+xeIHVLUsC
|
||||
AwEAAaNVMFMwUQYDVR0fBEowSDBGoESgQoZAaHR0cDovL3Rlc3QuY2xvdWRjYS5j
|
||||
cmwubmV0ZmxpeC5jb20vdGVzdERlY3JpcHRpb25DQVJvb3QvY3JsLnBlbTANBgkq
|
||||
hkiG9w0BAQsFAAOCAQEAiHREBKg7zhlQ/N7hDIkxgodRSWD7CVbJGSCdkR3Pvr6+
|
||||
jHBVNTJUrYqy7sL2pIutoeiSTQEH65/Gbm30mOnNu+lvFKxTxzof6kNYv8cyc8sX
|
||||
eBuBfSrlTodPFSHXQIpOexZgA0f30LOuXegqzxgXkKg+uMXOez5Zo5pNjTUow0He
|
||||
oe+V1hfYYvL1rocCmBOkhIGWz7622FxKDawRtZTGVsGsMwMIWyvS3+KQ04K8yHhp
|
||||
bQOg9zZAoYQuHY1inKBnA0II8eW0hPpJrlZoSqN8Tp0NSBpFiUk3m7KNFP2kITIf
|
||||
tTneAgyUsgfDxNDifZryZSzg7MH31sTBcYaotSmTXw==
|
||||
-----END CERTIFICATE-----
|
||||
"""
|
||||
|
||||
|
||||
def test_export_certificate_to_jks(app):
|
||||
from lemur.plugins.base import plugins
|
||||
p = plugins.get('java-export')
|
||||
options = {'passphrase': 'test1234'}
|
||||
raw = p.export(EXTERNAL_VALID_STR, "", PRIVATE_KEY_STR, options)
|
||||
assert raw != b""
|
268
lemur/static/app/angular/app.js
vendored
268
lemur/static/app/angular/app.js
vendored
@ -1,138 +1,158 @@
|
||||
'use strict';
|
||||
|
||||
var lemur = angular
|
||||
.module('lemur', [
|
||||
'ui.router',
|
||||
'ngTable',
|
||||
'ngAnimate',
|
||||
'chart.js',
|
||||
'restangular',
|
||||
'angular-loading-bar',
|
||||
'ui.bootstrap',
|
||||
'angular-spinkit',
|
||||
'toaster',
|
||||
'uiSwitch',
|
||||
'mgo-angular-wizard',
|
||||
'satellizer',
|
||||
'ngLetterAvatar',
|
||||
'angular-clipboard',
|
||||
'ngFileSaver'
|
||||
])
|
||||
.config(function ($stateProvider, $urlRouterProvider, $authProvider) {
|
||||
$urlRouterProvider.otherwise('/welcome');
|
||||
(function() {
|
||||
var lemur = angular
|
||||
.module('lemur', [
|
||||
'ui.router',
|
||||
'ngTable',
|
||||
'ngAnimate',
|
||||
'chart.js',
|
||||
'restangular',
|
||||
'angular-loading-bar',
|
||||
'ui.bootstrap',
|
||||
'angular-spinkit',
|
||||
'toaster',
|
||||
'uiSwitch',
|
||||
'mgo-angular-wizard',
|
||||
'satellizer',
|
||||
'ngLetterAvatar',
|
||||
'angular-clipboard',
|
||||
'ngFileSaver'
|
||||
]);
|
||||
|
||||
|
||||
function fetchData() {
|
||||
var initInjector = angular.injector(['ng']);
|
||||
var $http = initInjector.get('$http');
|
||||
|
||||
return $http.get('http://localhost:8000/api/1/auth/providers').then(function(response) {
|
||||
lemur.constant('providers', response.data);
|
||||
}, function(errorResponse) {
|
||||
console.log('Could not fetch SSO providers' + errorResponse);
|
||||
});
|
||||
}
|
||||
|
||||
function bootstrapApplication() {
|
||||
angular.element(document).ready(function() {
|
||||
angular.bootstrap(document, ['lemur']);
|
||||
});
|
||||
}
|
||||
|
||||
fetchData().then(bootstrapApplication);
|
||||
|
||||
lemur.config(function ($stateProvider, $urlRouterProvider, $authProvider, providers) {
|
||||
$urlRouterProvider.otherwise('/welcome');
|
||||
$stateProvider
|
||||
.state('welcome', {
|
||||
url: '/welcome',
|
||||
templateUrl: 'angular/welcome/welcome.html'
|
||||
});
|
||||
|
||||
$authProvider.oauth2({
|
||||
name: 'example',
|
||||
url: 'http://localhost:5000/api/1/auth/ping',
|
||||
redirectUri: 'http://localhost:3000/',
|
||||
clientId: 'client-id',
|
||||
responseType: 'code',
|
||||
scope: ['openid', 'email', 'profile', 'address'],
|
||||
scopeDelimiter: ' ',
|
||||
authorizationEndpoint: 'https://example.com/as/authorization.oauth2',
|
||||
requiredUrlParams: ['scope']
|
||||
});
|
||||
});
|
||||
|
||||
lemur.service('MomentService', function () {
|
||||
this.diffMoment = function (start, end) {
|
||||
if (end !== 'None') {
|
||||
return moment(end, 'YYYY-MM-DD HH:mm Z').diff(moment(start, 'YYYY-MM-DD HH:mm Z'), 'minutes') + ' minutes';
|
||||
}
|
||||
return 'Unknown';
|
||||
};
|
||||
this.createMoment = function (date) {
|
||||
if (date !== 'None') {
|
||||
return moment(date, 'YYYY-MM-DD HH:mm Z').fromNow();
|
||||
}
|
||||
return 'Unknown';
|
||||
};
|
||||
});
|
||||
|
||||
lemur.controller('datePickerController', function ($scope, $timeout){
|
||||
$scope.open = function() {
|
||||
$timeout(function() {
|
||||
$scope.opened = true;
|
||||
});
|
||||
};
|
||||
});
|
||||
|
||||
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) {
|
||||
return Restangular.withConfig(function (RestangularConfigurer) {
|
||||
RestangularConfigurer.setBaseUrl('http://localhost:8000/api/1');
|
||||
RestangularConfigurer.setDefaultHttpFields({withCredentials: true});
|
||||
|
||||
RestangularConfigurer.addResponseInterceptor(function (data, operation) {
|
||||
var extractedData;
|
||||
|
||||
// .. to look for getList operations
|
||||
if (operation === 'getList') {
|
||||
// .. and handle the data and meta data
|
||||
extractedData = data.items;
|
||||
extractedData.total = data.total;
|
||||
_.each(providers, function(provider) {
|
||||
if ($authProvider.hasOwnProperty(provider.name)) {
|
||||
$authProvider[provider.name] = provider;
|
||||
} else {
|
||||
extractedData = data;
|
||||
}
|
||||
|
||||
return extractedData;
|
||||
});
|
||||
|
||||
RestangularConfigurer.setErrorInterceptor(function(response) {
|
||||
if (response.status === 400) {
|
||||
if (response.data.message) {
|
||||
var data = '';
|
||||
_.each(response.data.message, function (value, key) {
|
||||
data = data + ' ' + key + ' ' + value;
|
||||
});
|
||||
response.data.message = data;
|
||||
}
|
||||
$authProvider.oauth2(provider);
|
||||
}
|
||||
});
|
||||
|
||||
RestangularConfigurer.addFullRequestInterceptor(function (element, operation, route, url, headers, params) {
|
||||
// We want to make sure the user is auth'd before any requests
|
||||
if (!$auth.isAuthenticated()) {
|
||||
$location.path('/login');
|
||||
return false;
|
||||
}
|
||||
|
||||
var regExp = /\[([^)]+)\]/;
|
||||
|
||||
var s = 'sorting';
|
||||
var f = 'filter';
|
||||
var newParams = {};
|
||||
for (var item in params) {
|
||||
if (item.indexOf(s) > -1) {
|
||||
newParams.sortBy = regExp.exec(item)[1];
|
||||
newParams.sortDir = params[item];
|
||||
} else if (item.indexOf(f) > -1) {
|
||||
var key = regExp.exec(item)[1];
|
||||
newParams.filter = key + ';' + params[item];
|
||||
} else {
|
||||
newParams[item] = params[item];
|
||||
}
|
||||
}
|
||||
return { params: newParams };
|
||||
});
|
||||
|
||||
});
|
||||
});
|
||||
|
||||
lemur.run(['$templateCache', function ($templateCache) {
|
||||
$templateCache.put('ng-table/pager.html', '<div class="ng-cloak ng-table-pager"> <div ng-if="params.settings().counts.length" class="ng-table-counts btn-group pull-left"> <button ng-repeat="count in params.settings().counts" type="button" ng-class="{\'active\':params.count()==count}" ng-click="params.count(count)" class="btn btn-default"> <span ng-bind="count"></span> </button></div><div class="pull-right"><ul style="margin: 0; padding: 0;" class="pagination ng-table-pagination"> <li ng-class="{\'disabled\': !page.active}" ng-repeat="page in pages" ng-switch="page.type"> <a ng-switch-when="prev" ng-click="params.page(page.number)" href="">«</a> <a ng-switch-when="first" ng-click="params.page(page.number)" href=""><span ng-bind="page.number"></span></a> <a ng-switch-when="page" ng-click="params.page(page.number)" href=""><span ng-bind="page.number"></span></a> <a ng-switch-when="more" ng-click="params.page(page.number)" href="">…</a> <a ng-switch-when="last" ng-click="params.page(page.number)" href=""><span ng-bind="page.number"></span></a> <a ng-switch-when="next" ng-click="params.page(page.number)" href="">»</a> </li> </ul> </div></div>');
|
||||
}]);
|
||||
lemur.service('MomentService', function () {
|
||||
this.diffMoment = function (start, end) {
|
||||
if (end !== 'None') {
|
||||
return moment(end, 'YYYY-MM-DD HH:mm Z').diff(moment(start, 'YYYY-MM-DD HH:mm Z'), 'minutes') + ' minutes';
|
||||
}
|
||||
return 'Unknown';
|
||||
};
|
||||
this.createMoment = function (date) {
|
||||
if (date !== 'None') {
|
||||
return moment(date, 'YYYY-MM-DD HH:mm Z').fromNow();
|
||||
}
|
||||
return 'Unknown';
|
||||
};
|
||||
});
|
||||
|
||||
lemur.controller('datePickerController', function ($scope, $timeout){
|
||||
$scope.open = function() {
|
||||
$timeout(function() {
|
||||
$scope.opened = true;
|
||||
});
|
||||
};
|
||||
});
|
||||
|
||||
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) {
|
||||
return Restangular.withConfig(function (RestangularConfigurer) {
|
||||
RestangularConfigurer.setBaseUrl('http://localhost:8000/api/1');
|
||||
RestangularConfigurer.setDefaultHttpFields({withCredentials: true});
|
||||
|
||||
RestangularConfigurer.addResponseInterceptor(function (data, operation) {
|
||||
var extractedData;
|
||||
|
||||
// .. to look for getList operations
|
||||
if (operation === 'getList') {
|
||||
// .. and handle the data and meta data
|
||||
extractedData = data.items;
|
||||
extractedData.total = data.total;
|
||||
} else {
|
||||
extractedData = data;
|
||||
}
|
||||
|
||||
return extractedData;
|
||||
});
|
||||
|
||||
RestangularConfigurer.setErrorInterceptor(function(response) {
|
||||
if (response.status === 400) {
|
||||
if (response.data.message) {
|
||||
var data = '';
|
||||
_.each(response.data.message, function (value, key) {
|
||||
data = data + ' ' + key + ' ' + value;
|
||||
});
|
||||
response.data.message = data;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
RestangularConfigurer.addFullRequestInterceptor(function (element, operation, route, url, headers, params) {
|
||||
// We want to make sure the user is auth'd before any requests
|
||||
if (!$auth.isAuthenticated()) {
|
||||
$location.path('/login');
|
||||
return false;
|
||||
}
|
||||
|
||||
var regExp = /\[([^)]+)\]/;
|
||||
|
||||
var s = 'sorting';
|
||||
var f = 'filter';
|
||||
var newParams = {};
|
||||
for (var item in params) {
|
||||
if (item.indexOf(s) > -1) {
|
||||
newParams.sortBy = regExp.exec(item)[1];
|
||||
newParams.sortDir = params[item];
|
||||
} else if (item.indexOf(f) > -1) {
|
||||
var key = regExp.exec(item)[1];
|
||||
newParams.filter = key + ';' + params[item];
|
||||
} else {
|
||||
newParams[item] = params[item];
|
||||
}
|
||||
}
|
||||
return { params: newParams };
|
||||
});
|
||||
|
||||
});
|
||||
});
|
||||
|
||||
lemur.run(['$templateCache', function ($templateCache) {
|
||||
$templateCache.put('ng-table/pager.html', '<div class="ng-cloak ng-table-pager"> <div ng-if="params.settings().counts.length" class="ng-table-counts btn-group pull-left"> <button ng-repeat="count in params.settings().counts" type="button" ng-class="{\'active\':params.count()==count}" ng-click="params.count(count)" class="btn btn-default"> <span ng-bind="count"></span> </button></div><div class="pull-right"><ul style="margin: 0; padding: 0;" class="pagination ng-table-pagination"> <li ng-class="{\'disabled\': !page.active}" ng-repeat="page in pages" ng-switch="page.type"> <a ng-switch-when="prev" ng-click="params.page(page.number)" href="">«</a> <a ng-switch-when="first" ng-click="params.page(page.number)" href=""><span ng-bind="page.number"></span></a> <a ng-switch-when="page" ng-click="params.page(page.number)" href=""><span ng-bind="page.number"></span></a> <a ng-switch-when="more" ng-click="params.page(page.number)" href="">…</a> <a ng-switch-when="last" ng-click="params.page(page.number)" href=""><span ng-bind="page.number"></span></a> <a ng-switch-when="next" ng-click="params.page(page.number)" href="">»</a> </li> </ul> </div></div>');
|
||||
}]);
|
||||
}());
|
||||
|
||||
|
||||
|
@ -8,11 +8,13 @@ angular.module('lemur')
|
||||
controller: 'LoginController'
|
||||
});
|
||||
})
|
||||
.controller('LoginController', function ($rootScope, $scope, AuthenticationService, UserService) {
|
||||
.controller('LoginController', function ($rootScope, $scope, AuthenticationService, UserService, providers) {
|
||||
$scope.login = AuthenticationService.login;
|
||||
$scope.authenticate = AuthenticationService.authenticate;
|
||||
$scope.logout = AuthenticationService.logout;
|
||||
|
||||
$scope.providers = providers;
|
||||
|
||||
UserService.getCurrentUser().then(function (user) {
|
||||
$scope.currentUser = user;
|
||||
});
|
||||
|
@ -3,8 +3,8 @@
|
||||
<div class="login">
|
||||
<div class="row">
|
||||
<div class="col-xs-12 col-sm-12 col-md-12">
|
||||
<button class="btn btn-block btn-default" ng-click="authenticate('Example')">
|
||||
Login with Example
|
||||
<button class="btn btn-block btn-default" ng-repeat="(key, value) in providers" ng-click="authenticate(value.name)">
|
||||
Login with {{ value.name }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -48,7 +48,7 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div ng-show="certificate.authority" class="form-group">
|
||||
<div class="form-group">
|
||||
<label class="control-label col-sm-2">
|
||||
Certificate Template
|
||||
</label>
|
||||
@ -110,6 +110,20 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group"
|
||||
ng-class="{'has-error': trackingForm.csr.$invalid&&trackingForm.csr.$dirty, 'has-success': !trackingForm.csr.$invalid&&trackingForm.csr.$dirty}">
|
||||
<label class="control-label col-sm-2">
|
||||
Certificate Signing Request (CSR)
|
||||
</label>
|
||||
<div class="col-sm-10">
|
||||
<textarea tooltip="Values defined in the CSR will take precedence" name="certificate signing request" ng-model="certificate.csr"
|
||||
placeholder="PEM encoded string..." class="form-control"
|
||||
ng-pattern="/^-----BEGIN CERTIFICATE REQUEST-----/"></textarea>
|
||||
|
||||
<p ng-show="trackingForm.csr.$invalid && !trackingForm.csr.$pristine"
|
||||
class="help-block">Enter a valid certificate signing request.</p>
|
||||
</div>
|
||||
</div>
|
||||
<div ng-include="'angular/certificates/certificate/replaces.tpl.html'"></div>
|
||||
<div ng-include="'angular/certificates/certificate/notifications.tpl.html'"></div>
|
||||
<div ng-include="'angular/certificates/certificate/destinations.tpl.html'"></div>
|
||||
|
@ -24,8 +24,8 @@
|
||||
<tr ng-class="{'even-row': $even }" ng-repeat-start="certificate in $data track by $index">
|
||||
<td data-title="'Name'" sortable="'name'" filter="{ 'name': 'text' }">
|
||||
<ul class="list-unstyled">
|
||||
<li>{{ ::certificate.name }}</li>
|
||||
<li><span class="text-muted">{{ ::certificate.owner }}</span></li>
|
||||
<li>{{ certificate.name }}</li>
|
||||
<li><span class="text-muted">{{ certificate.owner }}</span></li>
|
||||
</ul>
|
||||
</td>
|
||||
<td data-title="'Active'" filter="{ 'active': 'select' }" filter-data="getCertificateStatus()">
|
||||
@ -35,10 +35,10 @@
|
||||
</form>
|
||||
</td>
|
||||
<td data-title="'Issuer'" sortable="'issuer'" filter="{ 'issuer': 'text' }">
|
||||
{{ ::certificate.authority.name || certificate.issuer }}
|
||||
{{ certificate.authority.name || certificate.issuer }}
|
||||
</td>
|
||||
<td data-title="'Domains'" filter="{ 'cn': 'text'}">
|
||||
{{ ::certificate.cn }}
|
||||
{{ certificate.cn }}
|
||||
</td>
|
||||
<td class="col-md-2" data-title="''">
|
||||
<div class="btn-group pull-right">
|
||||
@ -62,19 +62,19 @@
|
||||
<li class="list-group-item">
|
||||
<strong>Creator</strong>
|
||||
<span class="pull-right">
|
||||
{{ ::certificate.creator.email }}
|
||||
{{ certificate.creator.email }}
|
||||
</span>
|
||||
</li>
|
||||
<li class="list-group-item">
|
||||
<strong>Not Before</strong>
|
||||
<span class="pull-right" tooltip="{{ ::certificate.notBefore }}">
|
||||
{{ ::momentService.createMoment(certificate.notBefore) }}
|
||||
<span class="pull-right" tooltip="{{ certificate.notBefore }}">
|
||||
{{ momentService.createMoment(certificate.notBefore) }}
|
||||
</span>
|
||||
</li>
|
||||
<li class="list-group-item">
|
||||
<strong>Not After</strong>
|
||||
<span class="pull-right" tooltip="{{ ::certificate.notAfter }}">
|
||||
{{ ::momentService.createMoment(certificate.notAfter) }}
|
||||
<span class="pull-right" tooltip="{{ certificate.notAfter }}">
|
||||
{{ momentService.createMoment(certificate.notAfter) }}
|
||||
</span>
|
||||
</li>
|
||||
<li class="list-group-item">
|
||||
@ -86,15 +86,15 @@
|
||||
</li>
|
||||
<li class="list-group-item">
|
||||
<strong>Bits</strong>
|
||||
<span class="pull-right">{{ ::certificate.bits }}</span>
|
||||
<span class="pull-right">{{ certificate.bits }}</span>
|
||||
</li>
|
||||
<li class="list-group-item">
|
||||
<strong>Signing Algorithm</strong>
|
||||
<span class="pull-right">{{ ::certificate.signingAlgorithm }}</span>
|
||||
<span class="pull-right">{{ certificate.signingAlgorithm }}</span>
|
||||
</li>
|
||||
<li class="list-group-item">
|
||||
<strong>Serial</strong>
|
||||
<span class="pull-right">{{ ::certificate.serial }}</span>
|
||||
<span class="pull-right">{{ certificate.serial }}</span>
|
||||
</li>
|
||||
<li
|
||||
tooltip="Lemur will attempt to check a certificates validity, this is used to track whether a certificate as been revoked"
|
||||
@ -108,7 +108,7 @@
|
||||
</li>
|
||||
<li class="list-group-item">
|
||||
<strong>Description</strong>
|
||||
<p>{{ ::certificate.description }}</p>
|
||||
<p>{{ certificate.description }}</p>
|
||||
</li>
|
||||
</ul>
|
||||
</tab>
|
||||
@ -116,8 +116,8 @@
|
||||
<tab-heading>Notifications</tab-heading>
|
||||
<ul class="list-group">
|
||||
<li class="list-group-item" ng-repeat="notification in certificate.notifications">
|
||||
<strong>{{ ::notification.label }}</strong>
|
||||
<span class="pull-right">{{ ::notification.description}}</span>
|
||||
<strong>{{ notification.label }}</strong>
|
||||
<span class="pull-right">{{ notification.description}}</span>
|
||||
</li>
|
||||
</ul>
|
||||
</tab>
|
||||
@ -125,24 +125,24 @@
|
||||
<tab-heading>Destinations</tab-heading>
|
||||
<ul class="list-group">
|
||||
<li class="list-group-item" ng-repeat="destination in certificate.destinations">
|
||||
<strong>{{ ::destination.label }}</strong>
|
||||
<span class="pull-right">{{ ::destination.description }}</span>
|
||||
<strong>{{ destination.label }}</strong>
|
||||
<span class="pull-right">{{ destination.description }}</span>
|
||||
</li>
|
||||
</ul>
|
||||
</tab>
|
||||
<tab>
|
||||
<tab-heading>Domains</tab-heading>
|
||||
<div class="list-group">
|
||||
<a href="#/domains/{{ ::domain.id }}" class="list-group-item"
|
||||
ng-repeat="domain in certificate.domains">{{ ::domain.name }}</a>
|
||||
<a href="#/domains/{{ domain.id }}" class="list-group-item"
|
||||
ng-repeat="domain in certificate.domains">{{ domain.name }}</a>
|
||||
</div>
|
||||
</tab>
|
||||
<tab>
|
||||
<tab-heading>Replaces</tab-heading>
|
||||
<ul class="list-group">
|
||||
<li class="list-group-item" ng-repeat="replacement in certificate.replacements">
|
||||
<strong>{{ ::replacement.name }}</strong>
|
||||
<p>{{ ::replacement.description}}</p>
|
||||
<strong>{{ replacement.name }}</strong>
|
||||
<p>{{ replacement.description}}</p>
|
||||
</li>
|
||||
</ul>
|
||||
</tab>
|
||||
@ -155,7 +155,7 @@
|
||||
tooltip="Copy chain to clipboard" tooltip-trigger="mouseenter" clipboard
|
||||
text="certificate.chain"></button>
|
||||
</tab-heading>
|
||||
<pre style="width: 100%">{{ ::certificate.chain }}</pre>
|
||||
<pre style="width: 100%">{{ certificate.chain }}</pre>
|
||||
</tab>
|
||||
<tab>
|
||||
<tab-heading>
|
||||
@ -164,7 +164,7 @@
|
||||
tooltip="Copy certificate to clipboard" tooltip-trigger="mouseenter" clipboard
|
||||
text="certificate.body"></button>
|
||||
</tab-heading>
|
||||
<pre style="width: 100%">{{ ::certificate.body }}</pre>
|
||||
<pre style="width: 100%">{{ certificate.body }}</pre>
|
||||
</tab>
|
||||
<tab ng-click="loadPrivateKey(certificate)">
|
||||
<tab-heading>
|
||||
@ -173,7 +173,7 @@
|
||||
tooltip="Copy key to clipboard" tooltip-trigger="mouseenter" clipboard
|
||||
text="certificate.privateKey"></button>
|
||||
</tab-heading>
|
||||
<pre style="width: 100%">{{ ::certificate.privateKey }}</pre>
|
||||
<pre style="width: 100%">{{ certificate.privateKey }}</pre>
|
||||
</tab>
|
||||
</tabset>
|
||||
</td>
|
||||
|
@ -30,8 +30,7 @@
|
||||
Password
|
||||
</label>
|
||||
<div class="col-sm-10">
|
||||
<input type="password" name="password" ng-model="user.password" placeholder="hunter2" class="form-control" required/>
|
||||
<p ng-show="createForm.password.$invalid && !createForm.password.$pristine" class="help-block">You must enter an password</p>
|
||||
<input type="password" name="password" ng-model="user.password" placeholder="hunter2" class="form-control" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
|
@ -32,7 +32,7 @@
|
||||
<!-- endbuild -->
|
||||
|
||||
</head>
|
||||
<body ng-app="lemur" ng-csp>
|
||||
<body ng-csp>
|
||||
<toaster-container></toaster-container>
|
||||
<!--[if lt IE 7]>
|
||||
<p class="browsehappy">You are using an <strong>outdated</strong> browser. Please <a href="http://browsehappy.com/">upgrade your browser</a> to improve your experience.</p>
|
||||
|
@ -52,7 +52,8 @@ class User(db.Model):
|
||||
:param password:
|
||||
:return:
|
||||
"""
|
||||
return bcrypt.check_password_hash(self.password, password)
|
||||
if self.password:
|
||||
return bcrypt.check_password_hash(self.password, password)
|
||||
|
||||
def hash_password(self):
|
||||
"""
|
||||
@ -60,8 +61,9 @@ class User(db.Model):
|
||||
|
||||
:return:
|
||||
"""
|
||||
self.password = bcrypt.generate_password_hash(self.password)
|
||||
return self.password
|
||||
if self.password:
|
||||
self.password = bcrypt.generate_password_hash(self.password)
|
||||
return self.password
|
||||
|
||||
@property
|
||||
def is_admin(self):
|
||||
|
@ -157,7 +157,7 @@ class UsersList(AuthenticatedResource):
|
||||
"""
|
||||
self.reqparse.add_argument('username', type=str, location='json', required=True)
|
||||
self.reqparse.add_argument('email', type=str, location='json', required=True)
|
||||
self.reqparse.add_argument('password', type=str, location='json', required=True)
|
||||
self.reqparse.add_argument('password', type=str, location='json', default=None)
|
||||
self.reqparse.add_argument('active', type=bool, default=True, location='json')
|
||||
self.reqparse.add_argument('roles', type=roles, default=[], location='json')
|
||||
|
||||
|
3
setup.py
3
setup.py
@ -74,6 +74,8 @@ docs_require = [
|
||||
|
||||
dev_requires = [
|
||||
'flake8>=2.0,<3.0',
|
||||
'invoke',
|
||||
'twine'
|
||||
]
|
||||
|
||||
|
||||
@ -164,6 +166,7 @@ setup(
|
||||
'aws_source = lemur.plugins.lemur_aws.plugin:AWSSourcePlugin',
|
||||
'email_notification = lemur.plugins.lemur_email.plugin:EmailNotificationPlugin',
|
||||
'java_export = lemur.plugins.lemur_java.plugin:JavaExportPlugin'
|
||||
'openssl_export = lemur.plugins.lemur_openssl.plugin:OpenSSLExportPlugin'
|
||||
],
|
||||
},
|
||||
classifiers=[
|
||||
|
Reference in New Issue
Block a user