fog-bouncer 0.0.6

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