Implement a ALLOW_CERT_DELETION option (boolean, default False). When enabled, the certificate delete API call will work and the UI
will no longer display deleted certificates. When disabled (the default), the delete API call will not work (405 method not allowed) and the UI will show all certificates, regardless of the 'deleted' flag.
This commit is contained in:
parent
c79d9c7051
commit
8abf95063c
|
@ -161,6 +161,13 @@ Specifying the `SQLALCHEMY_MAX_OVERFLOW` to 0 will enforce limit to not create c
|
||||||
|
|
||||||
Dump all imported or generated CSR and certificate details to stdout using OpenSSL. (default: `False`)
|
Dump all imported or generated CSR and certificate details to stdout using OpenSSL. (default: `False`)
|
||||||
|
|
||||||
|
.. data:: ALLOW_CERT_DELETION
|
||||||
|
:noindex:
|
||||||
|
|
||||||
|
When set to True, certificates can be marked as deleted via the API and deleted certificates will not be displayed
|
||||||
|
in the UI. When set to False (the default), the certificate delete API will always return "405 method not allowed"
|
||||||
|
and deleted certificates will always be visible in the UI. (default: `False`)
|
||||||
|
|
||||||
|
|
||||||
Certificate Default Options
|
Certificate Default Options
|
||||||
---------------------------
|
---------------------------
|
||||||
|
|
|
@ -101,7 +101,7 @@ class Certificate(db.Model):
|
||||||
issuer = Column(String(128))
|
issuer = Column(String(128))
|
||||||
serial = Column(String(128))
|
serial = Column(String(128))
|
||||||
cn = Column(String(128))
|
cn = Column(String(128))
|
||||||
deleted = Column(Boolean, index=True)
|
deleted = Column(Boolean, index=True, default=False)
|
||||||
dns_provider_id = Column(Integer(), ForeignKey('dns_providers.id', ondelete='CASCADE'), nullable=True)
|
dns_provider_id = Column(Integer(), ForeignKey('dns_providers.id', ondelete='CASCADE'), nullable=True)
|
||||||
|
|
||||||
not_before = Column(ArrowType)
|
not_before = Column(ArrowType)
|
||||||
|
|
|
@ -381,6 +381,9 @@ def render(args):
|
||||||
now = arrow.now().format('YYYY-MM-DD')
|
now = arrow.now().format('YYYY-MM-DD')
|
||||||
query = query.filter(Certificate.not_after <= to).filter(Certificate.not_after >= now)
|
query = query.filter(Certificate.not_after <= to).filter(Certificate.not_after >= now)
|
||||||
|
|
||||||
|
if current_app.config.get('ALLOW_CERT_DELETION', False):
|
||||||
|
query = query.filter(Certificate.deleted == False) # noqa
|
||||||
|
|
||||||
result = database.sort_and_page(query, Certificate, args)
|
result = database.sort_and_page(query, Certificate, args)
|
||||||
return result
|
return result
|
||||||
|
|
||||||
|
|
|
@ -6,10 +6,9 @@
|
||||||
.. moduleauthor:: Kevin Glisson <kglisson@netflix.com>
|
.. moduleauthor:: Kevin Glisson <kglisson@netflix.com>
|
||||||
"""
|
"""
|
||||||
import base64
|
import base64
|
||||||
import arrow
|
|
||||||
from builtins import str
|
from builtins import str
|
||||||
|
|
||||||
from flask import Blueprint, make_response, jsonify, g
|
from flask import Blueprint, make_response, jsonify, g, current_app
|
||||||
from flask_restful import reqparse, Api, inputs
|
from flask_restful import reqparse, Api, inputs
|
||||||
|
|
||||||
from lemur.common.schema import validate_schema
|
from lemur.common.schema import validate_schema
|
||||||
|
@ -678,17 +677,21 @@ class Certificates(AuthenticatedResource):
|
||||||
|
|
||||||
.. sourcecode:: http
|
.. sourcecode:: http
|
||||||
|
|
||||||
HTTP/1.1 200 OK
|
HTTP/1.1 204 OK
|
||||||
|
|
||||||
:reqheader Authorization: OAuth token to authenticate
|
:reqheader Authorization: OAuth token to authenticate
|
||||||
:statuscode 204: no error
|
:statuscode 204: no error
|
||||||
:statuscode 403: unauthenticated
|
:statuscode 403: unauthenticated
|
||||||
:statusoode 404: certificate not found
|
:statusoode 404: certificate not found
|
||||||
|
:statusoode 405: certificate deletion is disabled
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
if not current_app.config.get('ALLOW_CERT_DELETION', False):
|
||||||
|
return dict(message="Certificate deletion is disabled"), 405
|
||||||
|
|
||||||
cert = service.get(certificate_id)
|
cert = service.get(certificate_id)
|
||||||
|
|
||||||
if not cert:
|
if not cert or cert.deleted:
|
||||||
return dict(message="Cannot find specified certificate"), 404
|
return dict(message="Cannot find specified certificate"), 404
|
||||||
|
|
||||||
# allow creators
|
# allow creators
|
||||||
|
@ -699,12 +702,9 @@ class Certificates(AuthenticatedResource):
|
||||||
if not permission.can():
|
if not permission.can():
|
||||||
return dict(message='You are not authorized to delete this certificate'), 403
|
return dict(message='You are not authorized to delete this certificate'), 403
|
||||||
|
|
||||||
if arrow.get(cert.not_after) > arrow.utcnow():
|
|
||||||
return dict(message='Certificate is still valid, only expired certificates can be deleted'), 412
|
|
||||||
|
|
||||||
service.update(certificate_id, deleted=True)
|
service.update(certificate_id, deleted=True)
|
||||||
log_service.create(g.current_user, 'delete_cert', certificate=cert)
|
log_service.create(g.current_user, 'delete_cert', certificate=cert)
|
||||||
return '', 204
|
return 'Certificate deleted', 204
|
||||||
|
|
||||||
|
|
||||||
class NotificationCertificatesList(AuthenticatedResource):
|
class NotificationCertificatesList(AuthenticatedResource):
|
||||||
|
|
|
@ -0,0 +1,23 @@
|
||||||
|
""" Set 'deleted' flag from null to false on all certificates once
|
||||||
|
|
||||||
|
Revision ID: 318b66568358
|
||||||
|
Revises: 9f79024fe67b
|
||||||
|
Create Date: 2019-02-05 15:42:25.477587
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
# revision identifiers, used by Alembic.
|
||||||
|
revision = '318b66568358'
|
||||||
|
down_revision = '9f79024fe67b'
|
||||||
|
|
||||||
|
from alembic import op
|
||||||
|
|
||||||
|
|
||||||
|
def upgrade():
|
||||||
|
connection = op.get_bind()
|
||||||
|
# Delete duplicate entries
|
||||||
|
connection.execute('UPDATE certificates SET deleted = false WHERE deleted IS NULL')
|
||||||
|
|
||||||
|
|
||||||
|
def downgrade():
|
||||||
|
pass
|
|
@ -186,3 +186,5 @@ LDAP_BASE_DN = 'dc=example,dc=com'
|
||||||
LDAP_EMAIL_DOMAIN = 'example.com'
|
LDAP_EMAIL_DOMAIN = 'example.com'
|
||||||
LDAP_REQUIRED_GROUP = 'Lemur Access'
|
LDAP_REQUIRED_GROUP = 'Lemur Access'
|
||||||
LDAP_DEFAULT_ROLE = 'role1'
|
LDAP_DEFAULT_ROLE = 'role1'
|
||||||
|
|
||||||
|
ALLOW_CERT_DELETION = True
|
||||||
|
|
|
@ -737,8 +737,8 @@ def test_certificate_put_with_data(client, certificate, issuer_plugin):
|
||||||
|
|
||||||
@pytest.mark.parametrize("token,status", [
|
@pytest.mark.parametrize("token,status", [
|
||||||
(VALID_USER_HEADER_TOKEN, 403),
|
(VALID_USER_HEADER_TOKEN, 403),
|
||||||
(VALID_ADMIN_HEADER_TOKEN, 412),
|
(VALID_ADMIN_HEADER_TOKEN, 204),
|
||||||
(VALID_ADMIN_API_TOKEN, 412),
|
(VALID_ADMIN_API_TOKEN, 404),
|
||||||
('', 401)
|
('', 401)
|
||||||
])
|
])
|
||||||
def test_certificate_delete(client, token, status):
|
def test_certificate_delete(client, token, status):
|
||||||
|
|
Loading…
Reference in New Issue