fog-bouncer 0.0.6

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,19 @@
1
+ *.gem
2
+ *.rbc
3
+ *.swp
4
+ .bundle
5
+ .config
6
+ .yardoc
7
+ Gemfile.lock
8
+ InstalledFiles
9
+ Makefile
10
+ _yardoc
11
+ coverage
12
+ doc/
13
+ lib/bundler/man
14
+ pkg
15
+ rdoc
16
+ spec/reports
17
+ test/tmp
18
+ test/version_tmp
19
+ tmp
@@ -0,0 +1,3 @@
1
+ SimpleCov.start do
2
+ add_filter "spec/"
3
+ end
data/Gemfile ADDED
@@ -0,0 +1,6 @@
1
+ source 'http://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in fog-bouncer.gemspec
4
+ gemspec
5
+
6
+ gem 'simplecov', :require => false, :group => :test
@@ -0,0 +1,71 @@
1
+ # fog-bouncer
2
+
3
+ ![fog-bouncer](https://github.com/dylanegan/fog-bouncer/raw/master/bouncer.jpg)
4
+
5
+ A simple way to define and manage security groups for AWS with the backing support from fog.
6
+
7
+ ## Usage
8
+
9
+ ### Installation
10
+
11
+ ```
12
+ gem install fog-bouncer
13
+ ```
14
+
15
+ ### Doorlists
16
+
17
+ Create a doorlist to manage. Drop it in your project or anywhere on your filesystem. For the following lets assume it is at `/tmp/fog-bouncer.rb`.
18
+
19
+ ```
20
+ Fog::Bouncer.security :private do
21
+ account "user", "1234567890"
22
+
23
+ group "base", "Base Security Group" do
24
+ source "0.0.0.0/0" do
25
+ icmp 8..0
26
+ end
27
+
28
+ source "10.0.0.0/8" do
29
+ tcp 80, 22, 8080..8081
30
+ end
31
+ end
32
+
33
+ group "other", "Other Security Group" do
34
+ source "default@user" do
35
+ tcp 22
36
+ end
37
+ end
38
+ end
39
+ ```
40
+
41
+ ### Console
42
+
43
+ ```
44
+ ➜ ~ export AWS_ACCOUNT_ID=... \
45
+ AWS_ACCESS_KEY_ID=... \
46
+ AWS_SECRET_ACCESS_KEY=...
47
+
48
+ ➜ ~ irb
49
+ 1.9.3p0 :001 > require 'fog/bouncer'
50
+ => true
51
+ 1.9.3p0 :002 > doorlist = Fog::Bouncer.load('/tmp/fog-bouncer.rb')
52
+ 1.9.3p0 :003 > doorlist.import_remote_groups
53
+ 1.9.3p0 :004 > doorlist.sync
54
+ ```
55
+
56
+ ### CLI (TBD)
57
+
58
+ ```
59
+ ➜ ~ export AWS_ACCOUNT_ID=... \
60
+ AWS_ACCESS_KEY_ID=... \
61
+ AWS_SECRET_ACCESS_KEY=...
62
+
63
+ ➜ ~ fog-bouncer sync --list private --file /tmp/fog-bouncer.rb
64
+ ```
65
+
66
+ ## Environment
67
+
68
+ * `AWS_ACCOUNT_ID` - your Amazon Web Services account ID
69
+ * `AWS_ACCESS_KEY_ID` - your Amazon Web Services access key ID
70
+ * `AWS_SECRET_ACCESS_KEY` - your Amazon Web Services secret access key
71
+ * `PROVIDER_REGION` - your Amazon Web Services region. Defaults to us-east-1.
@@ -0,0 +1,11 @@
1
+ #!/usr/bin/env rake
2
+ require "bundler/gem_tasks"
3
+
4
+ require 'rake/testtask'
5
+
6
+ Rake::TestTask.new do |t|
7
+ t.libs.push "lib"
8
+ t.libs.push "spec"
9
+ t.test_files = FileList['spec/**/*_spec.rb']
10
+ t.verbose = true
11
+ end
@@ -0,0 +1,10 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ $:.unshift File.expand_path("../../lib", __FILE__)
4
+
5
+ require "rubygems"
6
+ require "bundler/setup"
7
+
8
+ require "fog/bouncer/cli"
9
+
10
+ Fog::Bouncer::CLI.run
Binary file
@@ -0,0 +1,25 @@
1
+ # -*- encoding: utf-8 -*-
2
+ require File.expand_path('../lib/fog/bouncer/version', __FILE__)
3
+
4
+ Gem::Specification.new do |gem|
5
+ gem.authors = ["Dylan Egan"]
6
+ gem.email = ["dylanegan@gmail.com"]
7
+ gem.description = %q{A simple way to define and manage security groups for AWS with the backing support of fog.}
8
+ gem.summary = %q{A manage security.}
9
+ gem.homepage = ""
10
+
11
+ gem.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
12
+ gem.files = `git ls-files`.split("\n")
13
+ gem.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
14
+ gem.name = "fog-bouncer"
15
+ gem.require_paths = ["lib"]
16
+ gem.version = Fog::Bouncer::VERSION
17
+
18
+ gem.add_dependency "clamp", "~> 0.3.0"
19
+ gem.add_dependency "fog", "~> 1.2.0"
20
+ gem.add_dependency "ipaddress", "~> 0.8.0"
21
+ gem.add_dependency "rake"
22
+ gem.add_dependency "scrolls", "~> 0.0.5"
23
+
24
+ gem.add_development_dependency "minitest"
25
+ end
@@ -0,0 +1,95 @@
1
+ require "fog"
2
+ require "fog/bouncer/group"
3
+ require "fog/bouncer/protocols"
4
+ require "fog/bouncer/security"
5
+ require "fog/bouncer/sources"
6
+ require "fog/bouncer/version"
7
+
8
+ require "fog/bouncer/ip_permissions"
9
+ require "fog/bouncer/group_manager"
10
+ require "fog/bouncer/source_manager"
11
+
12
+ require "scrolls"
13
+
14
+ module Fog
15
+ module Bouncer
16
+ def self.aws_account_id
17
+ ENV['AWS_ACCOUNT_ID']
18
+ end
19
+
20
+ def self.doorlists
21
+ @doorlists ||= {}
22
+ end
23
+
24
+ def self.fog
25
+ @fog ||= Fog::Compute.new(
26
+ :provider => "AWS",
27
+ :region => (ENV['PROVIDER_REGION'] || 'us-east-1'),
28
+ :aws_access_key_id => ENV['AWS_ACCESS_KEY_ID'],
29
+ :aws_secret_access_key => ENV['AWS_SECRET_ACCESS_KEY']
30
+ )
31
+ end
32
+
33
+ def self.log(data, &block)
34
+ log! unless logging?
35
+ Scrolls.log({ 'fog-bouncer' => true, 'pretending' => pretending? }.merge(data), &block)
36
+ end
37
+
38
+ def self.log!
39
+ Scrolls::Log.start(logger)
40
+ @logging = true
41
+ end
42
+
43
+ def self.logger
44
+ @logger ||= STDOUT
45
+ end
46
+
47
+ def self.logger=(logger)
48
+ @logger = logger
49
+ end
50
+
51
+ def self.logging?
52
+ @logging ||= false
53
+ end
54
+
55
+ def self.load(file)
56
+ if file && File.exists?(file)
57
+ Fog::Bouncer.log(load: true, file: file) do
58
+ instance_eval(File.read(file))
59
+ end
60
+ end
61
+ end
62
+
63
+ def self.pretend(&block)
64
+ if block_given?
65
+ @pretend = true
66
+ yield
67
+ @pretend = false
68
+ else
69
+ @pretend ||= false
70
+ end
71
+ end
72
+
73
+ def self.pretend=(value)
74
+ @pretend = value
75
+ end
76
+
77
+ def self.pretend!
78
+ @pretend = true
79
+ end
80
+
81
+ def self.pretending?
82
+ !!pretend
83
+ end
84
+
85
+ def self.reset
86
+ @doorlists = {}
87
+ end
88
+
89
+ def self.security(name, &block)
90
+ Fog::Bouncer.log(security: true, name: name) do
91
+ doorlists[name] = Fog::Bouncer::Security.new(name, &block)
92
+ end
93
+ end
94
+ end
95
+ end
@@ -0,0 +1,23 @@
1
+ require "clamp"
2
+
3
+ require "fog/bouncer"
4
+
5
+ module Fog
6
+ module Bouncer
7
+ module CLI
8
+ def self.run(*a)
9
+ MainCommand.run(*a)
10
+ end
11
+
12
+ class AbstractCommand < Clamp::Command
13
+ option "--version", :flag, "show version" do
14
+ puts "fog-bounder #{Fog::Bouncer::VERSION}"
15
+ exit 0
16
+ end
17
+ end
18
+
19
+ class MainCommand < AbstractCommand
20
+ end
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,140 @@
1
+ module Fog
2
+ module Bouncer
3
+ class Group
4
+ attr_reader :name, :description, :security
5
+ attr_accessor :local, :remote
6
+
7
+ def self.log(data, &block)
8
+ Fog::Bouncer.log({ group: true }.merge(data), &block)
9
+ end
10
+
11
+ def log(data, &block)
12
+ self.class.log({ name: name }.merge(data), &block)
13
+ end
14
+
15
+ def initialize(name, description, security, &block)
16
+ @name = name
17
+ @description = description
18
+ @security = security
19
+ @using = []
20
+ if block_given?
21
+ @local = true
22
+ instance_eval(&block)
23
+ apply_definitions
24
+ end
25
+ end
26
+
27
+ def extra_remote_sources
28
+ sources.select { |source| !source.local? && source.remote? }
29
+ end
30
+
31
+ def local?
32
+ !!local
33
+ end
34
+
35
+ def missing_remote_sources
36
+ sources.select { |source| source.local? && !source.remote? }
37
+ end
38
+
39
+ def remote?
40
+ !remote.nil?
41
+ end
42
+
43
+ def sources
44
+ @sources ||= []
45
+ end
46
+
47
+ def add_source(source, &block)
48
+ if existing = sources.find { |s| s.match(source) }
49
+ existing.instance_eval(&block)
50
+ else
51
+ sources << Sources.for(source, self, &block)
52
+ end
53
+ end
54
+
55
+ def sync
56
+ log(sync: true) do
57
+ create_missing_remote
58
+ synchronize_sources
59
+ end
60
+ end
61
+
62
+ def use(name)
63
+ @using << security.definitions(name)
64
+ end
65
+
66
+ def create_missing_remote
67
+ unless remote?
68
+ log(create_missing_remote: true) do
69
+ unless Fog::Bouncer.pretending?
70
+ @remote = Fog::Bouncer.fog.security_groups.create(:name => name, :description => description)
71
+ @remote.reload
72
+ end
73
+ end
74
+ end
75
+ end
76
+
77
+ def synchronize_sources
78
+ log(synchronize_sources: true) do
79
+ SourceManager.new(self).synchronize
80
+ end
81
+ end
82
+
83
+ def destroy
84
+ revoke
85
+ if remote?
86
+ if name != "default"
87
+ log(destroy: true) do
88
+ unless Fog::Bouncer.pretending?
89
+ remote.destroy
90
+ @remote = nil
91
+ @security.groups.delete_if { |g| g.name == name }
92
+ end
93
+ end
94
+ else
95
+ log(destroy: false, group_name: name)
96
+ end
97
+ end
98
+ end
99
+
100
+ def revoke
101
+ permissions = sources.map do |source|
102
+ source.protocols.select { |p| p.remote? }
103
+ end.flatten.compact
104
+
105
+ if remote? && permissions.any?
106
+ log(revoke: true) do
107
+ remote.connection.revoke_security_group_ingress(name, "IpPermissions" => IPPermissions.from(permissions)) unless Fog::Bouncer.pretending?
108
+ permissions.each do |protocol|
109
+ log({revoked: true}.merge(protocol.to_log))
110
+ protocol.source.protocols.delete_if { |p| p == protocol } unless Fog::Bouncer.pretending?
111
+ end
112
+ end
113
+ end
114
+ end
115
+
116
+ def ==(other)
117
+ name == other.name &&
118
+ description == other.description
119
+ end
120
+
121
+ def inspect
122
+ "<#{self.class.name} @name=#{name.inspect} @description=#{description.inspect} @local=#{local} @remote=#{remote} @sources=#{sources.inspect}>"
123
+ end
124
+
125
+ private
126
+
127
+ def apply_definitions
128
+ return if @using.empty?
129
+
130
+ @using.each do |definition|
131
+ add_source(definition[:source], &definition[:block])
132
+ end
133
+ end
134
+
135
+ def source(source, &block)
136
+ add_source(source, &block)
137
+ end
138
+ end
139
+ end
140
+ end
@@ -0,0 +1,75 @@
1
+ module Fog
2
+ module Bouncer
3
+ class GroupManager
4
+ def self.log(data, &block)
5
+ Fog::Bouncer.log({group_manager: true}.merge(data), &block)
6
+ end
7
+
8
+ def log(data, &block)
9
+ self.class.log(data, &block)
10
+ end
11
+
12
+ def initialize(security)
13
+ @security = security
14
+ end
15
+
16
+ def synchronize
17
+ log(synchronize: true) do
18
+ create_missing_remote_groups
19
+ synchronize_rules
20
+ remove_extra_remote_groups
21
+ end
22
+ end
23
+
24
+ def clear
25
+ @security.groups.each do |group|
26
+ group.revoke
27
+ end
28
+
29
+ @security.groups.each do |group|
30
+ begin
31
+ group.destroy
32
+ rescue Fog::Compute::AWS::Error => exception
33
+ unless exception.message =~ /InvalidGroup.InUse/
34
+ raise
35
+ end
36
+ log group_in_use: true, group_name: group.name
37
+ end
38
+ end
39
+ end
40
+
41
+ private
42
+
43
+ def create_missing_remote_groups
44
+ @security.missing_remote_groups.each do |group|
45
+ log(create_missing_remote_group: true, group_name: group.name) do
46
+ group.create_missing_remote
47
+ end
48
+ end
49
+ end
50
+
51
+ def remove_extra_remote_groups
52
+ @security.extra_remote_groups.each do |group|
53
+ log(remove_extra_remote_group: true, group_name: group.name) do
54
+ begin
55
+ group.destroy
56
+ rescue Fog::Compute::AWS::Error => exception
57
+ unless exception.message =~ /InvalidGroup.InUse/
58
+ raise
59
+ end
60
+ log group_in_use: true, group_name: group.name
61
+ end
62
+ end
63
+ end
64
+ end
65
+
66
+ def synchronize_rules
67
+ @security.groups.each do |group|
68
+ log(synchronize_rules: true, group_name: group.name) do
69
+ group.sync
70
+ end
71
+ end
72
+ end
73
+ end
74
+ end
75
+ end