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
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,5 @@
1
+
2
+ host 'app_host' do
3
+ runs :ssh
4
+ runs :http
5
+ 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,7 @@
1
+
2
+ address :load_balancers, ['lb0.myprovider.com', 'lb1.myprovider.com']
3
+ address :monitoring, 'pinger.monitoringservice.com'
4
+
5
+ host 'app_host' do
6
+ runs :http, from: [:load_balancers, :monitoring]
7
+ 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,9 @@
1
+
2
+ service :my_service do
3
+ ports 1337
4
+ protocols :tcp, :udp
5
+ end
6
+
7
+ host 'app_host' do
8
+ runs :my_service
9
+ 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
+
@@ -0,0 +1,8 @@
1
+
2
+ rule_set :creates_chaos do
3
+ command "-A INPUT -m statistic --mode random --probability 0.01 -j REJECT --reject-with host-unreach"
4
+ end
5
+
6
+ host 'app_host' do
7
+ creates_chaos
8
+ end
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