Merge pull request #2529 from rmoesbergen/allow-cert-deletion
Implement ALLOW_CERT_DELETION setting
This commit is contained in:
commit
bd27932783
|
@ -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,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):
|
||||||
|
|
|
@ -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,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)
|
||||||
])
|
])
|
||||||
|
|
Loading…
Reference in New Issue