holepunch 1.0.0

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.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: fe47a0599c4e1b19b2d568547aeffe9da1f6fe7c
4
+ data.tar.gz: 587e038f58741e6f9964cc3de7bbfe633408d5e5
5
+ SHA512:
6
+ metadata.gz: 91dcdcd8826236fe71ec2dfe9a1267797418f832611960912e95401a2c36478b1cdb2cb650b5467d2f6760859eeaf1beec58d94aedd6b638eca0b746b791facc
7
+ data.tar.gz: 11b27c316a5d8c62c69055c9b12271b33e05b57535c541518c954b9e2bfab5482e679bd13788bca94cf154c25e3e4b41dccb1381d106243f169319028f553ef3
@@ -0,0 +1,26 @@
1
+ #!/usr/bin/env ruby
2
+ #
3
+ # Copyright (C) 2014 Undead Labs, LLC
4
+ #
5
+ # Permission is hereby granted, free of charge, to any person obtaining a copy of
6
+ # this software and associated documentation files (the "Software"), to deal in
7
+ # the Software without restriction, including without limitation the rights to
8
+ # use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
9
+ # the Software, and to permit persons to whom the Software is furnished to do so,
10
+ # subject to the following conditions:
11
+ #
12
+ # The above copyright notice and this permission notice shall be included in all
13
+ # 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, FITNESS
17
+ # FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
18
+ # COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
19
+ # IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
20
+ # CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
21
+ #
22
+ lib = File.expand_path('../../lib', __FILE__)
23
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
24
+ require 'holepunch/cli'
25
+
26
+ HolePunch::Cli.new.execute!(ARGV.dup)
@@ -0,0 +1,48 @@
1
+ #
2
+ # Copyright (C) 2014 Undead Labs, LLC
3
+ #
4
+ # Permission is hereby granted, free of charge, to any person obtaining a copy of
5
+ # this software and associated documentation files (the "Software"), to deal in
6
+ # the Software without restriction, including without limitation the rights to
7
+ # use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
8
+ # the Software, and to permit persons to whom the Software is furnished to do so,
9
+ # subject to the following conditions:
10
+ #
11
+ # The above copyright notice and this permission notice shall be included in all
12
+ # copies or substantial portions of the Software.
13
+ #
14
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15
+ # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
16
+ # FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
17
+ # COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
18
+ # IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
19
+ # CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
20
+ #
21
+ require 'aws-sdk'
22
+ require 'holepunch/definition'
23
+ require 'holepunch/dsl'
24
+ require 'holepunch/ec2'
25
+ require 'holepunch/logger'
26
+ require 'holepunch/version'
27
+
28
+ module HolePunch
29
+ class HolePunchError < StandardError; end
30
+
31
+ class EnvNotDefinedError < HolePunchError; end
32
+ class GroupError < HolePunchError; end
33
+ class GroupDoesNotExistError < HolePunchError; end
34
+ class SecurityGroupsFileNotFoundError < HolePunchError; end
35
+ class SecurityGroupsFileError < HolePunchError; end
36
+
37
+ class << self
38
+ def cidr?(value)
39
+ value.to_s =~ /\d+\.\d+\.\d+\.\d+\/\d+/
40
+ end
41
+
42
+ def read_file(file)
43
+ content = File.open(file, 'rb') do |io|
44
+ io.read
45
+ end
46
+ end
47
+ end
48
+ end
@@ -0,0 +1,109 @@
1
+ #
2
+ # Copyright (C) 2014 Undead Labs, LLC
3
+ #
4
+ # Permission is hereby granted, free of charge, to any person obtaining a copy of
5
+ # this software and associated documentation files (the "Software"), to deal in
6
+ # the Software without restriction, including without limitation the rights to
7
+ # use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
8
+ # the Software, and to permit persons to whom the Software is furnished to do so,
9
+ # subject to the following conditions:
10
+ #
11
+ # The above copyright notice and this permission notice shall be included in all
12
+ # copies or substantial portions of the Software.
13
+ #
14
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15
+ # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
16
+ # FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
17
+ # COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
18
+ # IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
19
+ # CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
20
+ #
21
+ require 'holepunch'
22
+ require 'optparse'
23
+
24
+ module HolePunch
25
+ class Options < Struct.new(
26
+ :aws_access_key_id,
27
+ :aws_region,
28
+ :aws_secret_access_key,
29
+ :env,
30
+ :filename,
31
+ :verbose
32
+ ); end
33
+
34
+ class Cli
35
+ def initialize
36
+ Logger.output = LoggerOutputStdio.new
37
+ end
38
+
39
+ def execute!(args)
40
+ opts = parse_opts(args)
41
+ Logger.verbose = opts.verbose
42
+
43
+ definition = Definition.build(opts.filename, opts.env)
44
+
45
+ ec2 = EC2.new({
46
+ access_key_id: opts.aws_access_key_id,
47
+ secret_access_key: opts.aws_secret_access_key,
48
+ region: opts.aws_region,
49
+ })
50
+ ec2.apply(definition)
51
+
52
+ rescue EnvNotDefinedError => e
53
+ Logger.fatal('You have security groups that use an environment, but you did not specify one. See --help')
54
+ rescue HolePunchError => e
55
+ Logger.fatal(e.message)
56
+ end
57
+
58
+ private
59
+ def parse_opts(args)
60
+ opts = Options.new
61
+ opts.aws_access_key_id = ENV['AWS_ACCESS_KEY_ID']
62
+ opts.aws_secret_access_key = ENV['AWS_SECRET_ACCESS_KEY']
63
+ opts.aws_region = ENV['AWS_REGION']
64
+ opts.env = nil
65
+ opts.filename = "#{Dir.pwd}/SecurityGroups"
66
+ opts.verbose = false
67
+
68
+ OptionParser.new(<<-EOS.gsub(/^ {10}/, '')
69
+ Usage: holepunch [options]
70
+
71
+ Options:
72
+ EOS
73
+ ) do |parser|
74
+ parser.on('-A', '--aws-access-key KEY', String, 'Your AWS Access Key ID') do |value|
75
+ opts.aws_access_key_id = value
76
+ end
77
+ parser.on('-e', '--env ENV', String, 'Set the environment') do |value|
78
+ opts.env = value
79
+ end
80
+ parser.on('-f', '--file FILENAME', String, 'The location of the SecurityGroups file to use') do |value|
81
+ opts.filename = value
82
+ end
83
+ parser.on('-K', '--aws-secret-access-key SECRET', String, 'Your AWS API Secret Access Key') do |value|
84
+ opts.aws_secret_access_key = value
85
+ end
86
+ parser.on('-r', '--region REGION', String, 'Your AWS region') do |v|
87
+ opts.aws_region = v
88
+ end
89
+ parser.on('-v', '--verbose', 'verbose output') do |v|
90
+ opts.verbose = v
91
+ end
92
+ parser.on('-V', '--version', 'display the version and exit') do
93
+ puts VERSION
94
+ exit
95
+ end
96
+ parser.on_tail('-h', '--help', 'show this message') do
97
+ puts parser
98
+ exit
99
+ end
100
+ end.parse!(args)
101
+
102
+ Logger.fatal("AWS Access Key ID not defined. Use --aws-access-key or AWS_ACCESS_KEY_ID") if opts.aws_access_key_id.nil?
103
+ Logger.fatal("AWS Secret Access Key not defined. Use --aws-secret-access-key or AWS_SECRET_ACCESS_KEY") if opts.aws_secret_access_key.nil?
104
+ Logger.fatal("AWS Region not defined. Use --aws-region or AWS_REGION") if opts.aws_region.nil?
105
+
106
+ opts
107
+ end
108
+ end
109
+ end
@@ -0,0 +1,98 @@
1
+ #
2
+ # Copyright (C) 2014 Undead Labs, LLC
3
+ #
4
+ # Permission is hereby granted, free of charge, to any person obtaining a copy of
5
+ # this software and associated documentation files (the "Software"), to deal in
6
+ # the Software without restriction, including without limitation the rights to
7
+ # use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
8
+ # the Software, and to permit persons to whom the Software is furnished to do so,
9
+ # subject to the following conditions:
10
+ #
11
+ # The above copyright notice and this permission notice shall be included in all
12
+ # copies or substantial portions of the Software.
13
+ #
14
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15
+ # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
16
+ # FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
17
+ # COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
18
+ # IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
19
+ # CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
20
+ #
21
+ module HolePunch
22
+ class SecurityGroup
23
+ attr_accessor :id, :desc, :dependency, :ingresses
24
+
25
+ def initialize(id, opts = {})
26
+ opts = {
27
+ dependency: false
28
+ }.merge(opts)
29
+
30
+ @id = id
31
+ @desc = id
32
+ @dependency = opts[:dependency]
33
+ @ingresses = []
34
+ end
35
+
36
+ def include_ingress?(type, ports, source)
37
+ ports = ports.first if ports.is_a?(Range) and ports.size == 1
38
+
39
+ ingresses.any? do |ingress|
40
+ ingress.type == type && ingress.ports == ports && ingress.sources.include?(source)
41
+ end
42
+ end
43
+ end
44
+
45
+ class Permission < Struct.new(:type, :ports, :sources)
46
+ def icmp?
47
+ type == :icmp
48
+ end
49
+
50
+ def tcp?
51
+ type == :tcp
52
+ end
53
+
54
+ def udp?
55
+ type == :udp
56
+ end
57
+ end
58
+
59
+ class Definition
60
+ attr_reader :env
61
+ attr_reader :groups
62
+
63
+ class << self
64
+ def build(file, env)
65
+ filename = Pathname.new(file).expand_path
66
+ unless filename.file?
67
+ raise SecurityGroupsFileNotFoundError, "#{filename} not found"
68
+ end
69
+
70
+ DSL.evaluate(file, env)
71
+ end
72
+ end
73
+
74
+ def initialize(env = nil)
75
+ @env = env
76
+ @groups = {}
77
+ end
78
+
79
+ def add_group(group)
80
+ raise DuplicateGroupError, "another group already exists with id #{id}" if groups.include?(group.id)
81
+ groups[group.id] = group
82
+ end
83
+
84
+ def validate!
85
+ # verify group references are defined
86
+ groups.each do |id, group|
87
+ group.ingresses.each do |ingress|
88
+ ingress.sources.each do |source|
89
+ next if HolePunch.cidr?(source)
90
+ unless groups.include?(source)
91
+ raise GroupError, "group '#{source}' referenced by group '#{id}' does not exist"
92
+ end
93
+ end
94
+ end
95
+ end
96
+ end
97
+ end
98
+ end
@@ -0,0 +1,99 @@
1
+ #
2
+ # Copyright (C) 2014 Undead Labs, LLC
3
+ #
4
+ # Permission is hereby granted, free of charge, to any person obtaining a copy of
5
+ # this software and associated documentation files (the "Software"), to deal in
6
+ # the Software without restriction, including without limitation the rights to
7
+ # use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
8
+ # the Software, and to permit persons to whom the Software is furnished to do so,
9
+ # subject to the following conditions:
10
+ #
11
+ # The above copyright notice and this permission notice shall be included in all
12
+ # copies or substantial portions of the Software.
13
+ #
14
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15
+ # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
16
+ # FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
17
+ # COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
18
+ # IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
19
+ # CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
20
+ #
21
+ require 'pathname'
22
+
23
+ module HolePunch
24
+ class DSL
25
+ attr_reader :groups
26
+
27
+ def self.evaluate(filename, env = nil)
28
+ DSL.new(env).eval_dsl(filename)
29
+ end
30
+
31
+ def initialize(env)
32
+ @definition = Definition.new(env)
33
+ @group = nil
34
+ @groups = {}
35
+ end
36
+
37
+ def eval_dsl(filename)
38
+ instance_eval(HolePunch.read_file(filename.to_s), filename.to_s, 1)
39
+ @definition.validate!
40
+ @definition
41
+ rescue SyntaxError => e
42
+ raise SecurityGroupsFileError, "SecurityGroups syntax error #{e.message.gsub("#{filename.to_s}:", 'on line ')}"
43
+ end
44
+
45
+ def env
46
+ raise EnvNotDefinedError, 'env not defined' if @definition.env.nil?
47
+ @definition.env
48
+ end
49
+
50
+ def depends(id)
51
+ id = id.to_s
52
+ raise GroupError, "duplicate group id #{id}" if @definition.groups.include?(id)
53
+ raise HolePunchSyntaxError, "dependency group #{id} cannot have a block" if block_given?
54
+ @group = SecurityGroup.new(id, dependency: true)
55
+ @definition.add_group(@group)
56
+ yield if block_given?
57
+ ensure
58
+ @group = nil
59
+ end
60
+
61
+ def group(id, &block)
62
+ id = id.to_s
63
+ raise GroupError, "duplicate group id #{id}" if @definition.groups.include?(id)
64
+ @group = SecurityGroup.new(id, dependency: false)
65
+ @definition.add_group(@group)
66
+ yield if block_given?
67
+ ensure
68
+ @group = nil
69
+ end
70
+
71
+ def desc(str)
72
+ raise HolePunchSyntaxError, 'desc must be used inside a group' if @group.nil?
73
+ raise HolePunchSyntaxError, 'desc cannot be used in a dependency group (the group is expected to be already defined elsewhere)' if @group.dependency
74
+ @group.desc = str
75
+ end
76
+
77
+ def icmp(*sources)
78
+ raise HolePunchSyntaxError, 'ping/icmp must be used inside a group' if @group.nil?
79
+ raise HolePunchSyntaxError, 'ping/icmp cannot be used in a dependency group (the group is expected to be already defined elsewhere)' if @group.dependency
80
+ sources << '0.0.0.0/0' if sources.empty?
81
+ @group.ingresses << Permission.new(:icmp, nil, sources.flatten)
82
+ end
83
+ alias_method :ping, :icmp
84
+
85
+ def tcp(ports, *sources)
86
+ raise HolePunchSyntaxError, 'tcp must be used inside a group' if @group.nil?
87
+ raise HolePunchSyntaxError, 'tcp cannot be used in a dependency group (the group is expected to be already defined elsewhere)' if @group.dependency
88
+ sources << '0.0.0.0/0' if sources.empty?
89
+ @group.ingresses << Permission.new(:tcp, ports, sources.flatten)
90
+ end
91
+
92
+ def udp(ports, *sources)
93
+ raise HolePunchSyntaxError, 'udp must be used inside a group' if @group.nil?
94
+ raise HolePunchSyntaxError, 'udp cannot be used in a dependency group (the group is expected to be already defined elsewhere)' if @group.dependency
95
+ sources << '0.0.0.0/0' if sources.empty?
96
+ @group.ingresses << Permission.new(:udp, ports, sources.flatten)
97
+ end
98
+ end
99
+ end
@@ -0,0 +1,160 @@
1
+ #
2
+ # Copyright (C) 2014 Undead Labs, LLC
3
+ #
4
+ # Permission is hereby granted, free of charge, to any person obtaining a copy of
5
+ # this software and associated documentation files (the "Software"), to deal in
6
+ # the Software without restriction, including without limitation the rights to
7
+ # use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
8
+ # the Software, and to permit persons to whom the Software is furnished to do so,
9
+ # subject to the following conditions:
10
+ #
11
+ # The above copyright notice and this permission notice shall be included in all
12
+ # copies or substantial portions of the Software.
13
+ #
14
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15
+ # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
16
+ # FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
17
+ # COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
18
+ # IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
19
+ # CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
20
+ #
21
+ require 'shellwords'
22
+
23
+ module HolePunch
24
+ class EC2
25
+ attr_reader :ec2
26
+ attr_reader :region
27
+
28
+ def initialize(opts = {})
29
+ opts = {
30
+ access_key_id: ENV['AWS_ACCESS_KEY_ID'],
31
+ secret_access_key: ENV['AWS_SECRET_ACCESS_KEY'],
32
+ region: ENV['AWS_REGION'],
33
+ }.merge(opts)
34
+
35
+ AWS.config(opts)
36
+
37
+ @ec2 = AWS::EC2.new
38
+ @region = @ec2.regions[opts[:region]]
39
+ end
40
+
41
+ def apply(definition)
42
+ if definition.env.nil?
43
+ Logger.log("Creating security groups in '#{@region.name}' region")
44
+ else
45
+ Logger.log("Creating security groups for '#{definition.env}' environment in '#{@region.name}' region")
46
+ end
47
+
48
+ # get the security group data from the AWS servers
49
+ fetch!
50
+
51
+ # ensure dependency groups exist
52
+ definition.groups.select { |id, group| group.dependency }.each do |id, group|
53
+ unless exists?(id)
54
+ raise GroupDoesNotExistError, "Dependent security group '#{id}' does not exist"
55
+ end
56
+ end
57
+
58
+ # find/create the groups
59
+ ec2_groups = {}
60
+ definition.groups.each do |id, group|
61
+ ec2_group = find(id)
62
+ if ec2_group.nil?
63
+ Logger.log(:create, id)
64
+ ec2_group = create(id, group.desc)
65
+ end
66
+ ec2_groups[id] = ec2_group
67
+ end
68
+
69
+ definition.groups.each do |id, group|
70
+ next if group.dependency
71
+ ec2_group = ec2_groups[id]
72
+
73
+ # revoke existing ingresses no longer desired
74
+ ec2_group.ingress_ip_permissions.each do |ec2_perm|
75
+ revoke_sources = []
76
+ ec2_perm.groups.each do |source|
77
+ unless group.include_ingress?(ec2_perm.protocol, ec2_perm.port_range, source.name)
78
+ revoke_sources << source
79
+ end
80
+ end
81
+ ec2_perm.ip_ranges.each do |source|
82
+ unless group.include_ingress?(ec2_perm.protocol, ec2_perm.port_range, source)
83
+ revoke_sources << source
84
+ end
85
+ end
86
+ unless revoke_sources.empty?
87
+ Logger.log("revoke #{ec2_perm.protocol}", "#{id} #{sources_list_to_s(revoke_sources)}")
88
+ ec2_group.revoke_ingress(ec2_perm.protocol, ec2_perm.port_range, *revoke_sources)
89
+ end
90
+ end
91
+
92
+ # add new ingresses
93
+ group.ingresses.each do |perm|
94
+ new_sources = []
95
+ perm.sources.each do |source|
96
+ if HolePunch.cidr?(source)
97
+ unless group_has_ingress(ec2_group, perm.type, perm.ports, source)
98
+ new_sources << source
99
+ end
100
+ else
101
+ ec2_source_group = ec2_groups[source]
102
+ if ec2_source_group.nil?
103
+ raise GroupDoesNotExistError, "unknown security group '#{source}"
104
+ end
105
+ unless group_has_ingress(ec2_group, perm.type, perm.ports, ec2_source_group)
106
+ new_sources << ec2_source_group
107
+ end
108
+ end
109
+ end
110
+ unless new_sources.empty?
111
+ Logger.log(perm.type, "#{id} #{sources_list_to_s(new_sources)}")
112
+ ec2_group.authorize_ingress(perm.type, perm.ports, *new_sources)
113
+ end
114
+ end
115
+ end
116
+ end
117
+
118
+
119
+ private
120
+ def fetch!
121
+ @groups = @region.security_groups.to_a
122
+ end
123
+
124
+ def create(name, description)
125
+ group = @region.security_groups.create(name, description: description)
126
+ @groups << group
127
+ group
128
+ end
129
+
130
+ def find(name)
131
+ @groups.detect { |group| group.name == name }
132
+ end
133
+
134
+ def exists?(name)
135
+ !find(name).nil?
136
+ end
137
+
138
+ def group_has_ingress(group, protocol, ports, source)
139
+ ports = ports..ports unless ports.is_a?(Range)
140
+ is_cidr = HolePunch.cidr?(source)
141
+ group.ingress_ip_permissions.any? do |perm|
142
+ if is_cidr
143
+ perm.protocol == protocol && perm.port_range == ports && perm.ip_ranges.include?(source)
144
+ else
145
+ perm.protocol == protocol && perm.port_range == ports && perm.groups.include?(source)
146
+ end
147
+ end
148
+ end
149
+
150
+ def sources_list_to_s(sources)
151
+ sources.map do |source|
152
+ if source.is_a?(AWS::EC2::SecurityGroup)
153
+ source.name
154
+ else
155
+ source.to_s
156
+ end.shellescape
157
+ end.sort.join(' ')
158
+ end
159
+ end
160
+ end
@@ -0,0 +1,54 @@
1
+ #
2
+ # Copyright (C) 2014 Undead Labs, LLC
3
+ #
4
+ # Permission is hereby granted, free of charge, to any person obtaining a copy of
5
+ # this software and associated documentation files (the "Software"), to deal in
6
+ # the Software without restriction, including without limitation the rights to
7
+ # use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
8
+ # the Software, and to permit persons to whom the Software is furnished to do so,
9
+ # subject to the following conditions:
10
+ #
11
+ # The above copyright notice and this permission notice shall be included in all
12
+ # copies or substantial portions of the Software.
13
+ #
14
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15
+ # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
16
+ # FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
17
+ # COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
18
+ # IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
19
+ # CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
20
+ #
21
+ module HolePunch
22
+ class LoggerOutputStdio
23
+ def log(msg)
24
+ $stdout.puts(msg)
25
+ end
26
+
27
+ def fatal(msg)
28
+ $stderr.puts(msg)
29
+ end
30
+ end
31
+
32
+ module Logger
33
+ class << self
34
+ attr_accessor :verbose
35
+ attr_accessor :output
36
+
37
+ def log(prefix, msg = nil)
38
+ return unless verbose
39
+ return if output.nil?
40
+ if msg.nil?
41
+ output.log prefix
42
+ else
43
+ output.log "#{prefix.to_s.rjust(12)} #{msg}"
44
+ end
45
+ end
46
+
47
+ def fatal(msg, exit_code = 1)
48
+ return if output.nil?
49
+ output.fatal("ERROR: #{msg}")
50
+ exit(exit_code)
51
+ end
52
+ end
53
+ end
54
+ end
@@ -0,0 +1,23 @@
1
+ #
2
+ # Copyright (C) 2014 Undead Labs, LLC
3
+ #
4
+ # Permission is hereby granted, free of charge, to any person obtaining a copy of
5
+ # this software and associated documentation files (the "Software"), to deal in
6
+ # the Software without restriction, including without limitation the rights to
7
+ # use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
8
+ # the Software, and to permit persons to whom the Software is furnished to do so,
9
+ # subject to the following conditions:
10
+ #
11
+ # The above copyright notice and this permission notice shall be included in all
12
+ # copies or substantial portions of the Software.
13
+ #
14
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15
+ # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
16
+ # FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
17
+ # COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
18
+ # IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
19
+ # CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
20
+ #
21
+ module HolePunch
22
+ VERSION = '1.0.0'
23
+ end
metadata ADDED
@@ -0,0 +1,223 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: holepunch
3
+ version: !ruby/object:Gem::Version
4
+ version: 1.0.0
5
+ platform: ruby
6
+ authors:
7
+ - Ben Scott
8
+ - Pat Wyatt
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2014-06-13 00:00:00.000000000 Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: aws-sdk
16
+ requirement: !ruby/object:Gem::Requirement
17
+ requirements:
18
+ - - "~>"
19
+ - !ruby/object:Gem::Version
20
+ version: '1.32'
21
+ type: :runtime
22
+ prerelease: false
23
+ version_requirements: !ruby/object:Gem::Requirement
24
+ requirements:
25
+ - - "~>"
26
+ - !ruby/object:Gem::Version
27
+ version: '1.32'
28
+ - !ruby/object:Gem::Dependency
29
+ name: rake
30
+ requirement: !ruby/object:Gem::Requirement
31
+ requirements:
32
+ - - ">="
33
+ - !ruby/object:Gem::Version
34
+ version: 0.8.7
35
+ type: :development
36
+ prerelease: false
37
+ version_requirements: !ruby/object:Gem::Requirement
38
+ requirements:
39
+ - - ">="
40
+ - !ruby/object:Gem::Version
41
+ version: 0.8.7
42
+ - !ruby/object:Gem::Dependency
43
+ name: rspec
44
+ requirement: !ruby/object:Gem::Requirement
45
+ requirements:
46
+ - - "~>"
47
+ - !ruby/object:Gem::Version
48
+ version: '3.0'
49
+ type: :development
50
+ prerelease: false
51
+ version_requirements: !ruby/object:Gem::Requirement
52
+ requirements:
53
+ - - "~>"
54
+ - !ruby/object:Gem::Version
55
+ version: '3.0'
56
+ description: |
57
+ # holepunch
58
+ [![Build Status](https://travis-ci.org/undeadlabs/holepunch.svg?branch=master)](https://travis-ci.org/undeadlabs/holepunch)
59
+
60
+ Holepunch manages AWS EC2 security groups in a declarative way through a DSL.
61
+
62
+ ## Requirements
63
+
64
+ - Ruby 1.9.3 or newer.
65
+
66
+ ## Installation
67
+
68
+ ```bash
69
+ gem install holepunch
70
+ ```
71
+
72
+ or in your Gemfile
73
+
74
+ ```ruby
75
+ gem 'holepunch'
76
+ ```
77
+
78
+ ## Basic Configuration
79
+
80
+ You need to provide your AWS security credentials and a region. These can be
81
+ provided as the command-line options, or you can use the standard AWS
82
+ environment variables:
83
+
84
+ ```bash
85
+ export AWS_ACCESS_KEY_ID='...'
86
+ export AWS_SECRET_ACCESS_KEY='...'
87
+ export AWS_REGION='us-west-2'
88
+ ```
89
+
90
+ ## The SecurityGroups file
91
+
92
+ Specify your security groups in a `SecurityGroups` file in your project's root.
93
+ Declare security groups that you need and the ingresses you want to expose. You
94
+ can add ingresses using `tcp`, `udp`, and `ping`. For each ingress you can list
95
+ allowed hosts using group names or CIDR notation.
96
+
97
+ ```ruby
98
+ group 'web' do
99
+ desc 'Web servers'
100
+
101
+ tcp 80
102
+ end
103
+
104
+ group 'db' do
105
+ desc 'database servers'
106
+
107
+ tcp 5432, 'web'
108
+ end
109
+
110
+ group 'log' do
111
+ desc 'log server'
112
+
113
+ tcp 9999, 'web', 'db', '10.1.0.0/16'
114
+ end
115
+ ```
116
+
117
+ An environment can be specified which is available through the `env` variable.
118
+ This allows you to have custom security groups per server environment.
119
+
120
+ ```ruby
121
+ group "web-#{env}"
122
+ group "db-#{env}" do
123
+ tcp 5432, "web-#{env}"
124
+ end
125
+ ```
126
+
127
+ Your application may depend on security groups defined by other services. Ensure
128
+ they exist using the `depends` method.
129
+
130
+ ```ruby
131
+ depends 'my-other-service'
132
+ group 'my-service' do
133
+ udp 9999, 'my-other-service'
134
+ end
135
+ ```
136
+
137
+ You may specify port ranges for `tcp` and `udp` using the range operator.
138
+
139
+ ```ruby
140
+ group 'my-service' do
141
+ udp 5000..9999, '0.0.0.0/0'
142
+ end
143
+ ```
144
+
145
+ ## Usage
146
+
147
+ Simply navigate to the directory containing your `SecurityGroups` file and run `holepunch`.
148
+
149
+ ```
150
+ $ holepunch
151
+ ```
152
+
153
+ If you need to specify an environment:
154
+
155
+ ```
156
+ $ holepunch -e live
157
+ ```
158
+
159
+ ## Testing
160
+
161
+ You can run the unit tests by simply running rspec.
162
+
163
+ ```
164
+ $ rspec
165
+ ```
166
+
167
+ By default the integration tests with EC2 are not run. You may run them with:
168
+
169
+ ```
170
+ $ rspec -t integration
171
+ ```
172
+
173
+ ## Authors
174
+
175
+ - Ben Scott (gamepoet@gmail.com)
176
+ - Pat Wyatt (pat@codeofhonor.com)
177
+
178
+ ## License
179
+
180
+ Copyright 2014 Undead Labs, LLC.
181
+
182
+ Licensed under the MIT License: http://opensource.org/licenses/MIT
183
+ email:
184
+ - gamepoet@gmail.com
185
+ - pat@codeofhonor.com
186
+ executables:
187
+ - holepunch
188
+ extensions: []
189
+ extra_rdoc_files: []
190
+ files:
191
+ - bin/holepunch
192
+ - lib/holepunch.rb
193
+ - lib/holepunch/cli.rb
194
+ - lib/holepunch/definition.rb
195
+ - lib/holepunch/dsl.rb
196
+ - lib/holepunch/ec2.rb
197
+ - lib/holepunch/logger.rb
198
+ - lib/holepunch/version.rb
199
+ homepage: https://github.com/undeadlabs/holepunch
200
+ licenses:
201
+ - MIT
202
+ metadata: {}
203
+ post_install_message:
204
+ rdoc_options: []
205
+ require_paths:
206
+ - lib
207
+ required_ruby_version: !ruby/object:Gem::Requirement
208
+ requirements:
209
+ - - ">="
210
+ - !ruby/object:Gem::Version
211
+ version: 1.9.3
212
+ required_rubygems_version: !ruby/object:Gem::Requirement
213
+ requirements:
214
+ - - ">="
215
+ - !ruby/object:Gem::Version
216
+ version: '0'
217
+ requirements: []
218
+ rubyforge_project:
219
+ rubygems_version: 2.2.2
220
+ signing_key:
221
+ specification_version: 4
222
+ summary: Manage AWS security groups in a declarative way
223
+ test_files: []