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
data/Rakefile
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
require "bundler/gem_tasks"
|
data/asbestos.gemspec
ADDED
@@ -0,0 +1,26 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'asbestos/metadata'
|
5
|
+
|
6
|
+
Gem::Specification.new do |spec|
|
7
|
+
spec.name = "asbestos"
|
8
|
+
spec.version = Asbestos::VERSION
|
9
|
+
spec.authors = ["Michael Shapiro"]
|
10
|
+
spec.email = ["koudelka@ryoukai.org"]
|
11
|
+
spec.description = %q{Asbestos is a declarative DSL for building firewall rules (iptables, at this point)}
|
12
|
+
spec.summary = %q{Declarative firewall(iptables) DSL.}
|
13
|
+
spec.homepage = Asbestos::HOMEPAGE
|
14
|
+
spec.license = "MIT"
|
15
|
+
|
16
|
+
spec.files = `git ls-files`.split($/)
|
17
|
+
spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
|
18
|
+
spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
|
19
|
+
spec.require_paths = ["lib"]
|
20
|
+
|
21
|
+
spec.add_development_dependency "bundler", "~> 1.3"
|
22
|
+
spec.add_development_dependency "rake"
|
23
|
+
spec.add_development_dependency "rspec"
|
24
|
+
|
25
|
+
spec.add_dependency "system-getifaddrs", "~> 0.1.5"
|
26
|
+
end
|
data/bin/asbestos
ADDED
@@ -0,0 +1,112 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require 'asbestos'
|
4
|
+
require 'optparse'
|
5
|
+
require 'pathname'
|
6
|
+
|
7
|
+
options = {
|
8
|
+
:host => Asbestos.hostname
|
9
|
+
}
|
10
|
+
|
11
|
+
parser = \
|
12
|
+
OptionParser.new do |o|
|
13
|
+
o.banner = "Usage: #{o.program_name} COMMAND [OPTIONS] FILES"
|
14
|
+
o.separator ""
|
15
|
+
o.separator "Commands"
|
16
|
+
o.separator " rules: generates firewall rules for the given host"
|
17
|
+
#o.separator " graph: geneates a graphviz visualization of your topology"
|
18
|
+
o.separator ""
|
19
|
+
o.separator "Options"
|
20
|
+
|
21
|
+
o.on("-h", "--host HOST","the host to generate rules for") do |host|
|
22
|
+
options[:host] = host
|
23
|
+
end
|
24
|
+
|
25
|
+
o.on("-D","--debug","dumps lots of information about your topology's hosts/templates/services") do
|
26
|
+
$ASBESTOSDEBUG = true
|
27
|
+
end
|
28
|
+
|
29
|
+
o.on("--debug-stderr","suppresses the line-numbered output on stderr") do
|
30
|
+
options[:stderr] = true
|
31
|
+
end
|
32
|
+
|
33
|
+
o.on("--help","displays this help information") do
|
34
|
+
puts o
|
35
|
+
end
|
36
|
+
|
37
|
+
o.separator ""
|
38
|
+
o.separator "Examples"
|
39
|
+
o.separator " Generate rules for the current host:"
|
40
|
+
o.separator " $ #{o.program_name} rules my_services.rb my_hosts.rb"
|
41
|
+
o.separator ""
|
42
|
+
o.separator " Generate rules for an arbitrary host:"
|
43
|
+
o.separator " $ #{o.program_name} rules --host some_host my_services.rb my_hosts.rb"
|
44
|
+
o.separator ""
|
45
|
+
o.separator "DSL Examples"
|
46
|
+
o.separator " Please see #{Asbestos::HOMEPAGE}"
|
47
|
+
|
48
|
+
o.parse!
|
49
|
+
end
|
50
|
+
|
51
|
+
command = \
|
52
|
+
if ARGV.first.end_with? '.rb'
|
53
|
+
'rules'
|
54
|
+
else
|
55
|
+
ARGV.shift
|
56
|
+
end
|
57
|
+
|
58
|
+
files = ARGV
|
59
|
+
|
60
|
+
files.each do |file|
|
61
|
+
require Pathname.new(file).realpath.to_s
|
62
|
+
end
|
63
|
+
|
64
|
+
if $ASBESTOSDEBUG
|
65
|
+
$stderr.puts "-"*10 + "Asbestos Debug Enabled--" + "-"*10
|
66
|
+
$stderr.puts
|
67
|
+
$stderr.puts "Known RuleSets #{Asbestos::RuleSet.all.keys}"
|
68
|
+
$stderr.puts "Known Services #{Asbestos::Service.all.keys}"
|
69
|
+
$stderr.puts
|
70
|
+
$stderr.puts "Dumping all Hosts:"
|
71
|
+
$stderr.puts
|
72
|
+
Asbestos::Host.all.each do |_, host|
|
73
|
+
$stderr.puts "-"*20 + host.name.to_s + "-"*20
|
74
|
+
$stderr.puts host.debug
|
75
|
+
$stderr.puts host.rules
|
76
|
+
$stderr.puts
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
|
81
|
+
|
82
|
+
case command
|
83
|
+
when "rules"
|
84
|
+
if $ASBESTOSDEBUG
|
85
|
+
$stderr.puts '-' * 20
|
86
|
+
$stderr.puts "Running command 'rules' for host #{options[:host]}"
|
87
|
+
$stderr.puts '-' * 20
|
88
|
+
end
|
89
|
+
|
90
|
+
Host.all[options[:host]].tap do |host|
|
91
|
+
unless host
|
92
|
+
puts "Asbestos doesn't know about host '#{options[:host]}'!"
|
93
|
+
puts
|
94
|
+
puts "You've defined hosts:"
|
95
|
+
Host.all.keys.each do |name|
|
96
|
+
puts " - #{name}"
|
97
|
+
end
|
98
|
+
puts
|
99
|
+
puts "Try `asbestos rules --host #{Host.all.keys.first} #{files.join(' ')}`"
|
100
|
+
exit
|
101
|
+
end
|
102
|
+
host.rules.tap do |rules|
|
103
|
+
rules.each_with_index do |rule, line_number|
|
104
|
+
puts rule
|
105
|
+
$stderr.puts "#{line_number.to_s.rjust(rules.length.to_s.length)}: #{rule}" if options[:stderr]
|
106
|
+
end
|
107
|
+
end
|
108
|
+
end
|
109
|
+
#when "graph"
|
110
|
+
else
|
111
|
+
puts parser
|
112
|
+
end
|
@@ -0,0 +1,72 @@
|
|
1
|
+
#
|
2
|
+
# This is just a large contrived example. :)
|
3
|
+
#
|
4
|
+
|
5
|
+
address :the_office, "1.2.3.4"
|
6
|
+
|
7
|
+
|
8
|
+
|
9
|
+
host_template "squidco_host" do
|
10
|
+
interface :loopback, :lo0
|
11
|
+
interface :internal, :bond0
|
12
|
+
interface :external, :bond1
|
13
|
+
|
14
|
+
accept_from_self
|
15
|
+
allow_related_established
|
16
|
+
icmp_protection allowed_from: :the_office
|
17
|
+
sanity_check
|
18
|
+
|
19
|
+
runs :ssh, on: :internal, port: 22022
|
20
|
+
runs :ssh, on: :external, port: 22022, from: :the_office
|
21
|
+
runs :monit, on: :external, from: :the_office
|
22
|
+
end
|
23
|
+
|
24
|
+
host_template "vps_host" do
|
25
|
+
interface :external, :eth0
|
26
|
+
|
27
|
+
runs :ssh, on: :external, port: 22022
|
28
|
+
runs :worker_status, on: :external, from: :the_office
|
29
|
+
end
|
30
|
+
|
31
|
+
|
32
|
+
|
33
|
+
service :worker_status do
|
34
|
+
port 1337
|
35
|
+
end
|
36
|
+
|
37
|
+
|
38
|
+
|
39
|
+
2.times do |i|
|
40
|
+
squidco_host "loadbalancer_#{i}" do
|
41
|
+
group :loadbalancers
|
42
|
+
|
43
|
+
runs :http, on: :external
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
5.times do |i|
|
48
|
+
squidco_host "app_#{i}" do
|
49
|
+
group :app_hosts
|
50
|
+
|
51
|
+
runs :http, on: :internal, from: {:loadbalancers => :internal}
|
52
|
+
runs :http, on: :external, from: :the_office
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
3.times do |i|
|
57
|
+
squidco_host "db_#{i}" do
|
58
|
+
group :db_hosts
|
59
|
+
|
60
|
+
runs :mongodb, on: :internal, from: {:app_hosts => :internal}
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
squidco_host "background_queue" do
|
65
|
+
runs :redis, on: :external, from: [:the_office, {:background_workers => :external}]
|
66
|
+
end
|
67
|
+
|
68
|
+
5.times do |i|
|
69
|
+
vps_host "worker_#{i}" do
|
70
|
+
group :background_workers
|
71
|
+
end
|
72
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
|
2
|
+
host 'app_host' do
|
3
|
+
interface :internal, :eth0
|
4
|
+
# 'internal' is an arbitrary name, you can make it anything you want
|
5
|
+
|
6
|
+
runs :ssh
|
7
|
+
runs :http
|
8
|
+
end
|
9
|
+
|
10
|
+
host 'db_host' do
|
11
|
+
runs :ssh
|
12
|
+
runs :mongodb, from: { Host['app_host'] => :internal }
|
13
|
+
end
|
14
|
+
|
15
|
+
host 'db_host_more_specific' do
|
16
|
+
runs :ssh
|
17
|
+
runs :mongodb, from: { Host['app_host'] => :internal }
|
18
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
|
2
|
+
host 'app_host' do
|
3
|
+
interface :internal, :eth0
|
4
|
+
|
5
|
+
runs :ssh
|
6
|
+
runs :http
|
7
|
+
end
|
8
|
+
|
9
|
+
host 'dax' do
|
10
|
+
interface :internal, :eth0
|
11
|
+
end
|
12
|
+
|
13
|
+
host 'db_host' do
|
14
|
+
interface :internal, :eth0
|
15
|
+
|
16
|
+
runs :ssh
|
17
|
+
runs :mongodb, on: :internal, from: { Host['app_host'] => :internal,
|
18
|
+
Host['dax'] => :internal }
|
19
|
+
end
|
@@ -0,0 +1,39 @@
|
|
1
|
+
|
2
|
+
host 'app_host_0' do
|
3
|
+
group :app_hosts
|
4
|
+
|
5
|
+
interface :internal, :eth0
|
6
|
+
|
7
|
+
runs :ssh
|
8
|
+
runs :http
|
9
|
+
end
|
10
|
+
|
11
|
+
host 'app_host_1' do
|
12
|
+
group :app_hosts
|
13
|
+
|
14
|
+
interface :internal, :eth0
|
15
|
+
|
16
|
+
runs :ssh
|
17
|
+
runs :http
|
18
|
+
end
|
19
|
+
|
20
|
+
host 'app_host_2' do
|
21
|
+
group :app_hosts
|
22
|
+
|
23
|
+
interface :internal, :eth0
|
24
|
+
|
25
|
+
runs :ssh
|
26
|
+
runs :http
|
27
|
+
end
|
28
|
+
|
29
|
+
host 'dax' do
|
30
|
+
interface :internal, :eth0
|
31
|
+
end
|
32
|
+
|
33
|
+
host 'db_host' do
|
34
|
+
interface :internal, :eth0
|
35
|
+
|
36
|
+
runs :ssh
|
37
|
+
runs :mongodb, on: :internal, from: { :app_hosts => :internal,
|
38
|
+
Host['dax'] => :internal }
|
39
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
|
2
|
+
host_template 'app_host' do
|
3
|
+
group :app_hosts
|
4
|
+
|
5
|
+
interface :internal, :eth0
|
6
|
+
|
7
|
+
runs :ssh
|
8
|
+
runs :http
|
9
|
+
end
|
10
|
+
|
11
|
+
0.upto(2) do |i|
|
12
|
+
app_host "app_host_#{i}"
|
13
|
+
end
|
14
|
+
|
15
|
+
app_host 'app_host_3' do
|
16
|
+
runs :nfs
|
17
|
+
end
|
18
|
+
|
19
|
+
host 'dax' do
|
20
|
+
interface :internal, :eth0
|
21
|
+
end
|
22
|
+
|
23
|
+
host 'db_host' do
|
24
|
+
interface :internal, :eth0
|
25
|
+
|
26
|
+
runs :ssh
|
27
|
+
runs :mongodb, on: :internal, from: { :app_hosts => :internal,
|
28
|
+
Host['dax'] => :internal }
|
29
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
|
2
|
+
host 'dax' do
|
3
|
+
interface :external, :eth0 #=> address is "dax_external"
|
4
|
+
interface :dmz, [:eth1, :eth2] #=> addresses are "dax_dmz_eth1" and "dax_dmz_eth2"
|
5
|
+
|
6
|
+
runs :ssh, from: {Host['kira'] => :external}
|
7
|
+
end
|
8
|
+
|
9
|
+
|
10
|
+
host 'kira' do
|
11
|
+
group :developers
|
12
|
+
|
13
|
+
interface :external, :eth3 do |host|
|
14
|
+
[host.groups.join, host.name, 'foo'].join('_')
|
15
|
+
end
|
16
|
+
#=> address is "developers_kira_foo"
|
17
|
+
|
18
|
+
interface :internal, :eth4, 'bar' #=> address is "bar"
|
19
|
+
end
|
@@ -0,0 +1,37 @@
|
|
1
|
+
rule_set :icmp_protection do
|
2
|
+
accept :chain => :output,
|
3
|
+
:protocol => :icmp,
|
4
|
+
:icmp_type => 'echo-request',
|
5
|
+
:comment => "allow us to ping others"
|
6
|
+
|
7
|
+
accept :protocol => :icmp,
|
8
|
+
:icmp_type => 'echo-reply',
|
9
|
+
:comment => "allow us to receive ping responses"
|
10
|
+
|
11
|
+
|
12
|
+
interfaces[:external].each do |interface|
|
13
|
+
from_each_address(allowed_from) do |address|
|
14
|
+
accept :protocol => :icmp,
|
15
|
+
:icmp_type => 'echo-request',
|
16
|
+
:interface => interface,
|
17
|
+
:remote_address => address,
|
18
|
+
:limit => '22s',
|
19
|
+
:comment => "allow icmp from #{address}"
|
20
|
+
end
|
21
|
+
|
22
|
+
drop :protocol => :icmp,
|
23
|
+
:interface => interface,
|
24
|
+
:comment => "drop any icmp packets that haven't been explicitly allowed"
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
address :monitoring, 'pinger.monitoringservice.com'
|
29
|
+
|
30
|
+
host 'app_host' do
|
31
|
+
interface :external, ['eth1', 'eth1:0']
|
32
|
+
|
33
|
+
icmp_protection allowed_from: :monitoring
|
34
|
+
|
35
|
+
runs :ssh
|
36
|
+
end
|
37
|
+
|
data/lib/asbestos.rb
ADDED
@@ -0,0 +1,108 @@
|
|
1
|
+
require 'asbestos/metadata'
|
2
|
+
|
3
|
+
require 'socket'
|
4
|
+
require 'system/getifaddrs'
|
5
|
+
|
6
|
+
require 'forwardable'
|
7
|
+
|
8
|
+
|
9
|
+
module Asbestos
|
10
|
+
|
11
|
+
def self.hostname
|
12
|
+
Socket.gethostname[/[^.]*/]
|
13
|
+
end
|
14
|
+
|
15
|
+
def self.interfaces
|
16
|
+
System.get_ifaddrs
|
17
|
+
end
|
18
|
+
|
19
|
+
def self.os
|
20
|
+
case
|
21
|
+
when RUBY_PLATFORM[/linux/i]
|
22
|
+
:linux
|
23
|
+
when RUBY_PLATFORM[/darwin/i]
|
24
|
+
:darwin
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
def self.firewall
|
29
|
+
case os
|
30
|
+
when :linux
|
31
|
+
Asbestos::Firewall::IPTables
|
32
|
+
when :darwin
|
33
|
+
#FIXME
|
34
|
+
Asbestos::Firewall::IPTables
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
def self.reset!
|
39
|
+
[
|
40
|
+
Host.all,
|
41
|
+
Host.groups,
|
42
|
+
HostTemplate.all,
|
43
|
+
Address.all,
|
44
|
+
RuleSet.all,
|
45
|
+
Service.all,
|
46
|
+
].each do |collection|
|
47
|
+
collection.delete_if {|_| true}
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
#
|
52
|
+
# Didn't want to monkeypatch the Hash class.
|
53
|
+
#
|
54
|
+
def self.with_indifferent_access!(hash)
|
55
|
+
class << hash
|
56
|
+
def [](key)
|
57
|
+
fetch key.to_sym
|
58
|
+
rescue KeyError # key not found
|
59
|
+
nil
|
60
|
+
end
|
61
|
+
|
62
|
+
def []=(key, value)
|
63
|
+
store key.to_sym, value
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
module ClassCollection
|
69
|
+
def self.included(base)
|
70
|
+
base.extend ClassMethods
|
71
|
+
end
|
72
|
+
|
73
|
+
module ClassMethods
|
74
|
+
def class_collection(name, base = self)
|
75
|
+
# this is a little nasty, the 'name' variable isn't available
|
76
|
+
# in the scope of the eigenclass, so we have to class_eval
|
77
|
+
# the eigenclass
|
78
|
+
(class << base; self; end).instance_eval do
|
79
|
+
extend ::Forwardable
|
80
|
+
|
81
|
+
attr_accessor name
|
82
|
+
def_delegators name, :[], :[]= if name == :all
|
83
|
+
end
|
84
|
+
|
85
|
+
Hash.new.tap do |hash|
|
86
|
+
Asbestos.with_indifferent_access! hash
|
87
|
+
base.instance_variable_set "@#{name}", hash
|
88
|
+
end
|
89
|
+
end
|
90
|
+
end
|
91
|
+
|
92
|
+
end
|
93
|
+
|
94
|
+
end
|
95
|
+
|
96
|
+
|
97
|
+
|
98
|
+
|
99
|
+
require 'asbestos/rule_set'
|
100
|
+
require 'asbestos/service'
|
101
|
+
require 'asbestos/host_template'
|
102
|
+
require 'asbestos/host'
|
103
|
+
require 'asbestos/address'
|
104
|
+
require 'asbestos/dsl'
|
105
|
+
|
106
|
+
%w{firewalls services rule_sets}.each do |dir|
|
107
|
+
Dir["#{File.dirname(__FILE__)}/asbestos/#{dir}/*.rb"].each { |f| require File.expand_path(f) }
|
108
|
+
end
|