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.
- checksums.yaml +7 -0
- data/bin/holepunch +26 -0
- data/lib/holepunch.rb +48 -0
- data/lib/holepunch/cli.rb +109 -0
- data/lib/holepunch/definition.rb +98 -0
- data/lib/holepunch/dsl.rb +99 -0
- data/lib/holepunch/ec2.rb +160 -0
- data/lib/holepunch/logger.rb +54 -0
- data/lib/holepunch/version.rb +23 -0
- metadata +223 -0
checksums.yaml
ADDED
@@ -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
|
data/bin/holepunch
ADDED
@@ -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)
|
data/lib/holepunch.rb
ADDED
@@ -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
|
+
[](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: []
|