Dynamically proxy images via HTTPS using Camo
Replace HTML img tags with camo URLs using the Redcarpet and html-pipeline gems
https assets ruby rails markdown
Depending on your webbrowser, embedding images via unencrypted HTTP URIs in a webpage that is otherwise served through HTTPS results in warnings or error messages about mixed content. A common case for that: user input that allows referencing external images, e.g. in a blog-like application where the app itself is served using TLS.
Github had this issue (Github … issue… get it?) and came up with
camo, a tool that »makes insecure assets
look secure« by routing all insecure assets through a secure proxy application.
Your TLS-secured website will have to rewrite all <img src="…">
tags in order
to reference the camo-proxied image instead. This article describes how to do
it with Redcarpet markdown and the
html-pipeline gem.
Installing camo
Installing camo couldn’t be easier: The camo Github repo sports a »Deploy to Heroku« button. If you’re already registered at Heroku, deploying a camo instance to production is literally a thing of 5 minutes.
The »Deploy to Heroku« routine will create a secret key for your camo instance.
You can access it as the CAMO_KEY
environment variable. The following
paragraphs assume that you have your camo instance up and running. Also, make
sure you have your CAMO_KEY
available.
Redcarpet
The camo gem is needed for creating the
required HMAC digests. Add it to your Gemfile and bundle. The gem expects the
host and secret to be configured as CAMO_HOST
and CAMO_KEY
environment
variables.
To integrate with Redcarpet markdown, we need to hook into the expansion of
![alt-text](img-source)
tags. I created a new subclass of
Redcarpet::Render::HTML
that overrides the image
instance method of its
superclass:
# lib/redcarpet/render/camo.rb
module Redcarpet
module Render
class Camo < Redcarpet::Render::HTML
include ::Camo # provided by the camo gem
def image(link, title, alt_text)
if camo_configured? and link =~ %r[\Ahttp:]
link = camo(link) # rewrite with camo url
end
%Q[<img src="#{link}" alt="#{alt_text}" title="#{title}">]
end
private
# Predicate to make the overriden `image` instance method
# behave like the superclass version when essential ENV
# variables are not set.
def camo_configured?
[ENV["CAMO_HOST"], ENV["CAMO_KEY"]].all?
end
end
end
end
Make sure your custom class is required in your project. Render markdown like this:
renderer = Redcarpet::Render::Camo.new
Redcarpet::Markdown.new(renderer).render "![🔐](http://i.imgur.com/oPXo3kl.jpg)"
# Output (w/o the surrounding <p> tag):
# <img src="https://camo.aegisnet.de/56fec5e12d1be568a6ca7b650e7a88d78037d4e0/687474703a2f2f692e696d6775722e636f6d2f6f50586f336b6c2e6a7067" alt="🔐" title="">
Have a look at the tests for it in this changeset.
html-pipeline
The html-pipeline gem provides a set of filters that can be chained to form a pipeline for HTML content transformation. It’s a very versatile tool, again made by Github. You can read more about it in their blog post. It comes with a camo filter:
html = '<p><img src="http://i.imgur.com/oPXo3kl.jpg"></p>'
camo_filter = HTML::Pipeline::CamoFilter.new(html, asset_proxy: ENV['CAMO_HOST'], asset_proxy_secret_key: ENV['CAMO_KEY'])
camo_filter.call
# Output:
# <p><img src="https://camo.aegisnet.de/56fec5e12d1be568a6ca7b650e7a88d78037d4e0/687474703a2f2f692e696d6775722e636f6d2f6f50586f336b6c2e6a7067" data-canonical-src="http://i.imgur.com/oPXo3kl.jpg"></p>
I love the idea of adding the original, untainted image source as a data
attribute.
html-pipeline’s real strength is using it in conjunction with other filters, creating pipelines. Here’s how the camo filter fits in:
markdown = <<-EOMD
# Hello World
This is a ![proxied image](http://i.imgur.com/oPXo3kl.jpg)
EOMD
# The `context` hash gets all the config options for all the filters in the pipeline.
# Check the docs for the individual filter for available options.
context = { asset_proxy: ENV['CAMO_HOST'], asset_proxy_secret_key: ENV['CAMO_KEY'] }
CamoPipeline = HTML::Pipeline.new [HTML::Pipeline::MarkdownFilter, HTML::Pipeline::CamoFilter], context
result = CamoPipeline.call markdown
result[:output] # holds the rendered HTML
# Output:
# <h1>Hello World</h1>
#
# <p>This is a <img src="https://camo.aegisnet.de/89abf09cd9ef15a03c6bee187c00985cb3154640/687474703a2f2f692e696d6775722e636f6d2f6f50586f336b6c2e6a7067" alt="proxied image" data-canonical-src="http://i.imgur.com/oPXo3kl.jpg"></p>
Hint: Jekyll, the blog engine that powers this website, can also be used with html-pipeline using the jekyll-html-pipeline plugin.
comments powered by Disqus