asbestos 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (52) hide show
  1. checksums.yaml +15 -0
  2. data/.gitignore +18 -0
  3. data/.rspec +3 -0
  4. data/Gemfile +10 -0
  5. data/Guardfile +9 -0
  6. data/LICENSE.txt +22 -0
  7. data/README.md +461 -0
  8. data/Rakefile +1 -0
  9. data/asbestos.gemspec +26 -0
  10. data/bin/asbestos +112 -0
  11. data/examples/0_simple.rb +5 -0
  12. data/examples/10_kitchen_sink.rb +72 -0
  13. data/examples/1_two_hosts.rb +18 -0
  14. data/examples/2_accept_from_many.rb +19 -0
  15. data/examples/3_groups.rb +39 -0
  16. data/examples/4_host_templates.rb +29 -0
  17. data/examples/5_static_addresses.rb +7 -0
  18. data/examples/6_interface_addresses.rb +19 -0
  19. data/examples/7_services.rb +9 -0
  20. data/examples/8_rule_sets.rb +37 -0
  21. data/examples/9_literal_commands.rb +8 -0
  22. data/lib/asbestos.rb +108 -0
  23. data/lib/asbestos/address.rb +8 -0
  24. data/lib/asbestos/dsl.rb +40 -0
  25. data/lib/asbestos/firewalls/iptables.rb +127 -0
  26. data/lib/asbestos/host.rb +244 -0
  27. data/lib/asbestos/host_template.rb +15 -0
  28. data/lib/asbestos/metadata.rb +4 -0
  29. data/lib/asbestos/rule_set.rb +131 -0
  30. data/lib/asbestos/rule_sets/accept_from_self.rb +19 -0
  31. data/lib/asbestos/rule_sets/allow_related_established.rb +5 -0
  32. data/lib/asbestos/rule_sets/icmp_protection.rb +28 -0
  33. data/lib/asbestos/rule_sets/sanity_check.rb +41 -0
  34. data/lib/asbestos/service.rb +86 -0
  35. data/lib/asbestos/services/chef.rb +4 -0
  36. data/lib/asbestos/services/cube.rb +14 -0
  37. data/lib/asbestos/services/http.rb +8 -0
  38. data/lib/asbestos/services/memcached.rb +4 -0
  39. data/lib/asbestos/services/mongodb.rb +28 -0
  40. data/lib/asbestos/services/monit.rb +4 -0
  41. data/lib/asbestos/services/mysql.rb +4 -0
  42. data/lib/asbestos/services/nfs.rb +5 -0
  43. data/lib/asbestos/services/redis.rb +4 -0
  44. data/lib/asbestos/services/ssh.rb +4 -0
  45. data/spec/asbestos/address_spec.rb +25 -0
  46. data/spec/asbestos/firewalls/iptables_spec.rb +179 -0
  47. data/spec/asbestos/host_spec.rb +173 -0
  48. data/spec/asbestos/host_template_spec.rb +32 -0
  49. data/spec/asbestos/rule_set_spec.rb +55 -0
  50. data/spec/asbestos/service_spec.rb +60 -0
  51. data/spec/spec_helper.rb +20 -0
  52. metadata +159 -0
@@ -0,0 +1,19 @@
1
+
2
+ rule_set :accept_from_self do
3
+ # Accept any connections from the loopback device with local addresses bound for local addresses
4
+ interfaces[:loopback].each do |interface|
5
+ accept :interface => interface,
6
+ :local_address => '127.0.0.0/8',
7
+ :remote_address => '127.0.0.0/8',
8
+ :comment => "accept via loopback device with from and to loopback addresses"
9
+ end
10
+
11
+ # Accept anything from the interface to itself.
12
+ addresses.each do |interface, address|
13
+ next if interface == :loopback # handled by above rule
14
+
15
+ accept :local_address => address,
16
+ :remote_address => address,
17
+ :comment => "accept anything from myself to myself (#{interface})"
18
+ end
19
+ end
@@ -0,0 +1,5 @@
1
+
2
+ rule_set :allow_related_established do
3
+ # Allow all currently established and related packets
4
+ accept :state => 'RELATED,ESTABLISHED'
5
+ end
@@ -0,0 +1,28 @@
1
+
2
+ rule_set :icmp_protection do
3
+
4
+ accept :chain => :output,
5
+ :protocol => :icmp,
6
+ :icmp_type => 'echo-request',
7
+ :comment => "allow us to ping others"
8
+
9
+ accept :protocol => :icmp,
10
+ :icmp_type => 'echo-reply',
11
+ :comment => "allow us to receive ping responses"
12
+
13
+
14
+ interfaces[:external].each do |interface|
15
+ from_each_address(allowed_from) do |address|
16
+ accept :protocol => :icmp,
17
+ :icmp_type => 'echo-request',
18
+ :interface => interface,
19
+ :remote_address => address,
20
+ :limit => '22s',
21
+ :comment => "allow icmp from #{address}"
22
+ end
23
+
24
+ drop :protocol => :icmp,
25
+ :interface => interface,
26
+ :comment => "drop any icmp packets that haven't been explicitly allowed"
27
+ end
28
+ end
@@ -0,0 +1,41 @@
1
+ rule_set :sanity_check do
2
+ chain 'valid-src'
3
+ chain 'valid-dst'
4
+
5
+ # Require all packets to or from the internet to go through sanity checks.
6
+ interfaces[:external].each do |iface|
7
+ rule :chain => :input,
8
+ :action => 'valid-src',
9
+ :interface => interface,
10
+ :comment => "all traffic from internet goes through sanity check"
11
+
12
+ rule :chain => :output,
13
+ :action => 'valid-dst',
14
+ :interface => interface,
15
+ :comment => "all traffic from internet goes through sanity check"
16
+ end
17
+
18
+ # Private interface addresses should never be talking to our external IP.
19
+ [
20
+ '0.0.0.0/8',
21
+ '10.0.0.0/8',
22
+ '127.0.0.0/8',
23
+ '169.254.0.0/16',
24
+ '172.16.0.0/12',
25
+ '192.168.0.0/16',
26
+ '224.0.0.0/4',
27
+ '240.0.0.0/5'
28
+ ].each do |interal_ip_range|
29
+ drop :chain => 'valid-src',
30
+ :local_address => interal_ip_range,
31
+ :comment => "drop private ip talking to external interface"
32
+ end
33
+
34
+ drop :chain => 'valid-src',
35
+ :remote_address => '255.255.255.255',
36
+ :comment => "drop broadcast ip talking to external interface"
37
+
38
+ drop :chain => 'valid-dst',
39
+ :remote_address => '224.0.0.0/4',
40
+ :comment => "ignore multicast"
41
+ end
@@ -0,0 +1,86 @@
1
+
2
+ module Asbestos
3
+ class Service < RuleSet
4
+
5
+ class_collection :all
6
+
7
+ attr_reader :attributes
8
+
9
+ def initialize(name, host)
10
+ @name = name
11
+ @host = host
12
+ @attributes = {}
13
+ #
14
+ # Attribute defaults
15
+ #
16
+ @attributes[:protocols] = [:tcp]
17
+ end
18
+
19
+ def inspect
20
+ "#{name}:#{[*ports].join(',')}/#{@attributes.inspect}"
21
+ end
22
+
23
+ def firewall_rules
24
+ Array.new.tap do |rules|
25
+ from_each do |host_or_address, remote_interface_tag|
26
+ rules << open_port(:from => host_or_address, :remote_interface_tag => remote_interface_tag)
27
+ end
28
+ end
29
+ end
30
+
31
+ def open_port(args = {})
32
+ interfaces = on ? host.interfaces[on] : nil # nil -> all interfaces
33
+
34
+ Array.new.tap do |rules|
35
+ protocols.each do |protocol|
36
+ ports.each do |port|
37
+ comment_base = "allow #{name}(#{protocol} port #{port}) from"
38
+ case args[:from]
39
+ when Host # specific host, specific remote interface
40
+ raise "Host '#{args[:from].name}' doesn't have interface '#{args[:remote_interface_tag]}'" unless args[:from].interfaces[args[:remote_interface_tag]]
41
+ args[:from].interfaces[args[:remote_interface_tag]].each do |remote_interface|
42
+ comment = "#{comment_base} #{args[:from].name}:#{remote_interface} (#{args[:remote_interface_tag]})"
43
+ rules << Asbestos.firewall.open_port(interfaces, port, protocol, comment, args[:from].addresses[remote_interface])
44
+ end
45
+ when Symbol, String # an address
46
+ comment = "#{comment_base} #{args[:from]}"
47
+ rules << Asbestos.firewall.open_port(interfaces, port, protocol, comment, args[:from])
48
+ else
49
+ comment = "#{comment_base} anyone"
50
+ rules << Asbestos.firewall.open_port(interfaces, port, protocol, comment)
51
+ end
52
+ end
53
+ end
54
+ end
55
+ end
56
+
57
+ #
58
+ # DSL ------------------------------------------------------------------------------
59
+ #
60
+
61
+ #
62
+ # Most DSL calls needed by Service are caught and handled by method_missing in RuleSet
63
+ #
64
+
65
+
66
+ #
67
+ # This is a hack to intercept DSL calls to certain singular attributes and send them to
68
+ # method_missing on the superclass in their plural form.
69
+ #
70
+ [
71
+ :port,
72
+ :protocol,
73
+ :group,
74
+ ].each do |method|
75
+ define_method method do |*args|
76
+ if args.empty?
77
+ self.send "#{method}s"
78
+ else
79
+ self.send "#{method}s", *args
80
+ end
81
+ end
82
+ end
83
+
84
+
85
+ end
86
+ end
@@ -0,0 +1,4 @@
1
+
2
+ service :chef do
3
+ port 4000
4
+ end
@@ -0,0 +1,14 @@
1
+
2
+ #
3
+ # the cube metrics collector and evaluator
4
+ # https://github.com/square/cube
5
+ #
6
+ service :cube_collector do
7
+ ports 1080, 1180
8
+ protocols [:tcp, :udp]
9
+ end
10
+
11
+ service :cube_evaluator do
12
+ ports 1081
13
+ protocols [:tcp]
14
+ end
@@ -0,0 +1,8 @@
1
+
2
+ service :http do
3
+ port 80
4
+ end
5
+
6
+ service :https do
7
+ port 443
8
+ end
@@ -0,0 +1,4 @@
1
+
2
+ service :memcached do
3
+ port 11211
4
+ end
@@ -0,0 +1,28 @@
1
+
2
+ #
3
+ # the mongo shard routing server
4
+ #
5
+ service :mongos do
6
+ port 27017
7
+ end
8
+
9
+ #
10
+ # mongod running as a shard server
11
+ #
12
+ service :mongodb_shard do
13
+ port 27018
14
+ end
15
+
16
+ #
17
+ # mongod running as a config server
18
+ #
19
+ service :mongodb_config do
20
+ port 27019
21
+ end
22
+
23
+ #
24
+ # standard mongodb server
25
+ #
26
+ service :mongodb do
27
+ port 27017
28
+ end
@@ -0,0 +1,4 @@
1
+
2
+ service :monit do
3
+ port 2812
4
+ end
@@ -0,0 +1,4 @@
1
+
2
+ service :mysql do
3
+ port 3306
4
+ end
@@ -0,0 +1,5 @@
1
+
2
+ service :nfs do
3
+ ports :nfs, :sunrpc
4
+ protocols :udp, :tcp
5
+ end
@@ -0,0 +1,4 @@
1
+
2
+ service :redis do
3
+ port 6379
4
+ end
@@ -0,0 +1,4 @@
1
+
2
+ service :ssh do
3
+ port 22
4
+ end
@@ -0,0 +1,25 @@
1
+
2
+ require 'spec_helper'
3
+
4
+ describe Asbestos::Host do
5
+ before(:each) do
6
+ Asbestos.reset!
7
+ end
8
+
9
+ context "the 'address' DSL call" do
10
+
11
+ it "should create a new named address" do
12
+ address "some_host", '1.2.3.4'
13
+
14
+ Asbestos::Address['some_host'].should == ['1.2.3.4']
15
+ end
16
+
17
+ it "should create a new address set" do
18
+ address "some_host", ['1.2.3.4', 'some_hostname']
19
+
20
+ Asbestos::Address['some_host'].should == ['1.2.3.4', 'some_hostname']
21
+ end
22
+
23
+ end
24
+ end
25
+
@@ -0,0 +1,179 @@
1
+
2
+ require 'spec_helper'
3
+
4
+ describe Asbestos::Firewall::IPTables do
5
+ before(:each) do
6
+ Asbestos.reset!
7
+
8
+ host "hostname" do
9
+ chain :chainname
10
+ end
11
+
12
+ host "drop_by_default" do
13
+ chain :input, :drop
14
+ end
15
+
16
+ host "logs_denials" do
17
+ log_denials
18
+ end
19
+
20
+ @host = Host['hostname'].call
21
+ @drop_by_default_host = Host['drop_by_default'].call
22
+ @logs_denials_host = Host['logs_denials'].call
23
+ end
24
+
25
+ context "#preamble" do
26
+ it "denotes the start of the filter table" do
27
+ Asbestos::Firewall::IPTables.preamble(@host).should include('*filter')
28
+ end
29
+
30
+ it "generates preable with the host's chains" do
31
+ preamble = Asbestos::Firewall::IPTables.preamble(@host)
32
+ [:input, :output].each do |chain|
33
+ preamble.should include(":#{chain.upcase} ACCEPT [0:0]")
34
+ end
35
+
36
+ preamble.should include(":CHAINNAME - [0:0]")
37
+ end
38
+ end
39
+
40
+ context "#postamble" do
41
+ it "should COMMIT the rules" do
42
+ Asbestos::Firewall::IPTables.postamble(@host).should include('COMMIT')
43
+ end
44
+
45
+ context "log_denials" do
46
+ it "should log denials if told to do so" do
47
+ Asbestos.firewall.should_receive(:log)
48
+ Asbestos::Firewall::IPTables.postamble(@logs_denials_host)
49
+ end
50
+
51
+ it "should log denials if not told to do so" do
52
+ Asbestos.firewall.should_not_receive(:log)
53
+ Asbestos::Firewall::IPTables.postamble(@host)
54
+ end
55
+ end
56
+
57
+ context "default input chain action" do
58
+ it "should drop packets with a rule when :input chain's policy is ACCEPT" do
59
+ Asbestos.firewall.should_receive(:drop)
60
+ Asbestos::Firewall::IPTables.postamble(@host)
61
+ end
62
+
63
+ it "should pass to the chain's policy when :input chain's policy is not ACCEPT" do
64
+ Asbestos.firewall.should_not_receive(:drop)
65
+ Asbestos::Firewall::IPTables.postamble(@drop_by_default_host)
66
+ end
67
+ end
68
+ end
69
+
70
+ context "#chain" do
71
+ context "when no action is provided" do
72
+ it "should generate chain default action '-'" do
73
+ Asbestos::Firewall::IPTables.chain(:chainname, :none).should == ":CHAINNAME - [0:0]"
74
+ end
75
+ end
76
+
77
+ context "when an action is provided" do
78
+ it "should generate a proper default action" do
79
+ Asbestos::Firewall::IPTables.chain(:chainname, :accept).should == ":CHAINNAME ACCEPT [0:0]"
80
+ end
81
+ end
82
+ end
83
+
84
+ context "firewall verbs" do
85
+ [:accept, :reject, :drop, :log].each do |action|
86
+ it "should call #rule with :action => :#{action}" do
87
+ Asbestos.firewall.should_receive(:rule).with(:action => action)
88
+ Asbestos::Firewall::IPTables.send(action, {})
89
+ end
90
+ end
91
+ end
92
+
93
+ context "#rule" do
94
+ context ":chain" do
95
+ it "should default to the INPUT chain" do
96
+ Asbestos::Firewall::IPTables.rule({}).should == "-A INPUT"
97
+ end
98
+
99
+ it "should use the provided chain" do
100
+ Asbestos::Firewall::IPTables.rule(:chain => :somechain).should == "-A SOMECHAIN"
101
+ end
102
+ end
103
+
104
+ context ":action" do
105
+ it "should use the :action when provided " do
106
+ Asbestos::Firewall::IPTables.rule({:action => :drop}).should == "-A INPUT -j DROP"
107
+ end
108
+ end
109
+
110
+ context ":interface and :direction" do
111
+ context "when the chain is built-in" do
112
+ context "when :direction is provided" do
113
+ it "should use :direction if provided properly" do
114
+ Asbestos::Firewall::IPTables.rule({:interface => :eth0, :direction => :incoming}).should == "-A INPUT -i eth0"
115
+ Asbestos::Firewall::IPTables.rule({:interface => :eth0, :direction => :outgoing}).should == "-A INPUT -o eth0"
116
+ end
117
+
118
+ it "should raise an error if :direction isn't provided properly" do
119
+ expect do
120
+ Asbestos::Firewall::IPTables.rule({:interface => :eth0, :direction => :junkvalue})
121
+ end.to raise_error
122
+ end
123
+ end
124
+
125
+ context "when :direction is not provided" do
126
+ it "should use the incoming direction on the :input or :prerouting chains" do
127
+ Asbestos::Firewall::IPTables.rule({:chain => :input, :interface => :eth0}).should == "-A INPUT -i eth0"
128
+ Asbestos::Firewall::IPTables.rule({:chain => :prerouting, :interface => :eth0}).should == "-A PREROUTING -i eth0"
129
+ end
130
+
131
+ it "should use the outgoing direction on the :output or :postrouting chains" do
132
+ Asbestos::Firewall::IPTables.rule({:chain => :output, :interface => :eth0}).should == "-A OUTPUT -o eth0"
133
+ Asbestos::Firewall::IPTables.rule({:chain => :postrouting, :interface => :eth0}).should == "-A POSTROUTING -o eth0"
134
+ end
135
+ end
136
+ end
137
+
138
+ context "when using a user-defined chain" do
139
+ it "should raise an error if :direction isn't provided properly" do
140
+ expect do
141
+ Asbestos::Firewall::IPTables.rule({:chain => :mychain, :interface => :eth0, :direction => :junkvalue})
142
+ end.to raise_error
143
+ end
144
+
145
+ it "should use :direction if provided properly" do
146
+ Asbestos::Firewall::IPTables.rule({:chain => :mychain, :interface => :eth0, :direction => :incoming}).should == "-A MYCHAIN -i eth0"
147
+ Asbestos::Firewall::IPTables.rule({:chain => :mychain, :interface => :eth0, :direction => :outgoing}).should == "-A MYCHAIN -o eth0"
148
+ end
149
+ end
150
+ end
151
+
152
+ context ":comment" do
153
+ it "should provided the comment with the interface when :interface is provided" do
154
+ Asbestos::Firewall::IPTables.rule({:comment => 'this is a comment', :interface => :eth0}).should == %{-A INPUT -i eth0 -m comment --comment "this is a comment on eth0"}
155
+ end
156
+ end
157
+
158
+ {
159
+ [:action, :drop] => '-j DROP',
160
+ [:protocol, :tcp] => '-p tcp',
161
+ [:local_address, '1.2.3.4'] => '-d 1.2.3.4',
162
+ [:remote_address, '5.6.7.8'] => '-s 5.6.7.8',
163
+ [:port, 80] => '--dport 80',
164
+ [:limit, '5/min'] => '-m limit --limit 5/min',
165
+ [:log_prefix, 'iptables dropped: '] => %{--log-prefix "iptables dropped: "},
166
+ [:log_level, 7] => '--log-level 7',
167
+ [:icmp_type, 'echo-request'] => '--icmp-type echo-request',
168
+ [:comment, 'this is a comment'] => %{-m comment --comment "this is a comment"},
169
+ [:state, :new] => '-m state --state NEW'
170
+ }.each do |action_and_arg, expected|
171
+ action, arg = *action_and_arg
172
+ context ":#{action}" do
173
+ it "should use #{action} if provided" do
174
+ Asbestos::Firewall::IPTables.rule({action => arg}).should == "-A INPUT #{expected}"
175
+ end
176
+ end
177
+ end
178
+ end
179
+ end