CloudyScripts 0.0.2 → 0.0.3
Sign up to get free protection for your applications and to get access to all the features.
- data/LICENSE +1 -1
- data/README.rdoc +22 -0
- data/Rakefile +3 -3
- data/lib/cloudyscripts.rb +21 -2
- data/lib/help/dm_crypt_helper.rb +22 -7
- data/lib/help/remote_command_handler.rb +16 -2
- data/lib/help/script_execution_state.rb +1 -0
- data/lib/scripts/ec2/dm_encrypt.rb +33 -15
- data/lib/scripts/ec2/ec2_script.rb +15 -4
- metadata +4 -4
- data/README +0 -1
data/LICENSE
CHANGED
@@ -1,4 +1,4 @@
|
|
1
|
-
Copyright (c)
|
1
|
+
Copyright (c) 2010 Matthias Jung
|
2
2
|
|
3
3
|
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
4
4
|
|
data/README.rdoc
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
# =About
|
2
|
+
# CloudyScripts is a library that implements tasks that support common
|
3
|
+
# usecases on Cloud Computing Infrastructures (such as Amazon EC2 or Rackspace).
|
4
|
+
# It aims to facilitate the implementation of usecases that are not directly
|
5
|
+
# available via the providers' API (e.g. like encrypting storage,
|
6
|
+
# migrating instances betweem accounts, activating HTTPS). The scripts typically
|
7
|
+
# use the provider APIs plus remote access to command-line tools installed on
|
8
|
+
# the instances themselves.
|
9
|
+
#
|
10
|
+
# =Installation and Usage
|
11
|
+
# ==Installation
|
12
|
+
# <tt>gem install CloudyScripts</tt>
|
13
|
+
#
|
14
|
+
# ==Usage
|
15
|
+
# All scripts are available under /lib/scripts/<provider>
|
16
|
+
# They are initialized with a set of parameters and return a well-define
|
17
|
+
# set of return values.
|
18
|
+
#
|
19
|
+
# =Scripts
|
20
|
+
# Here are the scripts implemented so far:
|
21
|
+
# * #DmEncrypt (encrypt Amazon EBS Storage using dm-encrypt)
|
22
|
+
#
|
data/Rakefile
CHANGED
@@ -12,9 +12,9 @@ require 'rake/testtask'
|
|
12
12
|
|
13
13
|
spec = Gem::Specification.new do |s|
|
14
14
|
s.name = 'CloudyScripts'
|
15
|
-
s.version = '0.0.
|
15
|
+
s.version = '0.0.3'
|
16
16
|
s.has_rdoc = true
|
17
|
-
s.extra_rdoc_files = ['README', 'LICENSE']
|
17
|
+
s.extra_rdoc_files = ['README.rdoc', 'LICENSE']
|
18
18
|
s.summary = 'Scripts to facilitate programming for infrastructure clouds.'
|
19
19
|
s.description = s.summary
|
20
20
|
s.homepage = "http://elastic-security.com"
|
@@ -22,7 +22,7 @@ spec = Gem::Specification.new do |s|
|
|
22
22
|
s.author = 'Matthias Jung'
|
23
23
|
s.email = 'matthias.jung@gmail.com'
|
24
24
|
# s.executables = ['your_executable_here']
|
25
|
-
s.files = %w(LICENSE README Rakefile) + Dir.glob("{bin,lib,spec}/**/*")
|
25
|
+
s.files = %w(LICENSE README.rdoc Rakefile) + Dir.glob("{bin,lib,spec}/**/*")
|
26
26
|
s.require_path = "lib"
|
27
27
|
s.bindir = "bin"
|
28
28
|
s.has_rdoc = true
|
data/lib/cloudyscripts.rb
CHANGED
@@ -2,6 +2,25 @@ require 'rubygems'
|
|
2
2
|
require 'net/ssh'
|
3
3
|
require 'AWS'
|
4
4
|
|
5
|
+
# =About
|
6
|
+
# CloudyScripts is a library that implements tasks that support common
|
7
|
+
# usecases on Cloud Computing Infrastructures (such as Amazon EC2 or Rackspace).
|
8
|
+
# It aims to facilitate the implementation of usecases that are not directly
|
9
|
+
# available via the providers' API (e.g. like encrypting storage,
|
10
|
+
# migrating instances betweem accounts, activating HTTPS). The scripts typically
|
11
|
+
# use the provider APIs plus remote access to command-line tools installed on
|
12
|
+
# the instances themselves.
|
5
13
|
#
|
6
|
-
#
|
7
|
-
#
|
14
|
+
# =Installation and Usage
|
15
|
+
# ===Installation
|
16
|
+
# <tt>gem install CloudyScripts</tt>
|
17
|
+
#
|
18
|
+
# ===Usage
|
19
|
+
# All scripts are available under /lib/scripts/<em>provider</em>>
|
20
|
+
#
|
21
|
+
# ===Scripts
|
22
|
+
# Here are the scripts implemented so far:
|
23
|
+
# * #Scripts::EC2::DmEncrypt (encrypt Amazon EBS Storage using dm-encrypt)
|
24
|
+
#
|
25
|
+
class CloudyScripts
|
26
|
+
end
|
data/lib/help/dm_crypt_helper.rb
CHANGED
@@ -1,16 +1,23 @@
|
|
1
|
-
require '
|
1
|
+
require 'help/remote_command_handler'
|
2
|
+
|
3
|
+
# This class implements helper methods for Dm Encryption
|
4
|
+
# (see #Scripts::EC2::DmEncrypt)
|
2
5
|
|
3
6
|
class DmCryptHelper
|
4
7
|
|
8
|
+
# Passes an remote command handler object
|
9
|
+
# (see #Help::RemoteCommandHandler)
|
5
10
|
def set_ssh(ssh_session)
|
6
11
|
@ssh_session = ssh_session
|
7
12
|
end
|
8
13
|
|
14
|
+
# Installs the dm-crypt tools (if not yet done)
|
9
15
|
def install()
|
10
16
|
#TODO: dm-crypt seems to be installed automatically
|
11
17
|
true
|
12
18
|
end
|
13
19
|
|
20
|
+
# Checks if the dm-crypt tool is installed (true/false)
|
14
21
|
def tools_installed?()
|
15
22
|
@ssh_session.exec! "which dmsetup" do |ch, stream, data|
|
16
23
|
if stream == :stderr
|
@@ -26,9 +33,15 @@ class DmCryptHelper
|
|
26
33
|
true
|
27
34
|
end
|
28
35
|
|
36
|
+
# Encrypts the device and mounting it using dm-crypt tools.
|
37
|
+
# Params
|
38
|
+
# * name: name of the virtual volume
|
39
|
+
# * password: paraphrase to be used for encryption
|
40
|
+
# * device: device to be encrypted
|
41
|
+
# * path: path to which the encrypted device is mounted
|
29
42
|
def encrypt_storage(name, password, device, path)
|
30
43
|
# first: check if a file in /dev/mapper exists
|
31
|
-
if
|
44
|
+
if RemoteCommandHandler.file_exists?(@ssh_session, "/dev/mapper/dm-#{name}")
|
32
45
|
mapper_exists = true
|
33
46
|
else
|
34
47
|
mapper_exists = false
|
@@ -95,7 +108,7 @@ class DmCryptHelper
|
|
95
108
|
exec_string = "vgcreate vg-#{name} /dev/mapper/dm-#{name}"
|
96
109
|
puts "vg_exists == false; execute #{exec_string}"
|
97
110
|
@ssh_session.exec! exec_string do |ch, stream, data|
|
98
|
-
if stream == :stderr &&
|
111
|
+
if stream == :stderr && data != nil
|
99
112
|
err = "Failed during creation of volume group"
|
100
113
|
puts "#{err}: #{data}"
|
101
114
|
raise Exception.new(err)
|
@@ -105,7 +118,7 @@ class DmCryptHelper
|
|
105
118
|
exec_string = "lvcreate -n lv-#{name} -l100%FREE vg-#{name}"
|
106
119
|
puts "execute #{exec_string}"
|
107
120
|
@ssh_session.exec! exec_string do |ch, stream, data|
|
108
|
-
if stream == :stderr &&
|
121
|
+
if stream == :stderr && data != nil
|
109
122
|
err = "Failed during creation of logical volume"
|
110
123
|
puts "#{err}: #{data}"
|
111
124
|
raise Exception.new(err)
|
@@ -114,13 +127,13 @@ class DmCryptHelper
|
|
114
127
|
exec_string = "mkfs -t ext3 /dev/vg-#{name}/lv-#{name}"
|
115
128
|
puts "execute #{exec_string}"
|
116
129
|
@ssh_session.exec! exec_string #do |ch, stream, data|
|
117
|
-
#if stream == :stderr &&
|
130
|
+
#if stream == :stderr && data != nil
|
118
131
|
#err = "Failed during creation of file-system"
|
119
132
|
#puts "#{err}: #{data}"
|
120
133
|
#raise Exception.new(err)
|
121
134
|
#end
|
122
135
|
#end
|
123
|
-
if !
|
136
|
+
if !RemoteCommandHandler.file_exists?(@ssh_session,"/dev/vg-#{name}/lv-#{name}")
|
124
137
|
err = "Missing file: /dev/vg-#{name}/lv-#{name}"
|
125
138
|
raise Exception.new(err)
|
126
139
|
end
|
@@ -128,7 +141,7 @@ class DmCryptHelper
|
|
128
141
|
exec_string = "/sbin/vgchange -a y vg-#{name}"
|
129
142
|
puts "vg_exists == true; execute #{exec_string}"
|
130
143
|
@ssh_session.exec! exec_string do |ch, stream, data| #TODO: the right size instead L2G!
|
131
|
-
if stream == :stderr &&
|
144
|
+
if stream == :stderr && data != nil
|
132
145
|
err = "Failed during re-activation of volume group"
|
133
146
|
puts "#{err}: #{data}"
|
134
147
|
raise Exception.new(err)
|
@@ -137,10 +150,12 @@ class DmCryptHelper
|
|
137
150
|
end
|
138
151
|
end
|
139
152
|
|
153
|
+
# Check if the storage is encrypted (not yet implemented).
|
140
154
|
def test_storage_encryption(password, mount_point, path)
|
141
155
|
raise Exception.new("not yet implemented")
|
142
156
|
end
|
143
157
|
|
158
|
+
# Undo encryption for the volume specified by name and path
|
144
159
|
def undo_encryption(name, path)
|
145
160
|
exec_string = "umount #{path}"
|
146
161
|
puts "going to execute #{exec_string}"
|
@@ -1,11 +1,13 @@
|
|
1
1
|
require 'rubygems'
|
2
2
|
require 'net/ssh'
|
3
3
|
|
4
|
+
# Provides methods to be executed via ssh to remote instances.
|
4
5
|
class RemoteCommandHandler
|
5
6
|
def initialize
|
6
7
|
@crypto = DmCryptHelper.new #TODO: instantiate helpers for different tools
|
7
8
|
end
|
8
9
|
|
10
|
+
# Check if the path/file specified exists
|
9
11
|
def self.file_exists?(ssh_session, path)
|
10
12
|
result = true
|
11
13
|
ssh_session.exec!("ls #{path}") do |ch, stream, data|
|
@@ -15,28 +17,37 @@ class RemoteCommandHandler
|
|
15
17
|
end
|
16
18
|
result
|
17
19
|
end
|
18
|
-
|
20
|
+
|
21
|
+
# Connect to the machine as root using a keyfile.
|
22
|
+
# Params:
|
23
|
+
# * ip: ip address of the machine to connect to
|
24
|
+
# * keyfile: path of the keyfile to be used for authentication
|
19
25
|
def connect(ip, keyfile)
|
20
26
|
@ssh_session = Net::SSH.start(ip, 'root', :keys => [keyfile])
|
21
27
|
@crypto.set_ssh(@ssh_session)
|
22
28
|
end
|
23
29
|
|
30
|
+
# Disconnect the current handler
|
24
31
|
def disconnect
|
25
32
|
@ssh_session.close
|
26
33
|
end
|
27
34
|
|
35
|
+
# Installs the software package specified.
|
28
36
|
def install(software_package)
|
29
37
|
@crypto.install()
|
30
38
|
end
|
31
39
|
|
40
|
+
# Checks if the software package specified is installed.
|
32
41
|
def tools_installed?(software_package)
|
33
42
|
@crypto.tools_installed?
|
34
43
|
end
|
35
44
|
|
45
|
+
# Encrypt the storage (using the crypto-helper used, e.g. #Help::DmCryptHelper)
|
36
46
|
def encrypt_storage(name, password, device, path)
|
37
47
|
@crypto.encrypt_storage(name, password, device, path)
|
38
48
|
end
|
39
49
|
|
50
|
+
# Check if the storage is encrypted (using the crypto-helper used, e.g. #Help::DmCryptHelper)
|
40
51
|
def storage_encrypted?(password, device, path)
|
41
52
|
drive_mounted?(path) #TODO: must at least also check the name
|
42
53
|
end
|
@@ -79,6 +90,7 @@ class RemoteCommandHandler
|
|
79
90
|
drive_mounted
|
80
91
|
end
|
81
92
|
|
93
|
+
# Activates the encrypted volume, i.e. mounts it if not yet done.
|
82
94
|
def activate_encrypted_volume(name, path)
|
83
95
|
drive_mounted = drive_mounted?(path)
|
84
96
|
puts "drive #{path} mounted? #{drive_mounted}"
|
@@ -87,7 +99,7 @@ class RemoteCommandHandler
|
|
87
99
|
exec_string = "mount /dev/vg-#{name}/lv-#{name} #{path}"
|
88
100
|
puts "drive not mounted; execute: #{exec_string}"
|
89
101
|
@ssh_session.exec! "mount /dev/vg-#{name}/lv-#{name} #{path}" do |ch, stream, data|
|
90
|
-
if stream == :stderr &&
|
102
|
+
if stream == :stderr && data != nil
|
91
103
|
err = "Failed during mounting encrypted device"
|
92
104
|
puts "#{err}: #{data}"
|
93
105
|
puts "mount /dev/vg-#{name}/lv-#{name} #{path}"
|
@@ -97,10 +109,12 @@ class RemoteCommandHandler
|
|
97
109
|
end
|
98
110
|
end
|
99
111
|
|
112
|
+
# Unconfigure the storage (using the crypto-helper used, e.g. #Help::DmCryptHelper)
|
100
113
|
def undo_encryption(name, path)
|
101
114
|
@crypto.undo_encryption(name, path)
|
102
115
|
end
|
103
116
|
|
117
|
+
# Unmount the specified path.
|
104
118
|
def umount(path)
|
105
119
|
exec_string = "umount #{path}"
|
106
120
|
puts "going to execute #{exec_string}"
|
@@ -1,23 +1,28 @@
|
|
1
1
|
require "help/script_execution_state"
|
2
2
|
require "scripts/ec2/ec2_script"
|
3
|
+
require "help/remote_command_handler"
|
4
|
+
require "help/dm_crypt_helper"
|
5
|
+
require "AWS"
|
3
6
|
|
4
|
-
#
|
7
|
+
# Script to Encrypt an EC2 Storage (aka Elastic Block Storage)
|
8
|
+
#
|
5
9
|
class DmEncrypt < Ec2Script
|
6
10
|
def initialize(input_params)
|
7
11
|
super(input_params)
|
8
12
|
end
|
9
13
|
|
10
14
|
# Input parameters
|
11
|
-
# aws_access_key => the Amazon AWS Access Key (see Your Account -> Security Credentials)
|
12
|
-
# aws_secret_key => the Amazon AWS Secret Key
|
13
|
-
# ip_address => IP Address of the machine to connect to
|
14
|
-
# ssh_key_file => Path of the keyfile used to connect to the machine
|
15
|
-
# device => Path of the device to encrypt
|
16
|
-
# device_name => Name of the Device to encrypt
|
17
|
-
# storage_path => Path on which the encrypted device is mounted
|
18
|
-
#
|
19
|
-
#
|
20
|
-
#
|
15
|
+
# * aws_access_key => the Amazon AWS Access Key (see Your Account -> Security Credentials)
|
16
|
+
# * aws_secret_key => the Amazon AWS Secret Key
|
17
|
+
# * ip_address => IP Address of the machine to connect to
|
18
|
+
# * ssh_key_file => Path of the keyfile used to connect to the machine
|
19
|
+
# * device => Path of the device to encrypt
|
20
|
+
# * device_name => Name of the Device to encrypt
|
21
|
+
# * storage_path => Path on which the encrypted device is mounted
|
22
|
+
# * paraphrase => paraphrase used for encryption
|
23
|
+
# * remote_command_handler => object that allows to connect via ssh and execute commands (optional)
|
24
|
+
# * ec2_api_handler => object that allows to access the EC2 API (optional)
|
25
|
+
# * ec2_api_server => server to connect to (option, default is us-east-1.ec2.amazonaws.com)
|
21
26
|
#
|
22
27
|
def initialize(input_params)
|
23
28
|
super(input_params)
|
@@ -27,6 +32,18 @@ class DmEncrypt < Ec2Script
|
|
27
32
|
# Executes the script.
|
28
33
|
def start_script
|
29
34
|
begin
|
35
|
+
# optional parameters and initialization
|
36
|
+
if @input_params[:ec2_api_server] == nil
|
37
|
+
@input_params[:ec2_api_server] = "us-east-1.ec2.amazonaws.com"
|
38
|
+
end
|
39
|
+
if @input_params[:remote_command_handler] == nil
|
40
|
+
@input_params[:remote_command_handler] = RemoteCommandHandler.new
|
41
|
+
end
|
42
|
+
if @input_params[:ec2_api_handler] == nil
|
43
|
+
@input_params[:ec2_api_handler] = AWS::EC2::Base.new(:access_key_id => @input_params[:aws_access_key],
|
44
|
+
:secret_access_key => @input_params[:aws_secret_key], :server => @input_params[:ec2_api_server])
|
45
|
+
end
|
46
|
+
# start state machine
|
30
47
|
current_state = DmEncryptState.load_state(@input_params)
|
31
48
|
end_state = current_state.start_state_machine()
|
32
49
|
if end_state.failed?
|
@@ -40,10 +57,10 @@ class DmEncrypt < Ec2Script
|
|
40
57
|
puts "exception during encryption: #{e}"
|
41
58
|
puts e.backtrace.join("\n")
|
42
59
|
err = e.to_s
|
43
|
-
err += " (in #{current_state.end_state.to_s})" unless current_state
|
60
|
+
err += " (in #{current_state.end_state.to_s})" unless current_state == nil
|
44
61
|
@result[:failed] = true
|
45
62
|
@result[:failure_reason] = err
|
46
|
-
@result[:end_state] = current_state.end_state unless current_state
|
63
|
+
@result[:end_state] = current_state.end_state unless current_state == nil
|
47
64
|
ensure
|
48
65
|
begin
|
49
66
|
@input_params[:remote_command_handler].disconnect
|
@@ -66,7 +83,6 @@ class DmEncrypt < Ec2Script
|
|
66
83
|
private
|
67
84
|
|
68
85
|
# Here begins the state machine implementation
|
69
|
-
|
70
86
|
class DmEncryptState < ScriptExecutionState
|
71
87
|
|
72
88
|
def self.load_state(context)
|
@@ -135,7 +151,7 @@ class DmEncrypt < Ec2Script
|
|
135
151
|
end
|
136
152
|
#
|
137
153
|
@context[:remote_command_handler].encrypt_storage(@context[:device_name],
|
138
|
-
@context[:
|
154
|
+
@context[:paraphrase], @context[:device], @context[:storage_path])
|
139
155
|
VolumeCreatedState.new(@context)
|
140
156
|
end
|
141
157
|
|
@@ -146,6 +162,7 @@ class DmEncrypt < Ec2Script
|
|
146
162
|
|
147
163
|
end
|
148
164
|
|
165
|
+
# The encrypted Volume is created. Going to mount it.
|
149
166
|
class VolumeCreatedState < DmEncryptState
|
150
167
|
def enter
|
151
168
|
mount_and_activate()
|
@@ -159,6 +176,7 @@ class DmEncrypt < Ec2Script
|
|
159
176
|
end
|
160
177
|
end
|
161
178
|
|
179
|
+
# The encrypted storages is mounted. Cleanup and done.
|
162
180
|
class MountedAndActivatedState < DmEncryptState
|
163
181
|
def enter
|
164
182
|
cleanup()
|
@@ -1,12 +1,23 @@
|
|
1
1
|
# Base class for any script on EC2.
|
2
2
|
class Ec2Script
|
3
|
-
# Input parameters
|
4
|
-
# aws_access_key => the Amazon AWS Access Key (see Your Account -> Security Credentials)
|
5
|
-
# aws_secret_key => the Amazon AWS Secret Key
|
6
|
-
#
|
3
|
+
# Initialization. Common Input parameters:
|
4
|
+
# * aws_access_key => the Amazon AWS Access Key (see Your Account -> Security Credentials)
|
5
|
+
# * aws_secret_key => the Amazon AWS Secret Key
|
6
|
+
# Scripts may add specific key/value pairs.
|
7
7
|
def initialize(input_params)
|
8
8
|
@input_params = input_params
|
9
9
|
end
|
10
10
|
|
11
|
+
# Return a hash of results. Common values are:
|
12
|
+
# * :done => is true when the script has terminated, otherwise false
|
13
|
+
# * :failed => is false when the script succeeded
|
14
|
+
# * :failure_reason => returns a failure reason (string)
|
15
|
+
# * :end_state => returns the state, in which the script terminated (#Help::ScriptExecutionState)
|
16
|
+
# Scripts may add specific key/value pairs.
|
17
|
+
# *
|
18
|
+
def get_execution_result
|
19
|
+
raise Exception.new("must be implemented")
|
20
|
+
end
|
21
|
+
|
11
22
|
end
|
12
23
|
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: CloudyScripts
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0.
|
4
|
+
version: 0.0.3
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Matthias Jung
|
@@ -9,7 +9,7 @@ autorequire:
|
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
11
|
|
12
|
-
date: 2009-12-
|
12
|
+
date: 2009-12-18 00:00:00 +01:00
|
13
13
|
default_executable:
|
14
14
|
dependencies:
|
15
15
|
- !ruby/object:Gem::Dependency
|
@@ -39,11 +39,11 @@ executables: []
|
|
39
39
|
extensions: []
|
40
40
|
|
41
41
|
extra_rdoc_files:
|
42
|
-
- README
|
42
|
+
- README.rdoc
|
43
43
|
- LICENSE
|
44
44
|
files:
|
45
45
|
- LICENSE
|
46
|
-
- README
|
46
|
+
- README.rdoc
|
47
47
|
- Rakefile
|
48
48
|
- lib/cloudyscripts.rb
|
49
49
|
- lib/help/dm_crypt_helper.rb
|
data/README
DELETED
@@ -1 +0,0 @@
|
|
1
|
-
Scripts to facilitate programming for infrastructure clouds
|