Class: Reyes::RunManager

Inherits:
Object
  • Object
show all
Includes:
Chalk::Log
Defined in:
lib/reyes/run_manager.rb

Constant Summary collapse

IPSET_NAME_PATTERN =
/(\d+)(?<nogen>:(\w+):(sg-[a-f0-9]{8}):(\w+))/

Instance Method Summary collapse

Constructor Details

#initialize(group_manager, config) ⇒ RunManager

Returns a new instance of RunManager.

Parameters:



9
10
11
12
# File 'lib/reyes/run_manager.rb', line 9

def initialize(group_manager, config)
  @group_manager = group_manager
  @config = config
end

Instance Method Details

#apply_data!(data, options = {}) ⇒ Object

Parameters:

  • options (Hash) (defaults to: {})

Options Hash (options):

  • :dry_run (Boolean) — default: false

    Don’t actually apply changes

  • :interactive (Boolean) — default: false

    Whether to prompt for confirmation before applying rules

  • :log_accept (Boolean)

    Whether to log packets on ACCEPT

  • :log_drop (Boolean)

    Whether to log packets on DROP

See Also:

  • Reyes::RunManager.{GroupManager{GroupManager#generate_iptables_script}
  • Reyes::RunManager.{GroupManager{GroupManager#generate_ipsets}


55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
# File 'lib/reyes/run_manager.rb', line 55

def apply_data!(data, options={})
  options = {
    dry_run: false,
    interactive: false,
  }.merge(options)

  log.info("RunManager.apply_data!")

  gen_options = {}
  if options.include?(:log_accept)
    gen_options[:log_accept] = options.fetch(:log_accept)
  end
  if options.include?(:log_drop)
    gen_options[:log_drop] = options.fetch(:log_drop)
  end

  new_rules = @group_manager.generate_iptables_script(data, gen_options)
  new_ipsets = @group_manager.generate_ipsets(data)

  show_iptables_diff(new_rules)
  show_ipsets_diff(new_ipsets)

  if options.fetch(:dry_run)
    log.info('Dry run! Not applying changes')
    return
  end

  if options.fetch(:interactive)
    puts 'Press enter to continue...'
    STDIN.gets
  end

  materialize_ipsets(new_ipsets)
  iptables_restore(new_rules)

  ct_max = @config.reyes_config['nf_conntrack_max']
  if ct_max
    set_nf_conntrack_max(Integer(ct_max))
  end

  log.info('Finished RunManager.apply_data!')
end

#generate_data!(options = {}) ⇒ Object

Parameters:

  • options (Hash) (defaults to: {})

Options Hash (options):

  • :empty (Boolean) — default: false

    Generate an empty (default DROP) rule sets without actually looking up security groups



19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
# File 'lib/reyes/run_manager.rb', line 19

def generate_data!(options={})
  options = {
    empty: false,
    increment_generation: true,
  }.merge(options)

  log.info("RunManager.generate_data!")

  if options.fetch(:empty)
    log.warn("Generating empty (default DROP) rule set")
    data = @group_manager.generate_rules_empty
  else
    if options.fetch(:increment_generation)
      @group_manager.run_generation_increment!
    end
    data = @group_manager.generate_rules
  end

  log.info("Finished RunManager.generate_data!")

  data
end

#iptables_restore(new_rules) ⇒ Object

Call iptables-restore to atomically restore a set of iptables rules.

Parameters:

  • new_rules (String)


110
111
112
113
114
115
116
117
118
119
# File 'lib/reyes/run_manager.rb', line 110

def iptables_restore(new_rules)
  log.info("Restoring #{new_rules.count("\n")} lines of iptables rules")

  log.info('+ iptables-restore')
  Subprocess.check_call(['iptables-restore'],
                        stdin: Subprocess::PIPE) do |p|
    p.communicate(new_rules)
  end
  log.info('restored')
end

#materialize_ipsets(new_ipsets) ⇒ Object



98
99
100
101
102
103
104
# File 'lib/reyes/run_manager.rb', line 98

def materialize_ipsets(new_ipsets)
  log.info('Materializing ipsets')
  new_ipsets.each do |ipset|
    ipset.build
  end
  log.info('Done building ipsets')
end

#prune_ipsets(options = {}) ⇒ Object

Remove old IPSets from previous run generations.

Parameters:

  • options (Hash) (defaults to: {})

Options Hash (options):

  • :future_ok (Boolean)

    Whether to prune ipsets from future generations, which are normally skipped.



146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
# File 'lib/reyes/run_manager.rb', line 146

def prune_ipsets(options={})
  log.info('Pruning old IPSets')
  prune_ipsets = []
  current_ipsets = []

  current_gen = @group_manager.run_generation
  Reyes::IPSet.load_all.each do |set|
    s = Reyes::GroupManager.parse_ipset_name(set.name)
    unless s
      log.warn("Skipping unparseable ipset name #{set.name.inspect}")
      next
    end

    if s[:generation] < current_gen
      prune_ipsets << set
    elsif s[:generation] == current_gen
      current_ipsets << set
    else
      log.error("IPSet from a future generation detected: #{set.inspect}")
      if options[:future_ok]
        prune_ipsets << set
      end
    end
  end

  if current_ipsets.empty?
    log.error("No IPSets from current generation.")
    log.error("Cowardly refusing to proceed")
    raise Reyes::Error.new("Pruning would remove all IPSets")
  end

  prune_ipsets.each do |set|
    log.info("Pruning IPSet: #{set.name}")
    set.drop!
  end

  log.info('Done pruning old IPSets')
end

#set_nf_conntrack_max(value) ⇒ Object

Increase nf_conntrack_max to the desired value.

Parameters:

  • value (Integer)

Raises:

  • (ArgumentError)


125
126
127
128
129
130
131
132
133
134
135
136
137
# File 'lib/reyes/run_manager.rb', line 125

def set_nf_conntrack_max(value)
  raise ArgumentError.new('bad value') unless value.is_a?(Fixnum)

  log.info('Checking net.netfilter.nf_conntrack_max')
  current = File.read('/proc/sys/net/netfilter/nf_conntrack_max')

  current = Integer(current)

  if current != value
    log.info("Changing nf_conntrack_max from #{current} to #{value}")
    File.write('/proc/sys/net/netfilter/nf_conntrack_max', value)
  end
end

#show_ipsets_diff(new_ipsets) ⇒ Object



203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
# File 'lib/reyes/run_manager.rb', line 203

def show_ipsets_diff(new_ipsets)
  log.debug('show_ipsets_diff')

  diff = Diff.new

  dump = lambda do |f, ipset|
    ipset.sort_by(&:name).each do |ip|
      f.puts(gsub_ipset_generation(ip.name))
      ip.members.sort.each do |m|
        f.puts("\t#{m}")
      end
    end
  end

  dump.call(diff.old, Reyes::IPSet.load_all)
  dump.call(diff.new, new_ipsets)

  log.info 'Proposed IPSets diff:'
  log.info(diff.diff || '<unchanged>')
end

#show_iptables_diff(new_rules) ⇒ Object

Show a diff between current IPTables rules and new IPTables rules.

Parameters:

  • new_rules (String)


189
190
191
192
193
194
195
196
197
198
199
200
201
# File 'lib/reyes/run_manager.rb', line 189

def show_iptables_diff(new_rules)
  log.debug('show_iptables_diff')

  log.debug('+ iptables-save -t filter')
  current = Subprocess.check_output(%w{iptables-save -t filter})

  diff = Diff.new
  diff.old.puts(filter_iptables_output(current))
  diff.new.puts(filter_iptables_output(new_rules))

  log.info 'Proposed IPTables diff:'
  log.info(diff.diff || '<unchanged>')
end