deputy53 0.6.4

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 217f1845177eea5e0ab32c345d91b417e371be0b
4
+ data.tar.gz: 1a2e6eaf480af3717e8fa29cb6a2992fec35b775
5
+ SHA512:
6
+ metadata.gz: 5d60478b9b1940f528d873cfe2b5b4c4a2783593e16ad7328407fc056d0b0ec1d7afd9fd6c663f07757a84252d0b229a24f479d2ff3b41fffbc7bfc86bf81af3
7
+ data.tar.gz: 4d0e9fc2752d5ffcd6fd825976228aaac54126ee67148a96b44848bffa04f594f351b502945d6cf18f8c1e0a48236907a1f411a7ee13e8fe0618cd6b31abc171
data/Gemfile ADDED
@@ -0,0 +1,3 @@
1
+ source 'https://rubygems.org'
2
+
3
+ gemspec
data/LICENSE.txt ADDED
@@ -0,0 +1,21 @@
1
+
2
+ The MIT License (MIT)
3
+ Copyright © 2016 Chris Olstrom <chris@olstrom.com>
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the “Software”), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in
13
+ all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ THE SOFTWARE.
data/README.org ADDED
@@ -0,0 +1,39 @@
1
+ #+TITLE: Deputy53
2
+ #+LATEX: \pagebreak
3
+
4
+ * Overview
5
+
6
+ ~deputy53~ is a commandline tool to delegate control of a subdomain to another
7
+ hosted zone, and optionally grant control of that subdomain to an IAM user.
8
+
9
+ * Rationale
10
+
11
+ It is often useful to allow a user or group of users limited access to DNS
12
+ records. However, Route53 does not support granular access to a partial record
13
+ set for a zone.
14
+
15
+ One solution to this is to create an additional zone, and delegate to that
16
+ zone for a subset of records (a subdomain, for instance).
17
+
18
+ This process is cumbersome, and therefore prone to human error. ~deputy53~
19
+ simplifies this process.
20
+
21
+ * Installation
22
+
23
+ #+BEGIN_SRC shell
24
+ gem install deputy53
25
+ #+END_SRC
26
+
27
+ * Usage
28
+
29
+ #+BEGIN_SRC shell
30
+ deputy53 delegate <subdomain>
31
+ #+END_SRC
32
+
33
+ * License
34
+
35
+ ~deputy53~ is available under the [[https://tldrlegal.com/license/mit-license][MIT License]]. See ~LICENSE.txt~ for the full text.
36
+
37
+ * Contributors
38
+
39
+ - [[https://colstrom.github.io/][Chris Olstrom]] | [[mailto:chris@olstrom.com][e-mail]] | [[https://twitter.com/ChrisOlstrom][Twitter]]
data/bin/deputy53 ADDED
@@ -0,0 +1,10 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'instacli'
4
+ require_relative '../lib/deputy53/cli'
5
+
6
+ class CLI < InstaCLI::CLI
7
+ include InstaCLI::Demuxing
8
+ end
9
+
10
+ CLI.new(deputy53: Deputy53::CLI.new).execute(*ARGV)
data/deputy53.gemspec ADDED
@@ -0,0 +1,19 @@
1
+ Gem::Specification.new do |gem|
2
+ gem.name = 'deputy53'
3
+ gem.version = `git describe --tags --abbrev=0`.chomp
4
+ gem.licenses = 'MIT'
5
+ gem.authors = ['Chris Olstrom']
6
+ gem.email = 'chris@olstrom.com'
7
+ gem.homepage = 'https://github.com/colstrom/deputy53'
8
+ gem.summary = 'Delegates a subdomain to another zone with Route53'
9
+
10
+ gem.files = `git ls-files`.split("\n")
11
+ gem.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
12
+ gem.executables = `git ls-files -- bin/*`.split("\n").map { |f| File.basename(f) }
13
+ gem.require_paths = ['lib']
14
+
15
+ gem.add_runtime_dependency 'aws-sdk', '~> 2.3', '>= 2.3.0'
16
+ gem.add_runtime_dependency 'contracts', '~> 0.14', '>= 0.14.0'
17
+ gem.add_runtime_dependency 'exponential-backoff', '~> 0.0.2', '>= 0.0.2'
18
+ gem.add_runtime_dependency 'instacli', '~> 1.1', '>= 1.1.1'
19
+ end
@@ -0,0 +1,112 @@
1
+ require 'exponential_backoff'
2
+ require_relative 'contracted_object'
3
+ require_relative 'route53'
4
+ require_relative 'zone'
5
+
6
+ module Deputy53
7
+ # Handles creation and delegation
8
+ class Agent < ContractedObject
9
+ Contract String => Agent
10
+ def initialize(target)
11
+ @target = target
12
+ self
13
+ end
14
+
15
+ Contract None => String
16
+ def caller_reference
17
+ @caller_reference ||= "#{subdomain}@#{Time.now.to_i}"
18
+ end
19
+
20
+ Contract None => String
21
+ def domain
22
+ @domain ||= @target.split('.').last(2).join('.') << '.'
23
+ end
24
+
25
+ Contract None => String
26
+ def prefix
27
+ @prefix ||= @target.split('.').slice(0..-3).join('.')
28
+ end
29
+
30
+ Contract None => String
31
+ def subdomain
32
+ @subdomain ||= "#{prefix}.#{domain}"
33
+ end
34
+
35
+ Contract None => Route53
36
+ def route53!
37
+ @route53 = Route53.new
38
+ end
39
+
40
+ Contract None => Route53
41
+ def route53
42
+ @route53 ||= route53!
43
+ end
44
+
45
+ Contract None => Zone
46
+ def child
47
+ @child ||= Zone.new create subdomain
48
+ end
49
+
50
+ Contract None => Zone
51
+ def parent
52
+ @parent ||= Zone.new create domain
53
+ end
54
+
55
+ Contract String => String
56
+ def create(name)
57
+ return route53.id(name) if route53.zone? name
58
+
59
+ route53
60
+ .api
61
+ .create_hosted_zone(
62
+ name: name,
63
+ caller_reference: caller_reference
64
+ ).hosted_zone
65
+ .id
66
+ end
67
+
68
+ Contract None => Bool
69
+ def delegate
70
+ return true if parent.delegation(subdomain).sort == child.name_servers.sort
71
+ wait_for_change route53.api.change_resource_record_sets(payload).change_info
72
+ end
73
+
74
+ Contract Aws::Route53::Types::ChangeInfo => Bool
75
+ def wait_for_change(change)
76
+ ExponentialBackoff.new(0.5, 8.0).tap do |backoff|
77
+ while change.status == 'PENDING'
78
+ route53.api.get_change(id: change.id).change_info.tap do |info|
79
+ backoff.next_interval
80
+ message = "#{info.id} is #{info.status}"
81
+ if info.status == 'PENDING'
82
+ STDERR.puts "#{message} (recheck in #{backoff.current_interval}s)"
83
+ sleep backoff.current_interval
84
+ else
85
+ STDERR.puts message
86
+ end
87
+ change = info
88
+ end
89
+ end
90
+ end
91
+ true if change.status == 'INSYNC'
92
+ end
93
+
94
+ Contract None => Hash
95
+ def payload
96
+ {
97
+ hosted_zone_id: parent.id,
98
+ change_batch: {
99
+ changes: [
100
+ action: 'CREATE',
101
+ resource_record_set: {
102
+ name: subdomain,
103
+ type: 'NS',
104
+ ttl: 300,
105
+ resource_records: child.name_servers.map { |ns| { value: ns } }
106
+ }
107
+ ]
108
+ }
109
+ }
110
+ end
111
+ end
112
+ end
@@ -0,0 +1,9 @@
1
+ require_relative 'agent'
2
+
3
+ module Deputy53
4
+ class CLI
5
+ def delegate(subdomain)
6
+ Agent.new(subdomain).delegate
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,9 @@
1
+ require 'contracts'
2
+
3
+ module Deputy53
4
+ # An Object that supports Contracts
5
+ class ContractedObject
6
+ include Contracts::Core
7
+ include Contracts::Builtin
8
+ end
9
+ end
@@ -0,0 +1,42 @@
1
+ require 'aws-sdk'
2
+
3
+ module Deputy53
4
+ # A Route53 Client
5
+ class Route53 < ContractedObject
6
+ Contract None => ::Aws::Route53::Client
7
+ def api
8
+ @api ||= ::Aws::Route53::Client.new region: region
9
+ end
10
+
11
+ Contract None => String
12
+ def region
13
+ ENV.fetch('AWS_DEFAULT_REGION') { 'us-west-1' }
14
+ end
15
+
16
+ Contract None => ArrayOf[::Aws::Route53::Types::HostedZone]
17
+ def zones
18
+ @zones ||= api.list_hosted_zones.hosted_zones
19
+ end
20
+
21
+ Contract None => ArrayOf[String]
22
+ def names
23
+ @names ||= zones.map(&:name)
24
+ end
25
+
26
+ Contract String => ArrayOf[::Aws::Route53::Types::HostedZone]
27
+ def zones(name)
28
+ zones.select { |z| z.name == name }
29
+ end
30
+
31
+ Contract String => Bool
32
+ def zone?(name)
33
+ !zones(name).empty?
34
+ end
35
+
36
+ Contract String => String
37
+ def id(name)
38
+ raise KeyError unless zone? name
39
+ zones(name).first.id
40
+ end
41
+ end
42
+ end
@@ -0,0 +1,53 @@
1
+ require_relative 'contracted_object'
2
+ require_relative 'route53'
3
+
4
+ module Deputy53
5
+ # A DNS Zone
6
+ class Zone < ContractedObject
7
+ attr_reader :id
8
+
9
+ Contract String => Zone
10
+ def initialize(id)
11
+ @id = id
12
+ self
13
+ end
14
+
15
+ Contract None => Route53
16
+ def route53
17
+ @route53 ||= Route53.new
18
+ end
19
+
20
+ Contract None => ::Aws::Route53::Types::GetHostedZoneResponse
21
+ def zone
22
+ @zone ||= route53.api.get_hosted_zone(id: id).data
23
+ end
24
+
25
+ Contract None => ArrayOf[String]
26
+ def name_servers
27
+ @name_servers ||= zone.delegation_set.name_servers
28
+ end
29
+
30
+ Contract None => ArrayOf[::Aws::Route53::Types::ResourceRecordSet]
31
+ def records
32
+ @records ||= route53.api.list_resource_record_sets(hosted_zone_id: id).resource_record_sets
33
+ end
34
+
35
+ Contract String => ArrayOf[::Aws::Route53::Types::ResourceRecordSet]
36
+ def records(type)
37
+ records.select { |r| r.type == type }
38
+ end
39
+
40
+ Contract String => Bool
41
+ def delegating?(name)
42
+ records('NS').any? { |r| r.name == name }
43
+ end
44
+
45
+ Contract String => ArrayOf[String]
46
+ def delegation(name)
47
+ records('NS')
48
+ .select { |r| r.name == name }
49
+ .flat_map(&:resource_records)
50
+ .map(&:value)
51
+ end
52
+ end
53
+ end
data/lib/deputy53.rb ADDED
@@ -0,0 +1,5 @@
1
+ require_relative 'deputy53/agent'
2
+ require_relative 'deputy53/cli'
3
+ require_relative 'deputy53/contracted_object'
4
+ require_relative 'deputy53/route53'
5
+ require_relative 'deputy53/zone'
metadata ADDED
@@ -0,0 +1,136 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: deputy53
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.6.4
5
+ platform: ruby
6
+ authors:
7
+ - Chris Olstrom
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2016-06-21 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: aws-sdk
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '2.3'
20
+ - - ">="
21
+ - !ruby/object:Gem::Version
22
+ version: 2.3.0
23
+ type: :runtime
24
+ prerelease: false
25
+ version_requirements: !ruby/object:Gem::Requirement
26
+ requirements:
27
+ - - "~>"
28
+ - !ruby/object:Gem::Version
29
+ version: '2.3'
30
+ - - ">="
31
+ - !ruby/object:Gem::Version
32
+ version: 2.3.0
33
+ - !ruby/object:Gem::Dependency
34
+ name: contracts
35
+ requirement: !ruby/object:Gem::Requirement
36
+ requirements:
37
+ - - "~>"
38
+ - !ruby/object:Gem::Version
39
+ version: '0.14'
40
+ - - ">="
41
+ - !ruby/object:Gem::Version
42
+ version: 0.14.0
43
+ type: :runtime
44
+ prerelease: false
45
+ version_requirements: !ruby/object:Gem::Requirement
46
+ requirements:
47
+ - - "~>"
48
+ - !ruby/object:Gem::Version
49
+ version: '0.14'
50
+ - - ">="
51
+ - !ruby/object:Gem::Version
52
+ version: 0.14.0
53
+ - !ruby/object:Gem::Dependency
54
+ name: exponential-backoff
55
+ requirement: !ruby/object:Gem::Requirement
56
+ requirements:
57
+ - - "~>"
58
+ - !ruby/object:Gem::Version
59
+ version: 0.0.2
60
+ - - ">="
61
+ - !ruby/object:Gem::Version
62
+ version: 0.0.2
63
+ type: :runtime
64
+ prerelease: false
65
+ version_requirements: !ruby/object:Gem::Requirement
66
+ requirements:
67
+ - - "~>"
68
+ - !ruby/object:Gem::Version
69
+ version: 0.0.2
70
+ - - ">="
71
+ - !ruby/object:Gem::Version
72
+ version: 0.0.2
73
+ - !ruby/object:Gem::Dependency
74
+ name: instacli
75
+ requirement: !ruby/object:Gem::Requirement
76
+ requirements:
77
+ - - "~>"
78
+ - !ruby/object:Gem::Version
79
+ version: '1.1'
80
+ - - ">="
81
+ - !ruby/object:Gem::Version
82
+ version: 1.1.1
83
+ type: :runtime
84
+ prerelease: false
85
+ version_requirements: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - "~>"
88
+ - !ruby/object:Gem::Version
89
+ version: '1.1'
90
+ - - ">="
91
+ - !ruby/object:Gem::Version
92
+ version: 1.1.1
93
+ description:
94
+ email: chris@olstrom.com
95
+ executables:
96
+ - deputy53
97
+ extensions: []
98
+ extra_rdoc_files: []
99
+ files:
100
+ - Gemfile
101
+ - LICENSE.txt
102
+ - README.org
103
+ - bin/deputy53
104
+ - deputy53.gemspec
105
+ - lib/deputy53.rb
106
+ - lib/deputy53/agent.rb
107
+ - lib/deputy53/cli.rb
108
+ - lib/deputy53/contracted_object.rb
109
+ - lib/deputy53/route53.rb
110
+ - lib/deputy53/zone.rb
111
+ homepage: https://github.com/colstrom/deputy53
112
+ licenses:
113
+ - MIT
114
+ metadata: {}
115
+ post_install_message:
116
+ rdoc_options: []
117
+ require_paths:
118
+ - lib
119
+ required_ruby_version: !ruby/object:Gem::Requirement
120
+ requirements:
121
+ - - ">="
122
+ - !ruby/object:Gem::Version
123
+ version: '0'
124
+ required_rubygems_version: !ruby/object:Gem::Requirement
125
+ requirements:
126
+ - - ">="
127
+ - !ruby/object:Gem::Version
128
+ version: '0'
129
+ requirements: []
130
+ rubyforge_project:
131
+ rubygems_version: 2.5.1
132
+ signing_key:
133
+ specification_version: 4
134
+ summary: Delegates a subdomain to another zone with Route53
135
+ test_files: []
136
+ has_rdoc: