aws-sdk-s3 1.0.0.rc1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (69) hide show
  1. checksums.yaml +7 -0
  2. data/lib/aws-sdk-s3.rb +66 -0
  3. data/lib/aws-sdk-s3/bucket.rb +595 -0
  4. data/lib/aws-sdk-s3/bucket_acl.rb +168 -0
  5. data/lib/aws-sdk-s3/bucket_cors.rb +146 -0
  6. data/lib/aws-sdk-s3/bucket_lifecycle.rb +164 -0
  7. data/lib/aws-sdk-s3/bucket_logging.rb +142 -0
  8. data/lib/aws-sdk-s3/bucket_notification.rb +187 -0
  9. data/lib/aws-sdk-s3/bucket_policy.rb +138 -0
  10. data/lib/aws-sdk-s3/bucket_region_cache.rb +79 -0
  11. data/lib/aws-sdk-s3/bucket_request_payment.rb +128 -0
  12. data/lib/aws-sdk-s3/bucket_tagging.rb +143 -0
  13. data/lib/aws-sdk-s3/bucket_versioning.rb +188 -0
  14. data/lib/aws-sdk-s3/bucket_website.rb +177 -0
  15. data/lib/aws-sdk-s3/client.rb +3171 -0
  16. data/lib/aws-sdk-s3/client_api.rb +1991 -0
  17. data/lib/aws-sdk-s3/customizations.rb +29 -0
  18. data/lib/aws-sdk-s3/customizations/bucket.rb +127 -0
  19. data/lib/aws-sdk-s3/customizations/multipart_upload.rb +42 -0
  20. data/lib/aws-sdk-s3/customizations/object.rb +257 -0
  21. data/lib/aws-sdk-s3/customizations/object_summary.rb +65 -0
  22. data/lib/aws-sdk-s3/customizations/types/list_object_versions_output.rb +11 -0
  23. data/lib/aws-sdk-s3/encryption.rb +19 -0
  24. data/lib/aws-sdk-s3/encryption/client.rb +369 -0
  25. data/lib/aws-sdk-s3/encryption/decrypt_handler.rb +178 -0
  26. data/lib/aws-sdk-s3/encryption/default_cipher_provider.rb +63 -0
  27. data/lib/aws-sdk-s3/encryption/default_key_provider.rb +38 -0
  28. data/lib/aws-sdk-s3/encryption/encrypt_handler.rb +50 -0
  29. data/lib/aws-sdk-s3/encryption/errors.rb +13 -0
  30. data/lib/aws-sdk-s3/encryption/io_auth_decrypter.rb +50 -0
  31. data/lib/aws-sdk-s3/encryption/io_decrypter.rb +29 -0
  32. data/lib/aws-sdk-s3/encryption/io_encrypter.rb +69 -0
  33. data/lib/aws-sdk-s3/encryption/key_provider.rb +29 -0
  34. data/lib/aws-sdk-s3/encryption/kms_cipher_provider.rb +71 -0
  35. data/lib/aws-sdk-s3/encryption/materials.rb +58 -0
  36. data/lib/aws-sdk-s3/encryption/utils.rb +79 -0
  37. data/lib/aws-sdk-s3/errors.rb +23 -0
  38. data/lib/aws-sdk-s3/file_part.rb +75 -0
  39. data/lib/aws-sdk-s3/file_uploader.rb +58 -0
  40. data/lib/aws-sdk-s3/legacy_signer.rb +186 -0
  41. data/lib/aws-sdk-s3/multipart_file_uploader.rb +187 -0
  42. data/lib/aws-sdk-s3/multipart_upload.rb +287 -0
  43. data/lib/aws-sdk-s3/multipart_upload_error.rb +16 -0
  44. data/lib/aws-sdk-s3/multipart_upload_part.rb +314 -0
  45. data/lib/aws-sdk-s3/object.rb +942 -0
  46. data/lib/aws-sdk-s3/object_acl.rb +214 -0
  47. data/lib/aws-sdk-s3/object_copier.rb +99 -0
  48. data/lib/aws-sdk-s3/object_multipart_copier.rb +179 -0
  49. data/lib/aws-sdk-s3/object_summary.rb +794 -0
  50. data/lib/aws-sdk-s3/object_version.rb +406 -0
  51. data/lib/aws-sdk-s3/plugins/accelerate.rb +92 -0
  52. data/lib/aws-sdk-s3/plugins/bucket_dns.rb +89 -0
  53. data/lib/aws-sdk-s3/plugins/bucket_name_restrictions.rb +23 -0
  54. data/lib/aws-sdk-s3/plugins/dualstack.rb +70 -0
  55. data/lib/aws-sdk-s3/plugins/expect_100_continue.rb +29 -0
  56. data/lib/aws-sdk-s3/plugins/get_bucket_location_fix.rb +23 -0
  57. data/lib/aws-sdk-s3/plugins/http_200_errors.rb +47 -0
  58. data/lib/aws-sdk-s3/plugins/location_constraint.rb +33 -0
  59. data/lib/aws-sdk-s3/plugins/md5s.rb +79 -0
  60. data/lib/aws-sdk-s3/plugins/redirects.rb +41 -0
  61. data/lib/aws-sdk-s3/plugins/s3_signer.rb +208 -0
  62. data/lib/aws-sdk-s3/plugins/sse_cpk.rb +68 -0
  63. data/lib/aws-sdk-s3/plugins/url_encoded_keys.rb +94 -0
  64. data/lib/aws-sdk-s3/presigned_post.rb +647 -0
  65. data/lib/aws-sdk-s3/presigner.rb +160 -0
  66. data/lib/aws-sdk-s3/resource.rb +96 -0
  67. data/lib/aws-sdk-s3/types.rb +5750 -0
  68. data/lib/aws-sdk-s3/waiters.rb +178 -0
  69. metadata +154 -0
@@ -0,0 +1,178 @@
1
+ require 'base64'
2
+
3
+ module Aws
4
+ module S3
5
+ module Encryption
6
+ # @api private
7
+ class DecryptHandler < Seahorse::Client::Handler
8
+
9
+ V1_ENVELOPE_KEYS = %w(
10
+ x-amz-key
11
+ x-amz-iv
12
+ x-amz-matdesc
13
+ )
14
+
15
+ V2_ENVELOPE_KEYS = %w(
16
+ x-amz-key-v2
17
+ x-amz-iv
18
+ x-amz-cek-alg
19
+ x-amz-wrap-alg
20
+ x-amz-matdesc
21
+ )
22
+
23
+ POSSIBLE_ENVELOPE_KEYS = (V1_ENVELOPE_KEYS + V2_ENVELOPE_KEYS).uniq
24
+
25
+ POSSIBLE_ENCRYPTION_FORMATS = %w(
26
+ AES/GCM/NoPadding
27
+ AES/CBC/PKCS5Padding
28
+ )
29
+
30
+ def call(context)
31
+ attach_http_event_listeners(context)
32
+ @handler.call(context)
33
+ end
34
+
35
+ private
36
+
37
+ def attach_http_event_listeners(context)
38
+
39
+ context.http_response.on_headers(200) do
40
+ cipher = decryption_cipher(context)
41
+ decrypter = body_contains_auth_tag?(context) ?
42
+ authenticated_decrypter(context, cipher) :
43
+ IODecrypter.new(cipher, context.http_response.body)
44
+ context.http_response.body = decrypter
45
+ end
46
+
47
+ context.http_response.on_success(200) do
48
+ decrypter = context.http_response.body
49
+ decrypter.finalize
50
+ decrypter.io.rewind if decrypter.io.respond_to?(:rewind)
51
+ context.http_response.body = decrypter.io
52
+ end
53
+
54
+ context.http_response.on_error do
55
+ if context.http_response.body.respond_to?(:io)
56
+ context.http_response.body = context.http_response.body.io
57
+ end
58
+ end
59
+ end
60
+
61
+ def decryption_cipher(context)
62
+ if envelope = get_encryption_envelope(context)
63
+ context[:encryption][:cipher_provider].decryption_cipher(envelope)
64
+ else
65
+ raise Errors::DecryptionError, "unable to locate encryption envelope"
66
+ end
67
+ end
68
+
69
+ def get_encryption_envelope(context)
70
+ if context[:encryption][:envelope_location] == :metadata
71
+ envelope_from_metadata(context) || envelope_from_instr_file(context)
72
+ else
73
+ envelope_from_instr_file(context) || envelope_from_metadata(context)
74
+ end
75
+ end
76
+
77
+ def envelope_from_metadata(context)
78
+ possible_envelope = {}
79
+ POSSIBLE_ENVELOPE_KEYS.each do |suffix|
80
+ if value = context.http_response.headers["x-amz-meta-#{suffix}"]
81
+ possible_envelope[suffix] = value
82
+ end
83
+ end
84
+ extract_envelope(possible_envelope)
85
+ end
86
+
87
+ def envelope_from_instr_file(context)
88
+ suffix = context[:encryption][:instruction_file_suffix]
89
+ possible_envelope = Json.load(context.client.get_object(
90
+ bucket: context.params[:bucket],
91
+ key: context.params[:key] + suffix
92
+ ).body.read)
93
+ extract_envelope(possible_envelope)
94
+ rescue S3::Errors::ServiceError, Json::ParseError
95
+ nil
96
+ end
97
+
98
+ def extract_envelope(hash)
99
+ return v1_envelope(hash) if hash.key?('x-amz-key')
100
+ return v2_envelope(hash) if hash.key?('x-amz-key-v2')
101
+ if hash.keys.any? { |key| key.match(/^x-amz-key-(.+)$/) }
102
+ msg = "unsupported envelope encryption version #{$1}"
103
+ raise Errors::DecryptionError, msg
104
+ else
105
+ nil # no envelope found
106
+ end
107
+ end
108
+
109
+ def v1_envelope(envelope)
110
+ envelope
111
+ end
112
+
113
+ def v2_envelope(envelope)
114
+ unless POSSIBLE_ENCRYPTION_FORMATS.include? envelope['x-amz-cek-alg']
115
+ alg = envelope['x-amz-cek-alg'].inspect
116
+ msg = "unsupported content encrypting key (cek) format: #{alg}"
117
+ raise Errors::DecryptionError, msg
118
+ end
119
+ unless envelope['x-amz-wrap-alg'] == 'kms'
120
+ # possible to support
121
+ # RSA/ECB/OAEPWithSHA-256AndMGF1Padding
122
+ alg = envelope['x-amz-wrap-alg'].inspect
123
+ msg = "unsupported key wrapping algorithm: #{alg}"
124
+ raise Errors::DecryptionError, msg
125
+ end
126
+ unless V2_ENVELOPE_KEYS.sort == envelope.keys.sort
127
+ msg = "incomplete v2 encryption envelope:\n"
128
+ msg += " expected: #{V2_ENVELOPE_KEYS.join(',')}\n"
129
+ msg += " got: #{envelope_keys.join(', ')}"
130
+ raise Errors::DecryptionError, msg
131
+ end
132
+ envelope
133
+ end
134
+
135
+ # When the x-amz-meta-x-amz-tag-len header is present, it indicates
136
+ # that the body of this object has a trailing auth tag. The header
137
+ # indicates the length of that tag.
138
+ #
139
+ # This method fetches the tag from the end of the object by
140
+ # making a GET Object w/range request. This auth tag is used
141
+ # to initialize the cipher, and the decrypter truncates the
142
+ # auth tag from the body when writing the final bytes.
143
+ def authenticated_decrypter(context, cipher)
144
+ if RUBY_VERSION.match(/1.9/)
145
+ raise "authenticated decryption not supported by OpeenSSL in Ruby version ~> 1.9"
146
+ raise Aws::Errors::NonSupportedRubyVersionError, msg
147
+ end
148
+ http_resp = context.http_response
149
+ content_length = http_resp.headers['content-length'].to_i
150
+ auth_tag_length = http_resp.headers['x-amz-meta-x-amz-tag-len']
151
+ auth_tag_length = auth_tag_length.to_i / 8
152
+
153
+ auth_tag = context.client.get_object(
154
+ bucket: context.params[:bucket],
155
+ key: context.params[:key],
156
+ range: "bytes=-#{auth_tag_length}"
157
+ ).body.read
158
+
159
+ cipher.auth_tag = auth_tag
160
+ cipher.auth_data = ''
161
+
162
+ # The encrypted object contains both the cipher text
163
+ # plus a trailing auth tag. This decrypter will the body
164
+ # expect for the trailing auth tag.
165
+ decrypter = IOAuthDecrypter.new(
166
+ io: http_resp.body,
167
+ encrypted_content_length: content_length - auth_tag_length,
168
+ cipher: cipher)
169
+ end
170
+
171
+ def body_contains_auth_tag?(context)
172
+ context.http_response.headers['x-amz-meta-x-amz-tag-len']
173
+ end
174
+
175
+ end
176
+ end
177
+ end
178
+ end
@@ -0,0 +1,63 @@
1
+ require 'base64'
2
+
3
+ module Aws
4
+ module S3
5
+ module Encryption
6
+ # @api private
7
+ class DefaultCipherProvider
8
+
9
+ def initialize(options = {})
10
+ @key_provider = options[:key_provider]
11
+ end
12
+
13
+ # @return [Array<Hash,Cipher>] Creates an returns a new encryption
14
+ # envelope and encryption cipher.
15
+ def encryption_cipher
16
+ cipher = Utils.aes_encryption_cipher(:CBC)
17
+ envelope = {
18
+ 'x-amz-key' => encode64(encrypt(envelope_key(cipher))),
19
+ 'x-amz-iv' => encode64(envelope_iv(cipher)),
20
+ 'x-amz-matdesc' => materials_description,
21
+ }
22
+ [envelope, cipher]
23
+ end
24
+
25
+ # @return [Cipher] Given an encryption envelope, returns a
26
+ # decryption cipher.
27
+ def decryption_cipher(envelope)
28
+ master_key = @key_provider.key_for(envelope['x-amz-matdesc'])
29
+ key = Utils.decrypt(master_key, decode64(envelope['x-amz-key']))
30
+ iv = decode64(envelope['x-amz-iv'])
31
+ Utils.aes_decryption_cipher(:CBC, key, iv)
32
+ end
33
+
34
+ private
35
+
36
+ def envelope_key(cipher)
37
+ cipher.key = cipher.random_key
38
+ end
39
+
40
+ def envelope_iv(cipher)
41
+ cipher.iv = cipher.random_iv
42
+ end
43
+
44
+ def encrypt(data)
45
+ Utils.encrypt(@key_provider.encryption_materials.key, data)
46
+ end
47
+
48
+ def materials_description
49
+ @key_provider.encryption_materials.description
50
+ end
51
+
52
+ def encode64(str)
53
+ Base64.encode64(str).split("\n") * ""
54
+ end
55
+
56
+ def decode64(str)
57
+ Base64.decode64(str)
58
+ end
59
+
60
+ end
61
+ end
62
+ end
63
+ end
@@ -0,0 +1,38 @@
1
+ module Aws
2
+ module S3
3
+ module Encryption
4
+
5
+ # The default key provider is constructed with a single key
6
+ # that is used for both encryption and decryption, ignoring
7
+ # the possible per-object envelope encryption materials description.
8
+ # @api private
9
+ class DefaultKeyProvider
10
+
11
+ include KeyProvider
12
+
13
+ # @option options [required, OpenSSL::PKey::RSA, String] :encryption_key
14
+ # The master key to use for encrypting objects.
15
+ # @option options [String<JSON>] :materials_description ('{}')
16
+ # A description of the encryption key.
17
+ def initialize(options = {})
18
+ @encryption_materials = Materials.new(
19
+ key: options[:encryption_key],
20
+ description: options[:materials_description] || '{}'
21
+ )
22
+ end
23
+
24
+ # @return [Materials]
25
+ def encryption_materials
26
+ @encryption_materials
27
+ end
28
+
29
+ # @param [String<JSON>] materials_description
30
+ # @return Returns the key given in the constructor.
31
+ def key_for(materials_description)
32
+ @encryption_materials.key
33
+ end
34
+
35
+ end
36
+ end
37
+ end
38
+ end
@@ -0,0 +1,50 @@
1
+ require 'base64'
2
+
3
+ module Aws
4
+ module S3
5
+ module Encryption
6
+ # @api private
7
+ class EncryptHandler < Seahorse::Client::Handler
8
+
9
+ def call(context)
10
+ envelope, cipher = context[:encryption][:cipher_provider].encryption_cipher
11
+ apply_encryption_envelope(context, envelope, cipher)
12
+ apply_encryption_cipher(context, cipher)
13
+ @handler.call(context)
14
+ end
15
+
16
+ private
17
+
18
+ def apply_encryption_envelope(context, envelope, cipher)
19
+ context[:encryption][:cipher] = cipher
20
+ if context[:encryption][:envelope_location] == :metadata
21
+ context.params[:metadata] ||= {}
22
+ context.params[:metadata].update(envelope)
23
+ else # :instruction_file
24
+ suffix = context[:encryption][:instruction_file_suffix]
25
+ context.client.put_object(
26
+ bucket: context.params[:bucket],
27
+ key: context.params[:key] + suffix,
28
+ body: Json.dump(envelope)
29
+ )
30
+ end
31
+ end
32
+
33
+ def apply_encryption_cipher(context, cipher)
34
+ io = context.params[:body] || ''
35
+ io = StringIO.new(io) if String === io
36
+ context.params[:body] = IOEncrypter.new(cipher, io)
37
+ context.params[:metadata] ||= {}
38
+ context.params[:metadata]['x-amz-unencrypted-content-length'] = io.size
39
+ if md5 = context.params.delete(:content_md5)
40
+ context.params[:metadata]['x-amz-unencrypted-content-md5'] = md5
41
+ end
42
+ context.http_response.on_headers do
43
+ context.params[:body].close
44
+ end
45
+ end
46
+
47
+ end
48
+ end
49
+ end
50
+ end
@@ -0,0 +1,13 @@
1
+ module Aws
2
+ module S3
3
+ module Encryption
4
+ module Errors
5
+
6
+ class DecryptionError < RuntimeError; end
7
+
8
+ class EncryptionError < RuntimeError; end
9
+
10
+ end
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,50 @@
1
+ module Aws
2
+ module S3
3
+ module Encryption
4
+ # @api private
5
+ class IOAuthDecrypter
6
+
7
+ # @option options [required, IO#write] :io
8
+ # An IO-like object that responds to {#write}.
9
+ # @option options [required, Integer] :encrypted_content_length
10
+ # The number of bytes to decrypt from the `:io` object.
11
+ # This should be the total size of `:io` minus the length of
12
+ # the cipher auth tag.
13
+ # @option options [required, OpenSSL::Cipher] :cipher An initialized
14
+ # cipher that can be used to decrypt the bytes as they are
15
+ # written to the `:io` object. The cipher should already have
16
+ # its `#auth_tag` set.
17
+ def initialize(options = {})
18
+ @decrypter = IODecrypter.new(options[:cipher], options[:io])
19
+ @max_bytes = options[:encrypted_content_length]
20
+ @bytes_written = 0
21
+ end
22
+
23
+ def write(chunk)
24
+ chunk = truncate_chunk(chunk)
25
+ @bytes_written += chunk.bytesize
26
+ @decrypter.write(chunk)
27
+ end
28
+
29
+ def finalize
30
+ @decrypter.finalize
31
+ end
32
+
33
+ def io
34
+ @decrypter.io
35
+ end
36
+
37
+ private
38
+
39
+ def truncate_chunk(chunk)
40
+ if chunk.bytesize + @bytes_written <= @max_bytes
41
+ chunk
42
+ else
43
+ chunk[0..(@max_bytes - @bytes_written - 1)]
44
+ end
45
+ end
46
+
47
+ end
48
+ end
49
+ end
50
+ end
@@ -0,0 +1,29 @@
1
+ module Aws
2
+ module S3
3
+ module Encryption
4
+ # @api private
5
+ class IODecrypter
6
+
7
+ # @param [OpenSSL::Cipher] cipher
8
+ # @param [IO#write] io An IO-like object that responds to `#write`.
9
+ def initialize(cipher, io)
10
+ @cipher = cipher.clone
11
+ @io = io
12
+ end
13
+
14
+ # @return [#write]
15
+ attr_reader :io
16
+
17
+ def write(chunk)
18
+ # decrypt and write
19
+ @io.write(@cipher.update(chunk))
20
+ end
21
+
22
+ def finalize
23
+ @io.write(@cipher.final)
24
+ end
25
+
26
+ end
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,69 @@
1
+ require 'stringio'
2
+ require 'tempfile'
3
+
4
+ module Aws
5
+ module S3
6
+ module Encryption
7
+
8
+ # Provides an IO wrapper encrpyting a stream of data.
9
+ # It is possible to use this same object for decrypting. You must
10
+ # initialize it with a decryptiion cipher in that case and the
11
+ # IO object must contain cipher text instead of plain text.
12
+ # @api private
13
+ class IOEncrypter
14
+
15
+ # @api private
16
+ ONE_MEGABYTE = 1024 * 1024
17
+
18
+ def initialize(cipher, io)
19
+ @encrypted = io.size <= ONE_MEGABYTE ?
20
+ encrypt_to_stringio(cipher, io.read) :
21
+ encrypt_to_tempfile(cipher, io)
22
+ @size = @encrypted.size
23
+ end
24
+
25
+ # @return [Integer]
26
+ attr_reader :size
27
+
28
+ def read(bytes = nil, output_buffer = nil)
29
+ if Tempfile === @encrypted && @encrypted.closed?
30
+ @encrypted.open
31
+ @encrypted.binmode
32
+ end
33
+ @encrypted.read(bytes, output_buffer)
34
+ end
35
+
36
+ def rewind
37
+ @encrypted.rewind
38
+ end
39
+
40
+ # @api private
41
+ def close
42
+ @encrypted.close if Tempfile === @encrypted
43
+ end
44
+
45
+ private
46
+
47
+ def encrypt_to_stringio(cipher, plain_text)
48
+ if plain_text.empty?
49
+ StringIO.new(cipher.final)
50
+ else
51
+ StringIO.new(cipher.update(plain_text) + cipher.final)
52
+ end
53
+ end
54
+
55
+ def encrypt_to_tempfile(cipher, io)
56
+ encrypted = Tempfile.new(self.object_id.to_s)
57
+ encrypted.binmode
58
+ while chunk = io.read(ONE_MEGABYTE)
59
+ encrypted.write(cipher.update(chunk))
60
+ end
61
+ encrypted.write(cipher.final)
62
+ encrypted.rewind
63
+ encrypted
64
+ end
65
+
66
+ end
67
+ end
68
+ end
69
+ end