ec2_amitools 1.0.2
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.
- checksums.yaml +7 -0
- data/README.md +54 -0
- data/bin/console +14 -0
- data/bin/ec2-ami-tools-version +6 -0
- data/bin/ec2-bundle-image +6 -0
- data/bin/ec2-bundle-vol +6 -0
- data/bin/ec2-delete-bundle +6 -0
- data/bin/ec2-download-bundle +6 -0
- data/bin/ec2-migrate-bundle +6 -0
- data/bin/ec2-migrate-manifest +6 -0
- data/bin/ec2-unbundle +6 -0
- data/bin/ec2-upload-bundle +6 -0
- data/bin/setup +8 -0
- data/etc/ec2/amitools/cert-ec2-cn-north-1.pem +28 -0
- data/etc/ec2/amitools/cert-ec2-gov.pem +17 -0
- data/etc/ec2/amitools/cert-ec2.pem +23 -0
- data/etc/ec2/amitools/mappings.csv +9 -0
- data/lib/ec2/amitools/bundle.rb +251 -0
- data/lib/ec2/amitools/bundle_base.rb +58 -0
- data/lib/ec2/amitools/bundleimage.rb +94 -0
- data/lib/ec2/amitools/bundleimageparameters.rb +42 -0
- data/lib/ec2/amitools/bundlemachineparameters.rb +60 -0
- data/lib/ec2/amitools/bundleparameters.rb +120 -0
- data/lib/ec2/amitools/bundlevol.rb +240 -0
- data/lib/ec2/amitools/bundlevolparameters.rb +164 -0
- data/lib/ec2/amitools/crypto.rb +379 -0
- data/lib/ec2/amitools/decryptmanifest.rb +20 -0
- data/lib/ec2/amitools/defaults.rb +12 -0
- data/lib/ec2/amitools/deletebundle.rb +212 -0
- data/lib/ec2/amitools/deletebundleparameters.rb +78 -0
- data/lib/ec2/amitools/downloadbundle.rb +161 -0
- data/lib/ec2/amitools/downloadbundleparameters.rb +84 -0
- data/lib/ec2/amitools/exception.rb +86 -0
- data/lib/ec2/amitools/fileutil.rb +219 -0
- data/lib/ec2/amitools/format.rb +127 -0
- data/lib/ec2/amitools/instance-data.rb +97 -0
- data/lib/ec2/amitools/manifest_wrapper.rb +132 -0
- data/lib/ec2/amitools/manifestv20070829.rb +361 -0
- data/lib/ec2/amitools/manifestv20071010.rb +403 -0
- data/lib/ec2/amitools/manifestv3.rb +331 -0
- data/lib/ec2/amitools/mapids.rb +148 -0
- data/lib/ec2/amitools/migratebundle.rb +222 -0
- data/lib/ec2/amitools/migratebundleparameters.rb +173 -0
- data/lib/ec2/amitools/migratemanifest.rb +225 -0
- data/lib/ec2/amitools/migratemanifestparameters.rb +118 -0
- data/lib/ec2/amitools/minimalec2.rb +116 -0
- data/lib/ec2/amitools/parameter_exceptions.rb +34 -0
- data/lib/ec2/amitools/parameters_base.rb +168 -0
- data/lib/ec2/amitools/region.rb +93 -0
- data/lib/ec2/amitools/s3toolparameters.rb +183 -0
- data/lib/ec2/amitools/showversion.rb +12 -0
- data/lib/ec2/amitools/syschecks.rb +27 -0
- data/lib/ec2/amitools/tool_base.rb +224 -0
- data/lib/ec2/amitools/unbundle.rb +107 -0
- data/lib/ec2/amitools/unbundleparameters.rb +65 -0
- data/lib/ec2/amitools/uploadbundle.rb +361 -0
- data/lib/ec2/amitools/uploadbundleparameters.rb +108 -0
- data/lib/ec2/amitools/util.rb +532 -0
- data/lib/ec2/amitools/version.rb +33 -0
- data/lib/ec2/amitools/xmlbuilder.rb +237 -0
- data/lib/ec2/amitools/xmlutil.rb +55 -0
- data/lib/ec2/common/constants.rb +16 -0
- data/lib/ec2/common/curl.rb +110 -0
- data/lib/ec2/common/headers.rb +95 -0
- data/lib/ec2/common/headersv4.rb +173 -0
- data/lib/ec2/common/http.rb +333 -0
- data/lib/ec2/common/s3support.rb +231 -0
- data/lib/ec2/common/signature.rb +68 -0
- data/lib/ec2/oem/LICENSE.txt +58 -0
- data/lib/ec2/oem/open4.rb +399 -0
- data/lib/ec2/platform/base/architecture.rb +26 -0
- data/lib/ec2/platform/base/constants.rb +54 -0
- data/lib/ec2/platform/base/pipeline.rb +181 -0
- data/lib/ec2/platform/base.rb +57 -0
- data/lib/ec2/platform/current.rb +55 -0
- data/lib/ec2/platform/linux/architecture.rb +35 -0
- data/lib/ec2/platform/linux/constants.rb +23 -0
- data/lib/ec2/platform/linux/fstab.rb +99 -0
- data/lib/ec2/platform/linux/identity.rb +16 -0
- data/lib/ec2/platform/linux/image.rb +811 -0
- data/lib/ec2/platform/linux/mtab.rb +74 -0
- data/lib/ec2/platform/linux/pipeline.rb +40 -0
- data/lib/ec2/platform/linux/rsync.rb +114 -0
- data/lib/ec2/platform/linux/tar.rb +124 -0
- data/lib/ec2/platform/linux/uname.rb +50 -0
- data/lib/ec2/platform/linux.rb +83 -0
- data/lib/ec2/platform/solaris/architecture.rb +28 -0
- data/lib/ec2/platform/solaris/constants.rb +30 -0
- data/lib/ec2/platform/solaris/fstab.rb +43 -0
- data/lib/ec2/platform/solaris/identity.rb +16 -0
- data/lib/ec2/platform/solaris/image.rb +327 -0
- data/lib/ec2/platform/solaris/mtab.rb +29 -0
- data/lib/ec2/platform/solaris/pipeline.rb +40 -0
- data/lib/ec2/platform/solaris/rsync.rb +24 -0
- data/lib/ec2/platform/solaris/tar.rb +36 -0
- data/lib/ec2/platform/solaris/uname.rb +21 -0
- data/lib/ec2/platform/solaris.rb +38 -0
- data/lib/ec2/platform.rb +69 -0
- data/lib/ec2/version.rb +8 -0
- data/lib/ec2_amitools +1 -0
- data/lib/ec2_amitools.rb +7 -0
- metadata +184 -0
@@ -0,0 +1,403 @@
|
|
1
|
+
# Copyright 2008-2014 Amazon.com, Inc. or its affiliates. All Rights
|
2
|
+
# Reserved. Licensed under the Amazon Software License (the
|
3
|
+
# "License"). You may not use this file except in compliance with the
|
4
|
+
# License. A copy of the License is located at
|
5
|
+
# http://aws.amazon.com/asl or in the "license" file accompanying this
|
6
|
+
# file. This file is distributed on an "AS IS" BASIS, WITHOUT
|
7
|
+
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See
|
8
|
+
# the License for the specific language governing permissions and
|
9
|
+
# limitations under the License.
|
10
|
+
|
11
|
+
require 'ec2/amitools/crypto'
|
12
|
+
require 'ec2/amitools/format'
|
13
|
+
require 'ec2/amitools/xmlutil'
|
14
|
+
require 'pathname'
|
15
|
+
require 'rexml/document'
|
16
|
+
require 'ec2/amitools/xmlbuilder'
|
17
|
+
|
18
|
+
# Manifest Version 2007-10-10.
|
19
|
+
# Not backwards compatible
|
20
|
+
class ManifestV20071010
|
21
|
+
VERSION_STRING = '2007-10-10'
|
22
|
+
VERSION = VERSION_STRING.gsub('-','').to_i
|
23
|
+
|
24
|
+
# Expose the version
|
25
|
+
def self.version
|
26
|
+
VERSION
|
27
|
+
end
|
28
|
+
|
29
|
+
# AMI part information container.
|
30
|
+
class PartInformation
|
31
|
+
attr_reader :filename # Part's file basename.
|
32
|
+
attr_reader :digest # Part's digest hex encoded.
|
33
|
+
|
34
|
+
# Initialize with part's _filename_ and _digest_ as byte string.
|
35
|
+
def initialize( filename, digest )
|
36
|
+
@filename , @digest = filename, digest
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
def initialize( xml = nil )
|
41
|
+
if xml == nil
|
42
|
+
@doc = REXML::Document.new
|
43
|
+
else
|
44
|
+
# Convert to string if necessary.
|
45
|
+
xml = ( xml.kind_of?( IO ) ? xml.read : xml )
|
46
|
+
@doc = REXML::Document.new( xml )
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
# for debugging only
|
51
|
+
def doc
|
52
|
+
@doc
|
53
|
+
end
|
54
|
+
|
55
|
+
def mandatory_argument(arg, args)
|
56
|
+
raise "Missing mandatory argument #{arg} for manifest #{VERSION_STRING}" if !args.key?(arg)
|
57
|
+
args[arg]
|
58
|
+
end
|
59
|
+
|
60
|
+
def optional_argument(arg, args)
|
61
|
+
args[arg]
|
62
|
+
end
|
63
|
+
|
64
|
+
IMAGE_TYPE_KERNEL = "kernel"
|
65
|
+
|
66
|
+
# Initialize the manifest with AMI information.
|
67
|
+
# Return +true+ if the initialization was succesful.
|
68
|
+
# Raise an exception on error.
|
69
|
+
def init(args)
|
70
|
+
name = mandatory_argument(:name, args)
|
71
|
+
user = mandatory_argument(:user, args) # The user's account number.
|
72
|
+
arch = mandatory_argument(:arch, args) # Target architecture for AMI.
|
73
|
+
image_type = mandatory_argument(:image_type, args) # Type of image
|
74
|
+
reserved = mandatory_argument(:reserved, args) # Reserved for future use; pass nil.
|
75
|
+
parts = mandatory_argument(:parts, args) # A list of parts filenames and digest pairs.
|
76
|
+
size = mandatory_argument(:size, args) # The size of the AMI in bytes.
|
77
|
+
bundled_size = mandatory_argument(:bundled_size, args) # The size of the bunled AMI in bytes.
|
78
|
+
user_encrypted_key = mandatory_argument(:user_encrypted_key, args) # Hex encoded.
|
79
|
+
ec2_encrypted_key = mandatory_argument(:ec2_encrypted_key, args) # Hex encoded.
|
80
|
+
cipher_algorithm = mandatory_argument(:cipher_algorithm, args) # The cipher algorithm used to encrypted the AMI.
|
81
|
+
user_encrypted_iv = mandatory_argument(:user_encrypted_iv, args) # Hex encoded.
|
82
|
+
ec2_encrypted_iv = mandatory_argument(:ec2_encrypted_iv, args) # Hex encoded.
|
83
|
+
digest = mandatory_argument(:digest, args) # Hex encoded.
|
84
|
+
digest_algorithm = mandatory_argument(:digest_algorithm, args) # The digest algorithm.
|
85
|
+
privkey_filename = mandatory_argument(:privkey_filename, args) # The user's private key filename.
|
86
|
+
# Optional parameters
|
87
|
+
kernel_id = optional_argument(:kernel_id, args) # Optional default kernel image id
|
88
|
+
ramdisk_id = optional_argument(:ramdisk_id, args) # Optional default ramdisk image id
|
89
|
+
product_codes = optional_argument(:product_codes, args) # Optional array of product codes (strings)
|
90
|
+
ancestor_ami_ids = optional_argument(:ancestor_ami_ids, args) # Optional array of ancestor ami ids (strings)
|
91
|
+
bdm = optional_argument(:block_device_mapping, args) ||{} # Optional hash of block device mappings(strings)
|
92
|
+
bundler_name = optional_argument(:bundler_name, args)
|
93
|
+
bundler_version = optional_argument(:bundler_version, args)
|
94
|
+
bundler_release = optional_argument(:bundler_release, args)
|
95
|
+
|
96
|
+
# Conditional parameters
|
97
|
+
kernel_name = (image_type == IMAGE_TYPE_KERNEL ? mandatory_argument(:kernel_name, args) : nil) # Name of the kernel in the image
|
98
|
+
|
99
|
+
# Check reserved parameters are nil
|
100
|
+
raise ArgumentError.new( "reserved parameters not nil" ) unless reserved.nil?
|
101
|
+
|
102
|
+
# Check non-String parameter types.
|
103
|
+
raise ArgumentError.new( "parts parameter type invalid" ) unless parts.is_a? Array
|
104
|
+
|
105
|
+
# XML document.
|
106
|
+
@doc = REXML::Document.new
|
107
|
+
@doc << REXML::XMLDecl.new
|
108
|
+
|
109
|
+
# Yeah... the way we used to do this really sucked - manually building up REXML::Element and inserting them into
|
110
|
+
# parent nodes. So I've reinvented the wheel and done a baby xpath xml builder kinda thing
|
111
|
+
# Makes it much easier (from a code point of view) to build up xml docs. Probably less efficient in machine terms but c'mon
|
112
|
+
# if we cared about that we wouldn't be using ruby.
|
113
|
+
builder = XMLBuilder.new(@doc)
|
114
|
+
|
115
|
+
# version - indicate the manifest version.
|
116
|
+
builder['/manifest/version'] = VERSION_STRING
|
117
|
+
|
118
|
+
# bundler information
|
119
|
+
builder['/manifest/bundler/name'] = bundler_name
|
120
|
+
builder['/manifest/bundler/version'] = bundler_version
|
121
|
+
builder['/manifest/bundler/release'] = bundler_release
|
122
|
+
|
123
|
+
# machine_configuration - the target hardware description of the AMI.
|
124
|
+
builder['/manifest/machine_configuration/architecture'] = arch
|
125
|
+
bdm.keys.sort.each_with_index do |key, index|
|
126
|
+
builder["/manifest/machine_configuration/block_device_mapping/mapping[#{index}]/virtual"] = key
|
127
|
+
builder["/manifest/machine_configuration/block_device_mapping/mapping[#{index}]/device"] = bdm[key]
|
128
|
+
end
|
129
|
+
builder['/manifest/machine_configuration/kernel_id'] = kernel_id
|
130
|
+
builder['/manifest/machine_configuration/ramdisk_id'] = ramdisk_id
|
131
|
+
Array(product_codes).each_with_index do |product_code, index|
|
132
|
+
builder["/manifest/machine_configuration/product_codes/product_code[#{index}]"] = product_code
|
133
|
+
end
|
134
|
+
|
135
|
+
# image - the image element.
|
136
|
+
builder['manifest/image/name'] = name
|
137
|
+
|
138
|
+
# user - the user's AWS access key ID.
|
139
|
+
builder['/manifest/image/user'] = user
|
140
|
+
builder['/manifest/image/type'] = image_type
|
141
|
+
|
142
|
+
# The name of the kernel in the image. Only applicable to kernel images.
|
143
|
+
builder['/manifest/image/kernel_name'] = kernel_name
|
144
|
+
|
145
|
+
# ancestry - the parent ami ids
|
146
|
+
(ancestor_ami_ids || []).each_with_index do |ancestor_ami_id, index|
|
147
|
+
builder["/manifest/image/ancestry/ancestor_ami_id[#{index}]"] = ancestor_ami_id
|
148
|
+
end
|
149
|
+
|
150
|
+
# digest - the digest of the AMI.
|
151
|
+
builder['/manifest/image/digest'] = digest
|
152
|
+
builder['/manifest/image/digest/@algorithm'] = digest_algorithm
|
153
|
+
|
154
|
+
# size - the size of the uncompressed AMI.
|
155
|
+
builder['/manifest/image/size'] = size.to_s
|
156
|
+
|
157
|
+
# bundled size - the size of the bundled AMI.
|
158
|
+
builder['/manifest/image/bundled_size'] = bundled_size.to_s
|
159
|
+
|
160
|
+
# ec2 encrypted key element.
|
161
|
+
builder['/manifest/image/ec2_encrypted_key'] = ec2_encrypted_key
|
162
|
+
builder['/manifest/image/ec2_encrypted_key/@algorithm'] = cipher_algorithm
|
163
|
+
|
164
|
+
# user encrypted key element.
|
165
|
+
builder['/manifest/image/user_encrypted_key'] = user_encrypted_key
|
166
|
+
builder['/manifest/image/user_encrypted_key/@algorithm'] = cipher_algorithm
|
167
|
+
|
168
|
+
# ec2 encrypted iv element.
|
169
|
+
builder['/manifest/image/ec2_encrypted_iv'] = ec2_encrypted_iv
|
170
|
+
|
171
|
+
# user encrypted iv element.
|
172
|
+
builder['/manifest/image/user_encrypted_iv'] = user_encrypted_iv
|
173
|
+
|
174
|
+
# parts - list of the image parts.
|
175
|
+
builder['/manifest/image/parts/@count'] = parts.size
|
176
|
+
|
177
|
+
parts.each_with_index do |part, index|
|
178
|
+
# Add image part element for each image part.
|
179
|
+
builder["/manifest/image/parts/part[#{index}]/@index"] = index
|
180
|
+
builder["/manifest/image/parts/part[#{index}]/filename"] = part[0]
|
181
|
+
builder["/manifest/image/parts/part[#{index}]/digest"] = Format::bin2hex(part[1])
|
182
|
+
builder["/manifest/image/parts/part[#{index}]/digest/@algorithm"] = digest_algorithm
|
183
|
+
builder["/manifest/image/parts/part[#{index}]/@index"] = index
|
184
|
+
end
|
185
|
+
|
186
|
+
# Sign the manifest.
|
187
|
+
sign(privkey_filename)
|
188
|
+
|
189
|
+
return true
|
190
|
+
end
|
191
|
+
|
192
|
+
def self::version20071010?(xml)
|
193
|
+
doc = REXML::Document.new(xml)
|
194
|
+
version = REXML::XPath.first(doc.root, 'version')
|
195
|
+
return (version and version.text and version.text == VERSION_STRING)
|
196
|
+
end
|
197
|
+
|
198
|
+
# Get the kernel_name
|
199
|
+
def kernel_name()
|
200
|
+
return get_element_text_or_nil('image/kernel_name')
|
201
|
+
end
|
202
|
+
|
203
|
+
# Get the default kernel_id
|
204
|
+
def kernel_id()
|
205
|
+
return get_element_text_or_nil('machine_configuration/kernel_id')
|
206
|
+
end
|
207
|
+
|
208
|
+
# Get the default ramdisk id
|
209
|
+
def ramdisk_id()
|
210
|
+
return get_element_text_or_nil('machine_configuration/ramdisk_id')
|
211
|
+
end
|
212
|
+
|
213
|
+
# Get the default product codes
|
214
|
+
def product_codes()
|
215
|
+
product_codes = []
|
216
|
+
REXML::XPath.each(@doc, '/manifest/machine_configuration/product_codes/product_code') do |product_code|
|
217
|
+
product_codes << product_code.text
|
218
|
+
end
|
219
|
+
product_codes
|
220
|
+
end
|
221
|
+
|
222
|
+
# Get the manifest's ancestry
|
223
|
+
def ancestor_ami_ids()
|
224
|
+
ancestor_ami_ids = []
|
225
|
+
REXML::XPath.each(@doc, '/manifest/image/ancestry/ancestor_ami_id') do |node|
|
226
|
+
ancestor_ami_ids << node.text unless (node.text.nil? or node.text.empty?)
|
227
|
+
end
|
228
|
+
ancestor_ami_ids
|
229
|
+
end
|
230
|
+
|
231
|
+
# Return the AMI's digest hex encoded.
|
232
|
+
def digest()
|
233
|
+
return get_element_text( 'image/digest' )
|
234
|
+
end
|
235
|
+
|
236
|
+
def name()
|
237
|
+
return get_element_text( 'image/name' )
|
238
|
+
end
|
239
|
+
|
240
|
+
# The ec2 encrypted key hex encoded.
|
241
|
+
def ec2_encrypted_key()
|
242
|
+
return get_element_text('image/ec2_encrypted_key' )
|
243
|
+
end
|
244
|
+
|
245
|
+
# The user encrypted key hex encoded.
|
246
|
+
def user_encrypted_key()
|
247
|
+
return get_element_text( 'image/user_encrypted_key' )
|
248
|
+
end
|
249
|
+
|
250
|
+
# The ec2 encrypted initialization vector hex encoded.
|
251
|
+
def ec2_encrypted_iv()
|
252
|
+
return get_element_text( 'image/ec2_encrypted_iv' )
|
253
|
+
end
|
254
|
+
|
255
|
+
# The user encrypted initialization vector hex encoded.
|
256
|
+
def user_encrypted_iv()
|
257
|
+
return get_element_text( 'image/user_encrypted_iv' )
|
258
|
+
end
|
259
|
+
|
260
|
+
# Get digest algorithm used.
|
261
|
+
def digest_algorithm()
|
262
|
+
return REXML::XPath.first(@doc.root, 'image/digest/@algorithm').to_s
|
263
|
+
end
|
264
|
+
|
265
|
+
# Get cipher algorithm used.
|
266
|
+
def cipher_algorithm()
|
267
|
+
return REXML::XPath.first(@doc.root, 'image/ec2_encrypted_key/@algorithm').to_s
|
268
|
+
end
|
269
|
+
|
270
|
+
# Retrieve a list of AMI bundle parts info. Each element is a hash
|
271
|
+
# with the following elements:
|
272
|
+
# * 'digest'
|
273
|
+
# * 'filename'
|
274
|
+
# * 'index'
|
275
|
+
def ami_part_info_list
|
276
|
+
parts = Array.new
|
277
|
+
REXML::XPath.each( @doc.root,'image/parts/part' ) do |part|
|
278
|
+
index = part.attribute( 'index' ).to_s.to_i
|
279
|
+
filename = REXML::XPath.first( part, 'filename' ).text
|
280
|
+
digest = REXML::XPath.first( part, 'digest' ).text
|
281
|
+
parts << { 'digest'=>digest, 'filename'=>filename, 'index'=>index }
|
282
|
+
end
|
283
|
+
return parts
|
284
|
+
end
|
285
|
+
|
286
|
+
# A list of PartInformation instances representing the AMI parts.
|
287
|
+
def parts()
|
288
|
+
parts = []
|
289
|
+
REXML::XPath.each( @doc.root,'image/parts/part' ) do |part|
|
290
|
+
index = part.attribute( 'index' ).to_s.to_i
|
291
|
+
filename = REXML::XPath.first( part, 'filename' ).text
|
292
|
+
digest = Format::hex2bin( REXML::XPath.first( part, 'digest' ).text )
|
293
|
+
parts[index] = PartInformation.new( filename, digest )
|
294
|
+
end
|
295
|
+
return parts
|
296
|
+
end
|
297
|
+
|
298
|
+
# Get the block device mapping as a map.
|
299
|
+
def block_device_mapping()
|
300
|
+
bdm = {}
|
301
|
+
REXML::XPath.each(@doc.root,'machine_configuration/block_device_mapping/mapping/') do |mapping|
|
302
|
+
virtual = REXML::XPath.first(mapping, 'virtual').text
|
303
|
+
device = REXML::XPath.first(mapping, 'device').text
|
304
|
+
bdm[virtual] = device
|
305
|
+
end
|
306
|
+
bdm
|
307
|
+
end
|
308
|
+
|
309
|
+
# Return the size of the AMI.
|
310
|
+
def size()
|
311
|
+
return get_element_text( 'image/size' ).to_i()
|
312
|
+
end
|
313
|
+
|
314
|
+
# Return the (optional) architecture of the AMI.
|
315
|
+
def arch()
|
316
|
+
return get_element_text_or_nil('machine_configuration/architecture')
|
317
|
+
end
|
318
|
+
|
319
|
+
# Return the bundled size of the AMI.
|
320
|
+
def bundled_size()
|
321
|
+
return get_element_text( 'image/bundled_size' ).to_i
|
322
|
+
end
|
323
|
+
|
324
|
+
# Return the bundler name.
|
325
|
+
def bundler_name()
|
326
|
+
return get_element_text('bundler/name')
|
327
|
+
end
|
328
|
+
|
329
|
+
# Return the bundler version.
|
330
|
+
def bundler_version()
|
331
|
+
return get_element_text('bundler/version')
|
332
|
+
end
|
333
|
+
|
334
|
+
# Return the bundler release.
|
335
|
+
def bundler_release()
|
336
|
+
return get_element_text('bundler/release')
|
337
|
+
end
|
338
|
+
|
339
|
+
# Sign the manifest. If it is already signed, the signature and certificate
|
340
|
+
# will be replaced
|
341
|
+
def sign( privkey_filename )
|
342
|
+
unless privkey_filename.kind_of? String and File::exist?( privkey_filename )
|
343
|
+
raise ArgumentError.new( "privkey_filename parameter invalid" )
|
344
|
+
end
|
345
|
+
|
346
|
+
# Get the XML for <machine_configuration> and <image> elements and sign them.
|
347
|
+
machine_configuration_xml = XMLUtil.get_xml( @doc.to_s, 'machine_configuration' ) || ""
|
348
|
+
image_xml = XMLUtil.get_xml( @doc.to_s, 'image' )
|
349
|
+
sig = Crypto::sign( machine_configuration_xml + image_xml, privkey_filename )
|
350
|
+
|
351
|
+
# Create the signature and certificate elements.
|
352
|
+
XMLBuilder.new(@doc)['/manifest/signature'] = Format::bin2hex(sig)
|
353
|
+
end
|
354
|
+
|
355
|
+
# Return the signature
|
356
|
+
def signature
|
357
|
+
get_element_text('signature')
|
358
|
+
end
|
359
|
+
|
360
|
+
# Verify the signature
|
361
|
+
def authenticate(cert)
|
362
|
+
machine_configuration_xml = XMLUtil.get_xml( @doc.to_s, 'machine_configuration' ) || ""
|
363
|
+
image_xml = XMLUtil.get_xml( @doc.to_s, 'image' )
|
364
|
+
pubkey = Crypto::cert2pubkey(cert)
|
365
|
+
Crypto::authenticate(machine_configuration_xml + image_xml, Format::hex2bin(signature), pubkey)
|
366
|
+
end
|
367
|
+
|
368
|
+
# Return the manifest as an XML string.
|
369
|
+
def to_s()
|
370
|
+
return @doc.to_s
|
371
|
+
end
|
372
|
+
|
373
|
+
def user()
|
374
|
+
return get_element_text('image/user')
|
375
|
+
end
|
376
|
+
|
377
|
+
def image_type()
|
378
|
+
return get_element_text('image/type')
|
379
|
+
end
|
380
|
+
|
381
|
+
def version()
|
382
|
+
return get_element_text('version').gsub('-','').to_i
|
383
|
+
end
|
384
|
+
|
385
|
+
private
|
386
|
+
|
387
|
+
def get_element_text_or_nil(xpath)
|
388
|
+
element = REXML::XPath.first(@doc.root, xpath)
|
389
|
+
return element.text if element
|
390
|
+
return nil
|
391
|
+
end
|
392
|
+
|
393
|
+
def get_element_text(xpath)
|
394
|
+
element = REXML::XPath.first(@doc.root, xpath)
|
395
|
+
unless element
|
396
|
+
raise "invalid AMI manifest, #{xpath} element not present"
|
397
|
+
end
|
398
|
+
unless element.text
|
399
|
+
raise "invalid AMI manifest, #{xpath} element empty"
|
400
|
+
end
|
401
|
+
return element.text
|
402
|
+
end
|
403
|
+
end
|
@@ -0,0 +1,331 @@
|
|
1
|
+
# Copyright 2008-2014 Amazon.com, Inc. or its affiliates. All Rights
|
2
|
+
# Reserved. Licensed under the Amazon Software License (the
|
3
|
+
# "License"). You may not use this file except in compliance with the
|
4
|
+
# License. A copy of the License is located at
|
5
|
+
# http://aws.amazon.com/asl or in the "license" file accompanying this
|
6
|
+
# file. This file is distributed on an "AS IS" BASIS, WITHOUT
|
7
|
+
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See
|
8
|
+
# the License for the specific language governing permissions and
|
9
|
+
# limitations under the License.
|
10
|
+
|
11
|
+
require 'ec2/amitools/crypto'
|
12
|
+
require 'ec2/amitools/format'
|
13
|
+
require 'ec2/amitools/xmlutil'
|
14
|
+
require 'pathname'
|
15
|
+
require 'rexml/document'
|
16
|
+
|
17
|
+
# Manifest Version 3.
|
18
|
+
# Not backwards compatible
|
19
|
+
class ManifestV3
|
20
|
+
VERSION_STRING = '3'
|
21
|
+
VERSION = VERSION_STRING.to_i
|
22
|
+
|
23
|
+
# AMI part information container.
|
24
|
+
class PartInformation
|
25
|
+
attr_reader :filename # Part's file basename.
|
26
|
+
attr_reader :digest # Part's digest hex encoded.
|
27
|
+
|
28
|
+
# Initialize with part's _filename_ and _digest_ as byte string.
|
29
|
+
def initialize( filename, digest )
|
30
|
+
@filename , @digest = filename, digest
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
def initialize( xml = nil )
|
35
|
+
if xml == nil
|
36
|
+
@doc = REXML::Document.new
|
37
|
+
else
|
38
|
+
# Convert to string if necessary.
|
39
|
+
xml = ( xml.kind_of?( IO ) ? xml.read : xml )
|
40
|
+
@doc = REXML::Document.new( xml )
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
# for debugging only
|
45
|
+
def doc
|
46
|
+
@doc
|
47
|
+
end
|
48
|
+
|
49
|
+
# Initialize the manifest with AMI information.
|
50
|
+
# Return +true+ if the initialization was succesful.
|
51
|
+
# Raise an exception on error.
|
52
|
+
def init(
|
53
|
+
name,
|
54
|
+
user, # The user's account number.
|
55
|
+
parts, # A list of parts filenames and digest pairs.
|
56
|
+
size, # The size of the AMI in bytes.
|
57
|
+
bundled_size, # The size of the bunled AMI in bytes.
|
58
|
+
user_encrypted_key, # Hex encoded.
|
59
|
+
ec2_encrypted_key, # Hex encoded.
|
60
|
+
cipher_algorithm, # The cipher algorithm used to encrypted the AMI.
|
61
|
+
user_encrypted_iv, # Hex encoded.
|
62
|
+
ec2_encrypted_iv, # Hex encoded.
|
63
|
+
digest, # Hex encoded.
|
64
|
+
digest_algorithm, # The digest algorithm.
|
65
|
+
privkey_filename, # The user's private key filename.
|
66
|
+
bundler_name = nil,
|
67
|
+
bundler_version = nil,
|
68
|
+
bundler_release = nil )
|
69
|
+
# Check non-String parameter types.
|
70
|
+
raise ArgumentError.new( "parts parameter type invalid" ) unless parts.is_a? Array
|
71
|
+
|
72
|
+
# XML document.
|
73
|
+
@doc = REXML::Document.new
|
74
|
+
@doc << REXML::XMLDecl.new
|
75
|
+
|
76
|
+
# manifest - the root element.
|
77
|
+
manifest = REXML::Element.new( 'manifest' )
|
78
|
+
|
79
|
+
@doc.add_element( manifest )
|
80
|
+
|
81
|
+
# version - indicate the manifest version.
|
82
|
+
version = REXML::Element.new( 'version' )
|
83
|
+
version.text = VERSION_STRING
|
84
|
+
manifest.add_element( version )
|
85
|
+
|
86
|
+
# bundler information
|
87
|
+
if bundler_name or bundler_version or bundler_release
|
88
|
+
bundler_element = REXML::Element.new( 'bundler' )
|
89
|
+
manifest.add_element( bundler_element )
|
90
|
+
|
91
|
+
[['name', bundler_name ],
|
92
|
+
['version', bundler_version ],
|
93
|
+
['release', bundler_release ]].each do |element_name, text|
|
94
|
+
if element_name
|
95
|
+
element = REXML::Element.new( element_name )
|
96
|
+
element.text = text
|
97
|
+
bundler_element.add_element( element )
|
98
|
+
end
|
99
|
+
end
|
100
|
+
end
|
101
|
+
|
102
|
+
# image - the image element.
|
103
|
+
image = REXML::Element.new( 'image' )
|
104
|
+
name_element = REXML::Element.new( 'name' )
|
105
|
+
name_element.text = name
|
106
|
+
image.add_element( name_element )
|
107
|
+
manifest.add_element( image )
|
108
|
+
|
109
|
+
# user - the user's AWS access key ID.
|
110
|
+
user_element = REXML::Element.new( 'user' )
|
111
|
+
user_element.text = user
|
112
|
+
image.add_element( user_element )
|
113
|
+
|
114
|
+
# digest - the digest of the AMI.
|
115
|
+
digest_element = REXML::Element.new( 'digest' )
|
116
|
+
digest_element.add_attribute( 'algorithm', digest_algorithm )
|
117
|
+
digest_element.add_text( digest )
|
118
|
+
image.add_element( digest_element )
|
119
|
+
|
120
|
+
# size - the size of the uncompressed AMI.
|
121
|
+
size_element = REXML::Element.new( 'size' )
|
122
|
+
size_element.text = size.to_s
|
123
|
+
image.add_element( size_element )
|
124
|
+
|
125
|
+
# size - the size of the uncompressed AMI.
|
126
|
+
bundled_size_element = REXML::Element.new( 'bundled_size' )
|
127
|
+
bundled_size_element.text = bundled_size.to_s
|
128
|
+
image.add_element( bundled_size_element )
|
129
|
+
|
130
|
+
# ec2 encrypted key element.
|
131
|
+
ec2_encrypted_key_element = REXML::Element.new( 'ec2_encrypted_key' )
|
132
|
+
ec2_encrypted_key_element.add_attribute( 'algorithm', cipher_algorithm )
|
133
|
+
ec2_encrypted_key_element.add_text( ec2_encrypted_key )
|
134
|
+
image.add_element( ec2_encrypted_key_element )
|
135
|
+
|
136
|
+
# user encrypted key element.
|
137
|
+
user_encrypted_key_element = REXML::Element.new( 'user_encrypted_key' )
|
138
|
+
user_encrypted_key_element.add_attribute( 'algorithm', cipher_algorithm )
|
139
|
+
user_encrypted_key_element.add_text( user_encrypted_key )
|
140
|
+
image.add_element( user_encrypted_key_element )
|
141
|
+
|
142
|
+
# ec2 encrypted iv element.
|
143
|
+
ec2_encrypted_iv_element = REXML::Element.new( 'ec2_encrypted_iv' )
|
144
|
+
ec2_encrypted_iv_element.add_text( ec2_encrypted_iv )
|
145
|
+
image.add_element( ec2_encrypted_iv_element )
|
146
|
+
|
147
|
+
# user encrypted iv element.
|
148
|
+
user_encrypted_iv_element = REXML::Element.new( 'user_encrypted_iv' )
|
149
|
+
user_encrypted_iv_element.add_text( user_encrypted_iv )
|
150
|
+
image.add_element( user_encrypted_iv_element )
|
151
|
+
|
152
|
+
# parts - list of the image parts.
|
153
|
+
parts_element = REXML::Element.new( 'parts' )
|
154
|
+
parts_element.add_attributes( {'count' => parts.size.to_s} )
|
155
|
+
index=0
|
156
|
+
parts.each do |part|
|
157
|
+
# Add image part element for each image part.
|
158
|
+
part_element = REXML::Element.new( 'part' )
|
159
|
+
part_element.add_attribute( 'index', index.to_s )
|
160
|
+
filename = REXML::Element.new( 'filename' )
|
161
|
+
filename.add_text( part[0] )
|
162
|
+
part_element.add_element( filename )
|
163
|
+
digest = REXML::Element.new( 'digest' )
|
164
|
+
digest.add_attribute( 'algorithm', digest_algorithm )
|
165
|
+
digest.add_text( Format::bin2hex( part[1] ) )
|
166
|
+
part_element.add_element( digest )
|
167
|
+
parts_element.add_element( part_element )
|
168
|
+
index+=1
|
169
|
+
end
|
170
|
+
image.add_element( parts_element )
|
171
|
+
|
172
|
+
# Sign the manifest.
|
173
|
+
sign( privkey_filename )
|
174
|
+
|
175
|
+
return true
|
176
|
+
end
|
177
|
+
|
178
|
+
def ManifestV3::version3?( xml )
|
179
|
+
doc = REXML::Document.new( xml )
|
180
|
+
version = REXML::XPath.first( doc.root, 'version' )
|
181
|
+
return (version and version.text and version.text.to_i == VERSION)
|
182
|
+
end
|
183
|
+
|
184
|
+
# Return the AMI's digest hex encoded.
|
185
|
+
def digest()
|
186
|
+
return get_element_text( 'image/digest' )
|
187
|
+
end
|
188
|
+
|
189
|
+
def name()
|
190
|
+
return get_element_text( 'image/name' )
|
191
|
+
end
|
192
|
+
|
193
|
+
# The ec2 encrypted key hex encoded.
|
194
|
+
def ec2_encrypted_key()
|
195
|
+
return get_element_text('image/ec2_encrypted_key' )
|
196
|
+
end
|
197
|
+
|
198
|
+
# The user encrypted key hex encoded.
|
199
|
+
def user_encrypted_key()
|
200
|
+
return get_element_text( 'image/user_encrypted_key' )
|
201
|
+
end
|
202
|
+
|
203
|
+
# The ec2 encrypted initialization vector hex encoded.
|
204
|
+
def ec2_encrypted_iv()
|
205
|
+
return get_element_text( 'image/ec2_encrypted_iv' )
|
206
|
+
end
|
207
|
+
|
208
|
+
# The user encrypted initialization vector hex encoded.
|
209
|
+
def user_encrypted_iv()
|
210
|
+
return get_element_text( 'image/user_encrypted_iv' )
|
211
|
+
end
|
212
|
+
|
213
|
+
# Get digest algorithm used.
|
214
|
+
def digest_algorithm()
|
215
|
+
return REXML::XPath.first(@doc.root, 'image/digest/@algorithm').to_s
|
216
|
+
end
|
217
|
+
|
218
|
+
# Get cipher algorithm used.
|
219
|
+
def cipher_algorithm()
|
220
|
+
return REXML::XPath.first(@doc.root, 'image/ec2_encrypted_key/@algorithm').to_s
|
221
|
+
end
|
222
|
+
|
223
|
+
# Retrieve a list of AMI bundle parts info. Each element is a hash
|
224
|
+
# with the following elements:
|
225
|
+
# * 'digest'
|
226
|
+
# * 'filename'
|
227
|
+
# * 'index'
|
228
|
+
def ami_part_info_list
|
229
|
+
parts = Array.new
|
230
|
+
REXML::XPath.each( @doc.root,'image/parts/part' ) do |part|
|
231
|
+
index = part.attribute( 'index' ).to_s.to_i
|
232
|
+
filename = REXML::XPath.first( part, 'filename' ).text
|
233
|
+
digest = REXML::XPath.first( part, 'digest' ).text
|
234
|
+
parts << { 'digest'=>digest, 'filename'=>filename, 'index'=>index }
|
235
|
+
end
|
236
|
+
return parts
|
237
|
+
end
|
238
|
+
|
239
|
+
# A list of PartInformation instances representing the AMI parts.
|
240
|
+
def parts()
|
241
|
+
parts = []
|
242
|
+
REXML::XPath.each( @doc.root,'image/parts/part' ) do |part|
|
243
|
+
index = part.attribute( 'index' ).to_s.to_i
|
244
|
+
filename = REXML::XPath.first( part, 'filename' ).text
|
245
|
+
digest = Format::hex2bin( REXML::XPath.first( part, 'digest' ).text )
|
246
|
+
parts[index] = PartInformation.new( filename, digest )
|
247
|
+
end
|
248
|
+
return parts
|
249
|
+
end
|
250
|
+
|
251
|
+
# Return the size of the AMI.
|
252
|
+
def size()
|
253
|
+
return get_element_text( 'image/size' ).to_i()
|
254
|
+
end
|
255
|
+
|
256
|
+
# Return the bundled size of the AMI.
|
257
|
+
def bundled_size()
|
258
|
+
return get_element_text( 'image/bundled_size' ).to_i
|
259
|
+
end
|
260
|
+
|
261
|
+
# Return the bundler name.
|
262
|
+
def bundler_name()
|
263
|
+
return get_element_text('bundler/name')
|
264
|
+
end
|
265
|
+
|
266
|
+
# Return the bundler version.
|
267
|
+
def bundler_version()
|
268
|
+
return get_element_text('bundler/version')
|
269
|
+
end
|
270
|
+
|
271
|
+
# Return the bundler release.
|
272
|
+
def bundler_release()
|
273
|
+
return get_element_text('bundler/release')
|
274
|
+
end
|
275
|
+
|
276
|
+
# Sign the manifest. If it is already signed, the signature and certificate
|
277
|
+
# will be replaced
|
278
|
+
def sign( privkey_filename )
|
279
|
+
unless privkey_filename.kind_of? String and File::exist?( privkey_filename )
|
280
|
+
raise ArgumentError.new( "privkey_filename parameter invalid" )
|
281
|
+
end
|
282
|
+
|
283
|
+
# Get the XML for image element and sign it.
|
284
|
+
image_xml = XMLUtil.get_xml( @doc.to_s, 'image' )
|
285
|
+
sig = Crypto::sign( image_xml, privkey_filename )
|
286
|
+
|
287
|
+
# Create the signature and certificate elements.
|
288
|
+
signature = REXML::Element.new( 'signature' )
|
289
|
+
signature.add_text( Format::bin2hex( sig ) )
|
290
|
+
@doc.root.delete_element( 'signature' )
|
291
|
+
@doc.root.add_element( signature )
|
292
|
+
end
|
293
|
+
|
294
|
+
# Return the signature
|
295
|
+
def signature
|
296
|
+
get_element_text('signature')
|
297
|
+
end
|
298
|
+
|
299
|
+
# Verify the signature
|
300
|
+
def authenticate(cert)
|
301
|
+
image_xml = XMLUtil.get_xml( @doc.to_s, 'image' )
|
302
|
+
pubkey = Crypto::cert2pubkey(cert)
|
303
|
+
Crypto::authenticate(image_xml, Format::hex2bin(signature), pubkey)
|
304
|
+
end
|
305
|
+
|
306
|
+
# Return the manifest as an XML string.
|
307
|
+
def to_s()
|
308
|
+
return @doc.to_s
|
309
|
+
end
|
310
|
+
|
311
|
+
def user()
|
312
|
+
return get_element_text( 'image/user' )
|
313
|
+
end
|
314
|
+
|
315
|
+
def version()
|
316
|
+
return get_element_text( 'version' ).to_i
|
317
|
+
end
|
318
|
+
|
319
|
+
private
|
320
|
+
|
321
|
+
def get_element_text( xpath )
|
322
|
+
element = REXML::XPath.first( @doc.root, xpath )
|
323
|
+
unless element
|
324
|
+
raise "invalid AMI manifest, #{xpath} element not present"
|
325
|
+
end
|
326
|
+
unless element.text
|
327
|
+
raise "invalid AMI manifest, #{xpath} element empty"
|
328
|
+
end
|
329
|
+
return element.text
|
330
|
+
end
|
331
|
+
end
|