asbestos 0.0.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.
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