Merge pull request #2529 from rmoesbergen/allow-cert-deletion

Implement ALLOW_CERT_DELETION setting
This commit is contained in:
Hossein Shafagh 2019-03-07 12:45:53 -08:00 committed by GitHub
commit bd27932783
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 48 additions and 10 deletions

View File

@ -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
--------------------------- ---------------------------

View File

@ -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)

View File

@ -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

View File

@ -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,19 +677,26 @@ 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 :statuscode 404: certificate not found
:statuscode 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:
return dict(message="Cannot find specified certificate"), 404 return dict(message="Cannot find specified certificate"), 404
if cert.deleted:
return dict(message="Certificate is already deleted"), 412
# allow creators # allow creators
if g.current_user != cert.user: if g.current_user != cert.user:
owner_role = role_service.get_by_name(cert.owner) owner_role = role_service.get_by_name(cert.owner)
@ -699,12 +705,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):

View File

@ -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

View File

@ -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

View File

@ -737,7 +737,7 @@ 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, 412),
('', 401) ('', 401)
]) ])