Merge branch 'master' into check-revoke-revised
This commit is contained in:
@ -5,29 +5,19 @@
|
||||
:license: Apache, see LICENSE for more details.
|
||||
.. moduleauthor:: Kevin Glisson <kglisson@netflix.com>
|
||||
"""
|
||||
import sys
|
||||
import multiprocessing
|
||||
from tabulate import tabulate
|
||||
from sqlalchemy import or_
|
||||
|
||||
import sys
|
||||
from flask import current_app
|
||||
|
||||
from flask_script import Manager
|
||||
from flask_principal import Identity, identity_changed
|
||||
|
||||
from flask_script import Manager
|
||||
from sqlalchemy import or_
|
||||
from tabulate import tabulate
|
||||
|
||||
from lemur import database
|
||||
from lemur.extensions import sentry
|
||||
from lemur.extensions import metrics
|
||||
from lemur.plugins.base import plugins
|
||||
from lemur.constants import SUCCESS_METRIC_STATUS, FAILURE_METRIC_STATUS
|
||||
from lemur.deployment import service as deployment_service
|
||||
from lemur.endpoints import service as endpoint_service
|
||||
from lemur.notifications.messaging import send_rotation_notification
|
||||
from lemur.domains.models import Domain
|
||||
from lemur.authorities.models import Authority
|
||||
from lemur.certificates.schemas import CertificateOutputSchema
|
||||
from lemur.authorities.service import get as authorities_get_by_id
|
||||
from lemur.certificates.models import Certificate
|
||||
from lemur.certificates.schemas import CertificateOutputSchema
|
||||
from lemur.certificates.service import (
|
||||
reissue_certificate,
|
||||
get_certificate_primitives,
|
||||
@ -35,9 +25,16 @@ from lemur.certificates.service import (
|
||||
get_by_name,
|
||||
get_all_valid_certs,
|
||||
get,
|
||||
get_all_certs_attached_to_endpoint_without_autorotate,
|
||||
)
|
||||
|
||||
from lemur.certificates.verify import verify_string
|
||||
from lemur.constants import SUCCESS_METRIC_STATUS, FAILURE_METRIC_STATUS
|
||||
from lemur.deployment import service as deployment_service
|
||||
from lemur.domains.models import Domain
|
||||
from lemur.endpoints import service as endpoint_service
|
||||
from lemur.extensions import sentry, metrics
|
||||
from lemur.notifications.messaging import send_rotation_notification
|
||||
from lemur.plugins.base import plugins
|
||||
|
||||
manager = Manager(usage="Handles all certificate related tasks.")
|
||||
|
||||
@ -503,3 +500,45 @@ def check_revoked():
|
||||
cert.status = "unknown"
|
||||
|
||||
database.update(cert)
|
||||
|
||||
|
||||
@manager.command
|
||||
def automatically_enable_autorotate():
|
||||
"""
|
||||
This function automatically enables auto-rotation for unexpired certificates that are
|
||||
attached to an endpoint but do not have autorotate enabled.
|
||||
|
||||
WARNING: This will overwrite the Auto-rotate toggle!
|
||||
"""
|
||||
log_data = {
|
||||
"function": f"{__name__}.{sys._getframe().f_code.co_name}",
|
||||
"message": "Enabling auto-rotate for certificate"
|
||||
}
|
||||
|
||||
permitted_authorities = current_app.config.get("ENABLE_AUTO_ROTATE_AUTHORITY", [])
|
||||
|
||||
eligible_certs = get_all_certs_attached_to_endpoint_without_autorotate()
|
||||
for cert in eligible_certs:
|
||||
|
||||
if cert.authority_id not in permitted_authorities:
|
||||
continue
|
||||
|
||||
log_data["certificate"] = cert.name
|
||||
log_data["certificate_id"] = cert.id
|
||||
log_data["authority_id"] = cert.authority_id
|
||||
log_data["authority_name"] = authorities_get_by_id(cert.authority_id).name
|
||||
if cert.destinations:
|
||||
log_data["destination_names"] = ', '.join([d.label for d in cert.destinations])
|
||||
else:
|
||||
log_data["destination_names"] = "NONE"
|
||||
current_app.logger.info(log_data)
|
||||
metrics.send("automatically_enable_autorotate",
|
||||
"counter", 1,
|
||||
metric_tags={"certificate": log_data["certificate"],
|
||||
"certificate_id": log_data["certificate_id"],
|
||||
"authority_id": log_data["authority_id"],
|
||||
"authority_name": log_data["authority_name"],
|
||||
"destination_names": log_data["destination_names"]
|
||||
})
|
||||
cert.rotation = True
|
||||
database.update(cert)
|
||||
|
@ -321,7 +321,8 @@ class Certificate(db.Model):
|
||||
|
||||
@hybrid_property
|
||||
def expired(self):
|
||||
if self.not_after <= arrow.utcnow():
|
||||
# can't compare offset-naive and offset-aware datetimes
|
||||
if arrow.Arrow.fromdatetime(self.not_after) <= arrow.utcnow():
|
||||
return True
|
||||
|
||||
@expired.expression
|
||||
@ -445,6 +446,9 @@ def update_destinations(target, value, initiator):
|
||||
"""
|
||||
destination_plugin = plugins.get(value.plugin_name)
|
||||
status = FAILURE_METRIC_STATUS
|
||||
|
||||
if target.expired:
|
||||
return
|
||||
try:
|
||||
if target.private_key or not destination_plugin.requires_key:
|
||||
destination_plugin.upload(
|
||||
|
@ -6,6 +6,8 @@
|
||||
.. moduleauthor:: Kevin Glisson <kglisson@netflix.com>
|
||||
"""
|
||||
from flask import current_app
|
||||
from flask_restful import inputs
|
||||
from flask_restful.reqparse import RequestParser
|
||||
from marshmallow import fields, validate, validates_schema, post_load, pre_load
|
||||
from marshmallow.exceptions import ValidationError
|
||||
|
||||
@ -117,6 +119,9 @@ class CertificateInputSchema(CertificateCreationSchema):
|
||||
|
||||
@validates_schema
|
||||
def validate_authority(self, data):
|
||||
if 'authority' not in data:
|
||||
raise ValidationError("Missing Authority.")
|
||||
|
||||
if isinstance(data["authority"], str):
|
||||
raise ValidationError("Authority not found.")
|
||||
|
||||
@ -285,6 +290,16 @@ class CertificateOutputSchema(LemurOutputSchema):
|
||||
rotation_policy = fields.Nested(RotationPolicyNestedOutputSchema)
|
||||
|
||||
|
||||
class CertificateShortOutputSchema(LemurOutputSchema):
|
||||
id = fields.Integer()
|
||||
name = fields.String()
|
||||
owner = fields.Email()
|
||||
notify = fields.Boolean()
|
||||
authority = fields.Nested(AuthorityNestedOutputSchema)
|
||||
issuer = fields.String()
|
||||
cn = fields.String()
|
||||
|
||||
|
||||
class CertificateUploadInputSchema(CertificateCreationSchema):
|
||||
name = fields.String()
|
||||
authority = fields.Nested(AssociatedAuthoritySchema, required=False)
|
||||
@ -363,9 +378,22 @@ class CertificateRevokeSchema(LemurInputSchema):
|
||||
comments = fields.String()
|
||||
|
||||
|
||||
certificates_list_request_parser = RequestParser()
|
||||
certificates_list_request_parser.add_argument("short", type=inputs.boolean, default=False, location="args")
|
||||
|
||||
|
||||
def certificates_list_output_schema_factory():
|
||||
args = certificates_list_request_parser.parse_args()
|
||||
if args["short"]:
|
||||
return certificates_short_output_schema
|
||||
else:
|
||||
return certificates_output_schema
|
||||
|
||||
|
||||
certificate_input_schema = CertificateInputSchema()
|
||||
certificate_output_schema = CertificateOutputSchema()
|
||||
certificates_output_schema = CertificateOutputSchema(many=True)
|
||||
certificates_short_output_schema = CertificateShortOutputSchema(many=True)
|
||||
certificate_upload_input_schema = CertificateUploadInputSchema()
|
||||
certificate_export_input_schema = CertificateExportInputSchema()
|
||||
certificate_edit_input_schema = CertificateEditInputSchema()
|
||||
|
@ -123,12 +123,13 @@ def get_all_valid_certs(authority_plugin_name):
|
||||
)
|
||||
|
||||
|
||||
def get_all_pending_cleaning(source):
|
||||
def get_all_pending_cleaning_expired(source):
|
||||
"""
|
||||
Retrieves all certificates that are available for cleaning.
|
||||
Retrieves all certificates that are available for cleaning. These are certificates which are expired and are not
|
||||
attached to any endpoints.
|
||||
|
||||
:param source:
|
||||
:return:
|
||||
:param source: the source to search for certificates
|
||||
:return: list of pending certificates
|
||||
"""
|
||||
return (
|
||||
Certificate.query.filter(Certificate.sources.any(id=source.id))
|
||||
@ -138,6 +139,58 @@ def get_all_pending_cleaning(source):
|
||||
)
|
||||
|
||||
|
||||
def get_all_certs_attached_to_endpoint_without_autorotate():
|
||||
"""
|
||||
Retrieves all certificates that are attached to an endpoint, but that do not have autorotate enabled.
|
||||
|
||||
:return: list of certificates attached to an endpoint without autorotate
|
||||
"""
|
||||
return (
|
||||
Certificate.query.filter(Certificate.endpoints.any())
|
||||
.filter(Certificate.rotation == False)
|
||||
.filter(Certificate.not_after >= arrow.now())
|
||||
.filter(not_(Certificate.replaced.any()))
|
||||
.all() # noqa
|
||||
)
|
||||
|
||||
|
||||
def get_all_pending_cleaning_expiring_in_days(source, days_to_expire):
|
||||
"""
|
||||
Retrieves all certificates that are available for cleaning, not attached to endpoint,
|
||||
and within X days from expiration.
|
||||
|
||||
:param days_to_expire: defines how many days till the certificate is expired
|
||||
:param source: the source to search for certificates
|
||||
:return: list of pending certificates
|
||||
"""
|
||||
expiration_window = arrow.now().shift(days=+days_to_expire).format("YYYY-MM-DD")
|
||||
return (
|
||||
Certificate.query.filter(Certificate.sources.any(id=source.id))
|
||||
.filter(not_(Certificate.endpoints.any()))
|
||||
.filter(Certificate.not_after < expiration_window)
|
||||
.all()
|
||||
)
|
||||
|
||||
|
||||
def get_all_pending_cleaning_issued_since_days(source, days_since_issuance):
|
||||
"""
|
||||
Retrieves all certificates that are available for cleaning: not attached to endpoint, and X days since issuance.
|
||||
|
||||
:param days_since_issuance: defines how many days since the certificate is issued
|
||||
:param source: the source to search for certificates
|
||||
:return: list of pending certificates
|
||||
"""
|
||||
not_in_use_window = (
|
||||
arrow.now().shift(days=-days_since_issuance).format("YYYY-MM-DD")
|
||||
)
|
||||
return (
|
||||
Certificate.query.filter(Certificate.sources.any(id=source.id))
|
||||
.filter(not_(Certificate.endpoints.any()))
|
||||
.filter(Certificate.date_created > not_in_use_window)
|
||||
.all()
|
||||
)
|
||||
|
||||
|
||||
def get_all_pending_reissue():
|
||||
"""
|
||||
Retrieves all certificates that need to be rotated.
|
||||
@ -352,9 +405,11 @@ def render(args):
|
||||
|
||||
show_expired = args.pop("showExpired")
|
||||
if show_expired != 1:
|
||||
one_month_old = arrow.now()\
|
||||
.shift(months=current_app.config.get("HIDE_EXPIRED_CERTS_AFTER_MONTHS", -1))\
|
||||
one_month_old = (
|
||||
arrow.now()
|
||||
.shift(months=current_app.config.get("HIDE_EXPIRED_CERTS_AFTER_MONTHS", -1))
|
||||
.format("YYYY-MM-DD")
|
||||
)
|
||||
query = query.filter(Certificate.not_after > one_month_old)
|
||||
|
||||
time_range = args.pop("time_range")
|
||||
@ -414,6 +469,9 @@ def render(args):
|
||||
Certificate.cn.ilike(term),
|
||||
)
|
||||
)
|
||||
elif "fixedName" in terms:
|
||||
# only what matches the fixed name directly if a fixedname is provided
|
||||
query = query.filter(Certificate.name == terms[1])
|
||||
else:
|
||||
query = database.filter(query, Certificate, terms)
|
||||
|
||||
@ -440,7 +498,7 @@ def render(args):
|
||||
)
|
||||
|
||||
if time_range:
|
||||
to = arrow.now().replace(weeks=+time_range).format("YYYY-MM-DD")
|
||||
to = arrow.now().shift(weeks=+time_range).format("YYYY-MM-DD")
|
||||
now = arrow.now().format("YYYY-MM-DD")
|
||||
query = query.filter(Certificate.not_after <= to).filter(
|
||||
Certificate.not_after >= now
|
||||
@ -582,7 +640,7 @@ def stats(**kwargs):
|
||||
"""
|
||||
if kwargs.get("metric") == "not_after":
|
||||
start = arrow.utcnow()
|
||||
end = start.replace(weeks=+32)
|
||||
end = start.shift(weeks=+32)
|
||||
items = (
|
||||
database.db.session.query(Certificate.issuer, func.count(Certificate.id))
|
||||
.group_by(Certificate.issuer)
|
||||
|
@ -27,6 +27,7 @@ from lemur.certificates.schemas import (
|
||||
certificates_output_schema,
|
||||
certificate_export_input_schema,
|
||||
certificate_edit_input_schema,
|
||||
certificates_list_output_schema_factory,
|
||||
)
|
||||
|
||||
from lemur.roles import service as role_service
|
||||
@ -250,7 +251,7 @@ class CertificatesList(AuthenticatedResource):
|
||||
self.reqparse = reqparse.RequestParser()
|
||||
super(CertificatesList, self).__init__()
|
||||
|
||||
@validate_schema(None, certificates_output_schema)
|
||||
@validate_schema(None, certificates_list_output_schema_factory)
|
||||
def get(self):
|
||||
"""
|
||||
.. http:get:: /certificates
|
||||
|
Reference in New Issue
Block a user