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 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
@@ -0,0 +1,14 @@
1
+ /.bundle/
2
+ /.yardoc
3
+ /Gemfile.lock
4
+ /_yardoc/
5
+ /coverage/
6
+ /doc/
7
+ /pkg/
8
+ /spec/reports/
9
+ /tmp/
10
+ *.bundle
11
+ *.so
12
+ *.o
13
+ *.a
14
+ mkmf.log
data/CHANGELOG.md ADDED
@@ -0,0 +1,2 @@
1
+ # Route53Aliaser 0.0.1
2
+ - initial release
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in route53_aliaser.gemspec
4
+ gemspec
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,2 @@
1
+ require "bundler/gem_tasks"
2
+
@@ -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,3 @@
1
+ module Route53Aliaser
2
+ VERSION = "0.0.1"
3
+ 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: []