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.
Files changed (102) hide show
  1. checksums.yaml +7 -0
  2. data/README.md +54 -0
  3. data/bin/console +14 -0
  4. data/bin/ec2-ami-tools-version +6 -0
  5. data/bin/ec2-bundle-image +6 -0
  6. data/bin/ec2-bundle-vol +6 -0
  7. data/bin/ec2-delete-bundle +6 -0
  8. data/bin/ec2-download-bundle +6 -0
  9. data/bin/ec2-migrate-bundle +6 -0
  10. data/bin/ec2-migrate-manifest +6 -0
  11. data/bin/ec2-unbundle +6 -0
  12. data/bin/ec2-upload-bundle +6 -0
  13. data/bin/setup +8 -0
  14. data/etc/ec2/amitools/cert-ec2-cn-north-1.pem +28 -0
  15. data/etc/ec2/amitools/cert-ec2-gov.pem +17 -0
  16. data/etc/ec2/amitools/cert-ec2.pem +23 -0
  17. data/etc/ec2/amitools/mappings.csv +9 -0
  18. data/lib/ec2/amitools/bundle.rb +251 -0
  19. data/lib/ec2/amitools/bundle_base.rb +58 -0
  20. data/lib/ec2/amitools/bundleimage.rb +94 -0
  21. data/lib/ec2/amitools/bundleimageparameters.rb +42 -0
  22. data/lib/ec2/amitools/bundlemachineparameters.rb +60 -0
  23. data/lib/ec2/amitools/bundleparameters.rb +120 -0
  24. data/lib/ec2/amitools/bundlevol.rb +240 -0
  25. data/lib/ec2/amitools/bundlevolparameters.rb +164 -0
  26. data/lib/ec2/amitools/crypto.rb +379 -0
  27. data/lib/ec2/amitools/decryptmanifest.rb +20 -0
  28. data/lib/ec2/amitools/defaults.rb +12 -0
  29. data/lib/ec2/amitools/deletebundle.rb +212 -0
  30. data/lib/ec2/amitools/deletebundleparameters.rb +78 -0
  31. data/lib/ec2/amitools/downloadbundle.rb +161 -0
  32. data/lib/ec2/amitools/downloadbundleparameters.rb +84 -0
  33. data/lib/ec2/amitools/exception.rb +86 -0
  34. data/lib/ec2/amitools/fileutil.rb +219 -0
  35. data/lib/ec2/amitools/format.rb +127 -0
  36. data/lib/ec2/amitools/instance-data.rb +97 -0
  37. data/lib/ec2/amitools/manifest_wrapper.rb +132 -0
  38. data/lib/ec2/amitools/manifestv20070829.rb +361 -0
  39. data/lib/ec2/amitools/manifestv20071010.rb +403 -0
  40. data/lib/ec2/amitools/manifestv3.rb +331 -0
  41. data/lib/ec2/amitools/mapids.rb +148 -0
  42. data/lib/ec2/amitools/migratebundle.rb +222 -0
  43. data/lib/ec2/amitools/migratebundleparameters.rb +173 -0
  44. data/lib/ec2/amitools/migratemanifest.rb +225 -0
  45. data/lib/ec2/amitools/migratemanifestparameters.rb +118 -0
  46. data/lib/ec2/amitools/minimalec2.rb +116 -0
  47. data/lib/ec2/amitools/parameter_exceptions.rb +34 -0
  48. data/lib/ec2/amitools/parameters_base.rb +168 -0
  49. data/lib/ec2/amitools/region.rb +93 -0
  50. data/lib/ec2/amitools/s3toolparameters.rb +183 -0
  51. data/lib/ec2/amitools/showversion.rb +12 -0
  52. data/lib/ec2/amitools/syschecks.rb +27 -0
  53. data/lib/ec2/amitools/tool_base.rb +224 -0
  54. data/lib/ec2/amitools/unbundle.rb +107 -0
  55. data/lib/ec2/amitools/unbundleparameters.rb +65 -0
  56. data/lib/ec2/amitools/uploadbundle.rb +361 -0
  57. data/lib/ec2/amitools/uploadbundleparameters.rb +108 -0
  58. data/lib/ec2/amitools/util.rb +532 -0
  59. data/lib/ec2/amitools/version.rb +33 -0
  60. data/lib/ec2/amitools/xmlbuilder.rb +237 -0
  61. data/lib/ec2/amitools/xmlutil.rb +55 -0
  62. data/lib/ec2/common/constants.rb +16 -0
  63. data/lib/ec2/common/curl.rb +110 -0
  64. data/lib/ec2/common/headers.rb +95 -0
  65. data/lib/ec2/common/headersv4.rb +173 -0
  66. data/lib/ec2/common/http.rb +333 -0
  67. data/lib/ec2/common/s3support.rb +231 -0
  68. data/lib/ec2/common/signature.rb +68 -0
  69. data/lib/ec2/oem/LICENSE.txt +58 -0
  70. data/lib/ec2/oem/open4.rb +399 -0
  71. data/lib/ec2/platform/base/architecture.rb +26 -0
  72. data/lib/ec2/platform/base/constants.rb +54 -0
  73. data/lib/ec2/platform/base/pipeline.rb +181 -0
  74. data/lib/ec2/platform/base.rb +57 -0
  75. data/lib/ec2/platform/current.rb +55 -0
  76. data/lib/ec2/platform/linux/architecture.rb +35 -0
  77. data/lib/ec2/platform/linux/constants.rb +23 -0
  78. data/lib/ec2/platform/linux/fstab.rb +99 -0
  79. data/lib/ec2/platform/linux/identity.rb +16 -0
  80. data/lib/ec2/platform/linux/image.rb +811 -0
  81. data/lib/ec2/platform/linux/mtab.rb +74 -0
  82. data/lib/ec2/platform/linux/pipeline.rb +40 -0
  83. data/lib/ec2/platform/linux/rsync.rb +114 -0
  84. data/lib/ec2/platform/linux/tar.rb +124 -0
  85. data/lib/ec2/platform/linux/uname.rb +50 -0
  86. data/lib/ec2/platform/linux.rb +83 -0
  87. data/lib/ec2/platform/solaris/architecture.rb +28 -0
  88. data/lib/ec2/platform/solaris/constants.rb +30 -0
  89. data/lib/ec2/platform/solaris/fstab.rb +43 -0
  90. data/lib/ec2/platform/solaris/identity.rb +16 -0
  91. data/lib/ec2/platform/solaris/image.rb +327 -0
  92. data/lib/ec2/platform/solaris/mtab.rb +29 -0
  93. data/lib/ec2/platform/solaris/pipeline.rb +40 -0
  94. data/lib/ec2/platform/solaris/rsync.rb +24 -0
  95. data/lib/ec2/platform/solaris/tar.rb +36 -0
  96. data/lib/ec2/platform/solaris/uname.rb +21 -0
  97. data/lib/ec2/platform/solaris.rb +38 -0
  98. data/lib/ec2/platform.rb +69 -0
  99. data/lib/ec2/version.rb +8 -0
  100. data/lib/ec2_amitools +1 -0
  101. data/lib/ec2_amitools.rb +7 -0
  102. 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