Merge pull request #3115 from charhate/validity

Cert Validity UI Changes
This commit is contained in:
Hossein Shafagh 2020-09-11 16:48:50 -07:00 committed by GitHub
commit 5cc6279361
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 136 additions and 43 deletions

View File

@ -172,15 +172,16 @@ Specifying the `SQLALCHEMY_MAX_OVERFLOW` to 0 will enforce limit to not create c
PUBLIC_CA_MAX_VALIDITY_DAYS = 365 PUBLIC_CA_MAX_VALIDITY_DAYS = 365
.. data:: DEFAULT_MAX_VALIDITY_DAYS .. data:: DEFAULT_VALIDITY_DAYS
:noindex: :noindex:
Use this config to override the default limit of 1095 days (3 years) of validity. Any CA which is not listed in Use this config to override the default validity of 365 days for certificates offered through Lemur UI. Any CA which
PUBLIC_CA_AUTHORITY_NAMES will be using this validity to display date range on UI. Below example overrides the is not listed in PUBLIC_CA_AUTHORITY_NAMES will be using this value as default validity to be displayed on UI. Please
default validity of 1095 days and sets it to 365 days. note that this config is used for cert issuance only through Lemur UI. Below example overrides the default validity
of 365 days and sets it to 1095 days (3 years).
:: ::
DEFAULT_MAX_VALIDITY_DAYS = 365 DEFAULT_VALIDITY_DAYS = 1095
.. data:: DEBUG_DUMP .. data:: DEBUG_DUMP

View File

@ -112,6 +112,7 @@ class RootAuthorityCertificateOutputSchema(LemurOutputSchema):
not_after = fields.DateTime() not_after = fields.DateTime()
not_before = fields.DateTime() not_before = fields.DateTime()
max_issuance_days = fields.Integer() max_issuance_days = fields.Integer()
default_validity_days = fields.Integer()
owner = fields.Email() owner = fields.Email()
status = fields.Boolean() status = fields.Boolean()
user = fields.Nested(UserNestedOutputSchema) user = fields.Nested(UserNestedOutputSchema)
@ -137,7 +138,7 @@ class AuthorityNestedOutputSchema(LemurOutputSchema):
owner = fields.Email() owner = fields.Email()
plugin = fields.Nested(PluginOutputSchema) plugin = fields.Nested(PluginOutputSchema)
active = fields.Boolean() active = fields.Boolean()
authority_certificate = fields.Nested(RootAuthorityCertificateOutputSchema, only=["max_issuance_days"]) authority_certificate = fields.Nested(RootAuthorityCertificateOutputSchema, only=["max_issuance_days", "default_validity_days"])
authority_update_schema = AuthorityUpdateSchema() authority_update_schema = AuthorityUpdateSchema()

View File

@ -320,7 +320,13 @@ class Certificate(db.Model):
if self.name.lower() in [ca.lower() for ca in public_CA]: if self.name.lower() in [ca.lower() for ca in public_CA]:
return current_app.config.get("PUBLIC_CA_MAX_VALIDITY_DAYS", 397) return current_app.config.get("PUBLIC_CA_MAX_VALIDITY_DAYS", 397)
return current_app.config.get("DEFAULT_MAX_VALIDITY_DAYS", 1095) # 3 years default @property
def default_validity_days(self):
public_CA = current_app.config.get("PUBLIC_CA_AUTHORITY_NAMES", [])
if self.name.lower() in [ca.lower() for ca in public_CA]:
return current_app.config.get("PUBLIC_CA_MAX_VALIDITY_DAYS", 397)
return current_app.config.get("DEFAULT_VALIDITY_DAYS", 365) # 1 year default
@property @property
def subject(self): def subject(self):

View File

@ -18,8 +18,9 @@ import json
import arrow import arrow
import pem import pem
import requests import requests
import sys
from cryptography import x509 from cryptography import x509
from flask import current_app from flask import current_app, g
from lemur.common.utils import validate_conf from lemur.common.utils import validate_conf
from lemur.extensions import metrics from lemur.extensions import metrics
from lemur.plugins import lemur_digicert as digicert from lemur.plugins import lemur_digicert as digicert
@ -129,6 +130,9 @@ def map_fields(options, csr):
data["validity_years"] = determine_validity_years(options.get("validity_years")) data["validity_years"] = determine_validity_years(options.get("validity_years"))
elif options.get("validity_end"): elif options.get("validity_end"):
data["custom_expiration_date"] = determine_end_date(options.get("validity_end")).format("YYYY-MM-DD") data["custom_expiration_date"] = determine_end_date(options.get("validity_end")).format("YYYY-MM-DD")
# check if validity got truncated. If resultant validity is not equal to requested validity, it just got truncated
if data["custom_expiration_date"] != options.get("validity_end").format("YYYY-MM-DD"):
log_validity_truncation(options, f"{__name__}.{sys._getframe().f_code.co_name}")
else: else:
data["validity_years"] = determine_validity_years(0) data["validity_years"] = determine_validity_years(0)
@ -154,6 +158,9 @@ def map_cis_fields(options, csr):
validity_end = determine_end_date(arrow.utcnow().shift(years=options["validity_years"])) validity_end = determine_end_date(arrow.utcnow().shift(years=options["validity_years"]))
elif options.get("validity_end"): elif options.get("validity_end"):
validity_end = determine_end_date(options.get("validity_end")) validity_end = determine_end_date(options.get("validity_end"))
# check if validity got truncated. If resultant validity is not equal to requested validity, it just got truncated
if validity_end != options.get("validity_end"):
log_validity_truncation(options, f"{__name__}.{sys._getframe().f_code.co_name}")
else: else:
validity_end = determine_end_date(False) validity_end = determine_end_date(False)
@ -179,6 +186,18 @@ def map_cis_fields(options, csr):
return data return data
def log_validity_truncation(options, function):
log_data = {
"cn": options["common_name"],
"creator": g.user.username
}
metrics.send("digicert_validity_truncated", "counter", 1, metric_tags=log_data)
log_data["function"] = function
log_data["message"] = "Digicert Plugin truncated the validity of certificate"
current_app.logger.info(log_data)
def handle_response(response): def handle_response(response):
""" """
Handle the DigiCert API response and any errors it might have experienced. Handle the DigiCert API response and any errors it might have experienced.

View File

@ -107,7 +107,6 @@ angular.module('lemur')
startingDay: 1 startingDay: 1
}; };
$scope.open1 = function() { $scope.open1 = function() {
$scope.popup1.opened = true; $scope.popup1.opened = true;
}; };
@ -140,6 +139,14 @@ angular.module('lemur')
); );
$scope.create = function (certificate) { $scope.create = function (certificate) {
if(certificate.validityType === 'customDates' &&
(!certificate.validityStart || !certificate.validityEnd)) { // these are not mandatory fields in schema, thus handling validation in js
return showMissingDateError();
}
if(certificate.validityType === 'defaultDays') {
populateValidityDateAsPerDefault(certificate);
}
WizardHandler.wizard().context.loading = true; WizardHandler.wizard().context.loading = true;
CertificateService.create(certificate).then( CertificateService.create(certificate).then(
function () { function () {
@ -164,6 +171,30 @@ angular.module('lemur')
}); });
}; };
function showMissingDateError() {
let error = {};
error.message = '';
error.reasons = {};
error.reasons.validityRange = 'Valid start and end dates are needed, else select Default option';
toaster.pop({
type: 'error',
title: 'Validation Error',
body: 'lemur-bad-request',
bodyOutputType: 'directive',
directiveData: error,
timeout: 100000
});
}
function populateValidityDateAsPerDefault(certificate) {
// calculate start and end date as per default validity
let startDate = new Date(), endDate = new Date();
endDate.setDate(startDate.getDate() + certificate.authority.authorityCertificate.defaultValidityDays);
certificate.validityStart = startDate;
certificate.validityEnd = endDate;
}
$scope.templates = [ $scope.templates = [
{ {
'name': 'Client Certificate', 'name': 'Client Certificate',
@ -277,6 +308,14 @@ angular.module('lemur')
}; };
$scope.create = function (certificate) { $scope.create = function (certificate) {
if(certificate.validityType === 'customDates' &&
(!certificate.validityStart || !certificate.validityEnd)) { // these are not mandatory fields in schema, thus handling validation in js
return showMissingDateError();
}
if(certificate.validityType === 'defaultDays') {
populateValidityDateAsPerDefault(certificate);
}
WizardHandler.wizard().context.loading = true; WizardHandler.wizard().context.loading = true;
CertificateService.create(certificate).then( CertificateService.create(certificate).then(
function () { function () {
@ -301,6 +340,30 @@ angular.module('lemur')
}); });
}; };
function showMissingDateError() {
let error = {};
error.message = '';
error.reasons = {};
error.reasons.validityRange = 'Valid start and end dates are needed, else select Default option';
toaster.pop({
type: 'error',
title: 'Validation Error',
body: 'lemur-bad-request',
bodyOutputType: 'directive',
directiveData: error,
timeout: 100000
});
}
function populateValidityDateAsPerDefault(certificate) {
// calculate start and end date as per default validity
let startDate = new Date(), endDate = new Date();
endDate.setDate(startDate.getDate() + certificate.authority.authorityCertificate.defaultValidityDays);
certificate.validityStart = startDate;
certificate.validityEnd = endDate;
}
$scope.templates = [ $scope.templates = [
{ {
'name': 'Client Certificate', 'name': 'Client Certificate',

View File

@ -96,7 +96,7 @@
Certificate Authority Certificate Authority
</label> </label>
<div class="col-sm-10"> <div class="col-sm-10">
<ui-select class="input-md" ng-model="certificate.authority" theme="bootstrap" title="choose an authority"> <ui-select class="input-md" ng-model="certificate.authority" theme="bootstrap" title="choose an authority" ng-change="clearDates()">
<ui-select-match placeholder="select an authority...">{{$select.selected.name}}</ui-select-match> <ui-select-match placeholder="select an authority...">{{$select.selected.name}}</ui-select-match>
<ui-select-choices class="form-control" repeat="authority in authorities" <ui-select-choices class="form-control" repeat="authority in authorities"
refresh="getAuthoritiesByName($select.search)" refresh="getAuthoritiesByName($select.search)"
@ -133,22 +133,20 @@
</div> </div>
<div class="form-group" ng-hide="certificate.authority.plugin.slug == 'acme-issuer'"> <div class="form-group" ng-hide="certificate.authority.plugin.slug == 'acme-issuer'">
<label class="control-label col-sm-2" <label class="control-label col-sm-2"
uib-tooltip="If no date is selected Lemur attempts to issue a 1 year certificate"> uib-tooltip="You can select custom date range; however, we recommend continuing with default validity.">
Validity Range <span class="glyphicon glyphicon-question-sign"></span> Validity Range <span class="glyphicon glyphicon-question-sign"></span>
</label> </label>
<div class="col-sm-2"> <div class="col-sm-4">
<select ng-model="certificate.validityYears" class="form-control"> <div class="btn-group btn-group-toggle" data-toggle="buttons">
<option value="">-</option> <label class="btn btn-info" ng-model="certificate.validityType" uib-btn-radio="'defaultDays'" ng-click="clearDates()">
<option value="1">1 year</option> Default ({{certificate.authority.authorityCertificate.defaultValidityDays}} days)</label>
</select> <label class="btn btn-info" ng-model="certificate.validityType" uib-btn-radio="'customDates'" ng-change="clearDates()">Custom</label>
</div> </div>
<span style="padding-top: 15px" class="text-center col-sm-1"> </div>
<strong>or</strong> <div class="col-sm-3" ng-if="certificate.validityType==='customDates'">
</span>
<div class="col-sm-3">
<div class="input-group"> <div class="input-group">
<input type="text" class="form-control" <input type="text" class="form-control"
uib-tooltip="yyyy/MM/dd" uib-tooltip="Start Date (yyyy/MM/dd)"
uib-datepicker-popup="yyyy/MM/dd" uib-datepicker-popup="yyyy/MM/dd"
ng-model="certificate.validityStart" ng-model="certificate.validityStart"
ng-change="certificate.setValidityEndDateRange(certificate.validityStart)" ng-change="certificate.setValidityEndDateRange(certificate.validityStart)"
@ -159,6 +157,7 @@
min-date="certificate.authority.authorityCertificate.notBefore" min-date="certificate.authority.authorityCertificate.notBefore"
alt-input-formats="altInputFormats" alt-input-formats="altInputFormats"
placeholder="Start Date" placeholder="Start Date"
readonly="true"
/> />
<span class="input-group-btn"> <span class="input-group-btn">
<button type="button" class="btn btn-default" ng-click="open1()"><i <button type="button" class="btn btn-default" ng-click="open1()"><i
@ -166,10 +165,10 @@
</span> </span>
</div> </div>
</div> </div>
<div class="col-sm-3"> <div class="col-sm-3" ng-if="certificate.validityType==='customDates'">
<div class="input-group"> <div class="input-group">
<input type="text" class="form-control" <input type="text" class="form-control"
uib-tooltip="yyyy/MM/dd" uib-tooltip="End Date (yyyy/MM/dd)"
uib-datepicker-popup="yyyy/MM/dd" uib-datepicker-popup="yyyy/MM/dd"
ng-model="certificate.validityEnd" ng-model="certificate.validityEnd"
is-open="popup2.opened" is-open="popup2.opened"
@ -179,6 +178,7 @@
min-date="certificate.authority.authorityCertificate.minValidityEnd" min-date="certificate.authority.authorityCertificate.minValidityEnd"
alt-input-formats="altInputFormats" alt-input-formats="altInputFormats"
placeholder="End Date" placeholder="End Date"
readonly="true"
/> />
<span class="input-group-btn"> <span class="input-group-btn">
<button type="button" class="btn btn-default" ng-click="open2()"><i <button type="button" class="btn btn-default" ng-click="open2()"><i
@ -186,10 +186,6 @@
</span> </span>
</div> </div>
</div> </div>
<div class="col-sm-1">
<button uib-tooltip="Clear Validity" ng-click="clearDates()" class="btn btn-default"><i
class="glyphicon glyphicon-remove"></i></button>
</div>
</div> </div>
<div class="form-group" ng-show="certificate.authority.plugin.slug == 'acme-issuer'"> <div class="form-group" ng-show="certificate.authority.plugin.slug == 'acme-issuer'">
<label class="control-label col-sm-2"> <label class="control-label col-sm-2">

View File

@ -167,18 +167,20 @@ angular.module('lemur')
}, },
setValidityEndDateRange: function (value) { setValidityEndDateRange: function (value) {
// clear selected validity end date as we are about to calculate new range // clear selected validity end date as we are about to calculate new range
if(this.validityEnd) {
this.validityEnd = ''; this.validityEnd = '';
}
// Minimum end date will be same as selected start date // Minimum end date will be same as selected start date
this.authority.authorityCertificate.minValidityEnd = value; this.authority.authorityCertificate.minValidityEnd = value;
if(!this.authority.authorityCertificate || !this.authority.authorityCertificate.maxIssuanceDays) {
this.authority.authorityCertificate.maxValidityEnd = this.authority.authorityCertificate.notAfter;
} else {
// Move max end date by maxIssuanceDays // Move max end date by maxIssuanceDays
let endDate = new Date(value); let endDate = new Date(value);
endDate.setDate(endDate.getDate() + this.authority.authorityCertificate.maxIssuanceDays); endDate.setDate(endDate.getDate() + this.authority.authorityCertificate.maxIssuanceDays);
this.authority.authorityCertificate.maxValidityEnd = endDate; this.authority.authorityCertificate.maxValidityEnd = endDate;
} }
}
}); });
}); });
return LemurRestangular.all('certificates'); return LemurRestangular.all('certificates');
@ -195,7 +197,7 @@ angular.module('lemur')
CertificateService.create = function (certificate) { CertificateService.create = function (certificate) {
certificate.attachSubAltName(); certificate.attachSubAltName();
certificate.attachCustom(); certificate.attachCustom();
if (certificate.validityYears === '') { // if a user de-selects validity years we ignore it if (certificate.validityYears === '') { // if a user de-selects validity years we ignore it - might not be needed anymore
delete certificate.validityYears; delete certificate.validityYears;
} }
return CertificateApi.post(certificate); return CertificateApi.post(certificate);
@ -281,6 +283,9 @@ angular.module('lemur')
certificate.authority.authorityCertificate.minValidityEnd = defaults.authority.authorityCertificate.notBefore; certificate.authority.authorityCertificate.minValidityEnd = defaults.authority.authorityCertificate.notBefore;
certificate.authority.authorityCertificate.maxValidityEnd = defaults.authority.authorityCertificate.notAfter; certificate.authority.authorityCertificate.maxValidityEnd = defaults.authority.authorityCertificate.notAfter;
// pre-select validity type radio button to default days
certificate.validityType = 'defaultDays';
if (certificate.dnsProviderId) { if (certificate.dnsProviderId) {
certificate.dnsProvider = {id: certificate.dnsProviderId}; certificate.dnsProvider = {id: certificate.dnsProviderId};
} }

View File

@ -147,18 +147,20 @@ angular.module('lemur')
}, },
setValidityEndDateRange: function (value) { setValidityEndDateRange: function (value) {
// clear selected validity end date as we are about to calculate new range // clear selected validity end date as we are about to calculate new range
if(this.validityEnd) {
this.validityEnd = ''; this.validityEnd = '';
}
// Minimum end date will be same as selected start date // Minimum end date will be same as selected start date
this.authority.authorityCertificate.minValidityEnd = value; this.authority.authorityCertificate.minValidityEnd = value;
if(!this.authority.authorityCertificate || !this.authority.authorityCertificate.maxIssuanceDays) {
this.authority.authorityCertificate.maxValidityEnd = this.authority.authorityCertificate.notAfter;
} else {
// Move max end date by maxIssuanceDays // Move max end date by maxIssuanceDays
let endDate = new Date(value); let endDate = new Date(value);
endDate.setDate(endDate.getDate() + this.authority.authorityCertificate.maxIssuanceDays); endDate.setDate(endDate.getDate() + this.authority.authorityCertificate.maxIssuanceDays);
this.authority.authorityCertificate.maxValidityEnd = endDate; this.authority.authorityCertificate.maxValidityEnd = endDate;
} }
}
}); });
}); });
return LemurRestangular.all('pending_certificates'); return LemurRestangular.all('pending_certificates');