reyes 0.0.6 → 0.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +8 -8
- data/bin/reyes +65 -30
- data/lib/reyes/aws_manager.rb +135 -0
- data/lib/reyes/fake_aws.rb +71 -0
- data/lib/reyes/group_manager.rb +55 -41
- data/lib/reyes/iptables.rb +17 -2
- data/lib/reyes/run_generation.rb +16 -1
- data/lib/reyes/run_manager.rb +52 -16
- data/lib/reyes/version.rb +1 -1
- data/lib/reyes.rb +2 -1
- metadata +3 -2
checksums.yaml
CHANGED
@@ -1,15 +1,15 @@
|
|
1
1
|
---
|
2
2
|
!binary "U0hBMQ==":
|
3
3
|
metadata.gz: !binary |-
|
4
|
-
|
4
|
+
NGE0MzE5OTBmNzQ2YWI0YmMxYmJlZGMxMDgxMGEwNDJiOTgzNTg0Mg==
|
5
5
|
data.tar.gz: !binary |-
|
6
|
-
|
6
|
+
ZTY4MWFiMmNmMzg5MzEwNDBlYTc4MWUwZGVmYWZhMWYyOGMyZjI3YQ==
|
7
7
|
SHA512:
|
8
8
|
metadata.gz: !binary |-
|
9
|
-
|
10
|
-
|
11
|
-
|
9
|
+
ZTczYjRhNGIzZjZjMDViZjRlZDg4ODc2ZTNhYTQ5YjA4MjBiMDFmMjY5NWIw
|
10
|
+
NDE5M2ZmYzZkNDgzMDlmNjVlZTcwYjIyMGI2ODdiNzQ0MDg0ZWJmN2M3Y2Qy
|
11
|
+
MWJhYzljYTM5YzljNDYyMDI2MmQyYTZjODExZDUwZWM1YTFjNjg=
|
12
12
|
data.tar.gz: !binary |-
|
13
|
-
|
14
|
-
|
15
|
-
|
13
|
+
OWRhNjJjMWI4YTFjN2UwYmNkNWQxZjllZTRkMDM5ZGM5MWM0YzA0MzQyMDVm
|
14
|
+
ZDMzZDE2ZTc0ZjE2NzEyOWM1NzhmZjYyNzlmNmYwZWZkMzYzZWMyMjE4OWM5
|
15
|
+
M2MzM2FiYTRiMDZmZjUwZmQyYjk4MjQyOGFkOThjZGVmYjYwZTk=
|
data/bin/reyes
CHANGED
@@ -2,47 +2,64 @@
|
|
2
2
|
require 'optparse'
|
3
3
|
require_relative '../lib/reyes'
|
4
4
|
|
5
|
-
def
|
6
|
-
|
5
|
+
def command_dump(output_file, options)
|
6
|
+
aws = Reyes::AwsManager.new(options[:config])
|
7
|
+
aws.dump_fake_data(output_file)
|
8
|
+
end
|
9
|
+
|
10
|
+
def command_install(json_file, instance_id, options)
|
7
11
|
region = options.fetch(:region)
|
8
12
|
|
9
13
|
if options[:splay]
|
10
14
|
Reyes::Utils.sleep_random(options[:splay])
|
11
15
|
end
|
12
16
|
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
r = Reyes::RunManager.new(g)
|
17
|
+
fake = Reyes::FakeAws.new(File.open(json_file, 'r'))
|
18
|
+
g = Reyes::GroupManager.new(fake, region, instance_id)
|
19
|
+
r = Reyes::RunManager.new(g)
|
17
20
|
|
18
|
-
|
21
|
+
data = r.generate_data!(options.fetch(:gen_options))
|
22
|
+
r.apply_data!(data, options.fetch(:apply_options))
|
19
23
|
|
20
|
-
|
21
|
-
|
22
|
-
if options[:prune]
|
23
|
-
r.prune_ipsets
|
24
|
-
# Pruning IPTables rules doesn't make any sense, they are atomically replaced
|
25
|
-
end
|
24
|
+
if options[:prune]
|
25
|
+
r.prune_ipsets
|
26
26
|
end
|
27
|
-
|
28
27
|
end
|
29
28
|
|
30
|
-
|
31
29
|
def parse_args
|
32
30
|
options = {
|
33
|
-
:region => 'us-west-1',
|
34
|
-
:
|
31
|
+
:region => 'us-west-1', # TODO: make required
|
32
|
+
:gen_options => {},
|
33
|
+
:apply_options => {
|
34
|
+
:log_accept => true, # TODO remove this default
|
35
|
+
},
|
35
36
|
}
|
36
37
|
|
37
38
|
optparse = OptionParser.new do |opts|
|
38
39
|
opts.banner = <<-EOM
|
39
|
-
usage: #{File.basename($0)} [options]
|
40
|
+
usage: #{File.basename($0)} [options] COMMAND ARGS...
|
40
41
|
|
41
42
|
Manipulate IPTables rules based on EC2 security groups.
|
42
43
|
|
44
|
+
Commands:
|
45
|
+
|
46
|
+
install: load AWS data from JSON_FILE and install rules for INSTANCE_ID
|
47
|
+
|
48
|
+
#{File.basename($0)} [options] install JSON_FILE INSTANCE_ID
|
49
|
+
|
50
|
+
dump: generate JSON from AWS and serialize data to JSON_FILE
|
51
|
+
|
52
|
+
#{File.basename($0)} [options] dump JSON_FILE
|
53
|
+
|
54
|
+
|
55
|
+
Defaults:
|
56
|
+
region: #{options.fetch(:region)}
|
57
|
+
|
58
|
+
|
43
59
|
For example:
|
44
60
|
|
45
|
-
#{File.basename($0)} --prune
|
61
|
+
#{File.basename($0)} --prune install data.json $(facter -p ec2_instance_id)
|
62
|
+
|
46
63
|
|
47
64
|
Options:
|
48
65
|
EOM
|
@@ -55,32 +72,39 @@ Options:
|
|
55
72
|
options[:region] = region
|
56
73
|
end
|
57
74
|
|
58
|
-
opts.on('--
|
59
|
-
options[:
|
75
|
+
opts.on('--env ENV', 'Set SRFC4 env for dump') do |env|
|
76
|
+
options[:tag_filters] = {'env' => env}
|
60
77
|
end
|
61
78
|
|
62
|
-
opts.on('--
|
63
|
-
options[:
|
64
|
-
options[:instance_id] = id
|
79
|
+
opts.on('--prune', 'Prune old generations after creating rules') do
|
80
|
+
options[:prune] = true
|
65
81
|
end
|
66
82
|
|
67
83
|
# TODO: known bug: --dry-run does not prevent run generation increment
|
68
84
|
opts.on('-n', '--dry-run', 'Print diff without making changes') do
|
69
|
-
options[:
|
85
|
+
options[:apply_options][:dry_run] = true
|
70
86
|
end
|
71
87
|
|
72
88
|
opts.on('-i', '--interactive', 'Interactively confirm changes') do
|
73
|
-
options[:
|
89
|
+
options[:apply_options][:interactive] = true
|
74
90
|
end
|
75
91
|
|
76
92
|
opts.on('--empty', 'Generate empty (default DROP) rules') do
|
77
|
-
options[:
|
93
|
+
options[:gen_options][:empty] = true
|
78
94
|
end
|
79
95
|
|
80
96
|
opts.on('--splay SECS', 'Delay up to SECS before execution') do |arg|
|
81
97
|
options[:splay] = Integer(arg)
|
82
98
|
end
|
83
99
|
|
100
|
+
opts.on('--[no-]log-accept', 'Log packets in ACCEPT') do |arg|
|
101
|
+
options[:apply_options][:log_accept] = arg
|
102
|
+
end
|
103
|
+
|
104
|
+
opts.on('--[no-]log-drop', 'Log packets in DROP') do |arg|
|
105
|
+
options[:apply_options][:log_drop] = arg
|
106
|
+
end
|
107
|
+
|
84
108
|
opts.on('-h', '--help', 'Display this help message') do
|
85
109
|
STDERR.puts opts
|
86
110
|
exit 0
|
@@ -89,9 +113,20 @@ Options:
|
|
89
113
|
|
90
114
|
optparse.parse!
|
91
115
|
|
92
|
-
|
93
|
-
|
94
|
-
|
116
|
+
command = ARGV.shift
|
117
|
+
case command
|
118
|
+
when 'dump'
|
119
|
+
unless ARGV.length == 1
|
120
|
+
STDERR.puts optparse
|
121
|
+
exit 2
|
122
|
+
end
|
123
|
+
command_dump(ARGV.fetch(0), options)
|
124
|
+
when 'install'
|
125
|
+
unless ARGV.length == 2
|
126
|
+
STDERR.puts optparse
|
127
|
+
exit 2
|
128
|
+
end
|
129
|
+
command_install(ARGV.fetch(0), ARGV.fetch(1), options)
|
95
130
|
else
|
96
131
|
STDERR.puts optparse
|
97
132
|
STDERR.puts "\nError: Must provide a command"
|
data/lib/reyes/aws_manager.rb
CHANGED
@@ -1,4 +1,6 @@
|
|
1
1
|
require 'aws-sdk'
|
2
|
+
require 'json'
|
3
|
+
require 'socket'
|
2
4
|
|
3
5
|
module Reyes
|
4
6
|
class AwsManager
|
@@ -105,8 +107,141 @@ module Reyes
|
|
105
107
|
end
|
106
108
|
end
|
107
109
|
|
110
|
+
def generate_fake_data
|
111
|
+
log.info('Generating AWS data for serialization')
|
112
|
+
start = Time.now.utc
|
113
|
+
data = {
|
114
|
+
'metadata' => {
|
115
|
+
'generated' => start,
|
116
|
+
'generated_stamp' => start.to_i,
|
117
|
+
'hostname' => Socket.gethostname,
|
118
|
+
'pid' => Process.pid,
|
119
|
+
},
|
120
|
+
'vpcs' => vpcs.map(&:id),
|
121
|
+
'security_groups_by_name' => {},
|
122
|
+
'regions' => {},
|
123
|
+
}
|
124
|
+
|
125
|
+
regions.each do |region|
|
126
|
+
data['regions'][region] = fake_data_for_region(region)
|
127
|
+
end
|
128
|
+
|
129
|
+
# populate security_groups_by_name
|
130
|
+
data['regions'].each_pair do |region, rdata|
|
131
|
+
rdata.fetch('security_groups').each_pair do |sg_id, sg_data|
|
132
|
+
data['security_groups_by_name'][sg_data.fetch('name')] ||= []
|
133
|
+
data['security_groups_by_name'][sg_data.fetch('name')] << {
|
134
|
+
'region' => region,
|
135
|
+
'group_id' => sg_id,
|
136
|
+
'vpc' => sg_data.fetch('vpc_id'),
|
137
|
+
}
|
138
|
+
end
|
139
|
+
end
|
140
|
+
|
141
|
+
data['metadata']['generated_in'] = Time.now - start
|
142
|
+
|
143
|
+
log.info("Generated AWS data in #{(Time.now - start).round(1)}s")
|
144
|
+
|
145
|
+
data
|
146
|
+
end
|
147
|
+
|
148
|
+
def dump_fake_data(filename)
|
149
|
+
log.info("Dumping AWS data to #{filename.inspect}")
|
150
|
+
data = generate_fake_data
|
151
|
+
|
152
|
+
File.open(filename, File::WRONLY | File::CREAT | File::EXCL) do |fh|
|
153
|
+
fh.write(JSON.pretty_generate(data))
|
154
|
+
end
|
155
|
+
|
156
|
+
log.info('Wrote data to file')
|
157
|
+
end
|
158
|
+
|
108
159
|
private
|
109
160
|
|
161
|
+
def fake_data_for_region(region)
|
162
|
+
log.info("Generating data for #{region}")
|
163
|
+
|
164
|
+
data = {
|
165
|
+
'instances' => {},
|
166
|
+
'security_groups' => {},
|
167
|
+
}
|
168
|
+
|
169
|
+
AWS.memoize do
|
170
|
+
conn = ec2(region)
|
171
|
+
|
172
|
+
sg_members = {}
|
173
|
+
|
174
|
+
conn.instances.sort_by(&:instance_id).each do |i|
|
175
|
+
groups = i.security_groups.to_a.sort_by(&:name)
|
176
|
+
|
177
|
+
data['instances'][i.instance_id] = {
|
178
|
+
'tags' => i.tags.to_h.to_h,
|
179
|
+
'region' => region,
|
180
|
+
'availability_zone' => i.availability_zone,
|
181
|
+
'private_ip_address' => i.private_ip_address,
|
182
|
+
'public_ip_address' => i.public_ip_address,
|
183
|
+
'security_groups' => groups.map(&:group_id),
|
184
|
+
'security_group_names' => groups.map(&:name),
|
185
|
+
}
|
186
|
+
|
187
|
+
groups.map(&:id).each do |sg_id|
|
188
|
+
sg_members[sg_id] ||= []
|
189
|
+
sg_members[sg_id] << i.instance_id
|
190
|
+
end
|
191
|
+
end
|
192
|
+
|
193
|
+
conn.security_groups.sort_by(&:group_id).each do |g|
|
194
|
+
data['security_groups'][g.group_id] = {
|
195
|
+
'name' => g.name,
|
196
|
+
'description' => g.description,
|
197
|
+
'vpc_id' => g.vpc_id,
|
198
|
+
'region' => region,
|
199
|
+
'ipset_suffix' => ipset_suffix_for_group(region, g),
|
200
|
+
'ingress_ip_permissions' => g.ingress_ip_permissions.map {|perm|
|
201
|
+
|
202
|
+
if perm.port_range
|
203
|
+
port_start = perm.port_range.first
|
204
|
+
port_end = perm.port_range.last
|
205
|
+
else
|
206
|
+
port_start = nil
|
207
|
+
port_end = nil
|
208
|
+
if perm.protocol != :any
|
209
|
+
log.error("Invalid looking IpPermission object")
|
210
|
+
log.error(GroupTools.pretty_perm(perm))
|
211
|
+
raise Error.new("Missing port_range for perm")
|
212
|
+
end
|
213
|
+
end
|
214
|
+
|
215
|
+
{
|
216
|
+
'protocol' => perm.protocol,
|
217
|
+
'port_start' => port_start,
|
218
|
+
'port_end' => port_end,
|
219
|
+
'ip_ranges' => perm.ip_ranges.sort,
|
220
|
+
'group_names' => perm.groups.map(&:name),
|
221
|
+
}
|
222
|
+
},
|
223
|
+
# avoid g.instances to prevent O(n) behavior
|
224
|
+
'instances' => sg_members.fetch(g.group_id, []).sort,
|
225
|
+
}
|
226
|
+
end
|
227
|
+
end
|
228
|
+
|
229
|
+
data
|
230
|
+
end
|
231
|
+
|
232
|
+
|
233
|
+
# @param group [AWS::EC2::SecurityGroup]
|
234
|
+
#
|
235
|
+
# @return [String] A string title, at most 31 characters long
|
236
|
+
#
|
237
|
+
def ipset_suffix_for_group(region, group)
|
238
|
+
[
|
239
|
+
Reyes::GroupManager::RegionShortNames.fetch(region),
|
240
|
+
group.group_id,
|
241
|
+
group.name,
|
242
|
+
].join(':')[0...31]
|
243
|
+
end
|
244
|
+
|
110
245
|
def aws_config
|
111
246
|
@config.aws_config
|
112
247
|
end
|
@@ -0,0 +1,71 @@
|
|
1
|
+
require 'json'
|
2
|
+
require 'set'
|
3
|
+
|
4
|
+
module Reyes
|
5
|
+
class FakeAws
|
6
|
+
include Chalk::Log
|
7
|
+
|
8
|
+
# @param json_source
|
9
|
+
def initialize(json_source)
|
10
|
+
log.info('Loading JSON data')
|
11
|
+
@data = JSON.load(json_source)
|
12
|
+
log.info("Loaded JSON with metadata: #{metadata.inspect}")
|
13
|
+
end
|
14
|
+
|
15
|
+
def region_data(region)
|
16
|
+
@data.fetch('regions').fetch(region)
|
17
|
+
end
|
18
|
+
|
19
|
+
def instance(region, instance_id)
|
20
|
+
region_data(region).fetch('instances').fetch(instance_id)
|
21
|
+
end
|
22
|
+
|
23
|
+
def security_group(region, security_group_id)
|
24
|
+
region_data(region).fetch('security_groups').fetch(security_group_id)
|
25
|
+
end
|
26
|
+
|
27
|
+
def security_group_ids_for_instance(region, instance_id)
|
28
|
+
instance(region, instance_id).fetch('security_groups')
|
29
|
+
end
|
30
|
+
|
31
|
+
def security_groups_for_instance(region, instance_id)
|
32
|
+
data = {}
|
33
|
+
|
34
|
+
security_group_ids_for_instance(region, instance_id).each do |sg_id|
|
35
|
+
data[sg_id] = security_group(region, sg_id)
|
36
|
+
end
|
37
|
+
|
38
|
+
data
|
39
|
+
end
|
40
|
+
|
41
|
+
def addresses_for_security_group(region, security_group_id)
|
42
|
+
security_group_members(region, security_group_id).map do |instance_id|
|
43
|
+
instance(region, instance_id).fetch('private_ip_address')
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
def security_group_members(region, security_group_id)
|
48
|
+
security_group(region, security_group_id).fetch('instances')
|
49
|
+
end
|
50
|
+
|
51
|
+
def foreign_groups_by_name(group_name)
|
52
|
+
vpc_set = vpc_ids.to_set
|
53
|
+
groups = {}
|
54
|
+
@data.fetch('security_groups_by_name').fetch(group_name).each do |g|
|
55
|
+
next unless vpc_set.include?(g.fetch('vpc'))
|
56
|
+
groups[g.fetch('group_id')] = security_group(g.fetch('region'),
|
57
|
+
g.fetch('group_id'))
|
58
|
+
end
|
59
|
+
|
60
|
+
groups
|
61
|
+
end
|
62
|
+
|
63
|
+
def vpc_ids
|
64
|
+
@data.fetch('vpcs')
|
65
|
+
end
|
66
|
+
|
67
|
+
def metadata
|
68
|
+
@data.fetch('metadata')
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
data/lib/reyes/group_manager.rb
CHANGED
@@ -21,26 +21,31 @@ module Reyes
|
|
21
21
|
|
22
22
|
ReyesInputChain = 'reyes-ipsec-input'
|
23
23
|
|
24
|
-
attr_reader :
|
24
|
+
attr_reader :fake_aws, :instance_id
|
25
25
|
|
26
|
-
|
26
|
+
# @param fake_aws [Reyes::FakeAws]
|
27
|
+
# @param region [String]
|
28
|
+
# @param instance_id [String] A String instance ID of the target instance.
|
29
|
+
#
|
30
|
+
def initialize(fake_aws, region, instance_id, generation=nil)
|
27
31
|
log.info("Initializing #{self.class.name} for #{region} #{instance_id}")
|
28
|
-
log.info("This is reyes #{Reyes::VERSION}")
|
29
32
|
|
30
|
-
@
|
33
|
+
@fake_aws = fake_aws
|
31
34
|
@generation = generation || RunGeneration.new
|
35
|
+
@region = region
|
32
36
|
@instance_id = instance_id
|
33
|
-
@instance = @aws.ec2(region).instances[instance_id]
|
34
37
|
end
|
35
38
|
|
39
|
+
# @return [Hash]
|
36
40
|
def our_groups
|
37
|
-
@
|
38
|
-
@instance.security_groups
|
39
|
-
}
|
41
|
+
fake_aws.security_groups_for_instance(@region, @instance_id)
|
40
42
|
end
|
41
43
|
|
42
44
|
def generate_rules_empty
|
43
|
-
{
|
45
|
+
{
|
46
|
+
:groups => {},
|
47
|
+
:ipsets => {},
|
48
|
+
}
|
44
49
|
end
|
45
50
|
|
46
51
|
# Given our instance ID and security group rules, generate IPTables rules
|
@@ -53,52 +58,48 @@ module Reyes
|
|
53
58
|
# Also create a list of IPTables rules that will reference these ipsets.
|
54
59
|
#
|
55
60
|
# This method generates the data and returns it as a hash without making
|
56
|
-
# any changes to the system
|
57
|
-
# for ipset garbage collection).
|
61
|
+
# any changes to the system.
|
58
62
|
#
|
59
|
-
def generate_rules
|
60
|
-
run_generation_increment!
|
63
|
+
def generate_rules
|
61
64
|
log.info("Generating rules for generation #{run_generation}")
|
62
65
|
|
63
|
-
@aws.warm_sg_cache
|
64
|
-
|
65
66
|
data = generate_rules_empty
|
66
67
|
|
67
68
|
needed_groups = {}
|
68
69
|
|
69
|
-
our_groups.
|
70
|
-
group_key = "#{
|
70
|
+
our_groups.each_pair do |group_id, group|
|
71
|
+
group_key = "#{group_id}:#{group.fetch('name')}"
|
71
72
|
data[:groups][group_key] = []
|
72
73
|
|
73
74
|
# we only work with ingress permissions
|
74
|
-
group.ingress_ip_permissions.each do |perm|
|
75
|
+
group.fetch('ingress_ip_permissions').each do |perm|
|
75
76
|
perm_hash = {
|
76
|
-
protocol: perm.protocol,
|
77
|
-
port: perm.
|
77
|
+
protocol: perm.fetch('protocol'),
|
78
|
+
port: [perm.fetch('port_start'), perm.fetch('port_end')],
|
78
79
|
remote_addrs: [],
|
79
80
|
remote_sets: [],
|
80
81
|
}
|
81
82
|
|
82
|
-
case perm.protocol
|
83
|
-
when
|
83
|
+
case perm.fetch('protocol').to_s
|
84
|
+
when 'icmp'
|
84
85
|
log.info("Skipping ICMP rule")
|
85
86
|
next
|
86
|
-
when
|
87
|
+
when 'tcp', 'udp'
|
87
88
|
|
88
89
|
# append IP ranges
|
89
|
-
perm_hash[:remote_addrs] += perm.ip_ranges
|
90
|
+
perm_hash[:remote_addrs] += perm.fetch('ip_ranges')
|
90
91
|
|
91
92
|
# list the security groups we'll need to look up
|
92
|
-
perm.
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
perm_hash[:remote_sets] << ipset_name_for_group(fg)
|
93
|
+
perm.fetch('group_names').each do |g_name|
|
94
|
+
foreign_groups_by_name(g_name).each_pair do |fg_id, fg_data|
|
95
|
+
needed_groups[fg_id] = fg_data
|
96
|
+
perm_hash[:remote_sets] << ipset_name_for_group_hash(fg_data)
|
97
97
|
end
|
98
98
|
end
|
99
99
|
|
100
100
|
else
|
101
|
-
raise
|
101
|
+
raise NotImplementedError.new(
|
102
|
+
"Unexpected protocol #{perm['protocol'].inspect} in #{group_id}")
|
102
103
|
end
|
103
104
|
|
104
105
|
data[:groups][group_key] << perm_hash
|
@@ -106,9 +107,9 @@ module Reyes
|
|
106
107
|
end
|
107
108
|
|
108
109
|
data[:ipsets] = {}
|
109
|
-
needed_groups.
|
110
|
-
data[:ipsets][
|
111
|
-
addresses_for_group(
|
110
|
+
needed_groups.each_pair do |group_id, group_data|
|
111
|
+
data[:ipsets][ipset_name_for_group_hash(group_data)] = \
|
112
|
+
addresses_for_group(group_data.fetch('region'), group_id)
|
112
113
|
end
|
113
114
|
|
114
115
|
data
|
@@ -151,7 +152,11 @@ module Reyes
|
|
151
152
|
@generation.increment!
|
152
153
|
end
|
153
154
|
|
155
|
+
# TODO: delurk
|
156
|
+
# @param group [AWS::EC2::SecurityGroup]
|
157
|
+
#
|
154
158
|
# @return [String] A string title, at most 31 characters long
|
159
|
+
#
|
155
160
|
def ipset_name_for_group(group)
|
156
161
|
[
|
157
162
|
run_generation.to_s,
|
@@ -161,6 +166,17 @@ module Reyes
|
|
161
166
|
].join(':')[0...31]
|
162
167
|
end
|
163
168
|
|
169
|
+
# @param group_hash [Hash]
|
170
|
+
#
|
171
|
+
# @return [String] A string title, at most 31 characters long
|
172
|
+
#
|
173
|
+
def ipset_name_for_group_hash(group_hash)
|
174
|
+
[
|
175
|
+
run_generation.to_s,
|
176
|
+
group_hash.fetch('ipset_suffix'),
|
177
|
+
].join(':')[0...31]
|
178
|
+
end
|
179
|
+
|
164
180
|
# @return [Hash] the parsed names, or nil in case of error
|
165
181
|
def self.parse_ipset_name(name)
|
166
182
|
parts = name.split(":")
|
@@ -177,25 +193,23 @@ module Reyes
|
|
177
193
|
|
178
194
|
# Look up addresses for given group in remote VPCs.
|
179
195
|
#
|
180
|
-
# @param
|
196
|
+
# @param region [String]
|
197
|
+
# @param group_id [String]
|
181
198
|
#
|
182
199
|
# @return [Array<String>] A list of private instance IP addresses
|
183
200
|
#
|
184
|
-
def addresses_for_group(
|
185
|
-
|
186
|
-
groups.map { |g|
|
187
|
-
@aws.instances_in_security_group(g).map(&:private_ip_address)
|
188
|
-
}.flatten
|
201
|
+
def addresses_for_group(region, group_id)
|
202
|
+
fake_aws.addresses_for_security_group(region, group_id)
|
189
203
|
end
|
190
204
|
|
191
205
|
# Look up remote VPC security groups by name.
|
192
206
|
#
|
193
207
|
# @param name [String]
|
194
208
|
#
|
195
|
-
# @return [
|
209
|
+
# @return [Hash]
|
196
210
|
#
|
197
211
|
def foreign_groups_by_name(name)
|
198
|
-
|
212
|
+
fake_aws.foreign_groups_by_name(name)
|
199
213
|
end
|
200
214
|
|
201
215
|
# @param [Hash] data
|
data/lib/reyes/iptables.rb
CHANGED
@@ -6,7 +6,7 @@ module Reyes
|
|
6
6
|
# series of arguments appropriate for passing to IPTables.
|
7
7
|
#
|
8
8
|
# @param [Symbol] protocol
|
9
|
-
# @param [Range] dport_range
|
9
|
+
# @param [Range, Array(Integer, Integer)] dport_range
|
10
10
|
# @param [Array<String>] remote_addrs
|
11
11
|
# @param [Array<String>] remote_sets
|
12
12
|
# @param [String] input_chain
|
@@ -16,7 +16,10 @@ module Reyes
|
|
16
16
|
#
|
17
17
|
def self.generate_rules(protocol, dport_range, remote_addrs, remote_sets,
|
18
18
|
input_chain, accept_chain)
|
19
|
-
|
19
|
+
|
20
|
+
unless ['tcp', 'udp'].include?(protocol.to_s)
|
21
|
+
raise ArgumentError.new("Unsupported protocol #{protocol.inspect}")
|
22
|
+
end
|
20
23
|
|
21
24
|
unless block_given?
|
22
25
|
return enum_for(__method__, protocol, dport_range, remote_addrs,
|
@@ -25,6 +28,18 @@ module Reyes
|
|
25
28
|
|
26
29
|
cmd = ['-A', input_chain, '-p', protocol.to_s]
|
27
30
|
|
31
|
+
# dport_range may be a Range or two element Array
|
32
|
+
case dport_range
|
33
|
+
when Range
|
34
|
+
# OK
|
35
|
+
when Array
|
36
|
+
if dport_range.length != 2
|
37
|
+
raise ArgumentError.new("bad dport_range: #{dport_range.inspect}")
|
38
|
+
end
|
39
|
+
else
|
40
|
+
raise ArgumentError.new("invalid dport_range: #{dport_range.inspect}")
|
41
|
+
end
|
42
|
+
|
28
43
|
if dport_range.first == dport_range.last
|
29
44
|
# single port match
|
30
45
|
cmd += ['--dport', dport_range.first.to_s]
|
data/lib/reyes/run_generation.rb
CHANGED
@@ -31,6 +31,20 @@ module Reyes
|
|
31
31
|
@value
|
32
32
|
end
|
33
33
|
|
34
|
+
# Set the generation value and save it to disk.
|
35
|
+
#
|
36
|
+
# @param value [Integer]
|
37
|
+
#
|
38
|
+
def set!(value)
|
39
|
+
unless value.is_a?(Integer)
|
40
|
+
raise ArgumentError.new("Not an integer: #{value.inspect}")
|
41
|
+
end
|
42
|
+
@value = value
|
43
|
+
log.debug("Setting generation to #{@value}")
|
44
|
+
save(true)
|
45
|
+
@value
|
46
|
+
end
|
47
|
+
|
34
48
|
private
|
35
49
|
|
36
50
|
def reload
|
@@ -66,8 +80,9 @@ module Reyes
|
|
66
80
|
end
|
67
81
|
end
|
68
82
|
|
69
|
-
def save
|
83
|
+
def save(truncate=false)
|
70
84
|
@fh.rewind
|
85
|
+
@fh.truncate(0) if truncate
|
71
86
|
@fh.puts(@value.to_s)
|
72
87
|
@fh.flush
|
73
88
|
end
|
data/lib/reyes/run_manager.rb
CHANGED
@@ -8,34 +8,62 @@ module Reyes
|
|
8
8
|
|
9
9
|
# @param [Hash] options
|
10
10
|
#
|
11
|
-
# @option options :dry_run [Boolean] (false) Don't actually apply changes
|
12
11
|
# @option options :empty [Boolean] (false) Generate an empty (default DROP)
|
13
12
|
# rule sets without actually looking up security groups
|
14
|
-
# @option options :interactive [Boolean] (false) Whether to prompt for
|
15
|
-
# confirmation before applying rules
|
16
|
-
# @option options :log_accept [Boolean] (false) Whether to log packets on
|
17
|
-
# ACCEPT
|
18
13
|
#
|
19
|
-
def
|
14
|
+
def generate_data!(options={})
|
20
15
|
options = {
|
21
|
-
dry_run: false,
|
22
16
|
empty: false,
|
23
|
-
|
24
|
-
log_accept: false,
|
17
|
+
increment_generation: true,
|
25
18
|
}.merge(options)
|
26
19
|
|
27
|
-
|
28
|
-
|
29
|
-
log.info("Starting iptables rule generation run!")
|
20
|
+
log.info("RunManager.generate_data!")
|
30
21
|
|
31
22
|
if options.fetch(:empty)
|
32
23
|
log.warn("Generating empty (default DROP) rule set")
|
33
24
|
data = @group_manager.generate_rules_empty
|
34
25
|
else
|
35
|
-
|
26
|
+
if options.fetch(:increment_generation)
|
27
|
+
@group_manager.run_generation_increment!
|
28
|
+
end
|
29
|
+
data = @group_manager.generate_rules
|
36
30
|
end
|
37
31
|
|
38
|
-
|
32
|
+
log.info("Finished loading/generating data")
|
33
|
+
|
34
|
+
data
|
35
|
+
end
|
36
|
+
|
37
|
+
# @param [Hash] options
|
38
|
+
#
|
39
|
+
# @option options :dry_run [Boolean] (false) Don't actually apply changes
|
40
|
+
# @option options :interactive [Boolean] (false) Whether to prompt for
|
41
|
+
# confirmation before applying rules
|
42
|
+
# @option options :log_accept [Boolean] Whether to log packets on
|
43
|
+
# ACCEPT
|
44
|
+
# @option options :log_drop [Boolean] Whether to log packets on
|
45
|
+
# DROP
|
46
|
+
#
|
47
|
+
# @see {GroupManager#generate_iptables_script}
|
48
|
+
# @see {GroupManager#generate_ipsets}
|
49
|
+
#
|
50
|
+
def apply_data!(data, options={})
|
51
|
+
options = {
|
52
|
+
dry_run: false,
|
53
|
+
interactive: false,
|
54
|
+
}.merge(options)
|
55
|
+
|
56
|
+
log.info("RunManager.apply_data!")
|
57
|
+
|
58
|
+
gen_options = {}
|
59
|
+
if options.include?(:log_accept)
|
60
|
+
gen_options[:log_accept] = options.fetch(:log_accept)
|
61
|
+
end
|
62
|
+
if options.include?(:log_drop)
|
63
|
+
gen_options[:log_drop] = options.fetch(:log_drop)
|
64
|
+
end
|
65
|
+
|
66
|
+
new_rules = @group_manager.generate_iptables_script(data, gen_options)
|
39
67
|
new_ipsets = @group_manager.generate_ipsets(data)
|
40
68
|
|
41
69
|
show_iptables_diff(new_rules)
|
@@ -55,7 +83,7 @@ module Reyes
|
|
55
83
|
iptables_restore(new_rules)
|
56
84
|
|
57
85
|
# XXX(richo) Should we be pruning inside run! ?
|
58
|
-
log.info('Finished
|
86
|
+
log.info('Finished applying data')
|
59
87
|
end
|
60
88
|
|
61
89
|
def materialize_ipsets(new_ipsets)
|
@@ -64,6 +92,10 @@ module Reyes
|
|
64
92
|
end
|
65
93
|
end
|
66
94
|
|
95
|
+
# Call iptables-restore to atomically restore a set of iptables rules.
|
96
|
+
#
|
97
|
+
# @param new_rules [String]
|
98
|
+
#
|
67
99
|
def iptables_restore(new_rules)
|
68
100
|
log.info("Restoring #{new_rules.count("\n")} lines of iptables rules")
|
69
101
|
|
@@ -75,7 +107,7 @@ module Reyes
|
|
75
107
|
log.info('restored')
|
76
108
|
end
|
77
109
|
|
78
|
-
|
110
|
+
# Remove old IPSets from previous run generations.
|
79
111
|
def prune_ipsets
|
80
112
|
log.info('Pruning old IPSets')
|
81
113
|
old_ipsets = []
|
@@ -114,6 +146,10 @@ module Reyes
|
|
114
146
|
log.info('Done pruning old IPSets')
|
115
147
|
end
|
116
148
|
|
149
|
+
# Show a diff between current IPTables rules and new IPTables rules.
|
150
|
+
#
|
151
|
+
# @param new_rules [String]
|
152
|
+
#
|
117
153
|
def show_iptables_diff(new_rules)
|
118
154
|
diff = Diff.new
|
119
155
|
diff.old.puts(Subprocess.check_output(%w{iptables-save}))
|
data/lib/reyes/version.rb
CHANGED
data/lib/reyes.rb
CHANGED
@@ -14,10 +14,11 @@ require_relative './reyes/errors'
|
|
14
14
|
require_relative './reyes/aws_manager'
|
15
15
|
require_relative './reyes/config'
|
16
16
|
require_relative './reyes/diff'
|
17
|
+
require_relative './reyes/fake_aws'
|
17
18
|
require_relative './reyes/group_manager'
|
18
19
|
require_relative './reyes/group_tools'
|
19
20
|
require_relative './reyes/ipset'
|
20
21
|
require_relative './reyes/iptables'
|
21
22
|
require_relative './reyes/run_generation'
|
22
|
-
require_relative './reyes/utils'
|
23
23
|
require_relative './reyes/run_manager'
|
24
|
+
require_relative './reyes/utils'
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: reyes
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.1.1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Andy Brody
|
@@ -9,7 +9,7 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date: 2015-02-
|
12
|
+
date: 2015-02-19 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: aws-sdk
|
@@ -117,6 +117,7 @@ files:
|
|
117
117
|
- lib/reyes/config.rb
|
118
118
|
- lib/reyes/diff.rb
|
119
119
|
- lib/reyes/errors.rb
|
120
|
+
- lib/reyes/fake_aws.rb
|
120
121
|
- lib/reyes/group_manager.rb
|
121
122
|
- lib/reyes/group_tools.rb
|
122
123
|
- lib/reyes/ipset.rb
|