Merge branch 'powerdnsplugin_02' of github.com:Netflix/lemur into powerdnsplugin_02

This commit is contained in:
csine-nflx 2020-03-30 09:02:56 -07:00
commit 9d9bf9d7ba
4 changed files with 204 additions and 40 deletions

View File

@ -321,7 +321,8 @@ class Certificate(db.Model):
@hybrid_property @hybrid_property
def expired(self): 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 return True
@expired.expression @expired.expression
@ -445,6 +446,9 @@ def update_destinations(target, value, initiator):
""" """
destination_plugin = plugins.get(value.plugin_name) destination_plugin = plugins.get(value.plugin_name)
status = FAILURE_METRIC_STATUS status = FAILURE_METRIC_STATUS
if target.expired:
return
try: try:
if target.private_key or not destination_plugin.requires_key: if target.private_key or not destination_plugin.requires_key:
destination_plugin.upload( destination_plugin.upload(

View File

@ -102,12 +102,13 @@ def get_all_certs():
return Certificate.query.all() return Certificate.query.all()
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: :param source: the source to search for certificates
:return: :return: list of pending certificates
""" """
return ( return (
Certificate.query.filter(Certificate.sources.any(id=source.id)) Certificate.query.filter(Certificate.sources.any(id=source.id))
@ -117,6 +118,41 @@ def get_all_pending_cleaning(source):
) )
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(): def get_all_pending_reissue():
""" """
Retrieves all certificates that need to be rotated. Retrieves all certificates that need to be rotated.

View File

@ -325,14 +325,17 @@ class AWSDestinationPlugin(DestinationPlugin):
] ]
def upload(self, name, body, private_key, cert_chain, options, **kwargs): def upload(self, name, body, private_key, cert_chain, options, **kwargs):
iam.upload_cert( try:
name, iam.upload_cert(
body, name,
private_key, body,
self.get_option("path", options), private_key,
cert_chain=cert_chain, self.get_option("path", options),
account_number=self.get_option("accountNumber", options), cert_chain=cert_chain,
) account_number=self.get_option("accountNumber", options),
)
except ClientError:
sentry.captureException()
def deploy(self, elb_name, account, region, certificate): def deploy(self, elb_name, account, region, certificate):
pass pass

View File

@ -54,6 +54,17 @@ def validate_sources(source_strings):
return sources return sources
def execute_clean(plugin, certificate, source):
try:
plugin.clean(certificate, source.options)
certificate.sources.remove(source)
certificate_service.database.update(certificate)
return SUCCESS_METRIC_STATUS
except Exception as e:
current_app.logger.exception(e)
sentry.captureException()
@manager.option( @manager.option(
"-s", "-s",
"--sources", "--sources",
@ -132,11 +143,9 @@ def clean(source_strings, commit):
s = plugins.get(source.plugin_name) s = plugins.get(source.plugin_name)
if not hasattr(s, "clean"): if not hasattr(s, "clean"):
print( info_text = f"Cannot clean source: {source.label}, source plugin does not implement 'clean()'"
"Cannot clean source: {0}, source plugin does not implement 'clean()'".format( current_app.logger.warning(info_text)
source.label print(info_text)
)
)
continue continue
start_time = time.time() start_time = time.time()
@ -144,35 +153,147 @@ def clean(source_strings, commit):
print("[+] Staring to clean source: {label}!\n".format(label=source.label)) print("[+] Staring to clean source: {label}!\n".format(label=source.label))
cleaned = 0 cleaned = 0
for certificate in certificate_service.get_all_pending_cleaning(source): certificates = certificate_service.get_all_pending_cleaning_expired(source)
for certificate in certificates:
status = FAILURE_METRIC_STATUS status = FAILURE_METRIC_STATUS
if commit: if commit:
try: status = execute_clean(s, certificate, source)
s.clean(certificate, source.options)
certificate.sources.remove(source)
certificate_service.database.update(certificate)
status = SUCCESS_METRIC_STATUS
except Exception as e:
current_app.logger.exception(e)
sentry.captureException()
metrics.send( metrics.send(
"clean", "certificate_clean",
"counter", "counter",
1, 1,
metric_tags={"source": source.label, "status": status}, metric_tags={"status": status, "source": source.label, "certificate": certificate.name},
) )
current_app.logger.warning(f"Removed {certificate.name} from source {source.label} during cleaning")
current_app.logger.warning(
"Removed {0} from source {1} during cleaning".format(
certificate.name, source.label
)
)
cleaned += 1 cleaned += 1
print( info_text = f"[+] Finished cleaning source: {source.label}. " \
"[+] Finished cleaning source: {label}. Removed {cleaned} certificates from source. Run Time: {time}\n".format( f"Removed {cleaned} certificates from source. " \
label=source.label, time=(time.time() - start_time), cleaned=cleaned f"Run Time: {(time.time() - start_time)}\n"
print(info_text)
current_app.logger.warning(info_text)
@manager.option(
"-s",
"--sources",
dest="source_strings",
action="append",
help="Sources to operate on.",
)
@manager.option(
"-d",
"--days",
dest="days_to_expire",
type=int,
action="store",
required=True,
help="The expiry range within days.",
)
@manager.option(
"-c",
"--commit",
dest="commit",
action="store_true",
default=False,
help="Persist changes.",
)
def clean_unused_and_expiring_within_days(source_strings, days_to_expire, commit):
sources = validate_sources(source_strings)
for source in sources:
s = plugins.get(source.plugin_name)
if not hasattr(s, "clean"):
info_text = f"Cannot clean source: {source.label}, source plugin does not implement 'clean()'"
current_app.logger.warning(info_text)
print(info_text)
continue
start_time = time.time()
print("[+] Staring to clean source: {label}!\n".format(label=source.label))
cleaned = 0
certificates = certificate_service.get_all_pending_cleaning_expiring_in_days(source, days_to_expire)
for certificate in certificates:
status = FAILURE_METRIC_STATUS
if commit:
status = execute_clean(s, certificate, source)
metrics.send(
"certificate_clean",
"counter",
1,
metric_tags={"status": status, "source": source.label, "certificate": certificate.name},
) )
) current_app.logger.warning(f"Removed {certificate.name} from source {source.label} during cleaning")
cleaned += 1
info_text = f"[+] Finished cleaning source: {source.label}. " \
f"Removed {cleaned} certificates from source. " \
f"Run Time: {(time.time() - start_time)}\n"
print(info_text)
current_app.logger.warning(info_text)
@manager.option(
"-s",
"--sources",
dest="source_strings",
action="append",
help="Sources to operate on.",
)
@manager.option(
"-d",
"--days",
dest="days_since_issuance",
type=int,
action="store",
required=True,
help="Days since issuance.",
)
@manager.option(
"-c",
"--commit",
dest="commit",
action="store_true",
default=False,
help="Persist changes.",
)
def clean_unused_and_issued_since_days(source_strings, days_since_issuance, commit):
sources = validate_sources(source_strings)
for source in sources:
s = plugins.get(source.plugin_name)
if not hasattr(s, "clean"):
info_text = f"Cannot clean source: {source.label}, source plugin does not implement 'clean()'"
current_app.logger.warning(info_text)
print(info_text)
continue
start_time = time.time()
print("[+] Staring to clean source: {label}!\n".format(label=source.label))
cleaned = 0
certificates = certificate_service.get_all_pending_cleaning_issued_since_days(source, days_since_issuance)
for certificate in certificates:
status = FAILURE_METRIC_STATUS
if commit:
status = execute_clean(s, certificate, source)
metrics.send(
"certificate_clean",
"counter",
1,
metric_tags={"status": status, "source": source.label, "certificate": certificate.name},
)
current_app.logger.warning(f"Removed {certificate.name} from source {source.label} during cleaning")
cleaned += 1
info_text = f"[+] Finished cleaning source: {source.label}. " \
f"Removed {cleaned} certificates from source. " \
f"Run Time: {(time.time() - start_time)}\n"
print(info_text)
current_app.logger.warning(info_text)