CloudyScripts 0.0.11 → 0.0.12
Sign up to get free protection for your applications and to get access to all the features.
- data/LICENSE +7 -7
- data/README.rdoc +27 -27
- data/Rakefile +50 -50
- data/lib/cloudyscripts.rb +26 -26
- data/lib/help/dm_crypt_helper.rb +53 -54
- data/lib/help/ec2_helper.rb +54 -54
- data/lib/help/remote_command_handler.rb +213 -203
- data/lib/help/script_execution_state.rb +108 -97
- data/lib/help/state_change_listener.rb +13 -13
- data/lib/help/state_transition_helper.rb +362 -0
- data/lib/scripts/ec2/ami2_ebs_conversion.rb +180 -477
- data/lib/scripts/ec2/dm_encrypt.rb +147 -205
- data/lib/scripts/ec2/download_snapshot.rb +153 -0
- data/lib/scripts/ec2/ec2_script.rb +101 -52
- metadata +4 -2
@@ -1,206 +1,148 @@
|
|
1
|
-
require "help/script_execution_state"
|
2
|
-
require "scripts/ec2/ec2_script"
|
3
|
-
require "help/remote_command_handler"
|
4
|
-
require "help/dm_crypt_helper"
|
5
|
-
require "AWS"
|
6
|
-
|
7
|
-
# Script to Encrypt an EC2 Storage (aka Elastic Block Storage)
|
8
|
-
#
|
9
|
-
class DmEncrypt < Ec2Script
|
10
|
-
# dependencies: tools that need to be installed to make things work
|
11
|
-
TOOLS = ["cryptsetup"]
|
12
|
-
# the parameters for which
|
13
|
-
CHECK = ["cryptsetup"]
|
14
|
-
|
15
|
-
# Input parameters
|
16
|
-
# * aws_access_key => the Amazon AWS Access Key (see Your Account -> Security Credentials)
|
17
|
-
# * aws_secret_key => the Amazon AWS Secret Key
|
18
|
-
# * ip_address => IP Address of the machine to connect to
|
19
|
-
# * ssh_key_file => Path of the keyfile used to connect to the machine (optional, otherwise: ssh_key_data)
|
20
|
-
# * ssh_key_data => Key information (optional, otherwise: ssh_key_file)
|
21
|
-
# * device => Path of the device to encrypt
|
22
|
-
# * device_name => Name of the Device to encrypt
|
23
|
-
# * storage_path => Path on which the encrypted device is mounted
|
24
|
-
# * paraphrase => paraphrase used for encryption
|
25
|
-
# * remote_command_handler => object that allows to connect via ssh and execute commands (optional)
|
26
|
-
# * ec2_api_handler => object that allows to access the EC2 API (optional)
|
27
|
-
# * ec2_api_server => server to connect to (option, default is us-east-1.ec2.amazonaws.com)
|
28
|
-
#
|
29
|
-
|
30
|
-
def initialize(input_params)
|
31
|
-
super(input_params)
|
32
|
-
end
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
@
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
@context
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
|
148
|
-
|
149
|
-
def tools_installed?
|
150
|
-
CHECK.each() {|tool|
|
151
|
-
if !@context[:remote_command_handler].tools_installed?(tool)
|
152
|
-
return false
|
153
|
-
end
|
154
|
-
}
|
155
|
-
true
|
156
|
-
end
|
157
|
-
end
|
158
|
-
|
159
|
-
# Connected and Tools installed. Start encryption.
|
160
|
-
class ToolInstalledState < DmEncryptState
|
161
|
-
def enter
|
162
|
-
create_encrypted_volume()
|
163
|
-
end
|
164
|
-
|
165
|
-
private
|
166
|
-
def create_encrypted_volume
|
167
|
-
@context[:script].post_message("going to encrypt device #{@context[:device]} "+
|
168
|
-
"named '#{@context[:device_name]}' and mount it as #{@context[:storage_path]}...")
|
169
|
-
@logger.debug "ToolInstalledState.create_encrypted_volume"
|
170
|
-
@context[:remote_command_handler].encrypt_storage(@context[:device_name],
|
171
|
-
@context[:paraphrase], @context[:device], @context[:storage_path])
|
172
|
-
@context[:script].post_message("device #{@context[:device]} is encrypted and mounted")
|
173
|
-
MountedAndActivatedState.new(@context)
|
174
|
-
end
|
175
|
-
|
176
|
-
def calc_device_name
|
177
|
-
dev = @context[:device_name].gsub(/[-]/,"--")
|
178
|
-
"/dev/mapper/vg--#{dev}-lv--#{dev}"
|
179
|
-
end
|
180
|
-
|
181
|
-
end
|
182
|
-
|
183
|
-
# The encrypted storages is mounted and activated. Cleanup and done.
|
184
|
-
class MountedAndActivatedState < DmEncryptState
|
185
|
-
def enter
|
186
|
-
cleanup()
|
187
|
-
end
|
188
|
-
|
189
|
-
private
|
190
|
-
def cleanup()
|
191
|
-
@context[:script].post_message("disconnecting...")
|
192
|
-
@logger.debug "MountedAndActivatedState.cleanup"
|
193
|
-
@context[:remote_command_handler].disconnect()
|
194
|
-
@context[:script].post_message("done")
|
195
|
-
DoneState.new(@context)
|
196
|
-
end
|
197
|
-
|
198
|
-
end
|
199
|
-
|
200
|
-
class DoneState < DmEncryptState
|
201
|
-
def done?
|
202
|
-
true
|
203
|
-
end
|
204
|
-
end
|
205
|
-
|
1
|
+
require "help/script_execution_state"
|
2
|
+
require "scripts/ec2/ec2_script"
|
3
|
+
require "help/remote_command_handler"
|
4
|
+
require "help/dm_crypt_helper"
|
5
|
+
require "AWS"
|
6
|
+
|
7
|
+
# Script to Encrypt an EC2 Storage (aka Elastic Block Storage)
|
8
|
+
#
|
9
|
+
class DmEncrypt < Ec2Script
|
10
|
+
# dependencies: tools that need to be installed to make things work
|
11
|
+
TOOLS = ["cryptsetup"]
|
12
|
+
# the parameters for which
|
13
|
+
CHECK = ["cryptsetup"]
|
14
|
+
|
15
|
+
# Input parameters
|
16
|
+
# * aws_access_key => the Amazon AWS Access Key (see Your Account -> Security Credentials)
|
17
|
+
# * aws_secret_key => the Amazon AWS Secret Key
|
18
|
+
# * ip_address => IP Address of the machine to connect to
|
19
|
+
# * ssh_key_file => Path of the keyfile used to connect to the machine (optional, otherwise: ssh_key_data)
|
20
|
+
# * ssh_key_data => Key information (optional, otherwise: ssh_key_file)
|
21
|
+
# * device => Path of the device to encrypt
|
22
|
+
# * device_name => Name of the Device to encrypt
|
23
|
+
# * storage_path => Path on which the encrypted device is mounted
|
24
|
+
# * paraphrase => paraphrase used for encryption
|
25
|
+
# * remote_command_handler => object that allows to connect via ssh and execute commands (optional)
|
26
|
+
# * ec2_api_handler => object that allows to access the EC2 API (optional)
|
27
|
+
# * ec2_api_server => server to connect to (option, default is us-east-1.ec2.amazonaws.com)
|
28
|
+
#
|
29
|
+
|
30
|
+
def initialize(input_params)
|
31
|
+
super(input_params)
|
32
|
+
end
|
33
|
+
|
34
|
+
def check_input_parameters()
|
35
|
+
if @input_params[:ec2_api_server] == nil
|
36
|
+
@input_params[:ec2_api_server] = "us-east-1.ec2.amazonaws.com"
|
37
|
+
end
|
38
|
+
if @input_params[:remote_command_handler] == nil
|
39
|
+
@input_params[:remote_command_handler] = RemoteCommandHandler.new
|
40
|
+
end
|
41
|
+
if @input_params[:ec2_api_handler] == nil
|
42
|
+
@input_params[:ec2_api_handler] = AWS::EC2::Base.new(:access_key_id => @input_params[:aws_access_key],
|
43
|
+
:secret_access_key => @input_params[:aws_secret_key], :server => @input_params[:ec2_api_server])
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
def load_initial_state()
|
48
|
+
DmEncryptState.load_state(@input_params)
|
49
|
+
end
|
50
|
+
|
51
|
+
private
|
52
|
+
|
53
|
+
# Here begins the state machine implementation
|
54
|
+
class DmEncryptState < ScriptExecutionState
|
55
|
+
|
56
|
+
def self.load_state(context)
|
57
|
+
InitialState.new(context)
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
# Starting state. Tries to connect via ssh.
|
62
|
+
class InitialState < DmEncryptState
|
63
|
+
def enter
|
64
|
+
connect()
|
65
|
+
install_tools()
|
66
|
+
end
|
67
|
+
|
68
|
+
private
|
69
|
+
|
70
|
+
def install_tools
|
71
|
+
@context[:script].post_message("check if the system has the cryptset-package installed")
|
72
|
+
@logger.debug "ConnectedState.install_tools"
|
73
|
+
if !tools_installed?
|
74
|
+
@context[:script].post_message("cryptset-package not installed. Going to install it...")
|
75
|
+
TOOLS.each() {|tool|
|
76
|
+
@context[:remote_command_handler].install(tool)
|
77
|
+
}
|
78
|
+
end
|
79
|
+
if !@context[:remote_command_handler].remote_execute("modprobe dm_crypt")
|
80
|
+
raise Exception.new("dm-crypt module missing")
|
81
|
+
end
|
82
|
+
if tools_installed?
|
83
|
+
@context[:script].post_message("cryptset-package is available")
|
84
|
+
@logger.debug "system says that tools are installed"
|
85
|
+
ToolInstalledState.new(@context)
|
86
|
+
else
|
87
|
+
FailedState.new(@context, "Installation of Tools failed", ConnectedState.new(@context))
|
88
|
+
end
|
89
|
+
end
|
90
|
+
|
91
|
+
def tools_installed?
|
92
|
+
CHECK.each() {|tool|
|
93
|
+
if !@context[:remote_command_handler].tools_installed?(tool)
|
94
|
+
return false
|
95
|
+
end
|
96
|
+
}
|
97
|
+
true
|
98
|
+
end
|
99
|
+
end
|
100
|
+
|
101
|
+
# Connected and Tools installed. Start encryption.
|
102
|
+
class ToolInstalledState < DmEncryptState
|
103
|
+
def enter
|
104
|
+
create_encrypted_volume()
|
105
|
+
end
|
106
|
+
|
107
|
+
private
|
108
|
+
def create_encrypted_volume
|
109
|
+
@context[:script].post_message("going to encrypt device #{@context[:device]} "+
|
110
|
+
"named '#{@context[:device_name]}' and mount it as #{@context[:storage_path]}...")
|
111
|
+
@logger.debug "ToolInstalledState.create_encrypted_volume"
|
112
|
+
@context[:remote_command_handler].encrypt_storage(@context[:device_name],
|
113
|
+
@context[:paraphrase], @context[:device], @context[:storage_path])
|
114
|
+
@context[:script].post_message("device #{@context[:device]} is encrypted and mounted")
|
115
|
+
MountedAndActivatedState.new(@context)
|
116
|
+
end
|
117
|
+
|
118
|
+
def calc_device_name
|
119
|
+
dev = @context[:device_name].gsub(/[-]/,"--")
|
120
|
+
"/dev/mapper/vg--#{dev}-lv--#{dev}"
|
121
|
+
end
|
122
|
+
|
123
|
+
end
|
124
|
+
|
125
|
+
# The encrypted storages is mounted and activated. Cleanup and done.
|
126
|
+
class MountedAndActivatedState < DmEncryptState
|
127
|
+
def enter
|
128
|
+
cleanup()
|
129
|
+
end
|
130
|
+
|
131
|
+
private
|
132
|
+
def cleanup()
|
133
|
+
@context[:script].post_message("disconnecting...")
|
134
|
+
@logger.debug "MountedAndActivatedState.cleanup"
|
135
|
+
@context[:remote_command_handler].disconnect()
|
136
|
+
@context[:script].post_message("done")
|
137
|
+
DoneState.new(@context)
|
138
|
+
end
|
139
|
+
|
140
|
+
end
|
141
|
+
|
142
|
+
class DoneState < DmEncryptState
|
143
|
+
def done?
|
144
|
+
true
|
145
|
+
end
|
146
|
+
end
|
147
|
+
|
206
148
|
end
|
@@ -0,0 +1,153 @@
|
|
1
|
+
require "help/script_execution_state"
|
2
|
+
require "scripts/ec2/ec2_script"
|
3
|
+
require "help/remote_command_handler"
|
4
|
+
require "help/dm_crypt_helper"
|
5
|
+
require "AWS"
|
6
|
+
|
7
|
+
# Script to download a specific snapshot as ZIP
|
8
|
+
# * create a specific instance (with Apache Server),
|
9
|
+
# * create a volume based on the snapshot
|
10
|
+
# * attach the volume
|
11
|
+
# * create a XSF-file-system
|
12
|
+
# * freeze the file-system
|
13
|
+
# * zip the file-system and copy it to the apache folder
|
14
|
+
# * wait 5 minutes (now the zip-file can be downloaded)
|
15
|
+
# * alternatively: copy it to S3 and make it downloadable
|
16
|
+
# * alternatively: copy it to an FTP server
|
17
|
+
#
|
18
|
+
class DownloadSnapshot < Ec2Script
|
19
|
+
# context information needed
|
20
|
+
# * the EC2 credentials (see #Ec2Script)
|
21
|
+
# * ami_id: the ID of the AMI to be started to perform the operations and to run the web-server for download
|
22
|
+
# * security_group_name => name of the security group used to start the AMI (should open ports for SSH and HTTP)
|
23
|
+
# * ssh_key_data => Key information for the security group that starts the AMI [if not set, use ssh_key_files]
|
24
|
+
# * ssh_key_files => Key information for the security group that starts the AMI
|
25
|
+
# * snapshot_id => The ID of the snapshot to be downloaded
|
26
|
+
# * wait_time (optional, default = 300) => time in sec during which the zipped snapshot is downloadable
|
27
|
+
# * zip_file_dest (optional, default = '/var/www/html') => path of directory where the zipped volume is copied to
|
28
|
+
# * zip_file_name (option, default = 'download') => name of the zip-file to download
|
29
|
+
def initialize(input_params)
|
30
|
+
super(input_params)
|
31
|
+
end
|
32
|
+
|
33
|
+
def check_input_parameters()
|
34
|
+
if @input_params[:temp_device_name] == nil
|
35
|
+
@input_params[:temp_device_name] = "/dev/sdj"
|
36
|
+
end
|
37
|
+
if @input_params[:zip_file_dest] == nil
|
38
|
+
@input_params[:zip_file_dest] = "/var/www/html/"
|
39
|
+
end
|
40
|
+
if @input_params[:zip_file_name] == nil
|
41
|
+
@input_params[:zip_file_name] = "download"
|
42
|
+
end
|
43
|
+
if @input_params[:wait_time] == nil
|
44
|
+
@input_params[:wait_time] = 300
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
# Load the initial state for the script.
|
49
|
+
# Abstract method to be implemented by extending classes.
|
50
|
+
def load_initial_state()
|
51
|
+
DownloadSnapshotState.load_state(@input_params)
|
52
|
+
end
|
53
|
+
|
54
|
+
private
|
55
|
+
|
56
|
+
# Here begins the state machine implementation
|
57
|
+
class DownloadSnapshotState < ScriptExecutionState
|
58
|
+
|
59
|
+
def self.load_state(context)
|
60
|
+
InitialState.new(context)
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
#Connected.
|
65
|
+
# Start state. First thing to do is to launch the instance.
|
66
|
+
class InitialState < DownloadSnapshotState
|
67
|
+
def enter
|
68
|
+
launch_instance()
|
69
|
+
InstanceLaunchedState.new(context)
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
# Instance Launched. Create a volume based on the snapshot.
|
74
|
+
class InstanceLaunchedState < DownloadSnapshotState
|
75
|
+
def enter
|
76
|
+
create_volume_from_snapshot()
|
77
|
+
VolumeCreated.new(@context)
|
78
|
+
end
|
79
|
+
|
80
|
+
end
|
81
|
+
|
82
|
+
# Volume created. Attach it.
|
83
|
+
class VolumeCreated < DownloadSnapshotState
|
84
|
+
def enter
|
85
|
+
attach_volume()
|
86
|
+
VolumeAttached.new(@context)
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
# Volume attached. Create a file-system and mount it.
|
91
|
+
class VolumeAttached < DownloadSnapshotState
|
92
|
+
def enter
|
93
|
+
connect()
|
94
|
+
create_fs()
|
95
|
+
FileSystemCreated.new(@context)
|
96
|
+
end
|
97
|
+
end
|
98
|
+
|
99
|
+
# File system created. Mount it.
|
100
|
+
class FileSystemCreated < DownloadSnapshotState
|
101
|
+
def enter
|
102
|
+
mount_fs()
|
103
|
+
FileSystemMounted.new(@context)
|
104
|
+
end
|
105
|
+
|
106
|
+
end
|
107
|
+
|
108
|
+
# File System mounted. Zip the complete directory on the EBS.
|
109
|
+
class FileSystemMounted < DownloadSnapshotState
|
110
|
+
def enter
|
111
|
+
zip_volume()
|
112
|
+
VolumeZippedAndDownloadableState.new(@context)
|
113
|
+
end
|
114
|
+
|
115
|
+
end
|
116
|
+
|
117
|
+
# Volume is zipped and downloadable. Wait 5 minutes.
|
118
|
+
class VolumeZippedAndDownloadableState < DownloadSnapshotState
|
119
|
+
def enter
|
120
|
+
wait_some_time()
|
121
|
+
end
|
122
|
+
|
123
|
+
def get_link
|
124
|
+
"http://#{@context[:dns_name]}/#{@context[:zip_file_name]}.zip"
|
125
|
+
end
|
126
|
+
|
127
|
+
private
|
128
|
+
|
129
|
+
def wait_some_time
|
130
|
+
@context[:script].post_message("The snapshot can be downloaded during #{@context[:wait_time]} seconds from link: #{get_link()}")
|
131
|
+
sleep(@context[:wait_time])
|
132
|
+
DownloadStoppedState.new(@context)
|
133
|
+
end
|
134
|
+
end
|
135
|
+
|
136
|
+
# Snapshot can no longer be downloaded. Shut down the instance.
|
137
|
+
class DownloadStoppedState < DownloadSnapshotState
|
138
|
+
def enter
|
139
|
+
shut_down_instance()
|
140
|
+
InstanceShutDown.new(@context)
|
141
|
+
end
|
142
|
+
|
143
|
+
end
|
144
|
+
|
145
|
+
# Instance is shut down. Delete the volume created.
|
146
|
+
class InstanceShutDown < DownloadSnapshotState
|
147
|
+
def enter
|
148
|
+
delete_volume()
|
149
|
+
Done.new(@context)
|
150
|
+
end
|
151
|
+
end
|
152
|
+
|
153
|
+
end
|