Initial work allowing certificates to be revoked. (#941)
* Initial work allowing for certificates to be revoked.
This commit is contained in:
parent
ea6f5c920b
commit
bb08b1e637
|
@ -80,6 +80,7 @@ def get_or_increase_name(name):
|
||||||
class Certificate(db.Model):
|
class Certificate(db.Model):
|
||||||
__tablename__ = 'certificates'
|
__tablename__ = 'certificates'
|
||||||
id = Column(Integer, primary_key=True)
|
id = Column(Integer, primary_key=True)
|
||||||
|
external_id = Column(String(128))
|
||||||
owner = Column(String(128), nullable=False)
|
owner = Column(String(128), nullable=False)
|
||||||
name = Column(String(128), unique=True)
|
name = Column(String(128), unique=True)
|
||||||
description = Column(String(1024))
|
description = Column(String(1024))
|
||||||
|
@ -162,6 +163,7 @@ class Certificate(db.Model):
|
||||||
self.signing_algorithm = defaults.signing_algorithm(cert)
|
self.signing_algorithm = defaults.signing_algorithm(cert)
|
||||||
self.bits = defaults.bitstrength(cert)
|
self.bits = defaults.bitstrength(cert)
|
||||||
self.serial = defaults.serial(cert)
|
self.serial = defaults.serial(cert)
|
||||||
|
self.external_id = kwargs.get('external_id')
|
||||||
|
|
||||||
for domain in defaults.domains(cert):
|
for domain in defaults.domains(cert):
|
||||||
self.domains.append(Domain(name=domain))
|
self.domains.append(Domain(name=domain))
|
||||||
|
|
|
@ -172,6 +172,7 @@ class CertificateCloneSchema(LemurOutputSchema):
|
||||||
|
|
||||||
class CertificateOutputSchema(LemurOutputSchema):
|
class CertificateOutputSchema(LemurOutputSchema):
|
||||||
id = fields.Integer()
|
id = fields.Integer()
|
||||||
|
external_id = fields.String()
|
||||||
bits = fields.Integer()
|
bits = fields.Integer()
|
||||||
body = fields.String()
|
body = fields.String()
|
||||||
chain = fields.String()
|
chain = fields.String()
|
||||||
|
@ -253,6 +254,10 @@ class CertificateNotificationOutputSchema(LemurOutputSchema):
|
||||||
endpoints = fields.Nested(EndpointNestedOutputSchema, many=True, missing=[])
|
endpoints = fields.Nested(EndpointNestedOutputSchema, many=True, missing=[])
|
||||||
|
|
||||||
|
|
||||||
|
class CertificateRevokeSchema(LemurInputSchema):
|
||||||
|
comments = fields.String()
|
||||||
|
|
||||||
|
|
||||||
certificate_input_schema = CertificateInputSchema()
|
certificate_input_schema = CertificateInputSchema()
|
||||||
certificate_output_schema = CertificateOutputSchema()
|
certificate_output_schema = CertificateOutputSchema()
|
||||||
certificates_output_schema = CertificateOutputSchema(many=True)
|
certificates_output_schema = CertificateOutputSchema(many=True)
|
||||||
|
@ -260,3 +265,4 @@ certificate_upload_input_schema = CertificateUploadInputSchema()
|
||||||
certificate_export_input_schema = CertificateExportInputSchema()
|
certificate_export_input_schema = CertificateExportInputSchema()
|
||||||
certificate_edit_input_schema = CertificateEditInputSchema()
|
certificate_edit_input_schema = CertificateEditInputSchema()
|
||||||
certificate_notification_output_schema = CertificateNotificationOutputSchema()
|
certificate_notification_output_schema = CertificateNotificationOutputSchema()
|
||||||
|
certificate_revoke_schema = CertificateRevokeSchema()
|
||||||
|
|
|
@ -180,8 +180,8 @@ def mint(**kwargs):
|
||||||
private_key = None
|
private_key = None
|
||||||
csr_imported.send(authority=authority, csr=csr)
|
csr_imported.send(authority=authority, csr=csr)
|
||||||
|
|
||||||
cert_body, cert_chain = issuer.create_certificate(csr, kwargs)
|
cert_body, cert_chain, external_id = issuer.create_certificate(csr, kwargs)
|
||||||
return cert_body, private_key, cert_chain,
|
return cert_body, private_key, cert_chain, external_id
|
||||||
|
|
||||||
|
|
||||||
def import_certificate(**kwargs):
|
def import_certificate(**kwargs):
|
||||||
|
@ -234,10 +234,11 @@ def create(**kwargs):
|
||||||
"""
|
"""
|
||||||
Creates a new certificate.
|
Creates a new certificate.
|
||||||
"""
|
"""
|
||||||
cert_body, private_key, cert_chain = mint(**kwargs)
|
cert_body, private_key, cert_chain, external_id = mint(**kwargs)
|
||||||
kwargs['body'] = cert_body
|
kwargs['body'] = cert_body
|
||||||
kwargs['private_key'] = private_key
|
kwargs['private_key'] = private_key
|
||||||
kwargs['chain'] = cert_chain
|
kwargs['chain'] = cert_chain
|
||||||
|
kwargs['external_id'] = external_id
|
||||||
|
|
||||||
roles = create_certificate_roles(**kwargs)
|
roles = create_certificate_roles(**kwargs)
|
||||||
|
|
||||||
|
|
|
@ -18,8 +18,16 @@ from lemur.auth.service import AuthenticatedResource
|
||||||
from lemur.auth.permissions import AuthorityPermission, CertificatePermission
|
from lemur.auth.permissions import AuthorityPermission, CertificatePermission
|
||||||
|
|
||||||
from lemur.certificates import service
|
from lemur.certificates import service
|
||||||
from lemur.certificates.schemas import certificate_input_schema, certificate_output_schema, \
|
from lemur.plugins.base import plugins
|
||||||
certificate_upload_input_schema, certificates_output_schema, certificate_export_input_schema, certificate_edit_input_schema
|
from lemur.certificates.schemas import (
|
||||||
|
certificate_input_schema,
|
||||||
|
certificate_output_schema,
|
||||||
|
certificate_upload_input_schema,
|
||||||
|
certificates_output_schema,
|
||||||
|
certificate_export_input_schema,
|
||||||
|
certificate_edit_input_schema,
|
||||||
|
certificate_revoke_schema
|
||||||
|
)
|
||||||
|
|
||||||
from lemur.roles import service as role_service
|
from lemur.roles import service as role_service
|
||||||
from lemur.logs import service as log_service
|
from lemur.logs import service as log_service
|
||||||
|
@ -944,6 +952,73 @@ class CertificateExport(AuthenticatedResource):
|
||||||
return dict(extension=extension, passphrase=passphrase, data=base64.b64encode(data).decode('utf-8'))
|
return dict(extension=extension, passphrase=passphrase, data=base64.b64encode(data).decode('utf-8'))
|
||||||
|
|
||||||
|
|
||||||
|
class CertificateRevoke(AuthenticatedResource):
|
||||||
|
def __init__(self):
|
||||||
|
self.reqparse = reqparse.RequestParser()
|
||||||
|
super(CertificateRevoke, self).__init__()
|
||||||
|
|
||||||
|
@validate_schema(certificate_revoke_schema, None)
|
||||||
|
def put(self, certificate_id, data=None):
|
||||||
|
"""
|
||||||
|
.. http:put:: /certificates/1/revoke
|
||||||
|
|
||||||
|
Revoke a certificate
|
||||||
|
|
||||||
|
**Example request**:
|
||||||
|
|
||||||
|
.. sourcecode:: http
|
||||||
|
|
||||||
|
POST /certificates/1/revoke HTTP/1.1
|
||||||
|
Host: example.com
|
||||||
|
Accept: application/json, text/javascript
|
||||||
|
|
||||||
|
{
|
||||||
|
"comments": "Certificate no longer needed"
|
||||||
|
}
|
||||||
|
|
||||||
|
**Example response**:
|
||||||
|
|
||||||
|
.. sourcecode:: http
|
||||||
|
|
||||||
|
HTTP/1.1 200 OK
|
||||||
|
Vary: Accept
|
||||||
|
Content-Type: text/javascript
|
||||||
|
|
||||||
|
{
|
||||||
|
'id': 1
|
||||||
|
}
|
||||||
|
|
||||||
|
:reqheader Authorization: OAuth token to authenticate
|
||||||
|
:statuscode 200: no error
|
||||||
|
:statuscode 403: unauthenticated
|
||||||
|
|
||||||
|
"""
|
||||||
|
cert = service.get(certificate_id)
|
||||||
|
|
||||||
|
if not cert:
|
||||||
|
return dict(message="Cannot find specified certificate"), 404
|
||||||
|
|
||||||
|
# allow creators
|
||||||
|
if g.current_user != cert.user:
|
||||||
|
owner_role = role_service.get_by_name(cert.owner)
|
||||||
|
permission = CertificatePermission(owner_role, [x.name for x in cert.roles])
|
||||||
|
|
||||||
|
if not permission.can():
|
||||||
|
return dict(message='You are not authorized to revoke this certificate.'), 403
|
||||||
|
|
||||||
|
if not cert.external_id:
|
||||||
|
return dict(message='Cannot revoke certificate. No external id found.'), 400
|
||||||
|
|
||||||
|
if cert.endpoints:
|
||||||
|
return dict(message='Cannot revoke certificate. Endpoints are deployed with the given certificate.'), 403
|
||||||
|
|
||||||
|
plugin = plugins.get(cert.authority.plugin_name)
|
||||||
|
plugin.revoke_certificate(cert, data)
|
||||||
|
log_service.create(g.current_user, 'revoke_cert', certificate=cert)
|
||||||
|
return dict(id=cert.id)
|
||||||
|
|
||||||
|
|
||||||
|
api.add_resource(CertificateRevoke, '/certificates/<int:certificate_id>/revoke', endpoint='revokeCertificate')
|
||||||
api.add_resource(CertificatesList, '/certificates', endpoint='certificates')
|
api.add_resource(CertificatesList, '/certificates', endpoint='certificates')
|
||||||
api.add_resource(Certificates, '/certificates/<int:certificate_id>', endpoint='certificate')
|
api.add_resource(Certificates, '/certificates/<int:certificate_id>', endpoint='certificate')
|
||||||
api.add_resource(CertificatesStats, '/certificates/stats', endpoint='certificateStats')
|
api.add_resource(CertificatesStats, '/certificates/stats', endpoint='certificateStats')
|
||||||
|
|
|
@ -18,6 +18,6 @@ class Log(db.Model):
|
||||||
__tablename__ = 'logs'
|
__tablename__ = 'logs'
|
||||||
id = Column(Integer, primary_key=True)
|
id = Column(Integer, primary_key=True)
|
||||||
certificate_id = Column(Integer, ForeignKey('certificates.id'))
|
certificate_id = Column(Integer, ForeignKey('certificates.id'))
|
||||||
log_type = Column(Enum('key_view', 'create_cert', 'update_cert', name='log_type'), nullable=False)
|
log_type = Column(Enum('key_view', 'create_cert', 'update_cert', 'revoke_cert', name='log_type'), nullable=False)
|
||||||
logged_at = Column(ArrowType(), PassiveDefault(func.now()), nullable=False)
|
logged_at = Column(ArrowType(), PassiveDefault(func.now()), nullable=False)
|
||||||
user_id = Column(Integer, ForeignKey('users.id'), nullable=False)
|
user_id = Column(Integer, ForeignKey('users.id'), nullable=False)
|
||||||
|
|
|
@ -3,6 +3,9 @@ from alembic import context
|
||||||
from sqlalchemy import engine_from_config, pool
|
from sqlalchemy import engine_from_config, pool
|
||||||
from logging.config import fileConfig
|
from logging.config import fileConfig
|
||||||
|
|
||||||
|
import alembic_autogenerate_enums
|
||||||
|
|
||||||
|
|
||||||
# this is the Alembic Config object, which provides
|
# this is the Alembic Config object, which provides
|
||||||
# access to the values within the .ini file in use.
|
# access to the values within the .ini file in use.
|
||||||
config = context.config
|
config = context.config
|
||||||
|
|
|
@ -14,28 +14,8 @@ from alembic import op
|
||||||
|
|
||||||
|
|
||||||
def upgrade():
|
def upgrade():
|
||||||
connection = None
|
op.sync_enum_values('public', 'log_type', ['key_view'], ['create_cert', 'key_view', 'update_cert'])
|
||||||
|
|
||||||
if not op.get_context().as_sql:
|
|
||||||
connection = op.get_bind()
|
|
||||||
connection.execution_options(isolation_level='AUTOCOMMIT')
|
|
||||||
|
|
||||||
op.execute("ALTER TYPE log_type ADD VALUE 'create_cert'")
|
|
||||||
op.execute("ALTER TYPE log_type ADD VALUE 'update_cert'")
|
|
||||||
|
|
||||||
if connection is not None:
|
|
||||||
connection.execution_options(isolation_level='READ_COMMITTED')
|
|
||||||
|
|
||||||
|
|
||||||
def downgrade():
|
def downgrade():
|
||||||
connection = None
|
op.sync_enum_values('public', 'log_type', ['create_cert', 'key_view', 'update_cert'], ['key_view'])
|
||||||
|
|
||||||
if not op.get_context().as_sql:
|
|
||||||
connection = op.get_bind()
|
|
||||||
connection.execution_options(isolation_level='AUTOCOMMIT')
|
|
||||||
|
|
||||||
op.execute("ALTER TYPE log_type DROP VALUE 'create_cert'")
|
|
||||||
op.execute("ALTER TYPE log_type DROP VALUE 'update_cert'")
|
|
||||||
|
|
||||||
if connection is not None:
|
|
||||||
connection.execution_options(isolation_level='READ_COMMITTED')
|
|
||||||
|
|
|
@ -0,0 +1,28 @@
|
||||||
|
"""Adds external ID checking and modifying enum
|
||||||
|
|
||||||
|
Revision ID: b29e2c4bf8c9
|
||||||
|
Revises: 1ae8e3104db8
|
||||||
|
Create Date: 2017-09-26 10:50:35.740367
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
# revision identifiers, used by Alembic.
|
||||||
|
revision = 'b29e2c4bf8c9'
|
||||||
|
down_revision = '1ae8e3104db8'
|
||||||
|
|
||||||
|
from alembic import op
|
||||||
|
import sqlalchemy as sa
|
||||||
|
|
||||||
|
|
||||||
|
def upgrade():
|
||||||
|
# ### commands auto generated by Alembic - please adjust! ###
|
||||||
|
op.add_column('certificates', sa.Column('external_id', sa.String(128), nullable=True))
|
||||||
|
op.sync_enum_values('public', 'log_type', ['create_cert', 'key_view', 'update_cert'], ['create_cert', 'key_view', 'revoke_cert', 'update_cert'])
|
||||||
|
# ### end Alembic commands ###
|
||||||
|
|
||||||
|
|
||||||
|
def downgrade():
|
||||||
|
# ### commands auto generated by Alembic - please adjust! ###
|
||||||
|
op.sync_enum_values('public', 'log_type', ['create_cert', 'key_view', 'revoke_cert', 'update_cert'], ['create_cert', 'key_view', 'update_cert'])
|
||||||
|
op.drop_column('certificates', 'external_id')
|
||||||
|
# ### end Alembic commands ###
|
|
@ -21,3 +21,6 @@ class IssuerPlugin(Plugin):
|
||||||
|
|
||||||
def create_authority(self, options):
|
def create_authority(self, options):
|
||||||
raise NotImplementedError
|
raise NotImplementedError
|
||||||
|
|
||||||
|
def revoke_certificate(self, certificate, comments):
|
||||||
|
raise NotImplementedError
|
||||||
|
|
|
@ -49,7 +49,8 @@ class CfsslIssuerPlugin(IssuerPlugin):
|
||||||
response_json = json.loads(response.content.decode('utf_8'))
|
response_json = json.loads(response.content.decode('utf_8'))
|
||||||
cert = response_json['result']['certificate']
|
cert = response_json['result']['certificate']
|
||||||
|
|
||||||
return cert, current_app.config.get('CFSSL_INTERMEDIATE'),
|
# TODO add external ID
|
||||||
|
return cert, current_app.config.get('CFSSL_INTERMEDIATE'), None
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def create_authority(options):
|
def create_authority(options):
|
||||||
|
|
|
@ -187,7 +187,7 @@ class CryptographyIssuerPlugin(IssuerPlugin):
|
||||||
"""
|
"""
|
||||||
current_app.logger.debug("Issuing new cryptography certificate with options: {0}".format(options))
|
current_app.logger.debug("Issuing new cryptography certificate with options: {0}".format(options))
|
||||||
cert_pem, chain_cert_pem = issue_certificate(csr, options)
|
cert_pem, chain_cert_pem = issue_certificate(csr, options)
|
||||||
return cert_pem, chain_cert_pem
|
return cert_pem, chain_cert_pem, None
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def create_authority(options):
|
def create_authority(options):
|
||||||
|
|
|
@ -312,7 +312,17 @@ class DigiCertIssuerPlugin(IssuerPlugin):
|
||||||
# retrieve certificate
|
# retrieve certificate
|
||||||
certificate_url = "{0}/services/v2/certificate/{1}/download/format/pem_all".format(base_url, certificate_id)
|
certificate_url = "{0}/services/v2/certificate/{1}/download/format/pem_all".format(base_url, certificate_id)
|
||||||
end_entity, intermediate, root = pem.parse(self.session.get(certificate_url).content)
|
end_entity, intermediate, root = pem.parse(self.session.get(certificate_url).content)
|
||||||
return "\n".join(str(end_entity).splitlines()), "\n".join(str(intermediate).splitlines())
|
return "\n".join(str(end_entity).splitlines()), "\n".join(str(intermediate).splitlines()), certificate_id
|
||||||
|
|
||||||
|
def revoke_certificate(self, certificate, comments):
|
||||||
|
"""Revoke a Digicert certificate."""
|
||||||
|
base_url = current_app.config.get('DIGICERT_URL')
|
||||||
|
|
||||||
|
# make certificate revoke request
|
||||||
|
create_url = '{0}/certificate/{1}/revoke'.format(base_url, certificate.external_id)
|
||||||
|
metrics.send('digicert_revoke_certificate', 'counter', 1)
|
||||||
|
response = self.session.put(create_url, data=json.dumps({'comments': comments}))
|
||||||
|
return handle_response(response)
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def create_authority(options):
|
def create_authority(options):
|
||||||
|
@ -379,7 +389,22 @@ class DigiCertCISIssuerPlugin(IssuerPlugin):
|
||||||
|
|
||||||
self.session.headers.pop('Accept')
|
self.session.headers.pop('Accept')
|
||||||
end_entity = pem.parse(certificate_pem)[0]
|
end_entity = pem.parse(certificate_pem)[0]
|
||||||
return "\n".join(str(end_entity).splitlines()), current_app.config.get('DIGICERT_CIS_INTERMEDIATE')
|
return "\n".join(str(end_entity).splitlines()), current_app.config.get('DIGICERT_CIS_INTERMEDIATE'), data['id']
|
||||||
|
|
||||||
|
def revoke_certificate(self, certificate, comments):
|
||||||
|
"""Revoke a Digicert certificate."""
|
||||||
|
base_url = current_app.config.get('DIGICERT_CIS_URL')
|
||||||
|
|
||||||
|
# make certificate revoke request
|
||||||
|
revoke_url = '{0}/platform/cis/certificate/{1}/revoke'.format(base_url, certificate.external_id)
|
||||||
|
metrics.send('digicert_revoke_certificate_success', 'counter', 1)
|
||||||
|
response = self.session.put(revoke_url, data=json.dumps({'comments': comments}))
|
||||||
|
|
||||||
|
if response.status_code != 204:
|
||||||
|
metrics.send('digicert_revoke_certificate_failure', 'counter', 1)
|
||||||
|
raise Exception('Failed to revoke certificate.')
|
||||||
|
|
||||||
|
metrics.send('digicert_revoke_certificate_success', 'counter', 1)
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def create_authority(options):
|
def create_authority(options):
|
||||||
|
|
|
@ -171,7 +171,7 @@ ghi
|
||||||
adapter.register_uri('GET', 'mock://www.digicert.com/services/v2/certificate/cert123/download/format/pem_all', text=pem_fixture)
|
adapter.register_uri('GET', 'mock://www.digicert.com/services/v2/certificate/cert123/download/format/pem_all', text=pem_fixture)
|
||||||
subject.session.mount('mock', adapter)
|
subject.session.mount('mock', adapter)
|
||||||
|
|
||||||
cert, intermediate = subject.create_certificate("", {'common_name': 'test.com'})
|
cert, intermediate, external_id = subject.create_certificate("", {'common_name': 'test.com'})
|
||||||
|
|
||||||
assert cert == "-----BEGIN CERTIFICATE-----\nabc\n-----END CERTIFICATE-----"
|
assert cert == "-----BEGIN CERTIFICATE-----\nabc\n-----END CERTIFICATE-----"
|
||||||
assert intermediate == "-----BEGIN CERTIFICATE-----\ndef\n-----END CERTIFICATE-----"
|
assert intermediate == "-----BEGIN CERTIFICATE-----\ndef\n-----END CERTIFICATE-----"
|
||||||
|
|
|
@ -195,7 +195,8 @@ class VerisignIssuerPlugin(IssuerPlugin):
|
||||||
|
|
||||||
response = self.session.post(url, data=data)
|
response = self.session.post(url, data=data)
|
||||||
cert = handle_response(response.content)['Response']['Certificate']
|
cert = handle_response(response.content)['Response']['Certificate']
|
||||||
return cert, current_app.config.get('VERISIGN_INTERMEDIATE'),
|
# TODO add external id
|
||||||
|
return cert, current_app.config.get('VERISIGN_INTERMEDIATE'), None
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def create_authority(options):
|
def create_authority(options):
|
||||||
|
|
|
@ -58,6 +58,25 @@
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
lemur.directive('compareTo', function() {
|
||||||
|
return {
|
||||||
|
require: 'ngModel',
|
||||||
|
scope: {
|
||||||
|
otherModelValue: '=compareTo'
|
||||||
|
},
|
||||||
|
link: function(scope, element, attributes, ngModel) {
|
||||||
|
|
||||||
|
ngModel.$validators.compareTo = function(modelValue) {
|
||||||
|
return modelValue === scope.otherModelValue;
|
||||||
|
};
|
||||||
|
|
||||||
|
scope.$watch('otherModelValue', function() {
|
||||||
|
ngModel.$validate();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
lemur.service('MomentService', function () {
|
lemur.service('MomentService', function () {
|
||||||
this.diffMoment = function (start, end) {
|
this.diffMoment = function (start, end) {
|
||||||
if (end !== 'None') {
|
if (end !== 'None') {
|
||||||
|
|
|
@ -328,4 +328,36 @@ angular.module('lemur')
|
||||||
$scope.authorityService = AuthorityService;
|
$scope.authorityService = AuthorityService;
|
||||||
$scope.destinationService = DestinationService;
|
$scope.destinationService = DestinationService;
|
||||||
$scope.notificationService = NotificationService;
|
$scope.notificationService = NotificationService;
|
||||||
|
})
|
||||||
|
|
||||||
|
.controller('CertificateRevokeController', function ($scope, $uibModalInstance, CertificateApi, CertificateService, LemurRestangular, NotificationService, toaster, revokeId) {
|
||||||
|
CertificateApi.get(revokeId).then(function (certificate) {
|
||||||
|
$scope.certificate = certificate;
|
||||||
|
});
|
||||||
|
|
||||||
|
$scope.cancel = function () {
|
||||||
|
$uibModalInstance.dismiss('cancel');
|
||||||
|
};
|
||||||
|
|
||||||
|
$scope.revoke = function (certificate) {
|
||||||
|
CertificateService.revoke(certificate).then(
|
||||||
|
function () {
|
||||||
|
toaster.pop({
|
||||||
|
type: 'success',
|
||||||
|
title: certificate.name,
|
||||||
|
body: 'Successfully revoked!'
|
||||||
|
});
|
||||||
|
$uibModalInstance.close();
|
||||||
|
},
|
||||||
|
function (response) {
|
||||||
|
toaster.pop({
|
||||||
|
type: 'error',
|
||||||
|
title: certificate.name,
|
||||||
|
body: 'lemur-bad-request',
|
||||||
|
bodyOutputType: 'directive',
|
||||||
|
directiveData: response.data,
|
||||||
|
timeout: 100000
|
||||||
|
});
|
||||||
|
});
|
||||||
|
};
|
||||||
});
|
});
|
||||||
|
|
|
@ -0,0 +1,48 @@
|
||||||
|
<div class="modal-header">
|
||||||
|
<button type="button" class="close" ng-click="cancel()" aria-label="Close"><span aria-hidden="true">×</span>
|
||||||
|
</button>
|
||||||
|
<h3 class="modal-title">Revoke <span class="text-muted"><small>{{ certificate.name }}</small></span></h3>
|
||||||
|
</div>
|
||||||
|
<div class="modal-body">
|
||||||
|
<form name="revokeForm" ng-if="!certificate.endpoints.length" novalidate>
|
||||||
|
<p><strong>Certificate revocation is final. Once revoked the certificate is no longer valid.</strong></p>
|
||||||
|
<div class="form-horizontal">
|
||||||
|
<div class="form-group"
|
||||||
|
ng-class="{'has-error': revokeForm.confirm.$invalid, 'has-success': !revokeForm.$invalid&&revokeForm.confirm.$dirty}">
|
||||||
|
<label class="control-label col-sm-2">
|
||||||
|
Confirm Revocation
|
||||||
|
</label>
|
||||||
|
<div class="col-sm-10">
|
||||||
|
<input name="confirm" ng-model="confirm" placeholder='{{ certificate.name }}'
|
||||||
|
uib-tooltip="Confirm revocation by entering '{{ certificate.name }}'"
|
||||||
|
class="form-control"
|
||||||
|
compare-to="certificate.name"
|
||||||
|
required/>
|
||||||
|
|
||||||
|
<p ng-show="revokeForm.confirm.$invalid && !revokeForm.confirm.$pristine" class="help-block">
|
||||||
|
You must confirm certificate revocation.</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
<div ng-if="certificate.endpoints.length">
|
||||||
|
<p><strong>Certificate cannot be revoked, it is associated with the following endpoints. Disassociate this
|
||||||
|
certificate
|
||||||
|
before revoking.</strong></p>
|
||||||
|
<ul class="list-group">
|
||||||
|
<li class="list-group-item" ng-repeat="endpoint in certificate.endpoints">
|
||||||
|
<span class="pull-right"><label class="label label-default">{{ endpoint.type }}</label></span>
|
||||||
|
<ul class="list-unstyled">
|
||||||
|
<li>{{ endpoint.name }}</li>
|
||||||
|
<li><span class="text-muted">{{ endpoint.dnsname }}</span></li>
|
||||||
|
</ul>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="modal-footer">
|
||||||
|
<button type="submit" ng-click="revoke(certificate)" ng-disabled="revokeForm.confirm.$invalid"
|
||||||
|
class="btn btn-danger">Revoke
|
||||||
|
</button>
|
||||||
|
<button ng-click="cancel()" class="btn">Cancel</button>
|
||||||
|
</div>
|
|
@ -258,5 +258,9 @@ angular.module('lemur')
|
||||||
return certificate.customPOST(certificate.exportOptions, 'export');
|
return certificate.customPOST(certificate.exportOptions, 'export');
|
||||||
};
|
};
|
||||||
|
|
||||||
|
CertificateService.revoke = function (certificate) {
|
||||||
|
return certificate.customPUT(certificate.externalId, 'revoke');
|
||||||
|
};
|
||||||
|
|
||||||
return CertificateService;
|
return CertificateService;
|
||||||
});
|
});
|
||||||
|
|
|
@ -190,4 +190,19 @@ angular.module('lemur')
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
$scope.revoke = function (certificateId) {
|
||||||
|
$uibModal.open({
|
||||||
|
animation: true,
|
||||||
|
controller: 'CertificateRevokeController',
|
||||||
|
templateUrl: '/angular/certificates/certificate/revoke.tpl.html',
|
||||||
|
size: 'lg',
|
||||||
|
backdrop: 'static',
|
||||||
|
resolve: {
|
||||||
|
revokeId: function () {
|
||||||
|
return certificateId;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
});
|
});
|
||||||
|
|
|
@ -60,6 +60,7 @@
|
||||||
<li><a href ng-click="edit(certificate.id)">Edit</a></li>
|
<li><a href ng-click="edit(certificate.id)">Edit</a></li>
|
||||||
<li><a href ng-click="clone(certificate.id)">Clone</a></li>
|
<li><a href ng-click="clone(certificate.id)">Clone</a></li>
|
||||||
<li><a href ng-click="export(certificate.id)">Export</a></li>
|
<li><a href ng-click="export(certificate.id)">Export</a></li>
|
||||||
|
<li><a href ng-click="revoke(certificate.id)">Revoke</a></li>
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -188,27 +189,21 @@
|
||||||
<uib-tab>
|
<uib-tab>
|
||||||
<uib-tab-heading>
|
<uib-tab-heading>
|
||||||
Chain
|
Chain
|
||||||
<button class="btn btn-xs btn-default clipboard-btn glyphicon glyphicon-copy"
|
<i class="glyphicon glyphicon-copy" style="cursor: pointer" clipboard text="certificate.chain"></i>
|
||||||
uib-tooltip="Copy chain to clipboard" tooltip-trigger="mouseenter" clipboard
|
|
||||||
text="certificate.chain"></button>
|
|
||||||
</uib-tab-heading>
|
</uib-tab-heading>
|
||||||
<pre style="width: 100%">{{ certificate.chain }}</pre>
|
<pre style="width: 100%">{{ certificate.chain }}</pre>
|
||||||
</uib-tab>
|
</uib-tab>
|
||||||
<uib-tab>
|
<uib-tab>
|
||||||
<uib-tab-heading>
|
<uib-tab-heading>
|
||||||
Public Certificate
|
Public Certificate
|
||||||
<button class="btn btn-xs btn-default clipboard-btn glyphicon glyphicon-copy"
|
<i class="glyphicon glyphicon-copy" style="cursor: pointer" clipboard text="certificate.body"></i>
|
||||||
uib-tooltip="Copy certificate to clipboard" tooltip-trigger="mouseenter" clipboard
|
|
||||||
text="certificate.body"></button>
|
|
||||||
</uib-tab-heading>
|
</uib-tab-heading>
|
||||||
<pre style="width: 100%">{{ certificate.body }}</pre>
|
<pre style="width: 100%">{{ certificate.body }}</pre>
|
||||||
</uib-tab>
|
</uib-tab>
|
||||||
<uib-tab ng-click="loadPrivateKey(certificate)">
|
<uib-tab ng-click="loadPrivateKey(certificate)">
|
||||||
<uib-tab-heading>
|
<uib-tab-heading>
|
||||||
Private Key
|
Private Key
|
||||||
<button class="btn btn-xs btn-default clipboard-btn glyphicon glyphicon-copy"
|
<i class="glyphicon glyphicon-copy" style="cursor: pointer" clipboard text="certificate.privateKey"></i>
|
||||||
uib-tooltip="Copy key to clipboard" tooltip-trigger="mouseenter" clipboard
|
|
||||||
text="certificate.privateKey"></button>
|
|
||||||
</uib-tab-heading>
|
</uib-tab-heading>
|
||||||
<pre style="width: 100%">{{ certificate.privateKey }}</pre>
|
<pre style="width: 100%">{{ certificate.privateKey }}</pre>
|
||||||
</uib-tab>
|
</uib-tab>
|
||||||
|
|
|
@ -15,7 +15,7 @@ class TestIssuerPlugin(IssuerPlugin):
|
||||||
super(TestIssuerPlugin, self).__init__(*args, **kwargs)
|
super(TestIssuerPlugin, self).__init__(*args, **kwargs)
|
||||||
|
|
||||||
def create_certificate(self, csr, issuer_options):
|
def create_certificate(self, csr, issuer_options):
|
||||||
return INTERNAL_VALID_LONG_STR, INTERNAL_VALID_SAN_STR
|
return INTERNAL_VALID_LONG_STR, INTERNAL_VALID_SAN_STR, None
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def create_authority(options):
|
def create_authority(options):
|
||||||
|
|
|
@ -430,7 +430,7 @@ def test_get_account_number(client):
|
||||||
|
|
||||||
def test_mint_certificate(issuer_plugin, authority):
|
def test_mint_certificate(issuer_plugin, authority):
|
||||||
from lemur.certificates.service import mint
|
from lemur.certificates.service import mint
|
||||||
cert_body, private_key, chain = mint(authority=authority, csr=CSR_STR)
|
cert_body, private_key, chain, external_id = mint(authority=authority, csr=CSR_STR)
|
||||||
assert cert_body == INTERNAL_VALID_LONG_STR, INTERNAL_VALID_SAN_STR
|
assert cert_body == INTERNAL_VALID_LONG_STR, INTERNAL_VALID_SAN_STR
|
||||||
|
|
||||||
|
|
||||||
|
|
3
setup.py
3
setup.py
|
@ -66,7 +66,8 @@ install_requires = [
|
||||||
'raven[flask]==6.2.1',
|
'raven[flask]==6.2.1',
|
||||||
'jinja2==2.9.6',
|
'jinja2==2.9.6',
|
||||||
'pyldap==2.4.37', # required by ldap auth provider
|
'pyldap==2.4.37', # required by ldap auth provider
|
||||||
'paramiko==2.3.1' # required for lemur_linuxdst plugin
|
'paramiko==2.3.1', # required for lemur_linuxdst plugin
|
||||||
|
'alembic-autogenerate-enums==0.0.2'
|
||||||
]
|
]
|
||||||
|
|
||||||
tests_require = [
|
tests_require = [
|
||||||
|
|
Loading…
Reference in New Issue