Create a gist now

Instantly share code, notes, and snippets.

What would you like to do?
Ruby Signed Expiring Cloudfront URL
# A simple function to return a signed, expiring url for Amazon Cloudfront.
# As it's relatively difficult to figure out exactly what is required, I've posted my working code here.
# This will require openssl, digest/sha1, base64 and maybe other libraries.
# In my rails app, all of these are already loaded so I'm not sure of the exact dependencies.
module CloudFront
def get_signed_expiring_url(path, expires_in, private_key_filename, key_pair_id)
# AWS works on UTC, so make sure you are not using local time
expires = (Time.now.getutc + expires_in).to_i.to_s
private_key = OpenSSL::PKey::RSA.new(File.read(private_key_filename))
# path should be your S3 path without a leading slash and without a file extension.
# e.g. files/private/52
policy = %Q[{"Statement":[{"Resource":"#{path}","Condition":{"DateLessThan":{"AWS:EpochTime":#{expires}}}}]}]
signature = Base64.strict_encode64(private_key.sign(OpenSSL::Digest::SHA1.new, policy))
# I'm not sure exactly why this is required, but it's in Amazon's perl script and seems necessary
# Different base64 implementations maybe?
signature.tr!("+=/", "-_~")
"#{path}?Expires=#{expires}&Signature=#{signature}&Key-Pair-Id=#{key_pair_id}"
end
end
# Sample implementation for returning either a publicly accessibly or a time-limited url
class Video
def get_url
if public?
self.path
else
CloudFront.get_signed_expiring_url(self.path, 45.seconds, "#{Rails.root}/config/cloudfront.pem", "xxxKEYPAIRIDxxx")
end
end
end

Instead of

signature = Base64.strict_encode64(private_key.sign(OpenSSL::Digest::SHA1.new, policy))
signature.tr!("+=/", "-_~")

why not just use:

signature = Base64.urlsafe_encode64(private_key.sign(OpenSSL::Digest::SHA1.new, policy))

As an aside, the reason that Amazon's Perl script does that is due to: http://en.wikipedia.org/wiki/Base64#URL_applications

@wweidendorf urlsafe_encode64 complies with RFC 4648

Returns the Base64-encoded version of bin. This method complies with “Base 64 Encoding with URL and Filename Safe Alphabet” in RFC 4648. The alphabet uses ‘-’ instead of ‘+’ and ‘_’ instead of ‘/’.

And actually looks like this in the source ... (see here) ... seems it doesn't replace '=' with '_'. I'll also point out that Amazon's sample PHP code replaces all three as well so I think it's safer to stick with what they do though I must admit I've not tested if it fails.

def urlsafe_encode64(bin)
  strict_encode64(bin).tr("+/", "-_")
end

pkulak commented Jan 25, 2014

Amazon uses their own version of URL-safe Base64 that also accounts for "=", the spec only accounts for "+" and "/", so you have to do the replacement yourself.

Also, this worked great, but only if I used the entire URL, not just the path - extension. Thanks!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment