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 CHANGED
@@ -1,15 +1,15 @@
1
1
  ---
2
2
  !binary "U0hBMQ==":
3
3
  metadata.gz: !binary |-
4
- YjM4MDllMmJmMGJiZjZjZGZiMDIwYTAzMDc4YjViYzJmODA2YWVmZQ==
4
+ NGE0MzE5OTBmNzQ2YWI0YmMxYmJlZGMxMDgxMGEwNDJiOTgzNTg0Mg==
5
5
  data.tar.gz: !binary |-
6
- MDZjZTU2MjIxZTZhY2FmMTc1ODU4YjkyNzEyYWNmOTIwYzU5Yjk0NA==
6
+ ZTY4MWFiMmNmMzg5MzEwNDBlYTc4MWUwZGVmYWZhMWYyOGMyZjI3YQ==
7
7
  SHA512:
8
8
  metadata.gz: !binary |-
9
- NTNlNDgyMjg3Mzg0NzdiYjRmY2QxYTRlNGZkMDhmNzA3NDQyZTg2MWMzN2Ez
10
- YmJkNmQ4NmZlNGY5NWJjOGQ5MjYxNzkzMGJhZTk1MjQzOTUzYjQyN2YwYjM2
11
- ZWVkMmRlNmY0MWY3NTc4OWRjZjBiNzI0NTQ1OGJiY2RjNDdmMWY=
9
+ ZTczYjRhNGIzZjZjMDViZjRlZDg4ODc2ZTNhYTQ5YjA4MjBiMDFmMjY5NWIw
10
+ NDE5M2ZmYzZkNDgzMDlmNjVlZTcwYjIyMGI2ODdiNzQ0MDg0ZWJmN2M3Y2Qy
11
+ MWJhYzljYTM5YzljNDYyMDI2MmQyYTZjODExZDUwZWM1YTFjNjg=
12
12
  data.tar.gz: !binary |-
13
- NTZlNTU4MGRmZDExNWUwNWEwZDQ2NmE3MDNjMWJjOGU2N2U5MzdkZDhiNjBj
14
- YjBlNzlhNDJiZjUzMDAwZWJiNzIxMmY2NjFjZTkxYjcyNWQ3NmYxNjE5MjQ4
15
- YTMwODEyMjY5YjYyMWRiMmE2NTk3MWZjMGY2Y2E4N2RmYjljMGE=
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 command_install(options)
6
- instance_id = options.fetch(:instance_id)
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
- AWS.memoize do
14
- aws = Reyes::AwsManager.new(options[:config])
15
- g = Reyes::GroupManager.new(aws, region, instance_id, options[:config])
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
- options[:run_options][:log_accept] = true # TODO
21
+ data = r.generate_data!(options.fetch(:gen_options))
22
+ r.apply_data!(data, options.fetch(:apply_options))
19
23
 
20
- r.run!(options.fetch(:run_options))
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
- :run_options => {},
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 --install $(facter -p ec2_instance_id)
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('--prune', 'Prune old generations after creating rules') do
59
- options[:prune] = true
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('--install INSTANCE_ID', 'Configure this instance as INSTANCE_ID') do |id|
63
- options[:command] = :install
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[:run_options][:dry_run] = true
85
+ options[:apply_options][:dry_run] = true
70
86
  end
71
87
 
72
88
  opts.on('-i', '--interactive', 'Interactively confirm changes') do
73
- options[:run_options][:interactive] = true
89
+ options[:apply_options][:interactive] = true
74
90
  end
75
91
 
76
92
  opts.on('--empty', 'Generate empty (default DROP) rules') do
77
- options[:run_options][:empty] = true
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
- case options[:command]
93
- when :install
94
- command_install(options)
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"
@@ -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
@@ -21,26 +21,31 @@ module Reyes
21
21
 
22
22
  ReyesInputChain = 'reyes-ipsec-input'
23
23
 
24
- attr_reader :aws
24
+ attr_reader :fake_aws, :instance_id
25
25
 
26
- def initialize(aws, region, instance_id, config_file=nil, generation=nil)
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
- @aws = aws
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
- @our_groups ||= Reyes::AwsManager.with_retries {
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
- {:groups => {}, :ipsets => {}}
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 beyond incrementing the run generation (used
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.each do |group|
70
- group_key = "#{group.group_id}:#{group.name}"
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.port_range,
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 :icmp
83
+ case perm.fetch('protocol').to_s
84
+ when 'icmp'
84
85
  log.info("Skipping ICMP rule")
85
86
  next
86
- when :tcp, :udp
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.groups.each do |g|
93
- foreign = foreign_groups_by_name(g.name)
94
- foreign.each do |fg|
95
- needed_groups[fg.group_id] = fg
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 Error.new("Unexpected protocol #{perm.protocol.inspect}")
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.each_value do |group|
110
- data[:ipsets][ipset_name_for_group(group)] = \
111
- addresses_for_group(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 group [AWS::EC2::SecurityGroup]
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(group)
185
- groups = foreign_groups_by_name(group.name)
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 [Array<AWS::EC2::SecurityGroup>]
209
+ # @return [Hash]
196
210
  #
197
211
  def foreign_groups_by_name(name)
198
- aws.vpc_security_groups_by_name(name)
212
+ fake_aws.foreign_groups_by_name(name)
199
213
  end
200
214
 
201
215
  # @param [Hash] data
@@ -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
- raise 'Unsupported protocol' unless [:tcp, :udp].include?(protocol)
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]
@@ -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
@@ -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 run!(options={})
14
+ def generate_data!(options={})
20
15
  options = {
21
- dry_run: false,
22
16
  empty: false,
23
- interactive: false,
24
- log_accept: false,
17
+ increment_generation: true,
25
18
  }.merge(options)
26
19
 
27
- log_accept = options.fetch(:log_accept)
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
- data = @group_manager.generate_rules!
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
- new_rules = @group_manager.generate_iptables_script(data, log_accept: log_accept)
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 firewall configuration run')
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
@@ -1,3 +1,3 @@
1
1
  module Reyes
2
- VERSION = '0.0.6' unless defined?(self::VERSION)
2
+ VERSION = '0.1.1' unless defined?(self::VERSION)
3
3
  end
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.0.6
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-18 00:00:00.000000000 Z
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