asbestos 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +15 -0
- data/.gitignore +18 -0
- data/.rspec +3 -0
- data/Gemfile +10 -0
- data/Guardfile +9 -0
- data/LICENSE.txt +22 -0
- data/README.md +461 -0
- data/Rakefile +1 -0
- data/asbestos.gemspec +26 -0
- data/bin/asbestos +112 -0
- data/examples/0_simple.rb +5 -0
- data/examples/10_kitchen_sink.rb +72 -0
- data/examples/1_two_hosts.rb +18 -0
- data/examples/2_accept_from_many.rb +19 -0
- data/examples/3_groups.rb +39 -0
- data/examples/4_host_templates.rb +29 -0
- data/examples/5_static_addresses.rb +7 -0
- data/examples/6_interface_addresses.rb +19 -0
- data/examples/7_services.rb +9 -0
- data/examples/8_rule_sets.rb +37 -0
- data/examples/9_literal_commands.rb +8 -0
- data/lib/asbestos.rb +108 -0
- data/lib/asbestos/address.rb +8 -0
- data/lib/asbestos/dsl.rb +40 -0
- data/lib/asbestos/firewalls/iptables.rb +127 -0
- data/lib/asbestos/host.rb +244 -0
- data/lib/asbestos/host_template.rb +15 -0
- data/lib/asbestos/metadata.rb +4 -0
- data/lib/asbestos/rule_set.rb +131 -0
- data/lib/asbestos/rule_sets/accept_from_self.rb +19 -0
- data/lib/asbestos/rule_sets/allow_related_established.rb +5 -0
- data/lib/asbestos/rule_sets/icmp_protection.rb +28 -0
- data/lib/asbestos/rule_sets/sanity_check.rb +41 -0
- data/lib/asbestos/service.rb +86 -0
- data/lib/asbestos/services/chef.rb +4 -0
- data/lib/asbestos/services/cube.rb +14 -0
- data/lib/asbestos/services/http.rb +8 -0
- data/lib/asbestos/services/memcached.rb +4 -0
- data/lib/asbestos/services/mongodb.rb +28 -0
- data/lib/asbestos/services/monit.rb +4 -0
- data/lib/asbestos/services/mysql.rb +4 -0
- data/lib/asbestos/services/nfs.rb +5 -0
- data/lib/asbestos/services/redis.rb +4 -0
- data/lib/asbestos/services/ssh.rb +4 -0
- data/spec/asbestos/address_spec.rb +25 -0
- data/spec/asbestos/firewalls/iptables_spec.rb +179 -0
- data/spec/asbestos/host_spec.rb +173 -0
- data/spec/asbestos/host_template_spec.rb +32 -0
- data/spec/asbestos/rule_set_spec.rb +55 -0
- data/spec/asbestos/service_spec.rb +60 -0
- data/spec/spec_helper.rb +20 -0
- 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,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,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,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
|