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