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:
parent
1b861baf0a
commit
dcb18a57c4
|
@ -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:
|
||||||
|
|
||||||
|
|
|
@ -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):
|
||||||
|
|
|
@ -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):
|
||||||
|
|
|
@ -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
|
||||||
if data.get('validity_years'):
|
|
||||||
num_years = data['validity_years']
|
|
||||||
now = arrow.utcnow()
|
|
||||||
then = now.replace(years=+int(num_years))
|
|
||||||
|
|
||||||
data['validity_start'] = now.isoformat()
|
:param data:
|
||||||
data['validity_end'] = then.isoformat()
|
:return:
|
||||||
|
"""
|
||||||
|
if data.get('validity_years'):
|
||||||
|
now = arrow.utcnow()
|
||||||
|
data['validity_start'] = now.date().isoformat()
|
||||||
|
|
||||||
|
end = now.replace(years=+int(data['validity_years']))
|
||||||
|
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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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))
|
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -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()
|
|
@ -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)
|
|
||||||
assert True
|
|
||||||
except ValidationError:
|
|
||||||
assert False, "failed to validate private key as a bytes object"
|
|
||||||
|
|
||||||
|
private_key(PRIVATE_KEY_STR)
|
||||||
|
private_key(PRIVATE_KEY_STR.decode('utf-8'))
|
||||||
|
|
||||||
def test_private_key_str_object():
|
with pytest.raises(ValidationError):
|
||||||
from lemur.common.validators import private_key
|
|
||||||
try:
|
|
||||||
private_key(PRIVATE_KEY_STR.decode('utf-8'))
|
|
||||||
assert True
|
|
||||||
except ValidationError:
|
|
||||||
assert False, "failed to validate private key as a str object"
|
|
||||||
|
|
||||||
|
|
||||||
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)))
|
||||||
|
|
Loading…
Reference in New Issue