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.
- data/.gitignore +19 -0
- data/.simplecov +3 -0
- data/Gemfile +6 -0
- data/README.md +71 -0
- data/Rakefile +11 -0
- data/bin/fog-bouncer +10 -0
- data/bouncer.jpg +0 -0
- data/fog-bouncer.gemspec +25 -0
- data/lib/fog/bouncer.rb +95 -0
- data/lib/fog/bouncer/cli.rb +23 -0
- data/lib/fog/bouncer/group.rb +140 -0
- data/lib/fog/bouncer/group_manager.rb +75 -0
- data/lib/fog/bouncer/ip_permissions.rb +51 -0
- data/lib/fog/bouncer/protocols.rb +115 -0
- data/lib/fog/bouncer/security.rb +88 -0
- data/lib/fog/bouncer/source.rb +87 -0
- data/lib/fog/bouncer/source_manager.rb +59 -0
- data/lib/fog/bouncer/sources.rb +61 -0
- data/lib/fog/bouncer/version.rb +5 -0
- data/spec/fog/bouncer/group_spec.rb +61 -0
- data/spec/fog/bouncer/protocols_spec.rb +25 -0
- data/spec/fog/bouncer/security_spec.rb +85 -0
- data/spec/fog/bouncer/source_spec.rb +49 -0
- data/spec/fog/bouncer/sources/cidr_spec.rb +9 -0
- data/spec/fog/bouncer_spec.rb +45 -0
- data/spec/helper.rb +28 -0
- data/spec/support/security/private.rb +46 -0
- metadata +179 -0
data/.gitignore
ADDED
data/.simplecov
ADDED
data/Gemfile
ADDED
data/README.md
ADDED
@@ -0,0 +1,71 @@
|
|
1
|
+
# fog-bouncer
|
2
|
+
|
3
|
+

|
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.
|
data/Rakefile
ADDED
data/bin/fog-bouncer
ADDED
data/bouncer.jpg
ADDED
Binary file
|
data/fog-bouncer.gemspec
ADDED
@@ -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
|
data/lib/fog/bouncer.rb
ADDED
@@ -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
|