Encrypted files at rest on S3 using ruby (SSE)

Ok, so AWS offer to store encrypted files at rest on S3, they call it SSE (server-side encryption). I don’t know when they introduced this (UPDATE: almost exactly 6 years ago) but I haven’t used it since now. Why I ask myself? Because when you’ve started doing it, it’s actually kinda simple.

Anyways, I’ve always had a hard time understanding AWS’s documentation. Here is the beginning of it about SSE. I think the biggest issue is mapping that documentation into actual code. I mean, I think I understand the concepts, but since I’m a developer I also wanna implement it easily. And going from that documentation into the ruby sdk one, this is version 3 is the biggest challenge IMO.

After hours of trying to understand/remember and mixing up the concepts of the SDK with different versions and just generally staring at the http://docs.aws.amazon.com website I realized it’s dead simple (I hate hearing those things, a bit similar to using the word “just”, btw, here’s brad frost’s original post about it).

I’ve decided to use AWS KMS keys for code simplicity. It generally means that the encryption key is stored on AWS, and managed through the AWS console. It’s also possible to use different approaches, e.g., asymmetric encryption which is preferred in other cases. I’ll write about that in another post.

The code takes into account that you already have a S3 bucket and access to it. Also have: gem 'aws-sdk-s3', '~> 1' in your Gemfile. Ok, here comes some ruby:

credentials = Aws::Credentials.new(ENV['AWS_ACCESS_KEY'], ENV['AWS_ACCESS_SECRET'])

s3 = Aws::S3::Resource.new(region: 'eu-west-1', credentials: credentials)

bucket = s3.bucket('my-bucket')

obj = bucket.object('my-file.txt')

# Uploads the file
obj.put(
  body: 'Hello, this is the content of the file',
  acl: 'private',
  server_side_encryption: 'aws:kms',
  ssekms_key_id: ENV['AWS_KMS_KEY_ID'] # if not specified a default key is created and used
)

# If I wanna get a signed url that's valid for 60 seconds _just_ do:
url = obj.presigned_url(:get, expires_in: 60)

So that code ☝️ uploads a file named (my-file.txt) to my-bucket and gives me an signed url that’s valid for 60 seconds.

The biggest learning from this?

Start play around and test the SDK ASAP and try it out on a bucket and/or account that you can’t mess anything up on. Lesson learned.