AWS Now Supports SSL and Wildcard CNAMEs

Back in June Amazon announced support for custom SSL certificates with CloudFront, meaning you can now use your own domain name to serve content via https rather than their pesky CloudFront URLs (i.e. https://d6zo052ygi9mi.cloudfront.net).

The only downside was the price… $600 per month per certificate. If you needed a couple of different domains (i.e. http://www.mysite.com, images.mysite.com, vidoes.mysite.com), this could get expensive quickly.

Well now you can simply purchase a wildcard certificate and secure all of those domains with a single certificate.

More info can be found here and here.

Hosting Web Fonts on Amazon S3 (IE CSS3117 and Firefox bad URI or cross-site access not allowed errors)

If you’ve ever tried to host web fonts on a different domain you’ve probably run into the following errors in IE and Firefox:

Internet Explorer

CSS3117: @font-face failed cross-origin request. Resource access is restricted.

view raw
error.txt
hosted with ❤ by GitHub

Firefox

downloadable font: download failed (font-family: "MyFont" style:normal weight:normal stretch:normal src index:1): bad URI or cross-site access not allowed

view raw
error.txt
hosted with ❤ by GitHub

Usually this is a simple matter of updating your server with the Access-Control-Allow-Origin header. Unfortunately if you’re hosting your assets on Amazon S3 or CloudFront, editing the server config to add this isn’t possible.

This problem was originally posted to the AWS forums back in 2009… and a mere 3+ years later they finally released a fix.

Simply go to the Properties pane in the S3 Management Console and edit the CORS configuration. Here’s an example configuration:

<CORSConfiguration>
<CORSRule>
<AllowedOrigin>http://mydomain.com</AllowedOrigin>
<AllowedMethod>GET</AllowedMethod>
<MaxAgeSeconds>3000</MaxAgeSeconds>
<AllowedHeader>Authorization</AllowedHeader>
</CORSRule>
</CORSConfiguration>

view raw
config.xml
hosted with ❤ by GitHub

You can find more information about configuring CORS here: http://docs.aws.amazon.com/AmazonS3/latest/dev/cors.html#how-do-i-enable-cors

Amazon Route 53 – Creating a Static Failover

Not Django but this is something quite useful…

Amazon’s Route 53 DNS service now allows you to define a static failover site hosted on S3 in case your primary site goes down.

Read more here…

http://aws.typepad.com/aws/2013/02/create-a-backup-website-using-route-53-dns-failover-and-s3-website-hosting.html

Deploy to EC2 Via RightScale Using GIT_SSH

While RightScale’s built-in scripts make it simple to deploy code to an EC2 instance, they often need some tweaking to work correctly (i.e. the current version of app::do_update_code simply pulls down code, it doesn’t restart Apache or recompress assets).

The following is an example RightScript that utilizes a temporary SSH key, GIT_SSH and a sync script to automate the deployment process.

#!/bin/bash -e
# switch to the attachment directory
cd $RS_ATTACH_DIR
# move the sync script
mv sync.sh /home/rightscale/.ssh/sync.sh
# make script executable
chmod u+x /home/rightscale/.ssh/sync.sh
# create temporary SSH key
cat > "/home/rightscale/.ssh/id_rsa" << EOF
$GIT_SSH_KEY
EOF
# chmod to 600
chmod 600 /home/rightscale/.ssh/id_rsa
# set GIT_SSH
export GIT_SSH=/home/rightscale/.ssh/sync.sh
# clone the repo
git clone –depth 1 git@bitbucket.org:myrepo/reponame.git /home/rightscale/$APPLICATION
# recompress assets
python /home/webapps/$APPLICATION/manage.py collectstatic –noinput -i css -i js
python /home/webapps/$APPLICATION/manage.py compress
# stop apache
service apache2 stop
# remove the current app directory
rm -rf /home/webapps/$APPLICATION
# move the new app directory into place
mv /home/rightscale/$APPLICATION /home/webapps/$APPLICATION
# restart apache
service apache2 start
# remove temporary SSH key
rm /home/rightscale/.ssh/id_rsa
rm /home/rightscale/.ssh/sync.sh

view raw
deploy.sh
hosted with ❤ by GitHub

sync.sh (attached to above RightScript)

#!/bin/bash -e
/usr/bin/env ssh -q -2 -o "StrictHostKeyChecking=no" -i "/home/rightscale/.ssh/id_rsa" $1 $2

view raw
sync.sh
hosted with ❤ by GitHub

Creating a Custom Django Server on RightScale

UPDATE: RightScale has released version 13.2 of their server templates. I have updated the scripts to reflect the various changes and bug fixes in the new templates.

RightScale has released a beta server template for Django which makes it incredibly easy to get a server up and running on EC2. One drawback with this server template is that it is configured to work with a load balancer which isn’t always necessary.

To create a custom server template that doesn’t require a load balancer, start with the Base ServerTemplate for Linux (current version is 13.2) (be sure to use the Chef-based template rather the RightScript-based version). Then add the necessary scripts so that your boot and operational configs look like the following:

Now you’ve got a server that runs Apache, mod_wsgi and Django 1.4. It will also pull your code down from GIT or SVN and will install any PIP packages from your requirements.txt file (I prefer to install this stuff as part of the initial server config as shown below, or via a controlled server upgrade rather than automatically via requirements.txt).

But what about missing or outdated software? The following is a sample script that installs and upgrades a whole bunch of stuff. It also reconfigs Apache and runs compress. This was added as the last script in the template’s boot scripts. (Note – this script assumes you’re using the Ubuntu RightImage)

#!/bin/bash -e
#
# Test for a reboot, if this is a reboot just skip this script.
#
if test "$RS_REBOOT" = "true" ; then
echo "Skip software update on reboot."
logger -t RightScale "Software update, skipped on a reboot."
exit 0
fi
#
# install and upgrade a bunch of stuff
#
if [ $RS_DISTRO = ubuntu ]; then
# fix sftp issue
perl -p -i -e "s/\/usr\/libexec/\/usr\/lib/g" /etc/ssh/sshd_config
service ssh restart
# upgrade pip
yes | pip install –upgrade pip
# after updating pip, need to relink it so do_update_code works
ln -s /usr/local/bin/pip /usr/bin/pip
# install this for pip
apt-get install -y libyaml-dev
# install these for compressor
apt-get install -y default-jre
apt-get install -y npm
npm install -g less
npm install -g coffee-script
gem install sass
# link these so compress can find them
ln -s /usr/local/bin/lessc /usr/bin/lessc
ln -s /usr/local/bin/sass /usr/bin/sass
ln -s /usr/local/bin/coffee /usr/bin/coffee
# install Django beta
yes | pip uninstall Django
wget https://www.djangoproject.com/download/1.5b2/tarball/
mv index.html Django-1.5b2.tar.gz
tar xzvf Django-1.5b2.tar.gz
cd Django-1.5b2
python setup.py install
cd ../
rm -rf Django-1.5b2
rm Django-1.5b2.tar.gz
# install pip packages
yes | pip install –upgrade distribute
yes | pip install –upgrade MySQL-python
yes | pip install django-compressor
yes | pip install django-appconf
yes | pip install versiontools
yes | pip install –upgrade simplejson
yes | pip install "BeautifulSoup<4.0"
STATIC_DEPS=true
yes | sudo pip install lxml
yes | pip install odict
yes | pip install ply
yes | pip install html5lib
yes | pip install httplib2
yes | pip install slimit
yes | pip install boto
yes | pip install oauth2
yes | pip install python-openid
yes | pip install PyYAML
yes | pip install pytz
yes | pip install py-bcrypt
yes | pip install yolk
# cleanup simplejson (fixes yolk)
rm /usr/lib/python2.7/dist-packages/simplejson-2.3.2.egg-info
# remove generated wsgi.py in site root
rm /home/webapps/$APPLICATION/wsgi.py
# update apache config
perl -p -i -e "s/$APPLICATION\/wsgi.py/$APPLICATION\/conf\/wsgi.py/g" /etc/apache2/sites-available/http-80-localhost.vhost.conf
# copy and compress assets
python /home/webapps/$APPLICATION/manage.py collectstatic –noinput -i css -i js
python /home/webapps/$APPLICATION/manage.py compress
# restart apache
service apache2 restart
fi
logger -t RightScale "Software update completed."

view raw
install.sh
hosted with ❤ by GitHub

Sending Django Emails via AWS SES

Boto is the de facto standard for working with the AWS API from Python. Unfortunately their SES implementation only sends text or html emails, not both. You need to drop down to their send_raw_email method to send a multi-part email.

Using this Gist as a guideline, here’s an implementation of sending a multi-part email via a library class:

amazonses.py

from email.utils import COMMASPACE
from email.mime.multipart import MIMEMultipart
from email.mime.text import MIMEText
from boto.ses import SESConnection
from django.conf import settings
class SESMessage(object):
def __init__(self, source, to_addresses, subject, **kw):
self.ses = SESConnection(settings.AWS_ACCESS_KEY, settings.AWS_SECRET_KEY)
self._source = source
self._to_addresses = to_addresses
self._cc_addresses = None
self._bcc_addresses = None
self.subject = subject
self.text = None
self.html = None
self.attachments = []
def send(self):
if not self.ses:
raise Exception, 'No connection found'
if (self.text and not self.html and not self.attachments) or\
(self.html and not self.text and not self.attachments):
return self.ses.send_email(self._source, self.subject,
self.text or self.html,
self._to_addresses, self._cc_addresses,
self._bcc_addresses,
format='text' if self.text else 'html')
else:
if not self.attachments:
message = MIMEMultipart('alternative')
message['Subject'] = self.subject
message['From'] = self._source
if isinstance(self._to_addresses, (list, tuple)):
message['To'] = COMMASPACE.join(self._to_addresses)
else:
message['To'] = self._to_addresses
message.attach(MIMEText(self.text, 'plain'))
message.attach(MIMEText(self.html, 'html'))
else:
raise NotImplementedError, 'Attachments are not currently supported.'
return self.ses.send_raw_email(message.as_string(), source=self._source,
destinations=self._to_addresses)

view raw
amazonses.py
hosted with ❤ by GitHub

You’ll also need to add your AWS access key and secret to your settings file:

settings.py

# Amazon AWS
AWS_ACCESS_KEY = 'AKIA……..'
AWS_SECRET_KEY = 'w71v……..'

view raw
settings.py
hosted with ❤ by GitHub

You can then use the class like this:

from app.lib.amazonses import SESMessage
def contact(request):
if request.method == 'POST':
form = ContactForm(request.POST)
if form.is_valid():
# grab the form data
name = form.cleaned_data['name']
email = form.cleaned_data['email']
subject = form.cleaned_data['subject']
body = form.cleaned_data['body']
recipient = 'recipient@site.com'
# render the template with the submitted data
rendered = render_to_string('email/contact.html', {'name': name, 'email': email, 'subject': subject, 'body': body})
# create the message and send the email
# the from address must be a verified sender in SES
msg = SESMessage('valid_sending_address@site.com', recipient, subject)
msg.text = rendered
msg.html = rendered + ' html'
msg.send()
messages.add_message(request, messages.INFO, 'Your contact enquiry was successfully sent. Thank you!')
return HttpResponseRedirect(reverse('contact'))
else:
form = ContactForm()
return render(request, 'contact.html', {'form': form})

view raw
lib.py
hosted with ❤ by GitHub