Skip to content

Instantly share code, notes, and snippets.

@joshgarnett
Last active March 17, 2021 09:01
Show Gist options
  • Save joshgarnett/02920846fea35f738d3370fd991bb0e0 to your computer and use it in GitHub Desktop.
Save joshgarnett/02920846fea35f738d3370fd991bb0e0 to your computer and use it in GitHub Desktop.
Ruby script to use as a hook for the letsencrypt.sh client
#!/usr/bin/env ruby
require 'aws-sdk'
#
# This script requires you to have the following environment variables set:
# AWS_REGION="us-west-2"
# AWS_ACCESS_KEY_ID="<YOUR_KEY>"
# AWS_SECRET_ACCESS_KEY="<YOUR_SECRET_KEY>"
#
# Based on https://gist.github.com/asimihsan/d8d8f0f10bdc85fc6f8a
#
def find_hosted_zone(route53, domain)
route53 = Aws::Route53::Client.new
hosted_zones = route53.list_hosted_zones_by_name.hosted_zones
index = hosted_zones.index { |zone| domain.end_with?(zone.name.chop) }
if index.nil?
puts 'Unable to find matching zone.'
exit 1
end
hosted_zones[index]
end
def wait_for_change(route53, change_id)
status = ''
until status == 'INSYNC'
resp = route53.get_change(id: change_id)
status = resp.change_info.status
if status != 'INSYNC'
puts 'Waiting for dns change to complete'
sleep 5
end
end
end
def setup_dns(domain, txt_challenge)
route53 = Aws::Route53::Client.new
hosted_zone = find_hosted_zone(route53, domain)
changes = []
changes << {
action: 'UPSERT',
resource_record_set: {
name: "_acme-challenge.#{domain}.",
type: 'TXT',
ttl: 60,
resource_records: [
value: "\"#{txt_challenge}\""
]
}
}
resp = route53.change_resource_record_sets(
hosted_zone_id: hosted_zone.id,
change_batch: {
changes: changes
}
)
wait_for_change(route53, resp.change_info.id)
end
def delete_dns(domain, txt_challenge)
route53 = Aws::Route53::Client.new
hosted_zone = find_hosted_zone(route53, domain)
changes = []
changes << {
action: 'DELETE',
resource_record_set: {
name: "_acme-challenge.#{domain}.",
type: 'TXT',
ttl: 60,
resource_records: [
value: "\"#{txt_challenge}\""
]
}
}
resp = route53.change_resource_record_sets(
hosted_zone_id: hosted_zone.id,
change_batch: {
changes: changes
}
)
wait_for_change(route53, resp.change_info.id)
end
if __FILE__ == $PROGRAM_NAME
hook_stage = ARGV[0]
domain = ARGV[1]
txt_challenge = ARGV[3]
puts "stage: #{hook_stage} domain: #{domain} txt_challenge: #{txt_challenge}"
if hook_stage == 'deploy_challenge'
setup_dns(domain, txt_challenge)
elsif hook_stage == 'clean_challenge'
delete_dns(domain, txt_challenge)
end
end
@reinhard-brandstaedter
Copy link

I found that this script has problems when you have sub domain zones in AWS. Eg.

  • example.com
  • sub.example.com

index = hosted_zones.index { |zone| domain.end_with?(zone.name.chop) }

This will cause the script to create the challenge TXT record in the example.com zone but a DNS query will try to find it in the subdomain zone. To work around this I changed the comparison to the other way around. Instead of checking if the domain ends with the zone name I check if the zone name ends with the domain latter part:

index = hosted_zones.index { |zone| zone.name.chop.end_with?(domain.split(".",2)[1]) }

Something to consider!

@oveaurs
Copy link

oveaurs commented Mar 17, 2021

@reinhard-brandstaedter what I did to fix the same issue:
index = hosted_zones.rindex { |zone| domain.end_with?(zone.name.chop) }
It will then use the "earliest match"

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment