Class: ActiveStorageEncryption::EncryptedDiskService
- Inherits:
-
ActiveStorage::Service::DiskService
- Object
- ActiveStorage::Service::DiskService
- ActiveStorageEncryption::EncryptedDiskService
show all
- Includes:
- PrivateUrlPolicy
- Defined in:
- lib/active_storage_encryption/encrypted_disk_service.rb
Overview
Provides a local encrypted store for ActiveStorage blobs. Configure it like so:
local_encrypted:
service: EncryptedDisk
root: <%= Rails.root.join("storage/encrypted") %>
private_url_policy: stream
Defined Under Namespace
Classes: V1Scheme, V2Scheme
Constant Summary
collapse
- FILENAME_EXTENSIONS_PER_SCHEME =
{
".encrypted-v1" => "V1Scheme",
".encrypted-v2" => "V2Scheme"
}
PrivateUrlPolicy::DEFAULT_POLICY
Instance Method Summary
collapse
-
#compose(source_keys, destination_key, source_encryption_keys:, encryption_key:) ⇒ Object
-
#download(key, encryption_key:, &block) ⇒ Object
-
#download_chunk(key, range, encryption_key:) ⇒ Object
-
#encrypted? ⇒ Boolean
This lets the Blob encryption key methods know that this storage service must use encryption.
-
#exist?(key) ⇒ Boolean
-
#headers_for_direct_upload(key, content_type:, encryption_key:, checksum:) ⇒ Object
-
#headers_for_private_download(key, encryption_key:) ⇒ Object
-
#initialize(public: false, **options_for_disk_storage) ⇒ EncryptedDiskService
constructor
A new instance of EncryptedDiskService.
-
#path_for(key) ⇒ Object
-
#upload(key, io, encryption_key:, checksum: nil) ⇒ Object
-
#url_for_direct_upload(key, expires_in:, content_type:, content_length:, checksum:, encryption_key:, custom_metadata: {}) ⇒ Object
#private_url_for_streaming_via_controller, #private_url_policy, #private_url_policy=
Constructor Details
#initialize(public: false, **options_for_disk_storage) ⇒ EncryptedDiskService
Returns a new instance of EncryptedDiskService.
29
30
31
32
|
# File 'lib/active_storage_encryption/encrypted_disk_service.rb', line 29
def initialize(public: false, **options_for_disk_storage)
raise ArgumentError, "encrypted files cannot be served via a public URL or a CDN" if public
super
end
|
Instance Method Details
#compose(source_keys, destination_key, source_encryption_keys:, encryption_key:) ⇒ Object
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
|
# File 'lib/active_storage_encryption/encrypted_disk_service.rb', line 108
def compose(source_keys, destination_key, source_encryption_keys:, encryption_key:, **)
if source_keys.length != source_encryption_keys.length
raise ArgumentError, "With #{source_keys.length} keys to compose there should be exactly as many source_encryption_keys, but got #{source_encryption_keys.length}"
end
File.open(make_path_for(destination_key), "wb") do |destination_file|
writing_scheme = create_scheme(destination_key, encryption_key)
writing_scheme.streaming_encrypt(into_ciphertext_io: destination_file) do |writable|
source_keys.zip(source_encryption_keys).each do |(source_key, encryption_key_for_source)|
File.open(path_for(source_key), "rb") do |source_file|
reading_scheme = create_scheme(source_key, encryption_key_for_source)
reading_scheme.streaming_decrypt(from_ciphertext_io: source_file, into_plaintext_io: writable)
end
end
end
end
end
|
#download(key, encryption_key:, &block) ⇒ Object
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
|
# File 'lib/active_storage_encryption/encrypted_disk_service.rb', line 44
def download(key, encryption_key:, &block)
if block_given?
instrument :streaming_download, key: key do
stream key, encryption_key, &block
end
else
instrument :download, key: key do
(+"").b.tap do |buf|
download(key, encryption_key: encryption_key) do |data|
buf << data
end
end
end
end
end
|
#download_chunk(key, range, encryption_key:) ⇒ Object
60
61
62
63
64
65
66
67
68
69
|
# File 'lib/active_storage_encryption/encrypted_disk_service.rb', line 60
def download_chunk(key, range, encryption_key:)
instrument :download_chunk, key: key, range: range do
scheme = create_scheme(key, encryption_key)
File.open(path_for(key), "rb") do |file|
scheme.decrypt_range(from_ciphertext_io: file, range:)
end
rescue Errno::ENOENT
raise ActiveStorage::FileNotFoundError
end
end
|
#encrypted? ⇒ Boolean
This lets the Blob encryption key methods know that this storage service must use encryption
27
|
# File 'lib/active_storage_encryption/encrypted_disk_service.rb', line 27
def encrypted? = true
|
#exist?(key) ⇒ Boolean
104
105
106
|
# File 'lib/active_storage_encryption/encrypted_disk_service.rb', line 104
def exist?(key)
File.exist?(path_for(key))
end
|
125
126
127
128
129
130
131
132
|
# File 'lib/active_storage_encryption/encrypted_disk_service.rb', line 125
def (key, content_type:, encryption_key:, checksum:, **)
super.merge!("x-active-storage-encryption-key" => Base64.strict_encode64(encryption_key), "content-md5" => checksum)
end
|
134
135
136
|
# File 'lib/active_storage_encryption/encrypted_disk_service.rb', line 134
def (key, encryption_key:, **)
{"x-active-storage-encryption-key" => Base64.strict_encode64(encryption_key)}
end
|
#path_for(key) ⇒ Object
93
94
95
96
97
98
99
100
101
102
|
# File 'lib/active_storage_encryption/encrypted_disk_service.rb', line 93
def path_for(key) glob_pattern = File.join(root, folder_for(key), key + ".encrypted-*")
last_existing_path = Dir.glob(glob_pattern).max
path_for_new_file = File.join(root, folder_for(key), key + FILENAME_EXTENSIONS_PER_SCHEME.keys.last)
last_existing_path || path_for_new_file
end
|
#upload(key, io, encryption_key:, checksum: nil) ⇒ Object
34
35
36
37
38
39
40
41
42
|
# File 'lib/active_storage_encryption/encrypted_disk_service.rb', line 34
def upload(key, io, encryption_key:, checksum: nil, **)
instrument :upload, key: key, checksum: checksum do
scheme = create_scheme(key, encryption_key)
File.open(make_path_for(key), "wb") do |file|
scheme.streaming_encrypt(from_plaintext_io: io, into_ciphertext_io: file)
end
ensure_integrity_of(key, checksum, encryption_key) if checksum
end
end
|
#url_for_direct_upload(key, expires_in:, content_type:, content_length:, checksum:, encryption_key:, custom_metadata: {}) ⇒ Object
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
|
# File 'lib/active_storage_encryption/encrypted_disk_service.rb', line 71
def url_for_direct_upload(key, expires_in:, content_type:, content_length:, checksum:, encryption_key:, custom_metadata: {})
instrument :url, key: key do |payload|
upload_token = ActiveStorage.verifier.generate(
{
key: key,
content_type: content_type,
content_length: content_length,
encryption_key_sha256: Digest::SHA256.base64digest(encryption_key),
checksum: checksum,
service_name: name
},
expires_in: expires_in,
purpose: :encrypted_put
)
url_helpers = ActiveStorageEncryption::Engine.routes.url_helpers
url_helpers.encrypted_blob_put_url(upload_token, url_options).tap do |generated_url|
payload[:url] = generated_url
end
end
end
|