Skip to content

Instantly share code, notes, and snippets.

@skylander86
Created March 29, 2017 14:47
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save skylander86/0fb8a62c7e9def953c46c6c97ec5d5a8 to your computer and use it in GitHub Desktop.
Save skylander86/0fb8a62c7e9def953c46c6c97ec5d5a8 to your computer and use it in GitHub Desktop.
Get SSL certificates using Certbot and complete challenge automatically using Route 53. A pure Python solution.
#!/usr/bin/env python
from argparse import ArgumentParser
import logging
import os
import sys
import subprocess
import time
import boto3
logger = logging.getLogger(__name__)
def main():
parser = ArgumentParser(description='Script to get new certificates using certbot.')
parser.add_argument('-D', '--letsencrypt-dir', type=str, metavar='<dir>', default='letsencrypt', help='Directory to store letsencrypt configs and keys.')
parser.add_argument('-e', '--email', type=str, metavar='<email>', default='hello@example.com', help='Email address linked with these keys.')
parser.add_argument('-d', '--domains', type=str, metavar='<domain>', required=True, nargs='+', help='Domain names to get certificate for.')
logging.basicConfig(format=u'%(asctime)-15s [%(name)s-%(process)d] %(levelname)s: %(message)s', level=logging.INFO)
logging.getLogger('botocore.vendored.requests.packages.urllib3.connectionpool').setLevel(logging.WARNING)
logging.getLogger('botocore.credentials').setLevel(logging.WARNING)
script_path = os.path.realpath(__file__)
CERTBOT_DOMAIN = os.environ.get('CERTBOT_DOMAIN')
CERTBOT_VALIDATION = os.environ.get('CERTBOT_VALIDATION')
CERTBOT_AUTH_OUTPUT = os.environ.get('CERTBOT_AUTH_OUTPUT')
if CERTBOT_AUTH_OUTPUT: return
# CERTBOT_TOKEN = os.environ.get('CERTBOT_TOKEN')
if CERTBOT_DOMAIN:
client = boto3.client('route53')
response = client.list_hosted_zones_by_name()
zone_id = None
for zone in response['HostedZones']:
if CERTBOT_DOMAIN.endswith(zone['Name'][:-1]):
zone_id = zone['Id']
break
#end if
#end for
if zone_id is None:
logger.error('Hosted zone not found for {}'.format(CERTBOT_DOMAIN))
sys.exit(-1)
#end if
client.change_resource_record_sets(
HostedZoneId=zone_id,
ChangeBatch=dict(
Changes=[
dict(
Action='UPSERT',
ResourceRecordSet=dict(
Name='_acme-challenge.{}.'.format(CERTBOT_DOMAIN),
Type='TXT', TTL=3600,
ResourceRecords=[dict(Value='"{}"'.format(CERTBOT_VALIDATION))]
)
)
]
)
)
tries = 0
while tries < 10:
response = client.test_dns_answer(HostedZoneId=zone_id, RecordName='_acme-challenge.{}.'.format(CERTBOT_DOMAIN), RecordType='TXT', ResolverIP='8.8.8.8')
tries += 1
if response['RecordData'] and response['RecordData'][0] == '"{}"'.format(CERTBOT_VALIDATION):
logger.info('Updated TXT record for {} to "{}".'.format(CERTBOT_DOMAIN, CERTBOT_VALIDATION))
break
#end if
time.sleep(tries * 2)
#end while
if tries >= 10:
logger.error('Failed to set TXT record for {} to "{}".'.format(CERTBOT_DOMAIN, CERTBOT_VALIDATION))
sys.exit(-1)
#end if
else:
A = parser.parse_args()
for domain in A.domains:
logger.info('Getting certificates for "{}"...'.format(domain))
certbot_args = ['certbot', 'certonly', '--config-dir', A.letsencrypt_dir, '--work-dir', A.letsencrypt_dir, '--logs-dir', A.letsencrypt_dir, '--manual', '--preferred-challenge', 'dns', '--manual-auth-hook', script_path, '--manual-cleanup-hook', script_path, '--agree-tos', '-m', A.email, '--non-interactive', '--manual-public-ip-logging-ok', '-d', domain]
subprocess.call(certbot_args)
#end for
#end if
#end def
if __name__ == '__main__':
main()
sys.exit(0)
#end if
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment