reyes 0.0.6 → 0.1.1

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