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
.. data:: DEFAULT_MAX_VALIDITY_DAYS
.. data:: DEFAULT_VALIDITY_DAYS
:noindex:
Use this config to override the default limit of 1095 days (3 years) of validity. Any CA which is not listed in
PUBLIC_CA_AUTHORITY_NAMES will be using this validity to display date range on UI. Below example overrides the
default validity of 1095 days and sets it to 365 days.
Use this config to override the default validity of 365 days for certificates offered through Lemur UI. Any CA which
is not listed in PUBLIC_CA_AUTHORITY_NAMES will be using this value as default validity to be displayed on UI. Please
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

View File

@ -112,6 +112,7 @@ class RootAuthorityCertificateOutputSchema(LemurOutputSchema):
not_after = fields.DateTime()
not_before = fields.DateTime()
max_issuance_days = fields.Integer()
default_validity_days = fields.Integer()
owner = fields.Email()
status = fields.Boolean()
user = fields.Nested(UserNestedOutputSchema)
@ -137,7 +138,7 @@ class AuthorityNestedOutputSchema(LemurOutputSchema):
owner = fields.Email()
plugin = fields.Nested(PluginOutputSchema)
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()

View File

@ -320,7 +320,13 @@ class Certificate(db.Model):
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_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
def subject(self):

View File

@ -18,8 +18,9 @@ import json
import arrow
import pem
import requests
import sys
from cryptography import x509
from flask import current_app
from flask import current_app, g
from lemur.common.utils import validate_conf
from lemur.extensions import metrics
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"))
elif options.get("validity_end"):
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:
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"]))
elif 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:
validity_end = determine_end_date(False)
@ -179,6 +186,18 @@ def map_cis_fields(options, csr):
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):
"""
Handle the DigiCert API response and any errors it might have experienced.

View File

@ -107,7 +107,6 @@ angular.module('lemur')
startingDay: 1
};
$scope.open1 = function() {
$scope.popup1.opened = true;
};
@ -140,6 +139,14 @@ angular.module('lemur')
);
$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;
CertificateService.create(certificate).then(
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 = [
{
'name': 'Client Certificate',
@ -277,6 +308,14 @@ angular.module('lemur')
};
$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;
CertificateService.create(certificate).then(
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 = [
{
'name': 'Client Certificate',

View File

@ -96,7 +96,7 @@
Certificate Authority
</label>
<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-choices class="form-control" repeat="authority in authorities"
refresh="getAuthoritiesByName($select.search)"
@ -133,22 +133,20 @@
</div>
<div class="form-group" ng-hide="certificate.authority.plugin.slug == 'acme-issuer'">
<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>
</label>
<div class="col-sm-2">
<select ng-model="certificate.validityYears" class="form-control">
<option value="">-</option>
<option value="1">1 year</option>
</select>
<div class="col-sm-4">
<div class="btn-group btn-group-toggle" data-toggle="buttons">
<label class="btn btn-info" ng-model="certificate.validityType" uib-btn-radio="'defaultDays'" ng-click="clearDates()">
Default ({{certificate.authority.authorityCertificate.defaultValidityDays}} days)</label>
<label class="btn btn-info" ng-model="certificate.validityType" uib-btn-radio="'customDates'" ng-change="clearDates()">Custom</label>
</div>
<span style="padding-top: 15px" class="text-center col-sm-1">
<strong>or</strong>
</span>
<div class="col-sm-3">
</div>
<div class="col-sm-3" ng-if="certificate.validityType==='customDates'">
<div class="input-group">
<input type="text" class="form-control"
uib-tooltip="yyyy/MM/dd"
uib-tooltip="Start Date (yyyy/MM/dd)"
uib-datepicker-popup="yyyy/MM/dd"
ng-model="certificate.validityStart"
ng-change="certificate.setValidityEndDateRange(certificate.validityStart)"
@ -159,6 +157,7 @@
min-date="certificate.authority.authorityCertificate.notBefore"
alt-input-formats="altInputFormats"
placeholder="Start Date"
readonly="true"
/>
<span class="input-group-btn">
<button type="button" class="btn btn-default" ng-click="open1()"><i
@ -166,10 +165,10 @@
</span>
</div>
</div>
<div class="col-sm-3">
<div class="col-sm-3" ng-if="certificate.validityType==='customDates'">
<div class="input-group">
<input type="text" class="form-control"
uib-tooltip="yyyy/MM/dd"
uib-tooltip="End Date (yyyy/MM/dd)"
uib-datepicker-popup="yyyy/MM/dd"
ng-model="certificate.validityEnd"
is-open="popup2.opened"
@ -179,6 +178,7 @@
min-date="certificate.authority.authorityCertificate.minValidityEnd"
alt-input-formats="altInputFormats"
placeholder="End Date"
readonly="true"
/>
<span class="input-group-btn">
<button type="button" class="btn btn-default" ng-click="open2()"><i
@ -186,10 +186,6 @@
</span>
</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 class="form-group" ng-show="certificate.authority.plugin.slug == 'acme-issuer'">
<label class="control-label col-sm-2">

View File

@ -167,18 +167,20 @@ angular.module('lemur')
},
setValidityEndDateRange: function (value) {
// clear selected validity end date as we are about to calculate new range
if(this.validityEnd) {
this.validityEnd = '';
}
// Minimum end date will be same as selected start date
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
let endDate = new Date(value);
endDate.setDate(endDate.getDate() + this.authority.authorityCertificate.maxIssuanceDays);
this.authority.authorityCertificate.maxValidityEnd = endDate;
}
}
});
});
return LemurRestangular.all('certificates');
@ -195,7 +197,7 @@ angular.module('lemur')
CertificateService.create = function (certificate) {
certificate.attachSubAltName();
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;
}
return CertificateApi.post(certificate);
@ -281,6 +283,9 @@ angular.module('lemur')
certificate.authority.authorityCertificate.minValidityEnd = defaults.authority.authorityCertificate.notBefore;
certificate.authority.authorityCertificate.maxValidityEnd = defaults.authority.authorityCertificate.notAfter;
// pre-select validity type radio button to default days
certificate.validityType = 'defaultDays';
if (certificate.dnsProviderId) {
certificate.dnsProvider = {id: certificate.dnsProviderId};
}

View File

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