Last active
February 11, 2022 00:57
-
-
Save yolabingo/3c442bb4b00508fa6be82c426e26a39f to your computer and use it in GitHub Desktop.
list and simple search of AWS Certificate Manager SSL certs using python and boto3
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/usr/bin/env python3 | |
""" | |
usage: acm_search_certs.py [-h] [--region REGION] [--san-names SAN_NAMES] [--identifiers IDENTIFIERS] | |
[--tags TAGS] [--issuers ISSUERS] [--cert-in-use {0,1}] [--json] [--and-filters] | |
Fetches ACM SSL certificates. | |
options: | |
-h, --help show this help message and exit | |
--region REGION, -r REGION | |
AWS region (default "us-east-1") | |
--san-names SAN_NAMES, -n SAN_NAMES | |
substring patterns to match in SAN hostnames - comma-separate multiple patterns | |
--identifiers IDENTIFIERS, -d IDENTIFIERS | |
AWS identifier - exact match, comma-separate multiples | |
--tags TAGS, -t TAGS dotcms.client.name.short names - exact match, comma-separate multiples | |
--issuers ISSUERS, -i ISSUERS | |
substring patterns to match in cert issuer - comma-separate multiple patterns | |
--cert-in-use {0,1}, -u {0,1} | |
0 or 1 -- 'cert in use' filter | |
--json, -j print output as json | |
--and-filters, -a join multiple filters with AND rather than OR | |
""" | |
import argparse | |
import json | |
import boto3 | |
# cli search of ACM-managed SSL certifictes | |
class ACM: | |
"""if only AWS ACM were searchable""" | |
def __init__(self, region="us-east-1", debug=False): | |
"""initialize self.all_certs with all ACM certs in this region""" | |
self.region = region | |
self.debug = debug | |
self.client = boto3.client("acm", region_name=region) | |
self.tag_key = "dotcms.client.name.short" | |
# use arn as key in self.all_certs | |
self.all_certs = {} | |
for cert in self.client.list_certificates()["CertificateSummaryList"]: | |
arn = cert["CertificateArn"] | |
self.all_certs[arn] = self.client.describe_certificate(CertificateArn=arn)[ | |
"Certificate" | |
] | |
def filter_certs( | |
self, | |
san_names=(), | |
identifiers=(), | |
tags=(), | |
issuers=(), | |
cert_in_use=None, | |
and_filters=False, | |
): | |
""" | |
filters self.all_certs if filter patterns are provided | |
multiple filters are OR'd | |
""" | |
filtered_results = [] | |
if san_names: | |
filtered_results.append(self._filter_san_names(san_names)) | |
if identifiers: | |
filtered_results.append(self._filter_identifier(identifiers)) | |
if tags: | |
filtered_results.append(self._filter_tags(tags)) | |
if issuers: | |
filtered_results.append(self._filter_issuers(issuers)) | |
if cert_in_use is not None: | |
filter_results.append(self._filter_in_use(cert_in_use)) | |
if filtered_results: | |
if and_filters: | |
self._join_filters_and(filtered_results) | |
else: | |
self._join_filters_or(filtered_results) | |
def print_certs(self, json_output=False): | |
if json_output: | |
print(json.dumps([cert for cert in self.all_certs.values()], default=str)) | |
else: | |
for cert in self.all_certs.values(): | |
try: | |
print( | |
f""" | |
DomainName: {cert['DomainName']} | |
SubjectAlternativeNames: { ','.join(sorted(cert['SubjectAlternativeNames'])) } | |
Issuer: {cert['Issuer']} | |
Status: {cert['Status']} | |
ValidationStatus: {cert['ValidationStatus'] if 'ValidationStatus' in cert else '----'} | |
NotAfter: {cert['NotAfter'] if 'NotAfter' in cert else '----'} | |
InUse: {','.join(cert['InUseBy']) if cert['InUseBy'] else 'Not in use' } | |
CertificateArn: {cert['CertificateArn']} | |
Region: {self.region} | |
=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- """ | |
) | |
except KeyError as e: | |
print("Oops \n") | |
print(cert) | |
raise e | |
def _filter_identifier(self, identifiers=(), search_certs=None): | |
""" | |
identifier: list | |
search_certs: optionally pass in a dict of certs already filtered elsewhere, else filter all certs | |
returns list of certs matching identifiers | |
""" | |
filtered_certs = {} | |
if search_certs == None: | |
search_certs = self.all_certs | |
for identifier in identifiers: | |
for cert in search_certs.values(): | |
if cert["CertificateArn"].endswith(f"certificate/{identifier}"): | |
filtered_certs[cert["CertificateArn"]] = cert | |
return filtered_certs | |
def _filter_san_names(self, san_names=(), search_certs=None): | |
""" | |
san_names: list | |
search_certs: optionally pass in a dict of certs already filtered elsewhere, else filter all certs | |
returns list of certs that have a SAN name that substring matches any patten | |
""" | |
filtered_certs = {} | |
if search_certs == None: | |
search_certs = self.all_certs | |
for san_name in san_names: | |
san_name = san_name.lower() | |
for cert in search_certs.values(): | |
match = False | |
for san in cert["SubjectAlternativeNames"]: | |
if san_name in san: | |
match = True | |
if match: | |
filtered_certs[cert["CertificateArn"]] = cert | |
return filtered_certs | |
def _filter_tags(self, tags=(), search_certs=None): | |
""" | |
tags: list of tags | |
search_certs: optionally pass in a list of certs already filtered elsewhere, else filter all certs | |
returns list of certs matching the AWS tag | |
""" | |
filtered_certs = {} | |
if search_certs == None: | |
search_certs = self.all_certs | |
else: | |
search_certs = self.all_certs | |
for arn, cert in search_certs.items(): | |
cert_tags = self.client.list_tags_for_certificate(CertificateArn=arn) | |
for tag in tags: | |
try: | |
for cert_tag in cert_tags["Tags"]: | |
if cert_tag["Key"] == self.tag_key and cert_tag["Value"] == tag: | |
filtered_certs[cert["CertificateArn"]] = cert | |
except (TypeError, KeyError): | |
pass | |
return filtered_certs | |
def _filter_issuers(self, issuers=(), search_certs=None): | |
""" | |
issuers: list of substring patterns to match | |
search_certs: optionally pass in a list of certs already filtered elsewhere, else filter all certs | |
returns list of certs matching issuers | |
""" | |
filtered_certs = {} | |
if search_certs == None: | |
search_certs = self.all_certs | |
for issuer in issuers: | |
issuer = issuer.lower() | |
for cert in search_certs.values(): | |
if issuer in cert["Issuer"].lower(): | |
filtered_certs[cert["CertificateArn"]] = cert | |
return filtered_certs | |
def _filter_in_use(self, cert_in_use, search_certs=None): | |
""" | |
cert_in_use: bool | |
search_certs: optionally pass in a list of certs already filtered elsewhere, else filter all certs | |
returns list of certs that are/are not in use | |
""" | |
filtered_certs = {} | |
if search_certs == None: | |
search_certs = self.all_certs | |
if cert_in_use: | |
filtered_certs = { | |
arn: cert for (arn, cert) in search_certs.items() if cert["InUseBy"] | |
} | |
else: | |
filtered_certs = { | |
arn: cert for (arn, cert) in search_certs.items() if not cert["InUseBy"] | |
} | |
return filtered_certs | |
def _join_filters_and(self, filtered_results): | |
""" | |
filtered_results is a list of dicts arn: cert | |
""" | |
self.all_certs = {} | |
arns = set.intersection(*[set(r.keys()) for r in filtered_results]) | |
for cert_list in filtered_results: | |
for arn, cert in cert_list.items(): | |
if arn in arns: | |
self.all_certs[arn] = cert | |
def _join_filters_or(self, filtered_results): | |
""" | |
filtered_results is a list of dicts arn: cert | |
""" | |
self.all_certs = {} | |
for cert_list in filtered_results: | |
for arn, cert in cert_list.items(): | |
self.all_certs[arn] = cert | |
if __name__ == "__main__": | |
parser = argparse.ArgumentParser(description="Fetches ACM SSL certificates.") | |
parser.add_argument( | |
"--region", "-r", default="us-east-1", help='AWS region (default "us-east-1")' | |
) | |
parser.add_argument( | |
"--san-names", | |
"-n", | |
help="substring patterns to match in SAN hostnames - comma-separate multiple patterns", | |
) | |
parser.add_argument( | |
"--identifiers", | |
"-d", | |
help="AWS identifier - exact match, comma-separate multiples", | |
) | |
parser.add_argument( | |
"--tags", | |
"-t", | |
help="dotcms.client.name.short names - exact match, comma-separate multiples", | |
) | |
parser.add_argument( | |
"--issuers", | |
"-i", | |
help="substring patterns to match in cert issuer - comma-separate multiple patterns", | |
) | |
parser.add_argument( | |
"--cert-in-use", | |
"-u", | |
type=int, | |
choices={0, 1}, | |
help="0 or 1 -- 'cert in use' filter", | |
) | |
parser.add_argument( | |
"--json", "-j", action="store_true", help="print output as json" | |
) | |
parser.add_argument( | |
"--and-filters", | |
"-a", | |
action="store_true", | |
help="join multiple filters with AND rather than OR", | |
) | |
parser.add_argument("--debug", action="store_true") | |
args = parser.parse_args() | |
san_names = [] | |
if args.san_names: | |
san_names = args.san_names.split(",") | |
identifiers = [] | |
if args.identifiers: | |
identifiers = args.identifiers.split(",") | |
tags = [] | |
if args.tags: | |
tags = args.tags.split(",") | |
issuers = [] | |
if args.issuers: | |
issuers = args.issuers.split(",") | |
acm = ACM(args.region, args.debug) | |
acm.filter_certs( | |
san_names=san_names, | |
identifiers=identifiers, | |
tags=tags, | |
issuers=issuers, | |
cert_in_use=args.cert_in_use, | |
and_filters=args.and_filters, | |
) | |
acm.print_certs(json_output=args.json) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment