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