route53_aliaser 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/.gitignore +14 -0
- data/CHANGELOG.md +2 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +22 -0
- data/README.md +94 -0
- data/Rakefile +2 -0
- data/lib/route53_aliaser/aliaser.rb +74 -0
- data/lib/route53_aliaser/configuration.rb +19 -0
- data/lib/route53_aliaser/route53_updater.rb +45 -0
- data/lib/route53_aliaser/version.rb +3 -0
- data/lib/route53_aliaser.rb +30 -0
- data/route53_aliaser.gemspec +27 -0
- metadata +127 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 4f5f82836a1ef13c74c8ea8ee34e4b03adbf1bd5
|
4
|
+
data.tar.gz: 4eea87944e778b4d4cd94112aa9b8e58f3e47588
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 54e4b74a04201d403e573c9d58ce4f13ee6237356e7f788aec3d66d299bebcfb151844c567a9cb3bc7bd51b277c0ac1d1397baa9e68f7f26a605b6ce4ef9dabb
|
7
|
+
data.tar.gz: de24589f9f8feef46adc4f245cbfcdefe262598ced7a9d9e980a5a05e2e72e8ee06ba9d4f5285358509c38426d3f1dea0e9299e4dabeb08c86bba5bb3f1f6d71
|
data/.gitignore
ADDED
data/CHANGELOG.md
ADDED
data/Gemfile
ADDED
data/LICENSE.txt
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright (c) 2014 Ryan Dlugosz
|
2
|
+
|
3
|
+
MIT License
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
6
|
+
a copy of this software and associated documentation files (the
|
7
|
+
"Software"), to deal in the Software without restriction, including
|
8
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
9
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
10
|
+
permit persons to whom the Software is furnished to do so, subject to
|
11
|
+
the following conditions:
|
12
|
+
|
13
|
+
The above copyright notice and this permission notice shall be
|
14
|
+
included in all copies or substantial portions of the Software.
|
15
|
+
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
17
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
18
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
19
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
20
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
21
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
22
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,94 @@
|
|
1
|
+
# Route53Aliaser
|
2
|
+
|
3
|
+
Simulate DNS ALIAS-record support for [apex
|
4
|
+
zones](https://devcenter.heroku.com/articles/apex-domains) (a.k.a. bare / naked / root
|
5
|
+
domains) via Amazon [Route 53](https://aws.amazon.com/route53/).
|
6
|
+
|
7
|
+
This is useful because Heroku doesn't support these so-called naked domains
|
8
|
+
and there are a limited number of DNS providers who support the [ALIAS
|
9
|
+
record](http://support.dnsimple.com/articles/alias-record/) type (which is
|
10
|
+
essentially a CNAME record set on the root domain, that typically must be an
|
11
|
+
A-Record). Amazon Route 53 only supports ALIASes to a few specific types of
|
12
|
+
records, which doesn't solve the problem for Heroku users who require SSL.
|
13
|
+
|
14
|
+
This code will:
|
15
|
+
|
16
|
+
- Check to see if our cached lookup of the `source_record` has expired
|
17
|
+
- if no, return immediately (noop)
|
18
|
+
- if yes:
|
19
|
+
- Lookup the A record for our `source_record` (e.g., CNAME to Heroku)
|
20
|
+
- Lookup the A record for our `target_record` (probably the naked
|
21
|
+
domain)
|
22
|
+
- If the Target and Source addresses differ, update the `target_record`
|
23
|
+
|
24
|
+
## Installation
|
25
|
+
|
26
|
+
Add this line to your application's Gemfile:
|
27
|
+
|
28
|
+
gem 'route53_aliaser'
|
29
|
+
|
30
|
+
And then execute:
|
31
|
+
|
32
|
+
$ bundle
|
33
|
+
|
34
|
+
Or install it yourself as:
|
35
|
+
|
36
|
+
$ gem install route53_aliaser
|
37
|
+
|
38
|
+
## Usage
|
39
|
+
|
40
|
+
Add an initializer that looks like this:
|
41
|
+
|
42
|
+
# ./config/initializers/route53_aliaser.rb
|
43
|
+
|
44
|
+
Route53Aliaser.configure do |config|
|
45
|
+
config.target_record = ENV['RT53_TARGET_RECORD'] #This is the one that is updated, i.e., your ALIAS
|
46
|
+
config.source_record = ENV['RT53_SOURCE_RECORD'] #This is what the ALIAS should be pointed to
|
47
|
+
config.zone_id = ENV['RT53_ZONE_ID'] #Amazon Hosted Zone ID
|
48
|
+
|
49
|
+
# Only need to set these if you aren't already setting them for another AWS service
|
50
|
+
# config.aws_access_key_id = ENV['RT53_AWS_ACCESS_KEY_ID']
|
51
|
+
# config.aws_secret_access_key = ENV['RT53_AWS_SECRET_ACCESS_KEY']
|
52
|
+
end
|
53
|
+
|
54
|
+
|
55
|
+
Then, just call `Route53Aliaser.update_alias_if_needed` periodically.
|
56
|
+
|
57
|
+
Not sure what the best approach is for this yet, but here are a few ideas:
|
58
|
+
|
59
|
+
- Add a `cron` job executing a Rake task.
|
60
|
+
- Create a special controller action that you load every so often via a ping
|
61
|
+
monitoring service. This controller action would act similar to a webhook in
|
62
|
+
that it would just call `Route53Aliaser.update_alias_if_needed` and return a
|
63
|
+
success code.
|
64
|
+
- Dropping `Thread.new { Route53Aliaser.update_alias_if_needed }` into a
|
65
|
+
controller action that gets called relatively frequently (say, your home page).
|
66
|
+
This is the #YOLO approach, but shouldn't be terribly harmful since: A) this
|
67
|
+
is a very short-lived thread, and B) there are no real consequences if the
|
68
|
+
thread were suddenly killed. Note that I would not recommend calling this in
|
69
|
+
line (i.e., not in a separate thread) in a controller action since DNS
|
70
|
+
lookups / AWS calls might be slow & will block the request to your page.
|
71
|
+
|
72
|
+
## Contributing
|
73
|
+
|
74
|
+
I'm using this against a limited number of configurations so patches are
|
75
|
+
*very* welcome!
|
76
|
+
|
77
|
+
1. Fork it ( https://github.com/[my-github-username]/route53_aliaser/fork )
|
78
|
+
2. Create your feature branch (`git checkout -b my-new-feature`)
|
79
|
+
3. Commit your changes (`git commit -am 'Add some feature'`)
|
80
|
+
4. Push to the branch (`git push origin my-new-feature`)
|
81
|
+
5. Create a new Pull Request
|
82
|
+
|
83
|
+
### Todos
|
84
|
+
|
85
|
+
1. Add some tests! *(Yes, I feel dirty.)*
|
86
|
+
1. Extract the dependency on ActiveSupport. The only thing really in use is
|
87
|
+
the caching mechanism.
|
88
|
+
1. Include support for other API-enabled DNS Hosts, e.g., Rackspace.
|
89
|
+
|
90
|
+
## Warranty
|
91
|
+
|
92
|
+
This software is provided “as is” and without any express or implied
|
93
|
+
warranties, including, without limitation, the implied warranties of
|
94
|
+
merchantability and fitness for a particular purpose.
|
data/Rakefile
ADDED
@@ -0,0 +1,74 @@
|
|
1
|
+
module Route53Aliaser
|
2
|
+
class Aliaser
|
3
|
+
attr_reader :config
|
4
|
+
|
5
|
+
def initialize(config = Configuration.new)
|
6
|
+
@config = config
|
7
|
+
end
|
8
|
+
|
9
|
+
def stale?
|
10
|
+
config.cache.fetch(config.source_key).nil?
|
11
|
+
end
|
12
|
+
|
13
|
+
def call
|
14
|
+
# NOOP if we haven't expired
|
15
|
+
return unless stale?
|
16
|
+
|
17
|
+
target_ips = get_ips(config.target_record, config.target_key)
|
18
|
+
source_ips = get_ips(config.source_record, config.source_key)
|
19
|
+
|
20
|
+
if target_ips == source_ips
|
21
|
+
config.logger.debug "No Route 53 Update required."
|
22
|
+
else
|
23
|
+
config.logger.info "IPs for #{config.target_record} #{target_ips} differ from #{config.source_record} #{source_ips}; will attempt to update."
|
24
|
+
rt53 = Route53Updater.new(config)
|
25
|
+
rt53.update_target(config.target_record, source_ips, config.zone_id)
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
private
|
30
|
+
def get_ips(zone, key)
|
31
|
+
ips = retrieve_ips_from_cache(key)
|
32
|
+
if ips.empty?
|
33
|
+
ips, ttl = get_ips_and_ttl_from_dns(zone)
|
34
|
+
cache_results(key, zone, ips, ttl)
|
35
|
+
end
|
36
|
+
ips
|
37
|
+
end
|
38
|
+
|
39
|
+
def retrieve_ips_from_cache(key)
|
40
|
+
cached_value = config.cache.fetch(key)
|
41
|
+
if cached_value
|
42
|
+
cached_value.split(',')
|
43
|
+
else
|
44
|
+
[]
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
def get_ips_and_ttl_from_dns(zone)
|
49
|
+
ips, ttl = [], 0
|
50
|
+
resources = Resolv::DNS.new.getresources(zone, Resolv::DNS::Resource::IN::A)
|
51
|
+
if resources && resources.size > 0
|
52
|
+
ips = resources.collect{|res| res.address.to_s}.sort
|
53
|
+
ttl = resources.first.ttl
|
54
|
+
end
|
55
|
+
return ips, ttl
|
56
|
+
rescue ResolvError => e
|
57
|
+
config.logger.error e
|
58
|
+
return ips, ttl
|
59
|
+
end
|
60
|
+
|
61
|
+
def cache_results(key, zone, ips, ttl)
|
62
|
+
unless ips.empty?
|
63
|
+
config.cache.write(key,
|
64
|
+
ips.join(','),
|
65
|
+
expires_in: ttl.seconds,
|
66
|
+
race_condition_ttl: 10
|
67
|
+
)
|
68
|
+
config.logger.debug "Route53Aliaser Caching #{key}: #{ips} for #{ttl} seconds (ttl)"
|
69
|
+
else
|
70
|
+
config.logger.error "Route53Aliaser NOT Caching #{key} because no IPs were found."
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
module Route53Aliaser
|
2
|
+
class Configuration
|
3
|
+
attr_accessor :target_record, :source_record, :zone_id, :logger, :cache,
|
4
|
+
:aws_access_key_id, :aws_secret_access_key
|
5
|
+
|
6
|
+
def initialize
|
7
|
+
@logger = defined?(Rails) ? Rails.logger : Logger.new(STDOUT)
|
8
|
+
@cache = ActiveSupport::Cache::MemoryStore.new
|
9
|
+
end
|
10
|
+
|
11
|
+
def target_key
|
12
|
+
"rt53_#{target_record}_ips"
|
13
|
+
end
|
14
|
+
|
15
|
+
def source_key
|
16
|
+
"rt53_#{source_record}_ips"
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,45 @@
|
|
1
|
+
module Route53Aliaser
|
2
|
+
class Route53Updater
|
3
|
+
attr_reader :config
|
4
|
+
def initialize(config)
|
5
|
+
@config = config
|
6
|
+
end
|
7
|
+
|
8
|
+
def update_target(target_record, ips, zone_id)
|
9
|
+
change = {
|
10
|
+
action: "UPSERT",
|
11
|
+
resource_record_set: {
|
12
|
+
name: target_record,
|
13
|
+
type: "A",
|
14
|
+
ttl: 60,
|
15
|
+
resource_records: ips.collect{ |ip| { value: ip } }
|
16
|
+
}
|
17
|
+
}
|
18
|
+
change_batch = {
|
19
|
+
comment: "auto updated",
|
20
|
+
changes: [change]
|
21
|
+
}
|
22
|
+
options = {
|
23
|
+
hosted_zone_id: zone_id,
|
24
|
+
change_batch: change_batch
|
25
|
+
}
|
26
|
+
|
27
|
+
rt53 = get_configured_aws_client
|
28
|
+
|
29
|
+
res = rt53.client.change_resource_record_sets(options)
|
30
|
+
|
31
|
+
res && res.successful?
|
32
|
+
end
|
33
|
+
|
34
|
+
private
|
35
|
+
def get_configured_aws_client
|
36
|
+
if(config.aws_access_key_id && config.aws_secret_access_key)
|
37
|
+
AWS::Route53.new(access_key_id: config.aws_access_key_id,
|
38
|
+
secret_access_key: config.aws_secret_access_key)
|
39
|
+
else
|
40
|
+
# Use the config set at the AWS module level (probably set for something else)
|
41
|
+
AWS::Route53.new
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
require "route53_aliaser/version"
|
2
|
+
require "route53_aliaser/configuration"
|
3
|
+
require "route53_aliaser/route53_updater"
|
4
|
+
require "route53_aliaser/aliaser"
|
5
|
+
|
6
|
+
require "resolv"
|
7
|
+
|
8
|
+
require "aws-sdk"
|
9
|
+
require "active_support" #TODO: Can we break this dep?
|
10
|
+
|
11
|
+
module Route53Aliaser
|
12
|
+
class << self
|
13
|
+
attr_accessor :config
|
14
|
+
end
|
15
|
+
|
16
|
+
def self.configure
|
17
|
+
self.config ||= Route53Aliaser::Configuration.new
|
18
|
+
yield(config)
|
19
|
+
end
|
20
|
+
|
21
|
+
def self.update_alias_if_needed
|
22
|
+
unless(config && config.target_record && config.source_record && config.zone_id)
|
23
|
+
Configuration.new.logger.error "Route53Aliaser is not configured properly. Please check the docs."
|
24
|
+
return
|
25
|
+
end
|
26
|
+
|
27
|
+
aliaser = Route53Aliaser::Aliaser.new(config)
|
28
|
+
aliaser.call
|
29
|
+
end
|
30
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'route53_aliaser/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |spec|
|
7
|
+
spec.name = "route53_aliaser"
|
8
|
+
spec.version = Route53Aliaser::VERSION
|
9
|
+
spec.authors = ["Ryan Dlugosz"]
|
10
|
+
spec.email = ["ryan@dlugosz.net"]
|
11
|
+
spec.summary = %q{Simulate DNS ALIAS-record support for apex zones (a.k.a. bare / naked / root domains) via Amazon Route 53}
|
12
|
+
# spec.description = %q{TODO: Write a longer description. Optional.}
|
13
|
+
spec.homepage = "https://github.com/rdlugosz/route53_aliaser"
|
14
|
+
spec.license = "MIT"
|
15
|
+
|
16
|
+
spec.files = `git ls-files -z`.split("\x0")
|
17
|
+
spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
|
18
|
+
spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
|
19
|
+
spec.require_paths = ["lib"]
|
20
|
+
|
21
|
+
spec.add_development_dependency "bundler", "~> 1.7"
|
22
|
+
spec.add_development_dependency "rake", "~> 10.0"
|
23
|
+
spec.add_development_dependency "rspec", "~> 3.1"
|
24
|
+
|
25
|
+
spec.add_dependency "aws-sdk", "~> 1.57"
|
26
|
+
spec.add_dependency "activesupport", ">= 3.2"
|
27
|
+
end
|
metadata
ADDED
@@ -0,0 +1,127 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: route53_aliaser
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.1
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Ryan Dlugosz
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2014-12-03 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: bundler
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - "~>"
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '1.7'
|
20
|
+
type: :development
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - "~>"
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '1.7'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: rake
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - "~>"
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '10.0'
|
34
|
+
type: :development
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - "~>"
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '10.0'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: rspec
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - "~>"
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '3.1'
|
48
|
+
type: :development
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - "~>"
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '3.1'
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: aws-sdk
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - "~>"
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '1.57'
|
62
|
+
type: :runtime
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - "~>"
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: '1.57'
|
69
|
+
- !ruby/object:Gem::Dependency
|
70
|
+
name: activesupport
|
71
|
+
requirement: !ruby/object:Gem::Requirement
|
72
|
+
requirements:
|
73
|
+
- - ">="
|
74
|
+
- !ruby/object:Gem::Version
|
75
|
+
version: '3.2'
|
76
|
+
type: :runtime
|
77
|
+
prerelease: false
|
78
|
+
version_requirements: !ruby/object:Gem::Requirement
|
79
|
+
requirements:
|
80
|
+
- - ">="
|
81
|
+
- !ruby/object:Gem::Version
|
82
|
+
version: '3.2'
|
83
|
+
description:
|
84
|
+
email:
|
85
|
+
- ryan@dlugosz.net
|
86
|
+
executables: []
|
87
|
+
extensions: []
|
88
|
+
extra_rdoc_files: []
|
89
|
+
files:
|
90
|
+
- ".gitignore"
|
91
|
+
- CHANGELOG.md
|
92
|
+
- Gemfile
|
93
|
+
- LICENSE.txt
|
94
|
+
- README.md
|
95
|
+
- Rakefile
|
96
|
+
- lib/route53_aliaser.rb
|
97
|
+
- lib/route53_aliaser/aliaser.rb
|
98
|
+
- lib/route53_aliaser/configuration.rb
|
99
|
+
- lib/route53_aliaser/route53_updater.rb
|
100
|
+
- lib/route53_aliaser/version.rb
|
101
|
+
- route53_aliaser.gemspec
|
102
|
+
homepage: https://github.com/rdlugosz/route53_aliaser
|
103
|
+
licenses:
|
104
|
+
- MIT
|
105
|
+
metadata: {}
|
106
|
+
post_install_message:
|
107
|
+
rdoc_options: []
|
108
|
+
require_paths:
|
109
|
+
- lib
|
110
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
111
|
+
requirements:
|
112
|
+
- - ">="
|
113
|
+
- !ruby/object:Gem::Version
|
114
|
+
version: '0'
|
115
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
116
|
+
requirements:
|
117
|
+
- - ">="
|
118
|
+
- !ruby/object:Gem::Version
|
119
|
+
version: '0'
|
120
|
+
requirements: []
|
121
|
+
rubyforge_project:
|
122
|
+
rubygems_version: 2.2.2
|
123
|
+
signing_key:
|
124
|
+
specification_version: 4
|
125
|
+
summary: Simulate DNS ALIAS-record support for apex zones (a.k.a. bare / naked / root
|
126
|
+
domains) via Amazon Route 53
|
127
|
+
test_files: []
|