Skip to content

Commit

Permalink
route53: implement substitution_map
Browse files Browse the repository at this point in the history
Allow delegation of _acme-challenge by using CNAME and pre-defined
substitution record name.
  • Loading branch information
sorah committed Oct 8, 2022
1 parent 5055e50 commit 1cd2232
Show file tree
Hide file tree
Showing 3 changed files with 77 additions and 3 deletions.
11 changes: 10 additions & 1 deletion docs/challenge_responders/route53.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,16 @@ challenge_responders:

# Restore to original records on cleanup (after domain authorization). Default to false.
# Useful when you need to keep existing record as long as possible.
restore_to_original_records: true
restore_to_original_records: false

### Substitution record names map (optional)
## This specifies alias for specific _acme-challenge record. For instance the following example
## updates _acme-challenge.test-example-com.example.org instead of _acme-challenge.test.example.com.
##
## This eases using the route53 responder for domains not managed in route53, by registering CNAME record to
## the alias record name on the original record name in advance. This is called delegation.
substitution_map:
"test.example.com.": "test-example-com.example.org."
```
## IAM Policy
Expand Down
12 changes: 11 additions & 1 deletion lib/acmesmith/challenge_responders/route53.rb
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ def cap_respond_all?
true
end

def initialize(aws_access_key: nil, assume_role: nil, hosted_zone_map: {}, restore_to_original_records: false)
def initialize(aws_access_key: nil, assume_role: nil, hosted_zone_map: {}, restore_to_original_records: false, substitution_map: {})
aws_options = {region: 'us-east-1'}.tap do |opt|
opt[:credentials] = Aws::Credentials.new(aws_access_key['access_key_id'], aws_access_key['secret_access_key'], aws_access_key['session_token']) if aws_access_key
end
Expand All @@ -37,9 +37,13 @@ def initialize(aws_access_key: nil, assume_role: nil, hosted_zone_map: {}, resto

@restore_to_original_records = restore_to_original_records
@original_records = {}

@substitution_map = substitution_map.map { |k,v| [canonical_fqdn(k), v] }.to_h
end

def respond_all(*domain_and_challenges)
domain_and_challenges = apply_substitution_for_domain_and_challenges(domain_and_challenges)

save_original_records(*domain_and_challenges) if @restore_to_original_records

challenges_by_hosted_zone = domain_and_challenges.group_by { |(domain, _)| find_hosted_zone(domain) }
Expand All @@ -60,6 +64,8 @@ def respond_all(*domain_and_challenges)
end

def cleanup_all(*domain_and_challenges)
domain_and_challenges = apply_substitution_for_domain_and_challenges(domain_and_challenges)

challenges_by_hosted_zone = domain_and_challenges.group_by { |(domain, _)| find_hosted_zone(domain) }

zone_and_batches = challenges_by_hosted_zone.map do |zone_id, dcs|
Expand Down Expand Up @@ -264,6 +270,10 @@ def hosted_zone_list
end
end

def apply_substitution_for_domain_and_challenges(domain_and_challenges)
domain_and_challenges.map { |(domain, challenge)| [@substitution_map.fetch(canonical_fqdn(domain), domain), challenge] }
end

def list_existing_rrsets(hosted_zone_id, name)
rrsets = []
start_record_name = name
Expand Down
57 changes: 56 additions & 1 deletion spec/challenge_responders/route53_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ def print(*); end
let(:assume_role) { nil }
let(:hosted_zone_map) { {} }
let(:restore_to_original_records) { false }
let(:substitution_map) { {} }

let(:r53) { double(:route53) }

Expand All @@ -23,6 +24,7 @@ def print(*); end
assume_role: assume_role,
hosted_zone_map: hosted_zone_map,
restore_to_original_records: restore_to_original_records,
substitution_map: substitution_map,
)
end

Expand Down Expand Up @@ -115,7 +117,7 @@ def print(*); end

let(:list_hosted_zones) do
[
*%w(example.com corp.example.com).map do |_|
*%w(example.com corp.example.com example.org).map do |_|
Aws::Route53::Types::HostedZone.new(name: "#{_}.", id: "/hostedzone/#{_}", config: Aws::Route53::Types::HostedZoneConfig.new(private_zone: false))
end,
Aws::Route53::Types::HostedZone.new(name: "example.net.", id: "/hostedzone/example.net-true", config: Aws::Route53::Types::HostedZoneConfig.new(private_zone: false)),
Expand Down Expand Up @@ -494,6 +496,59 @@ def expect_change_rrset(hosted_zone_id:, changes:, comment:, wait: true)
roundtrip
end
end

context "when substitution_map is set" do
let(:substitution_map) { {"akane.example.com." => "_akane.example.org"} }
let(:domain_and_challenges) do
[
['akane.example.com', double_challenge],
['yaeka.example.com', double_challenge],
]
end

subject(:roundtrip) {
responder.respond_all(*domain_and_challenges)
responder.cleanup_all(*domain_and_challenges)
}

before do
expect_change_rrset(
hosted_zone_id: '/hostedzone/example.org',
comment: 'ACME challenge response ',
changes: [
change_object(action: 'UPSERT', name: "_akane.example.org", challenge: domain_and_challenges[0][1]),
],
)
expect_change_rrset(
hosted_zone_id: '/hostedzone/example.com',
comment: 'ACME challenge response ',
changes: [
change_object(action: 'UPSERT', name: domain_and_challenges[1][0], challenge: domain_and_challenges[1][1]),
],
)
expect_change_rrset(
hosted_zone_id: '/hostedzone/example.org',
comment: 'ACME challenge response (cleanup)',
changes: [
change_object(action: 'DELETE', name: "_akane.example.org", challenge: domain_and_challenges[0][1]),
],
wait: false,
)
expect_change_rrset(
hosted_zone_id: '/hostedzone/example.com',
comment: 'ACME challenge response (cleanup)',
changes: [
change_object(action: 'DELETE', name: domain_and_challenges[1][0], challenge: domain_and_challenges[1][1]),
],
wait: false,
)
end

it "uses another name for rrset" do
roundtrip
end
end

end


Expand Down

0 comments on commit 1cd2232

Please sign in to comment.