holepunch 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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: []