active_storage_encryption 0.1.0

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 (78) hide show
  1. checksums.yaml +7 -0
  2. data/Appraisals +7 -0
  3. data/MIT-LICENSE +20 -0
  4. data/README.md +236 -0
  5. data/Rakefile +17 -0
  6. data/bin/rails +26 -0
  7. data/bin/rubocop +8 -0
  8. data/config/initializers/active_storage_encryption.rb +9 -0
  9. data/config/routes.rb +7 -0
  10. data/gemfiles/rails_7.gemfile +7 -0
  11. data/gemfiles/rails_7.gemfile.lock +276 -0
  12. data/gemfiles/rails_8.gemfile +7 -0
  13. data/gemfiles/rails_8.gemfile.lock +276 -0
  14. data/lib/active_storage/service/encrypted_disk_service.rb +10 -0
  15. data/lib/active_storage/service/encrypted_mirror_service.rb +10 -0
  16. data/lib/active_storage/service/encrypted_s3_service.rb +10 -0
  17. data/lib/active_storage_encryption/encrypted_blobs_controller.rb +163 -0
  18. data/lib/active_storage_encryption/encrypted_disk_service/v1_scheme.rb +28 -0
  19. data/lib/active_storage_encryption/encrypted_disk_service/v2_scheme.rb +51 -0
  20. data/lib/active_storage_encryption/encrypted_disk_service.rb +186 -0
  21. data/lib/active_storage_encryption/encrypted_mirror_service.rb +76 -0
  22. data/lib/active_storage_encryption/encrypted_s3_service.rb +236 -0
  23. data/lib/active_storage_encryption/engine.rb +7 -0
  24. data/lib/active_storage_encryption/overrides.rb +201 -0
  25. data/lib/active_storage_encryption/private_url_policy.rb +53 -0
  26. data/lib/active_storage_encryption/resumable_gcs_upload.rb +194 -0
  27. data/lib/active_storage_encryption/version.rb +5 -0
  28. data/lib/active_storage_encryption.rb +79 -0
  29. data/lib/tasks/active_storage_encryption_tasks.rake +6 -0
  30. data/test/active_storage_encryption_test.rb +9 -0
  31. data/test/dummy/Rakefile +8 -0
  32. data/test/dummy/app/assets/stylesheets/application.css +1 -0
  33. data/test/dummy/app/controllers/application_controller.rb +6 -0
  34. data/test/dummy/app/helpers/application_helper.rb +4 -0
  35. data/test/dummy/app/models/application_record.rb +5 -0
  36. data/test/dummy/app/views/layouts/application.html.erb +22 -0
  37. data/test/dummy/app/views/pwa/manifest.json.erb +22 -0
  38. data/test/dummy/app/views/pwa/service-worker.js +26 -0
  39. data/test/dummy/bin/rails +4 -0
  40. data/test/dummy/bin/rake +4 -0
  41. data/test/dummy/bin/setup +37 -0
  42. data/test/dummy/config/application.rb +43 -0
  43. data/test/dummy/config/boot.rb +7 -0
  44. data/test/dummy/config/credentials.yml.enc +1 -0
  45. data/test/dummy/config/database.yml +32 -0
  46. data/test/dummy/config/environment.rb +7 -0
  47. data/test/dummy/config/environments/development.rb +59 -0
  48. data/test/dummy/config/environments/production.rb +81 -0
  49. data/test/dummy/config/environments/test.rb +53 -0
  50. data/test/dummy/config/initializers/content_security_policy.rb +27 -0
  51. data/test/dummy/config/initializers/filter_parameter_logging.rb +10 -0
  52. data/test/dummy/config/initializers/inflections.rb +18 -0
  53. data/test/dummy/config/initializers/permissions_policy.rb +15 -0
  54. data/test/dummy/config/locales/en.yml +31 -0
  55. data/test/dummy/config/master.key +1 -0
  56. data/test/dummy/config/puma.rb +36 -0
  57. data/test/dummy/config/routes.rb +5 -0
  58. data/test/dummy/config/storage.yml +21 -0
  59. data/test/dummy/config.ru +8 -0
  60. data/test/dummy/db/migrate/20250304023851_create_active_storage_tables.active_storage.rb +60 -0
  61. data/test/dummy/db/migrate/20250304023853_add_blob_encryption_key_column.rb +7 -0
  62. data/test/dummy/db/schema.rb +47 -0
  63. data/test/dummy/log/test.log +1022 -0
  64. data/test/dummy/public/404.html +67 -0
  65. data/test/dummy/public/406-unsupported-browser.html +66 -0
  66. data/test/dummy/public/422.html +67 -0
  67. data/test/dummy/public/500.html +66 -0
  68. data/test/dummy/public/icon.png +0 -0
  69. data/test/dummy/public/icon.svg +3 -0
  70. data/test/dummy/storage/test.sqlite3 +0 -0
  71. data/test/dummy/storage/x6/pl/x6plznfuhrsyjn9pox2a6xgmcs3x +0 -0
  72. data/test/dummy/storage/yq/sv/yqsvw5a72b3fv719zq8a6yb7lv0j +0 -0
  73. data/test/integration/encrypted_blobs_controller_test.rb +400 -0
  74. data/test/lib/encrypted_disk_service_test.rb +387 -0
  75. data/test/lib/encrypted_mirror_service_test.rb +159 -0
  76. data/test/lib/encrypted_s3_service_test.rb +293 -0
  77. data/test/test_helper.rb +19 -0
  78. metadata +264 -0
@@ -0,0 +1,293 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "test_helper"
4
+ require "net/http"
5
+
6
+ class ActiveStorageEncryption::EncryptedS3ServiceTest < ActiveSupport::TestCase
7
+ def config
8
+ {
9
+ access_key_id: ENV.fetch("AWS_ACCESS_KEY_ID"),
10
+ secret_access_key: ENV.fetch("AWS_SECRET_ACCESS_KEY"),
11
+ region: "eu-central-1",
12
+ bucket: "active-storage-encryption-test-bucket"
13
+ }
14
+ end
15
+
16
+ setup do
17
+ if ENV["AWS_ACCESS_KEY_ID"].blank? || ENV["AWS_SECRET_ACCESS_KEY"].blank?
18
+ skip "You need AWS_ACCESS_KEY_ID and AWS_SECRET_ACCESS_KEY set in your env to test the EncryptedS3Service"
19
+ end
20
+ end
21
+
22
+ setup do
23
+ require "active_storage/service/s3_service"
24
+ @service = ActiveStorageEncryption::EncryptedS3Service.new(**config)
25
+ @service.name = "amazing_encrypting_s3_service" # Needed for the DiskController and service lookup
26
+ end
27
+
28
+ def run_id
29
+ # We use a shared S3 bucket, and multiple runs of the test suite may write into it at the same time.
30
+ # To prevent clobbering and conflicts, assign a "test run ID" and mix it into the object keys. Keep that
31
+ # value stable across the test suite.
32
+ @test_suite_run_id ||= SecureRandom.base36(10)
33
+ end
34
+
35
+ def test_encrypted_question_method
36
+ assert @service.encrypted?
37
+ end
38
+
39
+ def test_forbids_private_urls_with_disabled_policy
40
+ @service.private_url_policy = :disable
41
+
42
+ rng = Random.new(Minitest.seed)
43
+ key = "#{run_id}-streamed-key-#{rng.hex(4)}"
44
+ k = Random.bytes(68)
45
+ plaintext_upload_bytes = rng.bytes(425)
46
+ @service.upload(key, StringIO.new(plaintext_upload_bytes), encryption_key: k)
47
+
48
+ # ActiveStorage wraps the passed filename in a wrapper thingy
49
+ filename_with_sanitization = ActiveStorage::Filename.new("temp.bin")
50
+
51
+ assert_raises(ActiveStorageEncryption::StreamingDisabled) do
52
+ @service.url(key, filename: filename_with_sanitization, content_type: "binary/octet-stream", disposition: "inline", encryption_key: k, expires_in: 10.seconds)
53
+ end
54
+ end
55
+
56
+ def test_generates_private_streaming_urls_with_streaming_policy
57
+ @service.private_url_policy = :stream
58
+
59
+ rng = Random.new(Minitest.seed)
60
+ key = "#{run_id}-streamed-key-#{rng.hex(4)}"
61
+ k = Random.bytes(68)
62
+ plaintext_upload_bytes = rng.bytes(425)
63
+ @service.upload(key, StringIO.new(plaintext_upload_bytes), encryption_key: k)
64
+
65
+ # The streaming URL generation uses Rails routing, so it needs
66
+ # ActiveStorage::Current.url_options to be set
67
+ # We need to use a hostname for ActiveStorage which is in the Rails authorized hosts.
68
+ # see https://stackoverflow.com/a/60573259/153886
69
+ ActiveStorage::Current.url_options = {
70
+ host: "www.example.com",
71
+ protocol: "https"
72
+ }
73
+
74
+ # ActiveStorage wraps the passed filename in a wrapper thingy
75
+ filename_with_sanitization = ActiveStorage::Filename.new("temp.bin")
76
+ url = @service.url(key, filename: filename_with_sanitization, content_type: "binary/octet-stream", disposition: "inline", encryption_key: k, expires_in: 10.seconds)
77
+ assert url.include?("/active-storage-encryption/blob/")
78
+ end
79
+
80
+ def test_generates_private_urls_with_require_headers_policy
81
+ @service.private_url_policy = :require_headers
82
+
83
+ rng = Random.new(Minitest.seed)
84
+ key = "#{run_id}-streamed-key-#{rng.hex(4)}"
85
+ k = Random.bytes(68)
86
+ plaintext_upload_bytes = rng.bytes(425)
87
+ @service.upload(key, StringIO.new(plaintext_upload_bytes), encryption_key: k)
88
+
89
+ # ActiveStorage wraps the passed filename in a wrapper thingy
90
+ filename_with_sanitization = ActiveStorage::Filename.new("temp.bin")
91
+ url = @service.url(key, filename: filename_with_sanitization, content_type: "binary/octet-stream", disposition: "inline", encryption_key: k, expires_in: 240.seconds)
92
+
93
+ assert url.include?("x-amz-server-side-encryption-customer-algorithm")
94
+ refute url.include?("x-amz-server-side-encryption-customer-key=") # The key should not be in the URL
95
+
96
+ uri = URI(url)
97
+ req = Net::HTTP::Get.new(uri)
98
+ res = Net::HTTP.start(uri.hostname, uri.port, use_ssl: uri.scheme == "https") { |http|
99
+ http.request(req)
100
+ }
101
+ assert_equal "400", res.code
102
+
103
+ headers = @service.headers_for_private_download(key, encryption_key: k)
104
+ headers.each_pair do |h, v|
105
+ req[h] = v
106
+ end
107
+
108
+ res = Net::HTTP.start(uri.hostname, uri.port, use_ssl: uri.scheme == "https") { |http|
109
+ http.request(req)
110
+ }
111
+ assert_equal "200", res.code
112
+ assert_equal plaintext_upload_bytes, res.body
113
+ end
114
+
115
+ def test_s3_config_sane_and_works_with_stock_service
116
+ # maybe remove later
117
+ stock_s3_service = ActiveStorage::Service::S3Service.new(**config)
118
+ rng = Random.new(Minitest.seed)
119
+ key = "#{run_id}-unencrypted-key-#{rng.hex(4)}"
120
+ plaintext_upload_bytes = rng.bytes(1024)
121
+ assert_nothing_raised do
122
+ stock_s3_service.upload(key, StringIO.new(plaintext_upload_bytes))
123
+ end
124
+ readback = stock_s3_service.download(key)
125
+ assert_equal readback, plaintext_upload_bytes
126
+ end
127
+
128
+ def test_exists
129
+ rng = Random.new(Minitest.seed)
130
+
131
+ key = "#{run_id}-encrypted-exists-key-#{rng.hex(4)}"
132
+ encryption_key = rng.bytes(47) # Make it bigger than required, to ensure the service truncates it
133
+ plaintext_upload_bytes = rng.bytes(1024)
134
+
135
+ assert_nothing_raised { @service.upload(key, StringIO.new(plaintext_upload_bytes), encryption_key:) }
136
+ refute @service.exist?(key + "-definitely-not-present")
137
+ assert @service.exist?(key)
138
+ end
139
+
140
+ def test_basic_s3_readback
141
+ rng = Random.new(Minitest.seed)
142
+
143
+ key = "#{run_id}-encrypted-key-#{rng.hex(4)}"
144
+ encryption_key = rng.bytes(47) # Make it bigger than required, to ensure the service truncates it
145
+ plaintext_upload_bytes = rng.bytes(1024)
146
+
147
+ assert_nothing_raised do
148
+ @service.upload(key, StringIO.new(plaintext_upload_bytes), encryption_key:)
149
+ end
150
+ readback = @service.download(key, encryption_key:)
151
+ assert_equal readback, plaintext_upload_bytes
152
+ end
153
+
154
+ def test_s3_upload_requiring_multipart
155
+ rng = Random.new(Minitest.seed)
156
+ encryption_key = rng.bytes(47) # Make it bigger than required, to ensure the service truncates it
157
+
158
+ # The minimum multipart part size is 5MB
159
+ multipart_threshold = 1024 * 1024 * 5
160
+ total_size = multipart_threshold + 3
161
+ plaintext_upload_bytes = rng.bytes(total_size)
162
+
163
+ key = "#{run_id}-encrypted-key-#{rng.hex(4)}"
164
+ service_with_smaller_part_size = ActiveStorageEncryption::EncryptedS3Service.new(**config, upload: {multipart_threshold:})
165
+ assert_nothing_raised do
166
+ service_with_smaller_part_size.upload(key, StringIO.new(plaintext_upload_bytes), encryption_key:)
167
+ end
168
+
169
+ readback = service_with_smaller_part_size.download(key, encryption_key:)
170
+ assert_equal total_size, readback.bytesize
171
+ end
172
+
173
+ def test_accepts_direct_upload_with_signature_and_headers
174
+ rng = Random.new(Minitest.seed)
175
+
176
+ key = "#{run_id}-encrypted-key-direct-upload-#{rng.hex(4)}"
177
+ encryption_key = rng.bytes(47) # Make it bigger than required, to ensure the service truncates it
178
+ plaintext_upload_bytes = rng.bytes(1024)
179
+
180
+ url = @service.url_for_direct_upload(key,
181
+ encryption_key:,
182
+ expires_in: 1.minute,
183
+ content_type: "binary/octet-stream",
184
+ content_length: plaintext_upload_bytes.bytesize,
185
+ checksum: Digest::MD5.base64digest(plaintext_upload_bytes))
186
+ headers = @service.headers_for_direct_upload(key,
187
+ encryption_key:,
188
+ content_type: "binary/octet-stream",
189
+ content_length: plaintext_upload_bytes.bytesize,
190
+ checksum: Digest::MD5.base64digest(plaintext_upload_bytes))
191
+
192
+ refute url.include?("x-amz-server-side-encryption-customer-key=") # The key should not be in the URL
193
+ assert url.include?("x-amz-server-side-encryption-customer-key-md5=") # The checksum must be in the URL
194
+
195
+ res = Net::HTTP.put(URI(url), plaintext_upload_bytes, headers)
196
+ assert_equal "200", res.code
197
+
198
+ assert_equal plaintext_upload_bytes, @service.download(key, encryption_key:)
199
+ end
200
+
201
+ def test_rejects_direct_upload_if_client_manipulates_the_encryption_key
202
+ skip "Currently does not work, investigate"
203
+
204
+ rng = Random.new(Minitest.seed)
205
+
206
+ key = "#{run_id}-encrypted-key-direct-upload-#{rng.hex(4)}"
207
+ encryption_key = rng.bytes(47) # Make it bigger than required, to ensure the service truncates it
208
+ plaintext_upload_bytes = rng.bytes(1024)
209
+
210
+ url = @service.url_for_direct_upload(key,
211
+ encryption_key:,
212
+ expires_in: 1.minute,
213
+ content_type: "binary/octet-stream",
214
+ content_length: plaintext_upload_bytes.bytesize,
215
+ checksum: Digest::MD5.base64digest(plaintext_upload_bytes))
216
+ headers = @service.headers_for_direct_upload(key,
217
+ encryption_key:,
218
+ content_type: "binary/octet-stream",
219
+ content_length: plaintext_upload_bytes.bytesize,
220
+ checksum: Digest::MD5.base64digest(plaintext_upload_bytes))
221
+
222
+ # Replace the key and its checksum
223
+ other_key = Random.bytes(32)
224
+ fake_headers = headers.merge({
225
+ "x-amz-server-side-encryption-customer-key" => Base64.strict_encode64(other_key),
226
+ "x-amz-server-side-encryption-customer-key-MD5" => Digest::MD5.base64digest(other_key)
227
+ })
228
+ res = Net::HTTP.put(URI(url), plaintext_upload_bytes, fake_headers)
229
+ refute_equal "200", res.code
230
+ end
231
+
232
+ # Read the objects from something slow, so that threads may switch between one another
233
+ class SnoozyStringIO < StringIO
234
+ def read(n = nil, outbuf = nil)
235
+ sleep(rand((0.1..0.2)))
236
+ super
237
+ end
238
+ end
239
+
240
+ def test_uploads_correctly_across_multiple_threads
241
+ # Due to a hack that we are applying to reuse most of the stock S3Service, we
242
+ # temporarily override @upload_options on the service when an upload is in progress.
243
+ # This must be done in a thread-local manner, otherwise some uploads may, potentially,
244
+ # get uploaded with the wrong encryption key - belonging to an upload from a different
245
+ # thread. While a test like this is by no means exhaustive, it should reveal this
246
+ # race condition if it occurs.
247
+ rng = Random.new(Minitest.seed)
248
+ objects = 12.times.map do |n|
249
+ key = "#{run_id}-threaded-upload-#{n}-#{rng.hex(4)}"
250
+ encryption_key = rng.bytes(32)
251
+ bytes = rng.bytes(512)
252
+ {key:, encryption_key:, io: SnoozyStringIO.new(bytes)}
253
+ end
254
+
255
+ threads = objects.map do |o|
256
+ Thread.new do
257
+ @service.upload(o.fetch(:key), o.fetch(:io), encryption_key: o.fetch(:encryption_key))
258
+ end
259
+ end
260
+ threads.map(&:join)
261
+
262
+ objects.each do |o|
263
+ readback = @service.download(o.fetch(:key), encryption_key: o.fetch(:encryption_key))
264
+ assert_equal o.fetch(:io).string, readback
265
+ end
266
+ end
267
+
268
+ def test_composes_objects
269
+ rng = Random.new(Minitest.seed)
270
+
271
+ key1 = "#{run_id}-to-compose-key-1-#{rng.hex(4)}"
272
+ k1 = rng.bytes(68)
273
+ buf1 = rng.bytes(1024 * 7)
274
+
275
+ key2 = "#{run_id}-to-compose-key-2-#{rng.hex(4)}"
276
+ k2 = rng.bytes(68)
277
+ buf2 = rng.bytes(1024 * 3)
278
+
279
+ assert_nothing_raised do
280
+ @service.upload(key1, StringIO.new(buf1), encryption_key: k1)
281
+ @service.upload(key2, StringIO.new(buf2), encryption_key: k2)
282
+ end
283
+
284
+ composed_key = "#{run_id}-composed-key-3-#{rng.hex(4)}"
285
+ k3 = Random.bytes(68)
286
+ assert_nothing_raised do
287
+ @service.compose([key1, key2], composed_key, source_encryption_keys: [k1, k2], encryption_key: k3, content_type: "binary/octet-stream")
288
+ end
289
+
290
+ readback_composed_bytes = @service.download(composed_key, encryption_key: k3)
291
+ assert_equal Digest::SHA256.hexdigest(buf1 + buf2), Digest::SHA256.hexdigest(readback_composed_bytes)
292
+ end
293
+ end
@@ -0,0 +1,19 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Configure Rails Environment
4
+ ENV["RAILS_ENV"] = "test"
5
+
6
+ require_relative "../test/dummy/config/environment"
7
+ ActiveRecord::Migrator.migrations_paths = [File.expand_path("../test/dummy/db/migrate", __dir__)]
8
+ ActiveRecord::Migrator.migrations_paths << File.expand_path("../db/migrate", __dir__)
9
+ require "rails/test_help"
10
+
11
+ require "minitest/mock"
12
+
13
+ # Load fixtures from the engine
14
+ if ActiveSupport::TestCase.respond_to?(:fixture_paths=)
15
+ ActiveSupport::TestCase.fixture_paths = [File.expand_path("fixtures", __dir__)]
16
+ ActionDispatch::IntegrationTest.fixture_paths = ActiveSupport::TestCase.fixture_paths
17
+ ActiveSupport::TestCase.file_fixture_path = File.expand_path("fixtures", __dir__) + "/files"
18
+ ActiveSupport::TestCase.fixtures :all
19
+ end
metadata ADDED
@@ -0,0 +1,264 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: active_storage_encryption
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Julik Tarkhanov
8
+ - Sebastian van Hesteren
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2025-03-12 00:00:00.000000000 Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: rails
16
+ requirement: !ruby/object:Gem::Requirement
17
+ requirements:
18
+ - - ">="
19
+ - !ruby/object:Gem::Version
20
+ version: 7.2.2.1
21
+ type: :runtime
22
+ prerelease: false
23
+ version_requirements: !ruby/object:Gem::Requirement
24
+ requirements:
25
+ - - ">="
26
+ - !ruby/object:Gem::Version
27
+ version: 7.2.2.1
28
+ - !ruby/object:Gem::Dependency
29
+ name: block_cipher_kit
30
+ requirement: !ruby/object:Gem::Requirement
31
+ requirements:
32
+ - - ">="
33
+ - !ruby/object:Gem::Version
34
+ version: 0.0.4
35
+ type: :runtime
36
+ prerelease: false
37
+ version_requirements: !ruby/object:Gem::Requirement
38
+ requirements:
39
+ - - ">="
40
+ - !ruby/object:Gem::Version
41
+ version: 0.0.4
42
+ - !ruby/object:Gem::Dependency
43
+ name: activestorage
44
+ requirement: !ruby/object:Gem::Requirement
45
+ requirements:
46
+ - - ">="
47
+ - !ruby/object:Gem::Version
48
+ version: '0'
49
+ type: :runtime
50
+ prerelease: false
51
+ version_requirements: !ruby/object:Gem::Requirement
52
+ requirements:
53
+ - - ">="
54
+ - !ruby/object:Gem::Version
55
+ version: '0'
56
+ - !ruby/object:Gem::Dependency
57
+ name: aws-sdk-s3
58
+ requirement: !ruby/object:Gem::Requirement
59
+ requirements:
60
+ - - ">="
61
+ - !ruby/object:Gem::Version
62
+ version: '0'
63
+ type: :development
64
+ prerelease: false
65
+ version_requirements: !ruby/object:Gem::Requirement
66
+ requirements:
67
+ - - ">="
68
+ - !ruby/object:Gem::Version
69
+ version: '0'
70
+ - !ruby/object:Gem::Dependency
71
+ name: net-http
72
+ requirement: !ruby/object:Gem::Requirement
73
+ requirements:
74
+ - - ">="
75
+ - !ruby/object:Gem::Version
76
+ version: '0'
77
+ type: :development
78
+ prerelease: false
79
+ version_requirements: !ruby/object:Gem::Requirement
80
+ requirements:
81
+ - - ">="
82
+ - !ruby/object:Gem::Version
83
+ version: '0'
84
+ - !ruby/object:Gem::Dependency
85
+ name: sqlite3
86
+ requirement: !ruby/object:Gem::Requirement
87
+ requirements:
88
+ - - ">="
89
+ - !ruby/object:Gem::Version
90
+ version: '0'
91
+ type: :development
92
+ prerelease: false
93
+ version_requirements: !ruby/object:Gem::Requirement
94
+ requirements:
95
+ - - ">="
96
+ - !ruby/object:Gem::Version
97
+ version: '0'
98
+ - !ruby/object:Gem::Dependency
99
+ name: standard
100
+ requirement: !ruby/object:Gem::Requirement
101
+ requirements:
102
+ - - ">="
103
+ - !ruby/object:Gem::Version
104
+ version: 1.35.1
105
+ type: :development
106
+ prerelease: false
107
+ version_requirements: !ruby/object:Gem::Requirement
108
+ requirements:
109
+ - - ">="
110
+ - !ruby/object:Gem::Version
111
+ version: 1.35.1
112
+ - !ruby/object:Gem::Dependency
113
+ name: appraisal
114
+ requirement: !ruby/object:Gem::Requirement
115
+ requirements:
116
+ - - ">="
117
+ - !ruby/object:Gem::Version
118
+ version: '0'
119
+ type: :development
120
+ prerelease: false
121
+ version_requirements: !ruby/object:Gem::Requirement
122
+ requirements:
123
+ - - ">="
124
+ - !ruby/object:Gem::Version
125
+ version: '0'
126
+ - !ruby/object:Gem::Dependency
127
+ name: magic_frozen_string_literal
128
+ requirement: !ruby/object:Gem::Requirement
129
+ requirements:
130
+ - - ">="
131
+ - !ruby/object:Gem::Version
132
+ version: '0'
133
+ type: :development
134
+ prerelease: false
135
+ version_requirements: !ruby/object:Gem::Requirement
136
+ requirements:
137
+ - - ">="
138
+ - !ruby/object:Gem::Version
139
+ version: '0'
140
+ - !ruby/object:Gem::Dependency
141
+ name: rake
142
+ requirement: !ruby/object:Gem::Requirement
143
+ requirements:
144
+ - - ">="
145
+ - !ruby/object:Gem::Version
146
+ version: '0'
147
+ type: :development
148
+ prerelease: false
149
+ version_requirements: !ruby/object:Gem::Requirement
150
+ requirements:
151
+ - - ">="
152
+ - !ruby/object:Gem::Version
153
+ version: '0'
154
+ description: Adds customer-supplied encryption keys to storage services.
155
+ email:
156
+ - me@julik.nl
157
+ executables: []
158
+ extensions: []
159
+ extra_rdoc_files: []
160
+ files:
161
+ - Appraisals
162
+ - MIT-LICENSE
163
+ - README.md
164
+ - Rakefile
165
+ - bin/rails
166
+ - bin/rubocop
167
+ - config/initializers/active_storage_encryption.rb
168
+ - config/routes.rb
169
+ - gemfiles/rails_7.gemfile
170
+ - gemfiles/rails_7.gemfile.lock
171
+ - gemfiles/rails_8.gemfile
172
+ - gemfiles/rails_8.gemfile.lock
173
+ - lib/active_storage/service/encrypted_disk_service.rb
174
+ - lib/active_storage/service/encrypted_mirror_service.rb
175
+ - lib/active_storage/service/encrypted_s3_service.rb
176
+ - lib/active_storage_encryption.rb
177
+ - lib/active_storage_encryption/encrypted_blobs_controller.rb
178
+ - lib/active_storage_encryption/encrypted_disk_service.rb
179
+ - lib/active_storage_encryption/encrypted_disk_service/v1_scheme.rb
180
+ - lib/active_storage_encryption/encrypted_disk_service/v2_scheme.rb
181
+ - lib/active_storage_encryption/encrypted_mirror_service.rb
182
+ - lib/active_storage_encryption/encrypted_s3_service.rb
183
+ - lib/active_storage_encryption/engine.rb
184
+ - lib/active_storage_encryption/overrides.rb
185
+ - lib/active_storage_encryption/private_url_policy.rb
186
+ - lib/active_storage_encryption/resumable_gcs_upload.rb
187
+ - lib/active_storage_encryption/version.rb
188
+ - lib/tasks/active_storage_encryption_tasks.rake
189
+ - test/active_storage_encryption_test.rb
190
+ - test/dummy/Rakefile
191
+ - test/dummy/app/assets/stylesheets/application.css
192
+ - test/dummy/app/controllers/application_controller.rb
193
+ - test/dummy/app/helpers/application_helper.rb
194
+ - test/dummy/app/models/application_record.rb
195
+ - test/dummy/app/views/layouts/application.html.erb
196
+ - test/dummy/app/views/pwa/manifest.json.erb
197
+ - test/dummy/app/views/pwa/service-worker.js
198
+ - test/dummy/bin/rails
199
+ - test/dummy/bin/rake
200
+ - test/dummy/bin/setup
201
+ - test/dummy/config.ru
202
+ - test/dummy/config/application.rb
203
+ - test/dummy/config/boot.rb
204
+ - test/dummy/config/credentials.yml.enc
205
+ - test/dummy/config/database.yml
206
+ - test/dummy/config/environment.rb
207
+ - test/dummy/config/environments/development.rb
208
+ - test/dummy/config/environments/production.rb
209
+ - test/dummy/config/environments/test.rb
210
+ - test/dummy/config/initializers/content_security_policy.rb
211
+ - test/dummy/config/initializers/filter_parameter_logging.rb
212
+ - test/dummy/config/initializers/inflections.rb
213
+ - test/dummy/config/initializers/permissions_policy.rb
214
+ - test/dummy/config/locales/en.yml
215
+ - test/dummy/config/master.key
216
+ - test/dummy/config/puma.rb
217
+ - test/dummy/config/routes.rb
218
+ - test/dummy/config/storage.yml
219
+ - test/dummy/db/migrate/20250304023851_create_active_storage_tables.active_storage.rb
220
+ - test/dummy/db/migrate/20250304023853_add_blob_encryption_key_column.rb
221
+ - test/dummy/db/schema.rb
222
+ - test/dummy/log/test.log
223
+ - test/dummy/public/404.html
224
+ - test/dummy/public/406-unsupported-browser.html
225
+ - test/dummy/public/422.html
226
+ - test/dummy/public/500.html
227
+ - test/dummy/public/icon.png
228
+ - test/dummy/public/icon.svg
229
+ - test/dummy/storage/test.sqlite3
230
+ - test/dummy/storage/x6/pl/x6plznfuhrsyjn9pox2a6xgmcs3x
231
+ - test/dummy/storage/yq/sv/yqsvw5a72b3fv719zq8a6yb7lv0j
232
+ - test/integration/encrypted_blobs_controller_test.rb
233
+ - test/lib/encrypted_disk_service_test.rb
234
+ - test/lib/encrypted_mirror_service_test.rb
235
+ - test/lib/encrypted_s3_service_test.rb
236
+ - test/test_helper.rb
237
+ homepage: https://github.com/cheddar-me/active_storage_encryption
238
+ licenses:
239
+ - MIT
240
+ metadata:
241
+ allowed_push_host: https://rubygems.org
242
+ homepage_uri: https://github.com/cheddar-me/active_storage_encryption
243
+ source_code_uri: https://github.com/cheddar-me/active_storage_encryption
244
+ changelog_uri: https://github.com/cheddar-me/active_storage_encryption/blob/main/CHANGELOG.md
245
+ post_install_message:
246
+ rdoc_options: []
247
+ require_paths:
248
+ - lib
249
+ required_ruby_version: !ruby/object:Gem::Requirement
250
+ requirements:
251
+ - - ">="
252
+ - !ruby/object:Gem::Version
253
+ version: 3.1.0
254
+ required_rubygems_version: !ruby/object:Gem::Requirement
255
+ requirements:
256
+ - - ">="
257
+ - !ruby/object:Gem::Version
258
+ version: '0'
259
+ requirements: []
260
+ rubygems_version: 3.4.10
261
+ signing_key:
262
+ specification_version: 4
263
+ summary: Customer-supplied encryption key support for ActiveStorage blobs.
264
+ test_files: []