Compare commits

..

78 Commits
0.1.2 ... 0.1.4

Author SHA1 Message Date
64c9b11c09 Merge pull request #106 from kevgliss/release
version bump
2015-09-28 14:55:24 -07:00
5f87c87751 version bump 2015-09-28 14:54:58 -07:00
70f9022aae Merge pull request #104 from kevgliss/guide
Adding connections in user guides
2015-09-24 16:28:52 -07:00
43683fe554 changing readme language 2015-09-24 16:09:34 -07:00
002de6f5e4 adding docker Link 2015-09-24 16:03:15 -07:00
63a388236e adding a link to our techblog 2015-09-24 14:36:14 -07:00
9560791002 Merge pull request #99 from pandragoq/patch-1
Update index.rst
2015-09-24 14:28:06 -07:00
ed93b5a2c5 SSL 2015-09-24 09:36:11 -07:00
21e4cc9f4d Adding connections in user guides 2015-09-24 09:21:08 -07:00
73e628cbdf Merge pull request #103 from kevgliss/required
Marking fields as required
2015-09-24 08:52:46 -07:00
7ebd0bf5d4 making fields required 2015-09-24 08:42:31 -07:00
3f1902e0fe Merge pull request #100 from ivuk/fix-typo
Fix typos in docs/administration/index.rst
2015-09-23 16:44:49 -07:00
3e546eaa21 Fix typos in docs/administration/index.rst 2015-09-23 21:00:52 +02:00
e70deb155d Update index.rst
Right package for postgres is postgresql in ubuntu.
2015-09-22 16:57:53 -07:00
4f289c790b Merge pull request #98 from stacybird/grammar_fix
Fix grammar in index.rst
2015-09-22 15:48:37 -07:00
c15f525167 Fix grammar in index.rst 2015-09-22 15:33:37 -07:00
bcbf642122 Merge pull request #96 from kevgliss/install
clearing up docs based on feedback
2015-09-22 14:54:11 -07:00
1559727f2d Making make build the static assets 2015-09-22 14:49:37 -07:00
a596793a9a clearing up docs based on feedback 2015-09-22 14:18:38 -07:00
862bf3f619 Merge pull request #94 from kevgliss/notifications
Notifications
2015-09-22 13:37:51 -07:00
83a86c06a4 Merge pull request #93 from pandragoq/patch-1
Update index.rst
2015-09-22 13:37:40 -07:00
06a69c09a0 Fixing a bug where notifications associated during certificate creation would not be respected. 2015-09-22 13:01:05 -07:00
6a24e88d9a removing pip install instructions until available 2015-09-22 10:22:12 -07:00
be6a5b859e adding notification example 2015-09-22 09:46:54 -07:00
2444191bf2 Update index.rst
Typo on nginx spelling
2015-09-21 17:43:56 -07:00
9226b1eb4a Merge pull request #92 from konklone/patch-1
Rename SSL to TLS in many places
2015-09-21 15:25:00 -07:00
3f53629175 Re 2015-09-21 18:16:40 -04:00
baef329a4d Rename SSL to TLS 2015-09-21 18:16:19 -04:00
b103fc7bfb Rename SSL to TLS 2015-09-21 18:16:04 -04:00
a3385bd2ac Rename SSL to TLS 2015-09-21 18:15:25 -04:00
7cb50c654b Rename SSL to TLS 2015-09-21 18:15:06 -04:00
52ba538037 Rename SSL to TLS 2015-09-21 18:14:12 -04:00
0a0460529f Merge pull request #89 from kevgliss/cleanup
Cleaning up unneed/unused files
2015-09-20 10:21:04 -07:00
fc0a884d5f Cleaning up unneed/unused files 2015-09-20 09:49:16 -07:00
dbbea29e75 Merge pull request #88 from kevgliss/requirements
adding additional requirements so rtd can build the documation correctly
2015-09-19 11:32:57 -07:00
bcd0aae8c6 adding additional requirements so rtd can build the documation correctly 2015-09-19 11:31:31 -07:00
50d3e6aff2 Merge pull request #87 from kevgliss/typo
fixing typo
2015-09-19 10:25:52 -07:00
1d45926122 fixing typo 2015-09-19 10:24:56 -07:00
45626c947c Merge pull request #86 from kevgliss/docs
More documentation fixes
2015-09-19 10:21:56 -07:00
d7ca6d4327 More documentation fixes 2015-09-19 10:12:12 -07:00
6411bd56e9 Merge pull request #85 from kevgliss/documentation
Documentation
2015-09-19 09:48:25 -07:00
1486e7b8f6 adding information about sub commands 2015-09-19 09:41:50 -07:00
e73f2bcb2b setting default theme 2015-09-19 09:38:39 -07:00
a412569ff7 aligning doc version with tagged version 2015-09-19 09:34:48 -07:00
387194d651 Merge pull request #84 from kevgliss/docs
Adding flask sphinx auto-docs
2015-09-18 17:29:17 -07:00
13d0359041 Adding flask sphinx auto-docs 2015-09-18 17:28:48 -07:00
365d927efb Update README.rst 2015-09-18 16:28:45 -07:00
aa76379d6c Merge pull request #81 from kevgliss/pipy
Minor fixes
2015-09-18 15:56:06 -07:00
ef72de89b3 Minor fixes 2015-09-18 15:50:59 -07:00
e2962a4b8d Merge pull request #79 from kevgliss/order
fixing an error where dates components were not replaced in logical o…
2015-09-16 11:14:47 -07:00
a563986ce4 fixing an error where dates components were not replaced in logical order 2015-09-16 11:10:09 -07:00
a4e294634a Merge pull request #78 from kevgliss/docs
Doc fixes
2015-09-14 15:28:42 -07:00
067122f8f4 improving docs 2015-09-14 13:46:39 -07:00
6a1a744eff removing duplicate route 2015-09-12 10:05:58 -07:00
d3cf273a45 Merge pull request #72 from kevgliss/docker
[WIP] Docker
2015-09-11 15:36:25 -07:00
52e267468a Merge pull request #76 from kevgliss/verisignSource
Verisign source
2015-09-11 08:49:11 -07:00
e80b58899d following RFC 2015-09-11 08:39:27 -07:00
25f652c1eb fixing merge conflict 2015-09-11 08:38:48 -07:00
0dde4c9f80 removing source plugin form being installed atm 2015-09-11 08:36:55 -07:00
a512b82196 Merge pull request #75 from kevgliss/fixes
Fixes
2015-09-11 08:36:24 -07:00
7f119e95e1 making the verisign urls more generic 2015-09-11 08:27:34 -07:00
bf957d2509 moving to hotfix version of cryptography 2015-09-11 08:19:35 -07:00
1e314b505f fixing keyerror 2015-09-08 18:18:14 -07:00
ef9a80ebfd adding actual recipients 2015-09-08 18:03:18 -07:00
84d0afae4c fixing email internvals 2015-09-08 17:56:20 -07:00
48a53ad436 fixing error in default password creation 2015-09-08 17:42:57 -07:00
2f4aee49e2 adding logging 2015-09-08 10:56:23 -07:00
f3f5b9eeb3 adding password commandline option 2015-09-08 10:56:23 -07:00
541d5420bb Merge pull request #73 from kevgliss/cleanup
Cleanup
2015-09-08 08:43:56 -07:00
0383e2a1e1 adding manifest 2015-09-07 22:14:18 -07:00
084604cf3c fixing setup.py 2015-09-07 21:54:23 -07:00
8ab9c06778 removing more netflix 2015-09-04 15:54:52 -07:00
0afd4c94b4 removing more netflix 2015-09-04 15:54:02 -07:00
aaae4d5a1f unifying lemur defaults 2015-09-04 15:52:56 -07:00
9da713ab06 cleaning up references to netflix 2015-09-04 15:29:57 -07:00
540d56f0b8 Merge pull request #71 from kevgliss/notifications
Fixing issue with expiration emails not being sent
2015-09-04 09:46:06 -07:00
160eaa6901 Fixing issue with expiration emails not being sent 2015-09-04 09:24:55 -07:00
180c8228e1 adding verisign source 2015-09-02 14:37:07 -07:00
52 changed files with 509 additions and 725 deletions

View File

@ -1,2 +1,2 @@
- Kevin Glisson (kglisson@netflix.com)
- Kevin Glisson <kglisson@netflix.com>
- Jeremy Heffner <jheffner@netflix.com>

4
MANIFEST.in Normal file
View File

@ -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 *~

View File

@ -9,6 +9,8 @@ develop: update-submodules setup-git
pip install -e .
pip install "file://`pwd`#egg=lemur[dev]"
pip install "file://`pwd`#egg=lemur[tests]"
node_modules/.bin/gulp build
node_modules/.bin/gulp package
@echo ""
dev-docs:

View File

@ -13,18 +13,20 @@ Lemur
:target: https://lemur.readthedocs.org
:alt: Latest Docs
.. image:: https://magnum.travis-ci.com/Netflix/lemur.svg?branch=master
:target: https://magnum.travis-ci.com/Netflix/lemur
.. image:: https://travis-ci.org/Netflix/lemur.svg
:target: https://travis-ci.org/Netflix/lemur
Lemur manages TLS certificate creation. While not able to issue certificates itself, Lemur acts as a broker between CAs
and environments providing a central portal for developers to issue TLS certificates with 'sane' defaults.
Lemur manages SSL certificate creation. It provides a central portal for developers to issuer their own SSL certificates with 'sane' defaults.
It works on CPython 2.7, 3.3, 3.4 It is known
to work on Ubuntu Linux and OS X.
It works on CPython 2.7, 3.3, 3.4. We deploy on Ubuntu and develop on OS X.
Project resources
=================
- `Lemur Blog Post <http://techblog.netflix.com/2015/09/introducing-lemur.html>`_
- `Documentation <http://lemur.readthedocs.org/>`_
- `Source code <https://github.com/netflix/lemur>`_
- `Issue tracker <https://github.com/netflix/lemur/issues>`_
- `Docker <https://github.com/Netflix/lemur-docker>`_

View File

@ -2,7 +2,7 @@ Configuration
=============
.. warning::
There are many secrets that Lemur uses that must be protected. All of these options are set via the Lemur configruation
There are many secrets that Lemur uses that must be protected. All of these options are set via the Lemur configuration
file. It is highly advised that you do not store your secrets in this file! Lemur provides functions
that allow you to encrypt files at rest and decrypt them when it's time for deployment. See :ref:`Credential Management <CredentialManagement>`
for more information.
@ -74,8 +74,6 @@ Basic Configuration
The TOKEN_SECRET is the secret used to create JWT tokens that are given out to users. This should be securely generated and be kept private.
See `SECRET_KEY` for methods on secure secret generation.
::
LEMUR_TOKEN_SECRET = 'supersecret'
@ -122,7 +120,7 @@ and are used when Lemur creates the CSR for your certificates.
::
LEMUR_DEFAULT_STATE = "CA"
LEMUR_DEFAULT_STATE = "California"
.. data:: LEMUR_DEFAULT_LOCATION
@ -152,15 +150,16 @@ and are used when Lemur creates the CSR for your certificates.
Notification Options
--------------------
Lemur currently has very basic support for notifications. Notifications are sent to the certificate creator, owner and
security team as specified by the `SECURITY_TEAM_EMAIL` configuration parameter.
Lemur currently has very basic support for notifications. Currently only expiration notifications are supported. Actual notification
is handled by the notification plugins that you have configured. Lemur ships with the 'Email' notification that allows expiration emails
to be sent to subscribers.
The template for all of these notifications lives under lemur/template/event.html and can be easily modified to fit your
needs.
Templates for expiration emails are located under `lemur/plugins/lemur_email/templates` and can be modified for your needs.
Notifications are sent to the certificate creator, owner and security team as specified by the `LEMUR_SECURITY_TEAM_EMAIL` configuration parameter.
Certificates marked as in-active will **not** be notified of upcoming expiration. This enables a user to essentially
silence the expiration. If a certificate is active and is expiring the above will be notified at 30, 15, 5, 2 days
respectively.
silence the expiration. If a certificate is active and is expiring the above will be notified according to the `LEMUR_DEFAULT_EXPIRATION_NOTIFICATION_INTERVALS` or
30, 15, 2 days before expiration if no intervals are set.
Lemur supports sending certification expiration notifications through SES and SMTP.
@ -197,11 +196,22 @@ Lemur supports sending certification expiration notifications through SES and SM
LEMUR_SECURITY_TEAM_EMAIL = ['security@example.com']
.. data:: LEMUR_DEFAULT_EXPIRATION_NOTIFICATION_INTERVALS
:noindex:
Lemur notification intervals
::
LEMUR_DEFAULT_EXPIRATION_NOTIFICATION_INTERVALS = [30, 15, 2]
Authority Options
-----------------
Authorities will each have their own configuration options. There are currently two plugins bundled with Lemur,
Verisign/Symantec and CloudCA
Authorities will each have their own configuration options. There is currently just one plugin bundled with Lemur,
Verisign/Symantec. Additional plugins may define additional options. Refer to the plugins own documentation
for those plugins.
.. data:: VERISIGN_URL
:noindex:
@ -212,28 +222,41 @@ Verisign/Symantec and CloudCA
.. data:: VERISIGN_PEM_PATH
:noindex:
This is the path to the mutual SSL certificate used for communicating with Verisign
This is the path to the mutual TLS certificate used for communicating with Verisign
.. data:: CLOUDCA_URL
.. data:: VERISIGN_FIRST_NAME
:noindex:
This is the URL for CLoudCA API
This is the first name to be used when requesting the certificate
.. data:: CLOUDCA_PEM_PATH
.. data:: VERISIGN_LAST_NAME
:noindex:
This is the path to the mutual SSL Certificate use for communicating with CLOUDCA
This is the last name to be used when requesting the certificate
.. data:: CLOUDCA_BUNDLE
.. data:: VERISIGN_EMAIL
:noindex:
This is the path to the CLOUDCA certificate bundle
This is the email to be used when requesting the certificate
.. data:: VERISIGN_INTERMEDIATE
:noindex:
This is the intermediate to be used for your CA chain
.. data:: VERISIGN_ROOT
:noindex:
This is the root to be used for your CA chain
Authentication
--------------
Lemur currently supports Basic Authentication and Ping OAuth2 out of the box, additional flows can be added relatively easily
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.
For more information about how to use social logins, see: `Satellizer <https://github.com/sahat/satellizer>`_
@ -274,20 +297,20 @@ AWS Plugin Configuration
In order for Lemur to manage it's own account and other accounts we must ensure it has the correct AWS permissions.
.. note:: AWS usage is completely optional. Lemur can upload, find and manage SSL certificates in AWS. But is not required to do so.
.. note:: AWS usage is completely optional. Lemur can upload, find and manage TLS certificates in AWS. But is not required to do so.
Setting up IAM roles
--------------------
Lemur's aws plugin uses boto heavily to talk to all the AWS resources it manages. By default it uses the on-instance credentials to make the necessary calls.
Lemur's AWS plugin uses boto heavily to talk to all the AWS resources it manages. By default it uses the on-instance credentials to make the necessary calls.
In order to limit the permissions we will create a new two IAM roles for Lemur. You can name them whatever you would like but for example sake we will be calling them LemurInstanceProfile and Lemur.
In order to limit the permissions, we will create two new IAM roles for Lemur. You can name them whatever you would like but for example sake we will be calling them LemurInstanceProfile and Lemur.
Lemur uses to STS to talk to different accounts. For managing one account this isn't necessary but we will still use it so that we can easily add new accounts.
LemurInstanceProfile is the IAM role you will launch your instance with. It actually has almost no rights. In fact it should really only be able to use STS to assume role to the Lemur role.
Here is are example polices for the LemurInstanceProfile:
Here are example policies for the LemurInstanceProfile:
SES-SendEmail
@ -327,6 +350,11 @@ STS-AssumeRole
Next we will create the the Lemur IAM role. Lemur
..note::
The default IAM role that Lemur assumes into is called `Lemur`, if you need to change this ensure you set `LEMUR_INSTANCE_PROFILE` to your role name in the configuration.
Here is an example policy for Lemur:
IAM-ServerCertificate
@ -451,7 +479,7 @@ Upgrading Lemur
===============
Lemur provides an easy way to upgrade between versions. Simply download the newest
version of Lemur from pypi and then apply any schema cahnges with the following command.
version of Lemur from pypi and then apply any schema changes with the following command.
.. code-block:: bash
@ -524,24 +552,6 @@ All commands default to `~/.lemur/lemur.conf.py` if a configuration is not speci
lemur db upgrade
.. data:: create_user
Creates new users within Lemur.
::
lemur create_user -u jim -e jim@example.com
.. data:: create_role
Creates new roles within Lemur.
::
lemur create_role -n example -d "a new role"
.. data:: check_revoked
Traverses every certificate that Lemur is aware of and attempts to understand it's validity.
@ -566,11 +576,31 @@ All commands default to `~/.lemur/lemur.conf.py` if a configuration is not speci
lemur sync -list
Sub-commands
------------
Lemur includes several sub-commands for interacting with Lemur such as creating new users, creating new roles and even
issuing certificates.
The best way to discover these commands is by using the built in help pages
::
lemur --help
and to get help on sub-commands
::
lemur certificates --help
Identity and Access Management
==============================
Lemur uses a Role Based Access Control (RBAC) mechanism to control which users have access to which resources. When a
user is first created in Lemur the can be assigned one or more roles. These roles are typically dynamically created
user is first created in Lemur they can be assigned one or more roles. These roles are typically dynamically created
depending on a external identity provider (Google, LDAP, etc.,) or are hardcoded within Lemur and associated with special
meaning.
@ -584,7 +614,8 @@ that the `Authority` is associated with it Lemur allows that user to user/view/u
This RBAC is also used when determining which users can access which certificate private key. Lemur's current permission
structure is setup such that if the user is a `Creator` or `Owner` of a given certificate they are allow to view that
private key.
private key. Owners can also be a role name, such that any user with the same role as owner will be allowed to view the
private key information.
These permissions are applied to the user upon login and refreshed on every request.

View File

@ -1,6 +1,6 @@
# -*- coding: utf-8 -*-
#
# security_monkey documentation build configuration file, created by
# lemur documentation build configuration file, created by
# sphinx-quickstart on Sat Jun 7 18:43:48 2014.
#
# This file is execfile()d with the current directory set to its
@ -57,7 +57,7 @@ copyright = u'2015, Netflix Inc.'
# The short X.Y version.
version = '0.1'
# The full version, including alpha/beta/rc tags.
release = '0.1.1'
release = '0.1.3'
# The language for content autogenerated by Sphinx. Refer to documentation
# for a list of supported languages.
@ -102,7 +102,7 @@ pygments_style = 'sphinx'
# The theme to use for HTML and HTML Help pages. See the documentation for
# a list of builtin themes.
html_theme = 'alabaster'
html_theme = 'default'
# Theme options are theme-specific and customize the look and feel of a theme
# further. For a list of options available for each theme, see the

View File

@ -1,20 +0,0 @@
lemur_cloudca Package
=====================
:mod:`lemur_cloudca` Package
----------------------------
.. automodule:: lemur.plugins.lemur_cloudca
:noindex:
:members:
:undoc-members:
:show-inheritance:
:mod:`plugin` Module
--------------------
.. automodule:: lemur.plugins.lemur_cloudca.plugin
:noindex:
:members:
:undoc-members:
:show-inheritance:

View File

@ -8,7 +8,7 @@ Several interfaces exist for extending Lemur:
* Source (lemur.plugins.base.source)
* Notification (lemur.plugins.base.notification)
Each interface has its own function that will need to be defined in order for
Each interface has its own functions that will need to be defined in order for
your plugin to work correctly. See :ref:`Plugin Interfaces <PluginInterfaces>` for details.
@ -91,7 +91,7 @@ Issuer
Issuer plugins are used when you have an external service that creates certificates or authorities.
In the simple case the third party only issues certificates (Verisign, DigiCert, etc.).
If you have a third party or internal service that creates authorities (CloudCA, EJBCA, etc.), Lemur has you covered,
If you have a third party or internal service that creates authorities (EJBCA, etc.), Lemur has you covered,
it can treat any issuer plugin as both a source of creating new certificates as well as new authorities.
@ -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 guarentee 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.

Binary file not shown.

After

Width:  |  Height:  |  Size: 125 KiB

BIN
docs/guide/create.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 44 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 57 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 56 KiB

BIN
docs/guide/create_role.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 50 KiB

BIN
docs/guide/create_user.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 39 KiB

View File

@ -1,14 +1,103 @@
Creating Users
==============
User Guide
==========
These guides are quick tutorials on how to perform basic tasks in Lemur.
Creating Roles
==============
Create a New Authority
~~~~~~~~~~~~~~~~~~~~~~
Before Lemur can issue certificates you must configure the authority you wish use. Lemur itself does
not issue certificates, it relies on external CAs and the plugins associated with those CAs to create the certificate
that Lemur can then manage.
Creating Authorities
====================
.. figure:: create.png
In the authority table select "Create"
.. figure:: create_authority.png
Enter a authority name and short description about the authority. Enter an owner,
and certificate common name. Depending on the authority and the authority/issuer plugin
these values may or may not be used.
.. figure:: create_authority_options.png
Again how many of these values get used largely depends on the underlying plugin. It
is important to make sure you select the right plugin that you wish to use.
Create a New Certificate
~~~~~~~~~~~~~~~~~~~~~~~~
.. figure:: create.png
In the certificate table select "Create"
.. figure:: create_certificate.png
Enter a owner, short description and the authority you wish to issue this certificate.
Enter a common name into the certificate, if no validity range is selected two years is
the default.
You can add notification options and upload the created certificate to a destination, both
of these are editable features and can be changed after the certificate has been created.
.. figure:: certificate_extensions.png
These options are typically for advanced users, the one exception is the `Subject Alternate Names` or SAN.
For certificates that need to include more than one domains, the first domain is the Common Name and all
other domains are added here as DNSName entries.
Import an Existing Certificate
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
.. figure:: upload_certificate.png
Enter a owner, short description and public certificate. If there are intermediates and private keys
Lemur will track them just as it does if the certificate were created through Lemur. Lemur generates
a certificate name but you can override that by passing a value to the `Custom Name` field.
You can add notification options and upload the created certificate to a destination, both
of these are editable features and can be changed after the certificate has been created.
Create a New User
~~~~~~~~~~~~~~~~~
.. figure:: settings.png
From the settings dropdown select "Users"
.. figure:: create.png
In the user table select "Create"
.. figure:: create_user.png
Enter the username, email and password for the user. You can also assign any
roles that the user will need when they login. While there is no deletion
(we want to track creators forever) you can mark a user as 'Inactive' that will
not allow them to login to Lemur.
Create a New Role
~~~~~~~~~~~~~~~~~
.. figure:: settings.png
From the settings dropdown select "Roles"
.. figure:: create.png
In the role table select "Create"
.. figure:: create_role.png
Enter a role name and short description about the role. You can optionally store
a user/password on the role. This is useful if your authority require specific roles.
You can then accurately map those roles onto Lemur users. Also optional you can assign
users to your new role.
Creating Certificates
=====================

BIN
docs/guide/settings.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 73 KiB

View File

@ -1,8 +1,8 @@
Lemur
=====
Lemur is a SSL management service. It attempts to help track and create certificates. By removing common issues with
CSR creation it gives normal developers 'sane' SSL defaults and helps security teams push SSL usage throughout an organization.
Lemur is a TLS management service. It attempts to help track and create certificates. By removing common issues with
CSR creation it gives normal developers 'sane' TLS defaults and helps security teams push TLS usage throughout an organization.
Installation
------------

View File

@ -6,18 +6,19 @@ There are several steps needed to make Lemur production ready. Here we focus on
Basics
======
Because of the sensitivity of the information stored and maintain by Lemur it is important that you follow standard host hardening practices:
Because of the sensitivity of the information stored and maintained by Lemur it is important that you follow standard host hardening practices:
- Run Lemur with a limited user
- Disabled any unneeded service
- Disabled any unneeded services
- Enable remote logging
- Restrict access to host
.. _CredentialManagement:
Credential Management
---------------------
Lemur often contains credentials such as mutual SSL keys that are used to communicate with third party resources and for encrypting stored secrets. Lemur comes with the ability
Lemur often contains credentials such as mutual TLS keys or API tokens that are used to communicate with third party resources and for encrypting stored secrets. Lemur comes with the ability
to automatically encrypt these keys such that your keys not be in clear text.
The keys are located within lemur/keys and broken down by environment
@ -30,7 +31,7 @@ and
``lemur unlock``
If you choose to use this feature ensure that the KEY are decrypted before Lemur starts as it will have trouble communicating with the database otherwise.
If you choose to use this feature ensure that the keys are decrypted before Lemur starts as it will have trouble communicating with the database otherwise.
Entropy
-------
@ -56,8 +57,8 @@ For additional information about OpenSSL entropy issues:
- `Managing and Understanding Entropy Usage <https://www.blackhat.com/docs/us-15/materials/us-15-Potter-Understanding-And-Managing-Entropy-Usage.pdf>`_
SSL
====
TLS/SSL
=======
Nginx
-----
@ -127,10 +128,10 @@ You can make some adjustments to get a better user experience::
}
This makes Nginx serve the favicon and static files which is is much better at than python.
This makes Nginx serve the favicon and static files which it is much better at than python.
It is highly recommended that you deploy SSL when deploying Lemur. This may be obvious given Lemur's purpose but the
sensitive nature of Lemur and what it controls makes this essential. This is a sample config for Lemur that also terminates SSL::
It is highly recommended that you deploy TLS when deploying Lemur. This may be obvious given Lemur's purpose but the
sensitive nature of Lemur and what it controls makes this essential. This is a sample config for Lemur that also terminates TLS::
server_tokens off;
add_header X-Frame-Options DENY;
@ -218,7 +219,7 @@ An example apache config::
...
</VirtualHost>
Also included in the configurations above are several best practices when it comes to deploying SSL. Things like enabling
Also included in the configurations above are several best practices when it comes to deploying TLS. Things like enabling
HSTS, disabling vulnerable ciphers are all good ideas when it comes to deploying Lemur into a production environment.
.. note::

View File

@ -4,6 +4,8 @@ Quickstart
This guide will step you through setting up a Python-based virtualenv, installing the required packages, and configuring the basic web service.
This guide assumes a clean Ubuntu 14.04 instance, commands may differ based on the OS and configuration being used.
Pressed for time? See the Lemur docker file on `Github <https://github.com/Netflix/lemur-docker>`_.
Dependencies
------------
@ -12,12 +14,13 @@ Some basic prerequisites which you'll need in order to run Lemur:
* A UNIX-based operating system. We test on Ubuntu, develop on OS X
* Python 2.7
* PostgreSQL
* Ngnix
* Nginx
.. note:: Lemur was built with in AWS in mind. This means that things such as databases (RDS), mail (SES), and SSL (ELB),
.. note:: Lemur was built with in AWS in mind. This means that things such as databases (RDS), mail (SES), and TLS (ELB),
are largely handled for us. Lemur does **not** require AWS to function. Our guides and documentation try to be
be as generic as possible and are not intended to document every step of launching Lemur into a given environment.
Setting up an Environment
-------------------------
@ -50,24 +53,7 @@ dependencies::
And optionally if your database is going to be on the same host as the webserver::
$ sudo apt-get install postgres
Installing Lemur
----------------
Once you've got the environment setup, you can install Lemur and all its dependencies with
the same command you used to grab virtualenv::
pip install -U lemur
Once everything is installed, you should be able to execute the Lemur CLI, via ``lemur``, and get something
like the following:
.. code-block:: bash
$ lemur
usage: lemur [--config=/path/to/settings.py] [command] [options]
$ sudo apt-get install postgresql
Installing from Source
@ -75,7 +61,14 @@ Installing from Source
If you're installing the Lemur source (e.g. from git), you'll also need to install **npm**.
Once your system is prepared, symlink your source into the virtualenv:
Once your system is prepared, ensure that you are in the virtualenv:
.. code-block:: bash
$ which python
And then run:
.. code-block:: bash
@ -168,8 +161,8 @@ Setup a Reverse Proxy
---------------------
By default, Lemur runs on port 5000. Even if you change this, under normal conditions you won't be able to bind to
port 80. To get around this (and to avoid running Lemur as a privileged user, which you shouldn't), we recommend
you setup a simple web proxy.
port 80. To get around this (and to avoid running Lemur as a privileged user, which you shouldn't), we need setup a
simple web proxy. There are many different web servers you can use for this, we like and recommend Nginx.
Proxying with Nginx
~~~~~~~~~~~~~~~~~~~
@ -194,12 +187,6 @@ You'll use the builtin HttpProxyModule within Nginx to handle proxying
index index.html;
}
location / {
root /www/lemur/lemur/static/dist;
include mime.types;
index index.html;
}
See :doc:`../production/index` for more details on using Nginx.
@ -283,7 +270,9 @@ Decrypts sensitive key material - Used to decrypt the secrets stored in source d
What's Next?
------------
The above gets you going, but for production there are several different security considerations to take into account,
Get familiar with how Lemur works by reviewing the :doc:`../guide/index`. When you're ready
see :doc:`../production/index` for more details on how to configure Lemur for production.
Remember the above just gets you going, but for production there are several different security considerations to take into account,
remember Lemur is handling sensitive data and security is imperative.
See :doc:`../production/index` for more details on how to configure Lemur for production.

View File

@ -2,4 +2,28 @@ Jinja2>=2.3
Pygments>=1.2
Sphinx>=1.3
docutils>=0.7
markupsafe
markupsafe
sphinxcontrib-httpdomain
Flask==0.10.1
Flask-RESTful==0.3.3
Flask-SQLAlchemy==2.0
Flask-Script==2.0.5
Flask-Migrate==1.4.0
Flask-Bcrypt==0.6.2
Flask-Principal==0.4.0
Flask-Mail==0.9.1
SQLAlchemy-Utils==0.30.11
BeautifulSoup4
requests==2.7.0
psycopg2==2.6.1
arrow==0.5.4
boto==2.38.0 # we might make this optional
six==1.9.0
gunicorn==19.3.0
pycrypto==2.6.1
cryptography==1.0.1
pyopenssl==0.15.1
pyjwt==1.0.1
xmltodict==0.9.2
lockfile==0.10.2
future==0.15.0

View File

@ -17,7 +17,7 @@ from lemur.domains.views import mod as domains_bp
from lemur.destinations.views import mod as destinations_bp
from lemur.authorities.views import mod as authorities_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.notifications.views import mod as notifications_bp
from lemur.sources.views import mod as sources_bp
@ -31,7 +31,7 @@ LEMUR_BLUEPRINTS = (
destinations_bp,
authorities_bp,
certificates_bp,
status_bp,
defaults_bp,
plugins_bp,
notifications_bp,
sources_bp

View File

@ -125,7 +125,7 @@ class Ping(Resource):
args = self.reqparse.parse_args()
# take the information we have received from Meechum to create a new request
# take the information we have received from the provider to create a new request
params = {
'client_id': args['clientId'],
'grant_type': 'authorization_code',
@ -138,7 +138,7 @@ class Ping(Resource):
access_token_url = current_app.config.get('PING_ACCESS_TOKEN_URL')
user_api_url = current_app.config.get('PING_USER_API_URL')
# the secret and cliendId will be given to you when you signup for meechum
# the secret and cliendId will be given to you when you signup for the provider
basic = base64.b64encode('{0}:{1}'.format(args['clientId'], current_app.config.get("PING_SECRET")))
headers = {'Authorization': 'Basic {0}'.format(basic)}
@ -220,7 +220,7 @@ class Ping(Resource):
profile['email'],
profile['email'],
True,
profile.get('thumbnailPhotoUrl'), # Encase profile isn't google+ enabled
profile.get('thumbnailPhotoUrl'), # incase profile isn't google+ enabled
roles
)

View File

@ -232,7 +232,7 @@ def create(**kwargs):
database.update_list(cert, 'notifications', Notification, kwargs.get('notifications'))
# create default notifications for this certificate if none are provided
notifications = []
notifications = cert.notifications
if not kwargs.get('notifications'):
notification_name = "DEFAULT_{0}".format(cert.owner.split('@')[0].upper())
notifications += notification_service.create_default_expiration_notifications(notification_name, [cert.owner])

View File

@ -7,7 +7,7 @@
"""
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 cryptography import x509
@ -208,6 +208,46 @@ class CertificatesList(AuthenticatedResource):
"notAfter": "2015-06-17T15:21:08",
"description": "dsfdsf"
},
"notifications": [
{
"description": "Default 30 day expiration notification",
"notificationOptions": [
{
"name": "interval",
"required": true,
"value": 30,
"helpMessage": "Number of days to be alert before expiration.",
"validation": "^\\d+$",
"type": "int"
},
{
"available": [
"days",
"weeks",
"months"
],
"name": "unit",
"required": true,
"value": "days",
"helpMessage": "Interval unit",
"validation": "",
"type": "select"
},
{
"name": "recipients",
"required": true,
"value": "bob@example.com",
"helpMessage": "Comma delimited list of email addresses",
"validation": "^([\\w+-.%]+@[\\w-.]+\\.[A-Za-z]{2,4},?)+$",
"type": "str"
}
],
"label": "DEFAULT_KGLISSON_30_DAY",
"pluginName": "email-notification",
"active": true,
"id": 7
}
],
"extensions": {
"basicConstraints": {},
"keyUsage": {
@ -276,18 +316,17 @@ class CertificatesList(AuthenticatedResource):
self.reqparse.add_argument('extensions', type=dict, location='json')
self.reqparse.add_argument('destinations', type=list, default=[], location='json')
self.reqparse.add_argument('notifications', type=list, default=[], location='json')
self.reqparse.add_argument('owner', type=str, location='json')
self.reqparse.add_argument('validityStart', type=str, location='json') # TODO validate
self.reqparse.add_argument('validityEnd', type=str, location='json') # TODO validate
self.reqparse.add_argument('authority', type=valid_authority, location='json')
self.reqparse.add_argument('description', type=str, location='json')
self.reqparse.add_argument('country', type=str, location='json')
self.reqparse.add_argument('state', type=str, location='json')
self.reqparse.add_argument('location', type=str, location='json')
self.reqparse.add_argument('organization', type=str, location='json')
self.reqparse.add_argument('organizationalUnit', type=str, location='json')
self.reqparse.add_argument('owner', type=str, location='json')
self.reqparse.add_argument('commonName', type=str, location='json')
self.reqparse.add_argument('authority', type=valid_authority, location='json', required=True)
self.reqparse.add_argument('description', type=str, location='json', required=True)
self.reqparse.add_argument('country', type=str, location='json', required=True)
self.reqparse.add_argument('state', type=str, location='json', required=True)
self.reqparse.add_argument('location', type=str, location='json', required=True)
self.reqparse.add_argument('organization', type=str, location='json', required=True)
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)
args = self.reqparse.parse_args()
@ -668,58 +707,9 @@ class NotificationCertificatesList(AuthenticatedResource):
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(Certificates, '/certificates/<int:certificate_id>', endpoint='certificate')
api.add_resource(CertificatesStats, '/certificates/stats', endpoint='certificateStats')
api.add_resource(CertificatesUpload, '/certificates/upload', endpoint='certificateUpload')
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(CertificatesDefaults, '/certificates/defaults', endpoint='certificatesDefault')

63
lemur/defaults/views.py Normal file
View File

@ -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')

View File

@ -112,13 +112,6 @@ SQLALCHEMY_DATABASE_URI = 'postgresql://lemur:lemur@localhost:5432/lemur'
# These will be dependent on which 3rd party that Lemur is
# configured to use.
# CLOUDCA_URL = ''
# CLOUDCA_PEM_PATH = ''
# CLOUDCA_BUNDLE = ''
# number of years to issue if not specified
# CLOUDCA_DEFAULT_VALIDITY = 2
# VERISIGN_URL = ''
# VERISIGN_PEM_PATH = ''
# VERISIGN_FIRST_NAME = ''
@ -263,18 +256,23 @@ class InitializeApp(Command):
Additionally a Lemur user will be created as a default user
and be used when certificates are discovered by Lemur.
"""
def run(self):
option_list = (
Option('-p', '--password', dest='password'),
)
def run(self, password):
create()
user = user_service.get_by_username("lemur")
if not user:
sys.stdout.write("We need to set Lemur's password to continue!\n")
password1 = prompt_pass("Password")
password2 = prompt_pass("Confirm Password")
if not password:
sys.stdout.write("We need to set Lemur's password to continue!\n")
password = prompt_pass("Password")
password1 = prompt_pass("Confirm Password")
if password1 != password2:
sys.stderr.write("[!] Passwords do not match!\n")
sys.exit(1)
if password != password1:
sys.stderr.write("[!] Passwords do not match!\n")
sys.exit(1)
role = role_service.get_by_name('admin')
@ -285,16 +283,16 @@ class InitializeApp(Command):
role = role_service.create('admin', description='this is the lemur administrator role')
sys.stdout.write("[+] Created 'admin' role\n")
user_service.create("lemur", password1, 'lemur@nobody', True, None, [role])
user_service.create("lemur", password, 'lemur@nobody', True, None, [role])
sys.stdout.write("[+] Added a 'lemur' user and added it to the 'admin' role!\n")
else:
sys.stdout.write("[-] Default user has already been created, skipping...!\n")
sys.stdout.write("[+] Creating expiration email notifications!\n")
sys.stdout.write("[!] Using {recipients} as specified by LEMUR_SECURITY_TEAM_EMAIL for notifications\n")
sys.stdout.write("[!] Using {0} as specified by LEMUR_SECURITY_TEAM_EMAIL for notifications\n".format("LEMUR_SECURITY_TEAM_EMAIL"))
intervals = current_app.config.get("LEMUR_DEFAULT_EXPIRATION_NOTIFICATION_INTERVALS")
intervals = current_app.config.get("LEMUR_DEFAULT_EXPIRATION_NOTIFICATION_INTERVALS", [])
sys.stdout.write(
"[!] Creating {num} notifications for {intervals} days as specified by LEMUR_DEFAULT_EXPIRATION_NOTIFICATION_INTERVALS\n".format(
num=len(intervals),
@ -569,11 +567,11 @@ class ProvisionELB(Command):
'authority': authority,
'owner': owner,
# defaults:
'organization': u'Netflix, Inc.',
'organizationalUnit': u'Operations',
'country': u'US',
'state': u'California',
'location': u'Los Gatos'
'organization': current_app.config.get('LEMUR_DEFAULT_ORGANIZATION'),
'organizationalUnit': current_app.config.get('LEMUR_DEFAULT_ORGANIZATIONAL_UNIT'),
'country': current_app.config.get('LEMUR_DEFAULT_COUNTRY'),
'state': current_app.config.get('LEMUR_DEFAULT_STATE'),
'location': current_app.config.get('LEMUR_DEFAULT_LOCATION')
}
return options

View File

@ -9,7 +9,6 @@
"""
import ssl
import socket
import arrow
@ -114,8 +113,9 @@ def _get_domain_certificate(name):
try:
pub_key = ssl.get_server_certificate((name, 443))
return cert_service.find_duplicates(pub_key.strip())
except socket.gaierror as e:
except Exception as e:
current_app.logger.info(str(e))
return []
def _find_superseded(cert):

View File

@ -1,5 +0,0 @@
try:
VERSION = __import__('pkg_resources') \
.get_distribution(__name__).version
except Exception as e:
VERSION = 'unknown'

View File

@ -1,364 +0,0 @@
"""
.. module: lemur.common.services.issuers.plugins.cloudca
: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 re
import ssl
import base64
from json import dumps
import arrow
import requests
from requests.adapters import HTTPAdapter
from requests.exceptions import ConnectionError
from flask import current_app
from lemur.exceptions import LemurException
from lemur.plugins.bases import IssuerPlugin, SourcePlugin
from lemur.plugins import lemur_cloudca as cloudca
from lemur.authorities import service as authority_service
class CloudCAException(LemurException):
def __init__(self, message):
self.message = message
current_app.logger.error(self)
def __str__(self):
return repr("CloudCA request failed: {0}".format(self.message))
class CloudCAHostNameCheckingAdapter(HTTPAdapter):
def cert_verify(self, conn, url, verify, cert):
super(CloudCAHostNameCheckingAdapter, self).cert_verify(conn, url, verify, cert)
conn.assert_hostname = False
def remove_none(options):
"""
Simple function that traverse the options and removed any None items
CloudCA really dislikes null values.
:param options:
:return:
"""
new_dict = {}
for k, v in options.items():
if v:
new_dict[k] = v
# this is super hacky and gross, cloudca doesn't like null values
if new_dict.get('extensions'):
if len(new_dict['extensions']['subAltNames']['names']) == 0:
del new_dict['extensions']['subAltNames']
return new_dict
def get_default_issuance(options):
"""
Gets the default time range for certificates
:param options:
:return:
"""
if not options.get('validityStart') and not options.get('validityEnd'):
start = arrow.utcnow()
options['validityStart'] = start.floor('second').isoformat()
options['validityEnd'] = start.replace(years=current_app.config.get('CLOUDCA_DEFAULT_VALIDITY'))\
.ceil('second').isoformat()
return options
def convert_to_pem(der):
"""
Converts DER to PEM Lemur uses PEM internally
:param der:
:return:
"""
decoded = base64.b64decode(der)
return ssl.DER_cert_to_PEM_cert(decoded)
def convert_date_to_utc_time(date):
"""
Converts a python `datetime` object to the current date + current time in UTC.
:param date:
:return:
"""
d = arrow.get(date)
return arrow.utcnow().replace(day=d.naive.day).replace(month=d.naive.month).replace(year=d.naive.year)\
.replace(microsecond=0)
def process_response(response):
"""
Helper function that processes responses from CloudCA.
:param response:
:return: :raise CloudCAException:
"""
if response.status_code == 200:
res = response.json()
if res['returnValue'] != 'success':
current_app.logger.debug(res)
if res.get('data'):
raise CloudCAException(" ".join([res['returnMessage'], res['data']['dryRunResultMessage']]))
else:
raise CloudCAException(res['returnMessage'])
else:
raise CloudCAException("There was an error with your request: {0}".format(response.status_code))
return response.json()
def get_auth_data(ca_name):
"""
Creates the authentication record needed to authenticate a user request to CloudCA.
:param ca_name:
:return: :raise CloudCAException:
"""
role = authority_service.get_authority_role(ca_name)
if role:
return {
"authInfo": {
"credType": "password",
"credentials": {
"username": role.username,
"password": role.password # we only decrypt when we need to
}
}
}
raise CloudCAException("You do not have the required role to issue certificates from {0}".format(ca_name))
class CloudCA(object):
def __init__(self, *args, **kwargs):
self.session = requests.Session()
self.session.mount('https://', CloudCAHostNameCheckingAdapter())
self.url = current_app.config.get('CLOUDCA_URL')
if current_app.config.get('CLOUDCA_PEM_PATH') and current_app.config.get('CLOUDCA_BUNDLE'):
self.session.cert = current_app.config.get('CLOUDCA_PEM_PATH')
self.ca_bundle = current_app.config.get('CLOUDCA_BUNDLE')
else:
current_app.logger.warning(
"No CLOUDCA credentials found, lemur will be unable to request certificates from CLOUDCA"
)
super(CloudCA, self).__init__(*args, **kwargs)
def post(self, endpoint, data):
"""
HTTP POST to CloudCA
:param endpoint:
:param data:
:return:
"""
data = dumps(dict(data.items() + get_auth_data(data['caName']).items()))
# we set a low timeout, if cloudca is down it shouldn't bring down
# lemur
try:
response = self.session.post(self.url + endpoint, data=data, timeout=10, verify=self.ca_bundle)
except ConnectionError:
raise Exception("Could not talk to CloudCA, is it up?")
return process_response(response)
def get(self, endpoint):
"""
HTTP GET to CloudCA
:param endpoint:
:return:
"""
try:
response = self.session.get(self.url + endpoint, timeout=10, verify=self.ca_bundle)
except ConnectionError:
raise Exception("Could not talk to CloudCA, is it up?")
return process_response(response)
def random(self, length=10):
"""
Uses CloudCA as a decent source of randomness.
:param length:
:return:
"""
endpoint = '/v1/random/{0}'.format(length)
response = self.session.get(self.url + endpoint, verify=self.ca_bundle)
return response
def get_authorities(self):
"""
Retrieves authorities that were made outside of Lemur.
:return:
"""
endpoint = '{0}/listCAs'.format(current_app.config.get('CLOUDCA_API_ENDPOINT'))
authorities = []
for ca in self.get(endpoint)['data']['caList']:
try:
authorities.append(ca['caName'])
except AttributeError:
current_app.logger.error("No authority has been defined for {}".format(ca['caName']))
return authorities
class CloudCAIssuerPlugin(IssuerPlugin, CloudCA):
title = 'CloudCA'
slug = 'cloudca-issuer'
description = 'Enables the creation of certificates from the cloudca API.'
version = cloudca.VERSION
author = 'Kevin Glisson'
author_url = 'https://github.com/netflix/lemur'
def create_authority(self, options):
"""
Creates a new certificate authority
:param options:
:return:
"""
# this is weird and I don't like it
endpoint = '{0}/createCA'.format(current_app.config.get('CLOUDCA_API_ENDPOINT'))
options['caDN']['email'] = options['ownerEmail']
if options['caType'] == 'subca':
options = dict(options.items() + self.auth_data(options['caParent']).items())
options['validityStart'] = convert_date_to_utc_time(options['validityStart']).isoformat()
options['validityEnd'] = convert_date_to_utc_time(options['validityEnd']).isoformat()
options['description'] = re.sub(r'[^a-zA-Z0-9]', '', options['caDescription'])
try:
response = self.session.post(self.url + endpoint, data=dumps(remove_none(options)), timeout=10,
verify=self.ca_bundle)
except ConnectionError:
raise Exception("Could not communicate with CloudCA, is it up?")
json = process_response(response)
roles = []
for cred in json['data']['authInfo']:
role = {
'username': cred['credentials']['username'],
'password': cred['credentials']['password'],
'name': "_".join([options['caName'], cred['credentials']['username']])
}
roles.append(role)
if options['caType'] == 'subca':
cert = convert_to_pem(json['data']['certificate'])
else:
cert = convert_to_pem(json['data']['rootCertificate'])
intermediates = []
for i in json['data']['intermediateCertificates']:
intermediates.append(convert_to_pem(i))
return cert, "".join(intermediates), roles,
def create_certificate(self, csr, options):
"""
Creates a new certificate from cloudca
If no start and end date are specified the default issue range
will be used.
:param csr:
:param options:
"""
endpoint = '{0}/enroll'.format(current_app.config.get('CLOUDCA_API_ENDPOINT'))
# lets default to two years if it's not specified
# we do some last minute data massaging
options = get_default_issuance(options)
cloudca_options = {
'extensions': options['extensions'],
'validityStart': convert_date_to_utc_time(options['validityStart']).isoformat(),
'validityEnd': convert_date_to_utc_time(options['validityEnd']).isoformat(),
'creator': options['creator'],
'ownerEmail': options['owner'],
'caName': options['authority'].name,
'csr': csr,
'comment': re.sub(r'[^a-zA-Z0-9]', '', options['description'])
}
response = self.post(endpoint, remove_none(cloudca_options))
# we return a concatenated list of intermediate because that is what aws
# expects
cert = convert_to_pem(response['data']['certificate'])
intermediates = [convert_to_pem(response['data']['rootCertificate'])]
for i in response['data']['intermediateCertificates']:
intermediates.append(convert_to_pem(i))
return cert, "".join(intermediates),
class CloudCASourcePlugin(SourcePlugin, CloudCA):
title = 'CloudCA'
slug = 'cloudca-source'
description = 'Discovers all SSL certificates in CloudCA'
version = cloudca.VERSION
author = 'Kevin Glisson'
author_url = 'https://github.com/netflix/lemur'
options = {
'pollRate': {'type': 'int', 'default': '60'}
}
def get_certificates(self, options, **kwargs):
certs = []
for authority in self.get_authorities():
certs += self.get_cert(ca_name=authority)
return certs
def get_cert(self, ca_name=None, cert_handle=None):
"""
Returns a given cert from CloudCA.
:param ca_name:
:param cert_handle:
:return:
"""
endpoint = '{0}/getCert'.format(current_app.config.get('CLOUDCA_API_ENDPOINT'))
response = self.session.post(self.url + endpoint, data=dumps({'caName': ca_name}), timeout=10,
verify=self.ca_bundle)
raw = process_response(response)
certs = []
for c in raw['data']['certList']:
cert = convert_to_pem(c['certValue'])
intermediates = []
for i in c['intermediateCertificates']:
intermediates.append(convert_to_pem(i))
certs.append({
'public_certificate': cert,
'intermediate_certificate': "\n".join(intermediates),
'owner': c['ownerEmail']
})
return certs

View File

@ -49,11 +49,11 @@
<td class="container-padding" bgcolor="#ffffff" style="background-color: #ffffff; padding-left: 30px; padding-right: 30px; font-size: 14px; line-height: 20px; font-family: Helvetica, sans-serif; color: #333;">
<br />
<div style="font-weight: bold; font-size: 18px; line-height: 24px; color: #202d3b">
<span style="color: #29abe0">Notice: Your SSL certificates are expiring!</span>
<span style="color: #29abe0">Notice: Your TLS certificates are expiring!</span>
<hr />
</div>
<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.
</p>
<p>

View File

@ -1,57 +0,0 @@
VERISIGN_INTERMEDIATE = """-----BEGIN CERTIFICATE-----
MIIFFTCCA/2gAwIBAgIQKC4nkXkzkuQo8iGnTsk3rjANBgkqhkiG9w0BAQsFADCB
yjELMAkGA1UEBhMCVVMxFzAVBgNVBAoTDlZlcmlTaWduLCBJbmMuMR8wHQYDVQQL
ExZWZXJpU2lnbiBUcnVzdCBOZXR3b3JrMTowOAYDVQQLEzEoYykgMTk5OSBWZXJp
U2lnbiwgSW5jLiAtIEZvciBhdXRob3JpemVkIHVzZSBvbmx5MUUwQwYDVQQDEzxW
ZXJpU2lnbiBDbGFzcyAzIFB1YmxpYyBQcmltYXJ5IENlcnRpZmljYXRpb24gQXV0
aG9yaXR5IC0gRzMwHhcNMTMxMDMxMDAwMDAwWhcNMjMxMDMwMjM1OTU5WjB+MQsw
CQYDVQQGEwJVUzEdMBsGA1UEChMUU3ltYW50ZWMgQ29ycG9yYXRpb24xHzAdBgNV
BAsTFlN5bWFudGVjIFRydXN0IE5ldHdvcmsxLzAtBgNVBAMTJlN5bWFudGVjIENs
YXNzIDMgU2VjdXJlIFNlcnZlciBDQSAtIEc0MIIBIjANBgkqhkiG9w0BAQEFAAOC
AQ8AMIIBCgKCAQEAstgFyhx0LbUXVjnFSlIJluhL2AzxaJ+aQihiw6UwU35VEYJb
A3oNL+F5BMm0lncZgQGUWfm893qZJ4Itt4PdWid/sgN6nFMl6UgfRk/InSn4vnlW
9vf92Tpo2otLgjNBEsPIPMzWlnqEIRoiBAMnF4scaGGTDw5RgDMdtLXO637QYqzu
s3sBdO9pNevK1T2p7peYyo2qRA4lmUoVlqTObQJUHypqJuIGOmNIrLRM0XWTUP8T
L9ba4cYY9Z/JJV3zADreJk20KQnNDz0jbxZKgRb78oMQw7jW2FUyPfG9D72MUpVK
Fpd6UiFjdS8W+cRmvvW1Cdj/JwDNRHxvSz+w9wIDAQABo4IBQDCCATwwHQYDVR0O
BBYEFF9gz2GQVd+EQxSKYCqy9Xr0QxjvMBIGA1UdEwEB/wQIMAYBAf8CAQAwawYD
VR0gBGQwYjBgBgpghkgBhvhFAQc2MFIwJgYIKwYBBQUHAgEWGmh0dHA6Ly93d3cu
c3ltYXV0aC5jb20vY3BzMCgGCCsGAQUFBwICMBwaGmh0dHA6Ly93d3cuc3ltYXV0
aC5jb20vcnBhMC8GA1UdHwQoMCYwJKAioCCGHmh0dHA6Ly9zLnN5bWNiLmNvbS9w
Y2EzLWczLmNybDAOBgNVHQ8BAf8EBAMCAQYwKQYDVR0RBCIwIKQeMBwxGjAYBgNV
BAMTEVN5bWFudGVjUEtJLTEtNTM0MC4GCCsGAQUFBwEBBCIwIDAeBggrBgEFBQcw
AYYSaHR0cDovL3Muc3ltY2QuY29tMA0GCSqGSIb3DQEBCwUAA4IBAQBbF1K+1lZ7
9Pc0CUuWysf2IdBpgO/nmhnoJOJ/2S9h3RPrWmXk4WqQy04q6YoW51KN9kMbRwUN
gKOomv4p07wdKNWlStRxPA91xQtzPwBIZXkNq2oeJQzAAt5mrL1LBmuaV4oqgX5n
m7pSYHPEFfe7wVDJCKW6V0o6GxBzHOF7tpQDS65RsIJAOloknO4NWF2uuil6yjOe
soHCL47BJ89A8AShP/U3wsr8rFNtqVNpT+F2ZAwlgak3A/I5czTSwXx4GByoaxbn
5+CdKa/Y5Gk5eZVpuXtcXQGc1PfzSEUTZJXXCm5y2kMiJG8+WnDcwJLgLeVX+OQr
J+71/xuzAYN6
-----END CERTIFICATE-----
"""
VERISIGN_ROOT = """-----BEGIN CERTIFICATE-----
MIIEGjCCAwICEQCbfgZJoz5iudXukEhxKe9XMA0GCSqGSIb3DQEBBQUAMIHKMQsw
CQYDVQQGEwJVUzEXMBUGA1UEChMOVmVyaVNpZ24sIEluYy4xHzAdBgNVBAsTFlZl
cmlTaWduIFRydXN0IE5ldHdvcmsxOjA4BgNVBAsTMShjKSAxOTk5IFZlcmlTaWdu
LCBJbmMuIC0gRm9yIGF1dGhvcml6ZWQgdXNlIG9ubHkxRTBDBgNVBAMTPFZlcmlT
aWduIENsYXNzIDMgUHVibGljIFByaW1hcnkgQ2VydGlmaWNhdGlvbiBBdXRob3Jp
dHkgLSBHMzAeFw05OTEwMDEwMDAwMDBaFw0zNjA3MTYyMzU5NTlaMIHKMQswCQYD
VQQGEwJVUzEXMBUGA1UEChMOVmVyaVNpZ24sIEluYy4xHzAdBgNVBAsTFlZlcmlT
aWduIFRydXN0IE5ldHdvcmsxOjA4BgNVBAsTMShjKSAxOTk5IFZlcmlTaWduLCBJ
bmMuIC0gRm9yIGF1dGhvcml6ZWQgdXNlIG9ubHkxRTBDBgNVBAMTPFZlcmlTaWdu
IENsYXNzIDMgUHVibGljIFByaW1hcnkgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkg
LSBHMzCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAMu6nFL8eB8aHm8b
N3O9+MlrlBIwT/A2R/XQkQr1F8ilYcEWQE37imGQ5XYgwREGfassbqb1EUGO+i2t
KmFZpGcmTNDovFJbcCAEWNF6yaRpvIMXZK0Fi7zQWM6NjPXr8EJJC52XJ2cybuGu
kxUccLwgTS8Y3pKI6GyFVxEa6X7jJhFUokWWVYPKMIno3Nij7SqAP395ZVc+FSBm
CC+Vk7+qRy+oRpfwEuL+wgorUeZ25rdGt+INpsyow0xZVYnm6FNcHOqd8GIWC6fJ
Xwzw3sJ2zq/3avL6QaaiMxTJ5Xpj055iN9WFZZ4O5lMkdBteHRJTW8cs54NJOxWu
imi5V5cCAwEAATANBgkqhkiG9w0BAQUFAAOCAQEAERSWwauSCPc/L8my/uRan2Te
2yFPhpk0djZX3dAVL8WtfxUfN2JzPtTnX84XA9s1+ivbrmAJXx5fj267Cz3qWhMe
DGBvtcC1IyIuBwvLqXTLR7sdwdela8wv0kL9Sd2nic9TutoAWii/gt/4uhMdUIaC
/Y4wjylGsB49Ndo4YhYYSq3mtlFs3q9i6wHQHiT+eo8SGhJouPtmmRQURVyu565p
F4ErWjfJXir0xuKhXFSbplQAz/DxwceYMBo7Nhbbo27q/a2ywtrvAkcTisDxszGt
TxzhT5yvDwyd93gN2PQ1VoDat20Xj50egWTh/sVFuq1ruQp6Tk9LhO5L8X3dEQ==
-----END CERTIFICATE-----
"""

View File

@ -13,9 +13,8 @@ import xmltodict
from flask import current_app
from lemur.plugins.bases import IssuerPlugin
from lemur.plugins.bases import IssuerPlugin, SourcePlugin
from lemur.plugins import lemur_verisign as verisign
from lemur.plugins.lemur_verisign import constants
from lemur.common.utils import get_psuedo_random_string
@ -132,7 +131,7 @@ class VerisignIssuerPlugin(IssuerPlugin):
version = verisign.VERSION
author = 'Kevin Glisson'
author_url = 'https://github.com/netflix/lemur'
author_url = 'https://github.com/netflix/lemur.git'
def __init__(self, *args, **kwargs):
self.session = requests.Session()
@ -147,7 +146,7 @@ class VerisignIssuerPlugin(IssuerPlugin):
:param issuer_options:
:return: :raise Exception:
"""
url = current_app.config.get("VERISIGN_URL") + '/enroll'
url = current_app.config.get("VERISIGN_URL") + '/rest/services/enroll'
data = process_options(issuer_options)
data['csr'] = csr
@ -156,7 +155,7 @@ class VerisignIssuerPlugin(IssuerPlugin):
response = self.session.post(url, data=data)
cert = handle_response(response.content)['Response']['Certificate']
return cert, constants.VERISIGN_INTERMEDIATE,
return cert, current_app.config.get('VERISIGN_INTERMEDIATE'),
@staticmethod
def create_authority(options):
@ -168,7 +167,7 @@ class VerisignIssuerPlugin(IssuerPlugin):
:return:
"""
role = {'username': '', 'password': '', 'name': 'verisign'}
return constants.VERISIGN_ROOT, "", [role]
return current_app.config.get('VERISIGN_ROOT'), "", [role]
def get_available_units(self):
"""
@ -177,6 +176,35 @@ class VerisignIssuerPlugin(IssuerPlugin):
:return:
"""
url = current_app.config.get("VERISIGN_URL") + '/getTokens'
url = current_app.config.get("VERISIGN_URL") + '/rest/services/getTokens'
response = self.session.post(url, headers={'content-type': 'application/x-www-form-urlencoded'})
return handle_response(response.content)['Response']['Order']
class VerisignSourcePlugin(SourcePlugin):
title = 'Verisign'
slug = 'verisign-source'
description = 'Allows for the polling of issued certificates from the VICE2.0 verisign API.'
version = verisign.VERSION
author = 'Kevin Glisson'
author_url = 'https://github.com/netflix/lemur.git'
def __init__(self, *args, **kwargs):
self.session = requests.Session()
self.session.cert = current_app.config.get('VERISIGN_PEM_PATH')
super(VerisignSourcePlugin, self).__init__(*args, **kwargs)
def get_certificates(self):
url = current_app.config.get('VERISIGN_URL') + '/reportingws'
end = arrow.now()
start = end.replace(years=-5)
data = {
'reportType': 'detail',
'startDate': start.format("MM/DD/YYYY"),
'endDate': end.format("MM/DD/YYYY"),
'structuredRecord': 'Y',
'certStatus': 'Valid',
}
current_app.logger.debug(data)
response = self.session.post(url, data=data)

View File

@ -0,0 +1 @@
from lemur.tests.conftest import * # noqa

View File

@ -0,0 +1,5 @@
def test_get_certificates(app):
from lemur.plugins.base import plugins
p = plugins.get('verisign-source')
p.get_certificates()

View File

@ -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) {
return Restangular.withConfig(function (RestangularConfigurer) {
RestangularConfigurer.setBaseUrl('http://localhost:5000/api/1');

View File

@ -30,6 +30,9 @@ angular.module('lemur')
.controller('AuthorityCreateController', function ($scope, $modalInstance, AuthorityService, LemurRestangular, RoleService, PluginService, WizardHandler) {
$scope.authority = LemurRestangular.restangularizeElement(null, {}, 'authorities');
// set the defaults
AuthorityService.getDefaults($scope.authority);
$scope.loading = false;
$scope.create = function (authority) {
WizardHandler.wizard().context.loading = true;

View File

@ -10,7 +10,7 @@
Owner
</label>
<div class="col-sm-10">
<input type="email" name="owner" ng-model="authority.owner" placeholder="owner@netflix.com"
<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

View File

@ -16,7 +16,7 @@
Owner
</label>
<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>
</div>
</div>

View File

@ -56,7 +56,7 @@ angular.module('lemur')
});
return LemurRestangular.all('authorities');
})
.service('AuthorityService', function ($location, AuthorityApi, toaster) {
.service('AuthorityService', function ($location, AuthorityApi, DefaultService, toaster) {
var AuthorityService = this;
AuthorityService.findAuthorityByName = function (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) {
return authority.getList('roles').then(function (roles) {
authority.roles = roles;

View File

@ -4,7 +4,7 @@
</label>
<div class="col-sm-10">
<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"
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"

View File

@ -10,7 +10,7 @@
Owner
</label>
<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/>
<p ng-show="editForm.owner.$invalid && !editForm.owner.$pristine" class="help-block">Enter a valid

View File

@ -6,7 +6,7 @@
Owner
</label>
<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>
</div>
</div>

View File

@ -11,7 +11,7 @@
</label>
<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/>
<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>
</label>
<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 class="form-group"

View File

@ -89,7 +89,7 @@ angular.module('lemur')
});
return LemurRestangular.all('certificates');
})
.service('CertificateService', function ($location, CertificateApi, LemurRestangular, toaster) {
.service('CertificateService', function ($location, CertificateApi, LemurRestangular, DefaultService, toaster) {
var CertificateService = this;
CertificateService.findCertificatesByName = function (filterValue) {
return CertificateApi.getList({'filter[name]': filterValue})
@ -207,7 +207,7 @@ angular.module('lemur')
};
CertificateService.getDefaults = function (certificate) {
return certificate.customGET('defaults').then(function (defaults) {
return DefaultService.get().then(function (defaults) {
certificate.country = defaults.country;
certificate.state = defaults.state;
certificate.location = defaults.location;

View File

@ -1,19 +1,12 @@
<div class="jumbotron">
<h1>Hey there!</h1>
<p>Welcome to Lemur! A central portal for all (most) of your SSL needs.</p>
<p>Welcome to Lemur! A central portal for all (most) of your TLS needs.</p>
<p><a href="/#/certificates/create" class="btn btn-primary btn-lg" role="button">Create a Certificate</a></p>
</div>
<div class="row featurette">
<div class="col-md-10">
<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>
<h2 class="featurette-heading">TLS In The Cloud <span class="text-muted">Encrypt it all </span></h2>
</div>
</div>

View File

@ -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"})

View File

@ -8,6 +8,7 @@ from lemur.roles import service as role_service
def pytest_addoption(parser):
parser.addoption("--lemurconfig", help="override the default test config")
parser.addoption("--runslow", action="store_true", help="run slow tests")
@ -29,12 +30,15 @@ def pytest_runtest_makereport(item, call):
@pytest.yield_fixture(scope="session")
def app():
def app(request):
"""
Creates a new Flask application for a test duration.
Uses application factory `create_app`.
"""
_app = create_app(os.path.dirname(os.path.realpath(__file__)) + '/conf.py')
if request.config.getoption('--lemurconfig'):
_app = create_app(request.config.getoption('--lemurconfig'))
else:
_app = create_app(os.path.dirname(os.path.realpath(__file__)) + '/conf.py')
ctx = _app.app_context()
ctx.push()

View File

@ -10,3 +10,6 @@ exclude = .tox,.git,*/migrations/*,lemur/static/*,docs/*
[wheel]
universal = 1
[metadata]
description-file = README.rst

View File

@ -2,21 +2,23 @@
Lemur
=====
Is an SSL management and orchestration tool.
Is a TLS management and orchestration tool.
:copyright: (c) 2015 by Netflix, see AUTHORS for more
:license: Apache, see LICENSE for more details.
"""
from __future__ import absolute_import
import json
import os.path
import datetime
from distutils import log
from distutils.core import Command
from setuptools.command.develop import develop
from setuptools.command.install import install
from setuptools.command.sdist import sdist
from setuptools import setup
from setuptools import setup, find_packages
from subprocess import check_output
ROOT = os.path.realpath(os.path.join(os.path.dirname(__file__)))
@ -39,7 +41,7 @@ install_requires = [
'six==1.9.0',
'gunicorn==19.3.0',
'pycrypto==2.6.1',
'cryptography==1.0',
'cryptography==1.0.1',
'pyopenssl==0.15.1',
'pyjwt==1.0.1',
'xmltodict==0.9.2',
@ -87,9 +89,18 @@ class DevelopWithBuildStatic(develop):
class SdistWithBuildStatic(sdist):
def make_distribution(self):
def make_release_tree(self, *a, **kw):
dist_path = self.distribution.get_fullname()
sdist.make_release_tree(self, *a, **kw)
self.reinitialize_command('build_static', work_path=dist_path)
self.run_command('build_static')
return sdist.make_distribution(self)
with open(os.path.join(dist_path, 'lemur-package.json'), 'w') as fp:
json.dump({
'createdAt': datetime.datetime.utcnow().isoformat() + 'Z',
}, fp)
class BuildStatic(Command):
@ -100,21 +111,27 @@ class BuildStatic(Command):
pass
def run(self):
log.info("running [npm install --quiet]")
check_output(['npm', 'install', '--quiet'], cwd=ROOT)
log.info("running [npm install --quiet] in {0}".format(ROOT))
try:
check_output(['npm', 'install', '--quiet'], cwd=ROOT)
log.info("running [gulp build]")
check_output([os.path.join(ROOT, 'node_modules', '.bin', 'gulp'), 'build'], cwd=ROOT)
log.info("running [gulp package]")
check_output([os.path.join(ROOT, 'node_modules', '.bin', 'gulp'), 'package'], cwd=ROOT)
log.info("running [gulp build]")
check_output([os.path.join(ROOT, 'node_modules', '.bin', 'gulp'), 'build'], cwd=ROOT)
log.info("running [gulp package]")
check_output([os.path.join(ROOT, 'node_modules', '.bin', 'gulp'), 'package'], cwd=ROOT)
except Exception as e:
log.warn("Unable to build static content")
setup(
name='lemur',
version='0.1',
version='0.1.4',
author='Kevin Glisson',
author_email='kglisson@netflix.com',
url='https://github.com/netflix/lemur',
download_url='https://github.com/Netflix/lemur/archive/0.1.3.tar.gz',
description='Certificate management and orchestration service',
long_description=open(os.path.join(ROOT, 'README.rst')).read(),
packages=['lemur'],
packages=find_packages(),
include_package_data=True,
zip_safe=False,
install_requires=install_requires,
@ -127,7 +144,6 @@ setup(
'build_static': BuildStatic,
'sdist': SdistWithBuildStatic,
'install': SmartInstall
},
entry_points={
'console_scripts': [
@ -135,8 +151,6 @@ setup(
],
'lemur.plugins': [
'verisign_issuer = lemur.plugins.lemur_verisign.plugin:VerisignIssuerPlugin',
'cloudca_issuer = lemur.plugins.lemur_cloudca.plugin:CloudCAIssuerPlugin',
'cloudca_source = lemur.plugins.lemur_cloudca.plugin:CloudCASourcePlugin',
'aws_destination = lemur.plugins.lemur_aws.plugin:AWSDestinationPlugin',
'aws_source = lemur.plugins.lemur_aws.plugin:AWSSourcePlugin',
'email_notification = lemur.plugins.lemur_email.plugin:EmailNotificationPlugin',