Active Record Encryption does a great job at helping defend Personally identifiable information (PII) and confidential data that is stored encrypted in the database against server intrusions.
However, any files “attached” via Active Storage are stored unencrypted in the file system , and thus remain open to attackers. Thus, it would be great to have these files encrypted as well, via a new Active Storage option encrypted: true , like:
class User < ApplicationRecord
has_many_attached :financial_statements, encrypted: true
has_many_attached :confidential_inventions, encrypted: true
has_many_attached :sensitive_pictures, encrypted: true
end
This is a great idea. It should probably feature a global option to set a default (e.g: encrypt all the attachments but default).
When using a cloud storage service such as S3, there is a very easy path to protect active storage attachments with encryption: encrypting they key column in blobs. It’s not the same as encrypting stored contents, but it’s effective to prevent locating the attached contents for a given attachment and blobs. This works on top of the at-rest encryption you should have enabled in the service, of course.
We do this in HEY to encrypt both the key and the filename in blobs:
ActiveSupport.on_load :active_storage_blob do
encrypts :key, deterministic: true
encrypts :filename
end
Notice that you should increase the the size of the encrypted columns to accommodate the encryption overload as explained in the encryption guide. We set 510 for 255 string columns.
@jorgemanrubia At Cheddar we are currently working on this for our own app, with support for Disk and GCP services. It is not hard to do but it requires some product decisions on ActiveStorage internals - for example, you want to store the encryption key on the Blob encrypted - which would mean that you would need encrypted attributes correctly configured in your app to use it. Storing the encryption key plain is both insecure and it would be binary. Our implementation is in the works now, but it requires most of the Service methods (or - rather - calls from Blob/controller into Service) to be augmented with an extra kwarg. If you think core is open to this we can think about upstreaming.
When using a cloud storage service such as S3, there is a very easy path to protect active storage attachments with encryption: encrypting they key column in blobs
This is not what you want - you want a per-blob encryption key applied to the actual bytes of the blobs. That key should be random and unique per-blob. With an encrypted key you can’t map a file in storage to a Blob in the database, but if you manage to exfiltrate the file from the cloud service you will have the actual file contents. Blob encryption would mean that to read the actual content of the blob you would need to supply an encryption key.
This gist encrypted_disk_service.rb · GitHub gives a good idea on how this looks with the Disk service. For GCS, you want to use customer-supplied encryption. S3 calls this “customer-provided keys” I believe, but I haven’t worked with S3 for quite a long while.
Note that according to S3 docs, this would kill presigned GET URLs - you could only use them if you add headers, so all such blobs would need to be streamed through an intermediary service to provide downloads (a separate controller, for example):
For non-SSE-C objects, you can generate a presigned URL and directly paste that URL into a browser to access the data.
However, you cannot do this for SSE-C objects, because in addition to the presigned URL, you also must include HTTP headers that are specific to SSE-C objects. Therefore, you can use presigned URLs for SSE-C objects only programmatically.
By the looks of it GCP does not have this limitation. It is also likely that random access using Range headers will be limited depenging on the service used.
@john-999 we are doing a limited beta of our gem now, if you DM me I can share the details. It’s pretty stable but we still need to upstream a few bits into it.
Well, what gets promised gets delivered! Stoked to announce availability of our active_storage_encryption gem! It integrates with ActiveStorage and allows you to encrypt the file data of your ActiveStorage blobs with a separate encryption key per blob. It considerably increases the security of storing sensitive files in your Rails apps by making a “cloud account access” attack less feasible - an attacker will have a hard time downloading your stored files even if they gain access to your storage bucket, as they would now need the database dump and your app credentials to access the files. Do give it a spin! @jorgemanrubia let me know if you want this upstreamed, we certainly can discuss. But we do need to fix the ProxyController etc.