aws-sdk 1.6.2 → 1.6.3

Sign up to get free protection for your applications and to get access to all the features.
@@ -25,4 +25,10 @@ AWS::Core::Configuration.module_eval do
25
25
 
26
26
  add_option :s3_server_side_encryption, nil
27
27
 
28
+ add_option :s3_encryption_key, nil
29
+
30
+ add_option :s3_encryption_materials_location, :metadata
31
+
32
+ add_option :s3_storage_class, 'STANDARD'
33
+
28
34
  end
@@ -16,82 +16,169 @@ require 'pathname'
16
16
  module AWS
17
17
  class S3
18
18
 
19
+ # Used by S3#S3Object and S3::Client to accept options with
20
+ # data that should be uploaded (streamed).
19
21
  # @private
20
22
  module DataOptions
21
23
 
22
24
  protected
23
25
 
24
- def data_stream_from options, &block
26
+ # @return [Hash] Returns a hash of options with a :data option that
27
+ # responds to #read and #eof?.
28
+ def compute_write_options *args, &block
25
29
 
26
- validate_data!(options, block)
30
+ options = convert_args_to_options_hash(*args)
27
31
 
28
- # block format
29
- if block_given?
30
- buffer = StringIO.new
31
- yield(buffer)
32
- buffer.rewind
33
- return buffer
32
+ validate_data!(options, &block)
33
+
34
+ rename_file_to_data(options)
35
+
36
+ convert_data_to_io_obj(options, &block)
37
+
38
+ try_to_determine_content_length(options)
39
+
40
+ options
41
+
42
+ end
43
+
44
+ # Converts an argument list into a single hash of options. Treats
45
+ # non-hash arguments in the first position as a data option.
46
+ def convert_args_to_options_hash *args
47
+ case args.count
48
+ when 0 then {}
49
+ when 1 then args[0].is_a?(Hash) ? args[0] : { :data => args[0] }
50
+ when 2 then args[1].merge(:data => args[0])
51
+ else
52
+ msg = "expected 0, 1 or 2 arguments, got #{args.count}"
53
+ raise ArgumentError, msg
34
54
  end
55
+ end
35
56
 
36
- # string, pathname, file, io-like object, etc
37
- data = options[:data]
38
- file_opts = ["rb"]
39
- file_opts << { :encoding => "BINARY" } if Object.const_defined?(:Encoding)
40
- case
41
- when data.is_a?(String)
57
+ # Moves options[:file] to options[:data]. If this option is a string
58
+ # then it is treated as a file path and is converted to an open file.
59
+ def rename_file_to_data options
60
+ if file = options.delete(:file)
61
+ options[:data] = file.is_a?(String) ? open_file(file) : file
62
+ end
63
+ end
64
+
65
+ # Converts the :data option to an IO-like object. This allows us
66
+ # to always perform streaming uploads.
67
+ def convert_data_to_io_obj options, &block
68
+
69
+ data = options.delete(:data)
70
+
71
+ if block_given?
72
+ options[:data] = IOProxy.new(block)
73
+ elsif data.is_a?(String)
42
74
  data.force_encoding("BINARY") if data.respond_to?(:force_encoding)
43
- StringIO.new(data)
44
- when data.is_a?(Pathname) then File.open(data.to_s, *file_opts)
45
- when options[:file] then File.open(options[:file], *file_opts)
46
- else data
75
+ options[:data] = StringIO.new(data)
76
+ elsif data.is_a?(Pathname)
77
+ options[:data] = open_file(data.to_s)
78
+ elsif io_like?(data)
79
+ options[:data] = data
80
+ else
81
+ msg = "invalid :data option, expected a String, Pathname or "
82
+ msg << "an object that responds to #read and #eof?"
83
+ raise ArgumentError, msg
47
84
  end
48
85
 
49
86
  end
50
87
 
51
- def content_length_from options
52
- data = options[:data]
53
- case
54
- when options[:content_length] then options[:content_length]
55
- when options[:file] then File.size(options[:file])
56
- when data.is_a?(Pathname) then File.size(data.to_s)
57
- when data.is_a?(File) then File.size(data.path)
58
- when data.respond_to?(:bytesize) then data.bytesize
59
- when data.respond_to?(:size) then data.size
60
- when data.respond_to?(:length) then data.length
61
- else raise ArgumentError, 'content_length was not provided ' +
62
- 'and could not be determined'
88
+ # Attempts to determine the content length of the :data option.
89
+ # This is only done when a content length is not already provided.
90
+ def try_to_determine_content_length options
91
+ unless options[:content_length]
92
+
93
+ data = options[:data]
94
+
95
+ length = case
96
+ when data.respond_to?(:bytesize) then data.bytesize
97
+ when data.respond_to?(:size) then data.size
98
+ when data.respond_to?(:length) then data.length
99
+ when data.respond_to?(:path) then File.size(data.path)
100
+ else nil
101
+ end
102
+
103
+ options[:content_length] = length if length
104
+
63
105
  end
64
106
  end
65
107
 
66
- def validate_data! options, block
108
+ def validate_data! options, &block
67
109
 
68
110
  data = options[:data]
69
- filename = options[:file]
111
+ file = options[:file]
70
112
 
71
- raise ArgumentError, 'data passed multiple ways' if
72
- [data, filename, block].compact.size > 1
113
+ raise ArgumentError, 'Object data passed multiple ways.' if
114
+ [data, file, block].compact.count > 1
73
115
 
74
- # accepting block format
75
- return if block and block.arity == 1
116
+ data = file if file
76
117
 
77
- # accepting file path
78
- return if filename.kind_of?(String)
79
-
80
- # accepting strings
118
+ return if block_given?
81
119
  return if data.kind_of?(String)
82
-
83
- # accepting pathname
84
120
  return if data.kind_of?(Pathname)
121
+ return if io_like?(data)
122
+
123
+ msg = ":data must be provided as a String, Pathname, File, or "
124
+ msg << "an object that responds to #read and #eof?"
125
+ raise ArgumentError, msg
85
126
 
86
- # accepts io-like objects (responds to read and eof?)
87
- if data.respond_to?(:read) and
88
- data.method(:read).arity != 0 and
89
- data.respond_to?(:eof?) then
90
- return true
127
+ end
128
+
129
+ # @return [Boolean] Returns +true+ if the object responds to
130
+ # +#read+ and +#eof?+.
131
+ def io_like? io
132
+ io.respond_to?(:read) and io.respond_to?(:eof?)
133
+ end
134
+
135
+ # @param [String] path Path to a file on disk.
136
+ # @return [File] Given a path string, returns an open File.
137
+ def open_file path
138
+ file_opts = ['rb']
139
+ file_opts << { :encoding => "BINARY" } if Object.const_defined?(:Encoding)
140
+ File.open(path, *file_opts)
141
+ end
142
+
143
+ # A utility class that turns a block (with 2 args) into an
144
+ # IO object that responds to #read and #eof.
145
+ # @private
146
+ class IOProxy
147
+
148
+ def initialize write_block
149
+ unless write_block.arity == 2
150
+ msg = "a write block must accept 2 yield params: a buffer and "
151
+ msg << "a number of bytes to write"
152
+ raise ArgumentError, msg
153
+ end
154
+ @write_block = write_block
155
+ @eof = false
156
+ end
157
+
158
+ def read bytes = nil
159
+ if bytes
160
+ buffer = StringIO.new
161
+ @write_block.call(buffer, bytes)
162
+ buffer.rewind
163
+ @eof = true if buffer.size < bytes
164
+ buffer.size == 0 ? nil : buffer.read
165
+ else
166
+ read_all
167
+ end
168
+ end
169
+
170
+ def eof?
171
+ @eof
91
172
  end
92
173
 
93
- raise ArgumentError, 'data must be provided as a String, ' +
94
- 'Pathname, file path, or an object that responds to #read and #eof?'
174
+ protected
175
+
176
+ def read_all
177
+ buffer = StringIO.new
178
+ buffer << read(1024 * 1024 * 5) until eof?
179
+ buffer.rewind
180
+ buffer.read
181
+ end
95
182
 
96
183
  end
97
184
 
@@ -0,0 +1,144 @@
1
+ # Copyright 2011-2012 Amazon.com, Inc. or its affiliates. All Rights Reserved.
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License"). You
4
+ # may not use this file except in compliance with the License. A copy of
5
+ # the License is located at
6
+ #
7
+ # http://aws.amazon.com/apache2.0/
8
+ #
9
+ # or in the "license" file accompanying this file. This file is
10
+ # distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF
11
+ # ANY KIND, either express or implied. See the License for the specific
12
+ # language governing permissions and limitations under the License.
13
+
14
+ require 'openssl'
15
+
16
+ module AWS
17
+ class S3
18
+ # @private
19
+ module EncryptionUtils
20
+
21
+ protected
22
+
23
+
24
+ # @param [OpenSSL::PKey::RSA, String] key Key used to encrypt.
25
+ #
26
+ # @param [String] data Data to be encrypted.
27
+ #
28
+ # @note Use check_encryption_materials before this method to check
29
+ # formatting of keys
30
+ #
31
+ # @return [String] Returns the data encrypted with the key given.
32
+ def encrypt data, key
33
+ rsa = OpenSSL::PKey::RSA
34
+ ## Encrypting data key
35
+ case key
36
+ when rsa # Asymmetric encryption
37
+ key.public_encrypt(data)
38
+ when String # Symmetric encryption
39
+ cipher = get_aes_cipher(:encrypt, :ECB, key)
40
+ cipher.update(data) + cipher.final
41
+ end
42
+ end
43
+
44
+ # @param [OpenSSL::PKey::RSA, String] key Key used to encrypt.
45
+ #
46
+ # @param [String] data Data to be encrypted.
47
+ #
48
+ # @note Use check_encryption_materials before this method to check
49
+ # formatting of keys
50
+ #
51
+ # @return [String] Returns the data decrypted with the key given.
52
+ def decrypt data, key
53
+ rsa = OpenSSL::PKey::RSA
54
+ begin
55
+ case key
56
+ when rsa # Asymmetric Decryption
57
+ key.private_decrypt(data)
58
+ when String # Symmetric Decryption
59
+ cipher = get_aes_cipher(:decrypt, :ECB, key)
60
+ cipher.update(data) + cipher.final
61
+ end
62
+ rescue OpenSSL::Cipher::CipherError
63
+ raise RuntimeError, "decryption failed, incorrect key?"
64
+ end
65
+ end
66
+
67
+ # Checks for any formatting problems for keys and initialization vectors
68
+ # supported with EncryptionUtils.
69
+ #
70
+ # @param [OpenSSL::PKey::RSA, String] key Key used to encrypt.
71
+ #
72
+ # @param [String] data Data to be encrypted.
73
+ #
74
+ def check_encryption_materials mode, key
75
+ rsa = OpenSSL::PKey::RSA
76
+ case key
77
+ when rsa
78
+ unless key.private? or mode == :encrypt
79
+ msg = "invalid key, #{rsa} requires a private key"
80
+ raise ArgumentError, msg
81
+ end
82
+ when String # no problem
83
+ else
84
+ msg = "invalid key, must be an #{rsa} or a cipher key string"
85
+ raise ArgumentError, msg
86
+ end
87
+ end
88
+
89
+ # @param [OpenSSL::Cipher] cipher The cipher with configured key and iv.
90
+ #
91
+ # @yield [String, String] key_iv_pair A randomly generated key, iv pair
92
+ # for use with the given cipher. Sets the key and iv on the cipher.
93
+ def generate_aes_key cipher, &block
94
+ key_iv_pair = [cipher.random_key, cipher.random_iv]
95
+ yield(key_iv_pair) if block_given?
96
+ end
97
+
98
+ # @param [Symbol] mode The encryption/decryption mode. Valid inputs are
99
+ # :encrypt or :decrypt
100
+ #
101
+ # @param [String] key Key for the cipher.
102
+ #
103
+ # @param [String] iv IV for the cipher.
104
+ #
105
+ # @return [OpenSSL::Cipher] Will return a configured +OpenSSL::Cipher+.
106
+ def get_aes_cipher mode, block_mode, key = nil, iv = nil
107
+
108
+ # If no key given, default to 256 bit
109
+ cipher_size = (key) ? get_cipher_size(key.length) : 256
110
+
111
+ cipher = OpenSSL::Cipher.new("AES-#{cipher_size}-#{block_mode}")
112
+
113
+ (mode == :encrypt) ? cipher.encrypt : cipher.decrypt
114
+ cipher.key = key if key
115
+ cipher.iv = iv if iv
116
+ cipher
117
+ end
118
+
119
+ # @param [Fixnum] Size of data given.
120
+ # @return [Fixnum] Returns the AES encrypted size based on a given size.
121
+ def get_encrypted_size size
122
+ # The next multiple of 16
123
+ ((size / 16) + 1) * 16
124
+ end
125
+ module_function :get_encrypted_size
126
+
127
+ private
128
+
129
+ # @param [Fixnum] key_length Length of the key given.
130
+ # @return [Fixnum] Returns the cipher size based on the key length.
131
+ def get_cipher_size(key_length)
132
+ case key_length
133
+ when 32 then 256
134
+ when 24 then 192
135
+ when 16 then 128
136
+ else
137
+ msg = "invalid key, symmetric key required to be 16, 24, or 32 bytes "
138
+ msg << "in length, saw length #{key_length}"
139
+ raise ArgumentError, msg
140
+ end
141
+ end
142
+ end
143
+ end
144
+ end
@@ -76,6 +76,20 @@ module AWS
76
76
 
77
77
  end
78
78
 
79
+ # This error is special, because S3 must first retrieve the client
80
+ # side encryption key in it's encrypted form before finding if the
81
+ # key is incorrect.
82
+ class IncorrectClientSideEncryptionKey < AWS::Errors::Base
83
+
84
+ include AWS::Errors::ClientError
85
+
86
+ def initialize(msg)
87
+ super("",
88
+ "",
89
+ "IncorrectClientSideEncryptionKey",
90
+ msg)
91
+ end
92
+ end
79
93
  end
80
94
  end
81
95
  end
@@ -110,8 +110,10 @@ module AWS
110
110
  # storage consumed by all parts.
111
111
  # @return [nil]
112
112
  def abort
113
- client.abort_multipart_upload(base_opts)
114
- @aborted = true
113
+ unless aborted?
114
+ client.abort_multipart_upload(base_opts)
115
+ @aborted = true
116
+ end
115
117
  nil
116
118
  end
117
119
  alias_method :delete, :abort
@@ -257,8 +259,9 @@ module AWS
257
259
  # returns the object. If no upload was attempted (e.g. if it
258
260
  # was aborted or if no parts were uploaded), returns +nil+.
259
261
  def close
260
- return if aborted?
261
- if completed_parts.empty?
262
+ if aborted?
263
+ nil
264
+ elsif completed_parts.empty?
262
265
  abort
263
266
  else
264
267
  complete
@@ -81,8 +81,8 @@ module AWS
81
81
  #
82
82
  # @param [String] key Where in S3 to write the object.
83
83
  # @return [S3Object]
84
- def create key, *args
85
- self[key].write(*args)
84
+ def create key, *args, &block
85
+ self[key].write(*args, &block)
86
86
  end
87
87
 
88
88
  # Returns an S3Object given its name. For example:
@@ -49,7 +49,7 @@ module AWS
49
49
  :get_bucket_notification => "s3:GetBucketNotification",
50
50
  :set_bucket_notification => "s3:PutBucketNotification"
51
51
  }
52
-
52
+
53
53
  protected
54
54
  def resource_arn resource
55
55
  prefix = 'arn:aws:s3:::'
@@ -28,9 +28,6 @@ module AWS
28
28
  # @return [String] S3 object key
29
29
  attr_accessor :key
30
30
 
31
- # @private
32
- attr_accessor :body_stream
33
-
34
31
  # @private
35
32
  attr_accessor :force_path_style
36
33
 
@@ -48,6 +45,14 @@ module AWS
48
45
  end
49
46
  end
50
47
 
48
+ def server_side_encryption= sse
49
+ if sse.is_a?(Symbol)
50
+ headers['x-amz-server-side-encryption'] = sse.to_s.upcase
51
+ elsif sse
52
+ headers['x-amz-server-side-encryption'] = sse
53
+ end
54
+ end
55
+
51
56
  def host
52
57
  path_style? ? @host : "#{bucket}.#{@host}"
53
58
  end
@@ -76,30 +81,13 @@ module AWS
76
81
 
77
82
  end
78
83
 
79
- # @param [String, IO] body The http request body. This can be a string or
80
- # any object that responds to #read and #eof? (like an IO object).
81
- def body= body
82
- @body_stream = StringIO.new(body)
83
- end
84
-
85
- # @return [String, nil] The http request body.
86
- def body
87
- if @body_stream
88
- string = @body_stream.read
89
- @body_stream.rewind
90
- string
91
- else
92
- nil
93
- end
94
- end
95
-
96
84
  # From the S3 developer guide:
97
85
  #
98
- # StringToSign =
99
- # HTTP-Verb + "\n" +
100
- # content-md5 + "\n" +
101
- # content-type + "\n" +
102
- # date + "\n" +
86
+ # StringToSign =
87
+ # HTTP-Verb + "\n" +
88
+ # content-md5 + "\n" +
89
+ # content-type + "\n" +
90
+ # date + "\n" +
103
91
  # CanonicalizedAmzHeaders + CanonicalizedResource;
104
92
  #
105
93
  def string_to_sign
@@ -124,11 +112,11 @@ module AWS
124
112
 
125
113
  # From the S3 developer guide
126
114
  #
127
- # CanonicalizedResource =
128
- # [ "/" + Bucket ] +
115
+ # CanonicalizedResource =
116
+ # [ "/" + Bucket ] +
129
117
  # <HTTP-Request-URI, from the protocol name up to the querystring> +
130
- # [ sub-resource, if present. e.g. "?acl", "?location",
131
- # "?logging", or "?torrent"];
118
+ # [ sub-resource, if present. e.g. "?acl", "?location",
119
+ # "?logging", or "?torrent"];
132
120
  #
133
121
  def canonicalized_resource
134
122
 
@@ -139,11 +127,11 @@ module AWS
139
127
  if Client.dns_compatible_bucket_name?(bucket) and !path_style?
140
128
  parts << "/#{bucket}"
141
129
  end
142
-
130
+
143
131
  # all requests require the portion of the un-decoded uri up to
144
132
  # but not including the query string
145
133
  parts << path
146
-
134
+
147
135
  # lastly any sub resource querystring params need to be appened
148
136
  # in lexigraphical ordered joined by '&' and prefixed by '?'
149
137
  params = (sub_resource_params + query_parameters_for_signature)
@@ -190,8 +178,8 @@ module AWS
190
178
  class << self
191
179
 
192
180
  def sub_resources
193
- %w(acl location logging notification partNumber policy
194
- requestPayment torrent uploadId uploads versionId
181
+ %w(acl location logging notification partNumber policy
182
+ requestPayment torrent uploadId uploads versionId
195
183
  versioning versions delete lifecycle)
196
184
  end
197
185