I use Shrine in my Rails app to upload files to Amazon S3. Some of the user's files connected to GDPR compliance and I need to implement client-side encryption ([Shrine documentation] (https://github.com/shrinerb/shrine/blob/v2.16.0/doc/storage/s3.md#encryption)). In Shrine docs you can see information for file encryption via AWS KMS, but no info about decryption.
How can I decrypt the file, when I download it from aws s3?
Here is my code:
config/initializers/shrine.rb - In this place I specify configs for Shrine.
require 'shrine'
require 'shrine/storage/s3'
require 'shrine/storage/file_system'
class Shrine
plugin :activerecord
class << self
Aws::S3::Encryption::Client.extend Forwardable
Aws::S3::Encryption::Client.delegate(
[:head_object, :get_object, :config, :build_request] => :client
)
def s3_options_encrypted
{
prefix: 'encrypted',
region: ENV['AWS_REGION'],
bucket: ENV['AWS_BUCKET_NAME'] ,
endpoint: ENV['AWS_ENDPOINT'],
access_key_id: ENV['AWS_ACCESS_KEY_ID'],
secret_access_key: ENV['AWS_SECRET_ACCESS_KEY'],
upload_options: { acl: 'private' },
client: Aws::S3::Encryption::Client.new(kms_key_id: ENV['KMS_KEY_ID'])
}
end
def storages
{
cache: Shrine::Storage::FileSystem.new('public', prefix: 'cache_files'),
encrypted: Shrine::Storage::S3.new(**s3_options_encrypted)
}
end
end
end
models/document.rb - Model
class Document < ApplicationRecord
include DocumentsUploader::Attachment.new(:file, {store: :encrypted})
end
controllers/downloads_controller.rb - in this place I am downloading file and need to decrypt it.
class DownloadsController < ApplicationController
def documents
document = Document.find(id) if Document.exists?(id: params[:id])
if document.nil? || !document&.file
redirect_to root_path and return
end
temp_file = document.file.download
name = "#{document.name}.pdf"
type = 'application/pdf'
send_file temp_file, filename: name, type: type, disposition: 'attachment'
end
end
The developer of the Shrink framework helped me to solve the problem in this chat
It was a mistake in the delegation. I delegated [:head_object, :get_object, :config, :build_request]
to :client
, but I should delegate [:head_object, :delete_object, :config, :build_request]
. The correct delegation look like this:
Aws::S3::Encryption::Client.delegate(
[:head_object, :delete_object, :config, :build_request] => :client
)
Aws::S3::Encryption::Client#get_object
is already implemented and it's what does the decryption, so adding that #get_object
delegate would use the regular Aws::S3::Client#get_object
which doesn't do any decryption.
With this, downloads are automatically decrypted.