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) ⇒ RunManager

Returns a new instance of RunManager.



7
8
9
# File 'lib/reyes/run_manager.rb', line 7

def initialize(group_manager)
  @group_manager = group_manager
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}


52
53
54
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
# File 'lib/reyes/run_manager.rb', line 52

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)

  # XXX(richo) Should we be pruning inside run! ?
  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



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

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)


103
104
105
106
107
108
109
110
111
112
# File 'lib/reyes/run_manager.rb', line 103

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



91
92
93
94
95
96
97
# File 'lib/reyes/run_manager.rb', line 91

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

#prune_ipsetsObject

Remove old IPSets from previous run generations.



115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
# File 'lib/reyes/run_manager.rb', line 115

def prune_ipsets
  log.info('Pruning old IPSets')
  old_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
      old_ipsets << set
    elsif s[:generation] == current_gen
      current_ipsets << set
    else
      log.error("IPSet from a future generation detected: #{set.inspect}")
      log.error("Cowardly refusing to proceed")
      raise Reyes::Error.new("IPSet from future generation detected")
    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

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

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

#show_ipsets_diff(new_ipsets) ⇒ Object



171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
# File 'lib/reyes/run_manager.rb', line 171

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)


157
158
159
160
161
162
163
164
165
166
167
168
169
# File 'lib/reyes/run_manager.rb', line 157

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