Adds option to restrict certificate expiration dates to weekdays. (#453)

* Adding ability to restrict certificate creation to weekdays.

* Ensuring that we test for weekends.
This commit is contained in:
kevgliss 2016-10-15 00:04:35 -07:00 committed by GitHub
parent 1b861baf0a
commit dcb18a57c4
11 changed files with 97 additions and 52 deletions

View File

@ -51,7 +51,7 @@ Basic Configuration
CORS = False CORS = False
.. data:: SQLACHEMY_DATABASE_URI .. data:: SQLALCHEMY_DATABASE_URI
:noindex: :noindex:
If you have ever used sqlalchemy before this is the standard connection string used. Lemur uses a postgres database and the connection string would look something like: If you have ever used sqlalchemy before this is the standard connection string used. Lemur uses a postgres database and the connection string would look something like:
@ -61,6 +61,11 @@ Basic Configuration
SQLALCHEMY_DATABASE_URI = 'postgresql://<user>:<password>@<hostname>:5432/lemur' SQLALCHEMY_DATABASE_URI = 'postgresql://<user>:<password>@<hostname>:5432/lemur'
.. data:: LEMUR_ALLOW_WEEKEND_EXPIRATION
:noindex:
Specifies whether to allow certificates created by Lemur to expire on weekends. Default is True.
.. data:: LEMUR_RESTRICTED_DOMAINS .. data:: LEMUR_RESTRICTED_DOMAINS
:noindex: :noindex:

View File

@ -62,7 +62,7 @@ class AuthorityInputSchema(LemurInputSchema):
@pre_load @pre_load
def ensure_dates(self, data): def ensure_dates(self, data):
return missing.dates(data) return missing.convert_validity_years(data)
class AuthorityUpdateSchema(LemurInputSchema): class AuthorityUpdateSchema(LemurInputSchema):

View File

@ -72,7 +72,7 @@ class CertificateInputSchema(CertificateCreationSchema):
@pre_load @pre_load
def ensure_dates(self, data): def ensure_dates(self, data):
return missing.dates(data) return missing.convert_validity_years(data)
class CertificateEditInputSchema(CertificateSchema): class CertificateEditInputSchema(CertificateSchema):

View File

@ -1,13 +1,26 @@
import arrow import arrow
from flask import current_app
from lemur.common.utils import is_weekend
def dates(data): def convert_validity_years(data):
# ensure that validity_start and validity_end are always set """
if not(data.get('validity_start') and data.get('validity_end')): Convert validity years to validity_start and validity_end
:param data:
:return:
"""
if data.get('validity_years'): if data.get('validity_years'):
num_years = data['validity_years']
now = arrow.utcnow() now = arrow.utcnow()
then = now.replace(years=+int(num_years)) data['validity_start'] = now.date().isoformat()
data['validity_start'] = now.isoformat() end = now.replace(years=+int(data['validity_years']))
data['validity_end'] = then.isoformat() data['validity_end'] = end.date().isoformat()
if not current_app.config.get('LEMUR_ALLOW_WEEKEND_EXPIRATION', True):
if is_weekend(end):
end = end.replace(days=-2)
data['validity_end'] = end.date().isoformat()
return data

View File

@ -41,3 +41,14 @@ def parse_certificate(body):
return x509.load_pem_x509_certificate(body, default_backend()) return x509.load_pem_x509_certificate(body, default_backend())
return x509.load_pem_x509_certificate(bytes(body, 'utf8'), default_backend()) return x509.load_pem_x509_certificate(bytes(body, 'utf8'), default_backend())
return x509.load_pem_x509_certificate(body.encode('utf-8'), default_backend()) return x509.load_pem_x509_certificate(body.encode('utf-8'), default_backend())
def is_weekend(date):
"""
Determines if a given date is on a weekend.
:param date:
:return:
"""
if date.weekday() > 5:
return True

View File

@ -1,16 +1,14 @@
import arrow
import re import re
from flask import current_app
from marshmallow.exceptions import ValidationError
from flask import current_app
from cryptography import x509 from cryptography import x509
from cryptography.hazmat.backends import default_backend from cryptography.hazmat.backends import default_backend
from cryptography.hazmat.primitives import serialization from cryptography.hazmat.primitives import serialization
from marshmallow.exceptions import ValidationError
from lemur.common.utils import parse_certificate
from lemur.domains import service as domain_service
from lemur.auth.permissions import SensitiveDomainPermission from lemur.auth.permissions import SensitiveDomainPermission
from lemur.common.utils import parse_certificate, is_weekend
from lemur.domains import service as domain_service
def public_certificate(body): def public_certificate(body):
@ -102,6 +100,10 @@ def dates(data):
raise ValidationError('If validity end is specified so must validity start.') raise ValidationError('If validity end is specified so must validity start.')
if data.get('validity_start') and data.get('validity_end'): if data.get('validity_start') and data.get('validity_end'):
if not current_app.config.get('LEMUR_ALLOW_WEEKEND_EXPIRATION', True):
if is_weekend(data.get('validity_end')):
raise ValidationError('Validity end must not land on a weekend.')
if not data['validity_start'] < data['validity_end']: if not data['validity_start'] < data['validity_end']:
raise ValidationError('Validity start must be before validity end.') raise ValidationError('Validity start must be before validity end.')
@ -112,13 +114,4 @@ def dates(data):
if data.get('validity_end').replace(hour=0, minute=0, second=0, tzinfo=None) > data['authority'].authority_certificate.not_after.replace(hour=0, minute=0, second=0): if data.get('validity_end').replace(hour=0, minute=0, second=0, tzinfo=None) > data['authority'].authority_certificate.not_after.replace(hour=0, minute=0, second=0):
raise ValidationError('Validity end must not be after {0}'.format(data['authority'].authority_certificate.not_after)) raise ValidationError('Validity end must not be after {0}'.format(data['authority'].authority_certificate.not_after))
if data.get('validity_years'): return data
now = arrow.utcnow()
end = now.replace(years=+data['validity_years'])
if data.get('authority'):
if now.naive < data['authority'].authority_certificate.not_before:
raise ValidationError('Validity start must not be before {0}'.format(data['authority'].authority_certificate.not_before))
if end.naive > data['authority'].authority_certificate.not_after:
raise ValidationError('Validity end must not be after {0}'.format(data['authority'].authority_certificate.not_after))

View File

@ -46,6 +46,8 @@ LEMUR_DEFAULT_LOCATION = 'Los Gatos'
LEMUR_DEFAULT_ORGANIZATION = 'Example, Inc.' LEMUR_DEFAULT_ORGANIZATION = 'Example, Inc.'
LEMUR_DEFAULT_ORGANIZATIONAL_UNIT = 'Example' LEMUR_DEFAULT_ORGANIZATIONAL_UNIT = 'Example'
LEMUR_ALLOW_WEEKEND_EXPIRATION = False
# Database # Database
# modify this if you are not using a local database # modify this if you are not using a local database

View File

@ -5,7 +5,7 @@ import json
from lemur.certificates.views import * # noqa from lemur.certificates.views import * # noqa
from .vectors import VALID_ADMIN_HEADER_TOKEN, VALID_USER_HEADER_TOKEN, CSR_STR, \ from lemur.tests.vectors import VALID_ADMIN_HEADER_TOKEN, VALID_USER_HEADER_TOKEN, CSR_STR, \
INTERNAL_VALID_LONG_STR, INTERNAL_VALID_SAN_STR, PRIVATE_KEY_STR INTERNAL_VALID_LONG_STR, INTERNAL_VALID_SAN_STR, PRIVATE_KEY_STR

View File

@ -0,0 +1,17 @@
import arrow
from freezegun import freeze_time
def test_convert_validity_years(session):
from lemur.common.missing import convert_validity_years
with freeze_time("2016-01-01"):
data = convert_validity_years(dict(validity_years=2))
assert data['validity_start'] == arrow.utcnow().date().isoformat()
assert data['validity_end'] == arrow.utcnow().replace(years=+2).date().isoformat()
with freeze_time("2015-01-10"):
data = convert_validity_years(dict(validity_years=1))
assert data['validity_end'] == arrow.utcnow().replace(years=+1, days=-2).date().isoformat()

View File

@ -1,29 +1,32 @@
from marshmallow.exceptions import ValidationError import pytest
from datetime import datetime
from .vectors import PRIVATE_KEY_STR from .vectors import PRIVATE_KEY_STR
from marshmallow.exceptions import ValidationError
def test_private_key(): def test_private_key(session):
from lemur.common.validators import private_key from lemur.common.validators import private_key
try:
private_key(PRIVATE_KEY_STR) private_key(PRIVATE_KEY_STR)
assert True
except ValidationError:
assert False, "failed to validate private key as a bytes object"
def test_private_key_str_object():
from lemur.common.validators import private_key
try:
private_key(PRIVATE_KEY_STR.decode('utf-8')) private_key(PRIVATE_KEY_STR.decode('utf-8'))
assert True
except ValidationError:
assert False, "failed to validate private key as a str object"
with pytest.raises(ValidationError):
def test_private_key_invalid():
from lemur.common.validators import private_key
try:
private_key('invalid_private_key') private_key('invalid_private_key')
assert False, "invalid private key should have raised an exception"
except ValidationError:
assert True def test_dates(session):
from lemur.common.validators import dates
dates(dict(validity_start=datetime(2016, 1, 1), validity_end=datetime(2016, 1, 5)))
with pytest.raises(ValidationError):
dates(dict(validity_start=datetime(2016, 1, 1)))
with pytest.raises(ValidationError):
dates(dict(validity_end=datetime(2016, 1, 1)))
with pytest.raises(ValidationError):
dates(dict(validity_start=datetime(2016, 1, 5), validity_end=datetime(2016, 1, 1)))
with pytest.raises(ValidationError):
dates(dict(validity_start=datetime(2016, 1, 1), validity_end=datetime(2016, 1, 10)))

View File

@ -63,7 +63,8 @@ install_requires = [
'boto3==1.3.0', 'boto3==1.3.0',
'acme==0.1.0', 'acme==0.1.0',
'retrying==1.3.3', 'retrying==1.3.3',
'tabulate==0.7.5' 'tabulate==0.7.5',
'freezegun==0.3.7',
] ]
tests_require = [ tests_require = [