ec2_amitools 1.0.2

Sign up to get free protection for your applications and to get access to all the features.
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