The Curious Dev

Various programming sidetracks and shiny-object detours

Static Hosting With AWS

This site is hosted from an AWS S3 bucket and fronted by the AWS CloudFront CDN service. Continuing a recent theme about security, I figured I’d provide an updated guide to configuring S3 and CloudFront hosting with the additional angle of securing with TLS.

I briefly covered CDNs and CloudFront in easy wins for website performance. Essentially a CDN is a vast collection of servers distributed throughout the world in a way that they’re “close” to the consumers of the assets served. Most commonly this is done to save on latency of sending images/html/etc all the way around the world.

In this guide I’ll use my little play domain easyas.info to demonstrate. For the most part the AWS CLI client is a readily accessible way to do most things on AWS, but sometimes it’s just easier to drop into the web console, so I do a bit of both.

Hosting from a bucket [S3]

Create a bucket:

aws --region us-east-1 s3 mb s3://easyas.info

Configure the bucket for website serving:

aws --region us-east-1 s3 website s3://easyas.info/ --index-document index.html

I’ve not bothered at this point, but you could also specify an –error-document property at this point too, to serve up to the user if they have a bad URL or some other HTTP 4xx error code.

Next, add a bucket policy to allow visitors to view the site:

aws --region us-east-1 s3api put-bucket-policy --bucket easyas.info --policy file://easyas.info.policy

Here, the easyas.info.policy file contains:

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Sid": "PublicReadForGetBucketObjects",
            "Effect": "Allow",
            "Principal": "*",
            "Action": "s3:GetObject",
            "Resource": "arn:aws:s3:::easyas.info/*"
        },
        {
            "Sid": "ListBucketObjects",
            "Effect": "Allow",
            "Principal": "*",
            "Action": "s3:ListBucket",
            "Resource": "arn:aws:s3:::easyas.info"
        }
    ]
}

There are many, many other options for website hosting on S3, but that is enough to get up and going.

Put something in your bucket, a basic web page to get going, here’s a starter:

<html>
<head><title>Hosted on S3!</title></head>
<body>
<h2>easyas.info</h2>
<ol>
  <li>S3</li>
  <li>Cloudfront</li>
  <li>Route53</li>
  <li>ACM</li>
</ol>
</body>
</html>

Throw that up into the bucket with something like this:

aws --region us-east-1 s3 cp index.html s3://easyas.info/index.html

Over in the AWS console, find your bucket and drill down to the Static Website Hosting > Enable website hosting section and put this XML in the Redirection Rules text area:

<RoutingRules>
    <RoutingRule>
        <Condition>
            <KeyPrefixEquals>/</KeyPrefixEquals>
        </Condition>
        <Redirect>
            <ReplaceKeyPrefixWith>/index.html</ReplaceKeyPrefixWith>
        </Redirect>
    </RoutingRule>
</RoutingRules>

This will ensure that if you’ve got sub-directories with index.html documents, they’ll be served up when you just have the directory name in the URL, thus https://easyas.info/about will work just as good as https://easyas.info/about/index.html.

Configuring a CDN [CloudFront]

Creating a CloudFront distribution requires quite a few configuration items, but most of them can just be left as default.

From the main screen of CloudFront, click the Create Distribution button.

Create Distribution

For the delivery method, we want to choose Web, so click the relevant Get Started button.

For the Origin Settings, in the Origin Domain Name box, choose your S3 bucket from the drop down (click in the text box). But don’t leave it as that, you’ll want to change the URL for the Origin Name and Path to be the full S3 Website Hosting URL, i.e. easyas.info.s3-website-us-east-1.amazonaws.com (rather than easyas.info.s3.amazonaws.com), doing this enables more detailed configuration that you’ll need for website hosting.

Origin Settings

For the Default Cache Behaviour Settings you could pretty much leave it as default, except perhaps change it to Redirect HTTP to HTTPS. It’s easy enough to come back in and tweak these settings at any point in the future so don’t dwell on the specific options.

Default Settings

Continuing down, Price Class, I chose Use only US, Europe and Asia but you may choose to vary this (if you’re huge in Brazil perhaps) :)

Alternate Domain Names (CNAMES), here you want to put your desirable domain names that your site visitors will type in, so for easyas.info it’ll be both easyas.info and www.easyas.info.

SSL Certificate, for now just leave it as the Default CloudFront Certificate, we’ll revisit this in a little while.

Finish Create Distribution

So that’s basically it for CloudFront, click the Create Distribution button to finish. Take note of the Domain Name for the Distribution in the list to use in the next step. The distribution creation will likely take up to 15 minutes.

Origin Settings

Configure a domain name [Route53]

So up to this point we’ve created our bucket and configured the CloudFront distribution but we need to actually point those CNAMEs from the previous step at the distribution. For that, we need Route53.

Without a domain name, you’ll have to use the bucket address, similar to this: http://easyas.info.s3-website-us-east-1.amazonaws.com/ or the CloudFront distribution: https://d1u0vlids9dn5r.cloudfront.net, neither of which are exactly memorable :)

Just because we can, returning to our command line, we configure the CNAMEs in Route53.

First, we add a Hosted Zone for our domain: Click the Create Hosted Zone button and simply put in the domain name, i.e. easyas.info.

Create Hosted Zone

This could alternatively be done via command line with something like this:

aws route53 create-hosted-zone --name easyas.info --caller-reference 201606024-2308

After creation you’re taken into the Hosted Zone which allows us to Configure the DNS entries, so hit the Create Record Set button. I’ve used www.easyas.info for the name, create an A - IPv4 Address, with Alias = yes, and the Alias Target set to the Domain Name from the CloudFront Distribution i.e. d1u0vlids9dn5r.cloudfront.net, which you can choose from the dropdown.

Configure www.easyas.info

The click Create.

Create another entry with no www in the name i.e. just easyas.info.

Configure easyas.info

And that’s it! The DNS will take a while to propagate depending on your ISP or other random acts of DNS, but once it does you’ll be able to hit your site with a nice and simple domain.

Adding a real TLS certificate to finish off [ACM]

Whilst the TLS certificate served up by CloudFront is a valid certificate, it will likely prompt some browser warnings (Chrome might even say the world is going to end) as the host names don’t match. What we really need is our own certificate for our domain, with AWS Certificate Manager (ACM) this is both easy and free.

Go back into your CloudFront distribution and edit it. In the SSL Certificate section, choose the Custom SSL Certificate option this time. The hit the Request an ACM certificate button.

This launches a short wizard in a new page. First off, just put in the domain name with and without the www.

ACM Wizard 1 of 3

You could also put in other domains that you might want to use on this certificate. Click the Review and request button.

On the second step, confirm you’ve entered your domain(s) correctly then hit Confirm and request.

ACM Wizard 2 of 3

One the third and final screen you’re informed that a whole pile of email addresses have been sent an email requiring the domain(s) to be validated. Typically, there will be an address that should be accessible by you.

ACM Wizard 3 of 3

Importantly, you need to approve all of these domain(s), so in my case I’ve received two emails (for both www.easyas.info AND easyas.info) and clicked on the link in both of them.

Validation of Domain

In the ACM management screen you’ll now have an issued certificate.

Issued Certificate

This leaves us with just one last step, choosing the certificate in the CloudFront distribution. Find the other browser window for your CloudFront distribution and hit the little refresh button, in the dropdown you’ll now have your new certificate.

Choose your Certificate

Hit the Yes, Edit button at the bottom, and you’re done! The CloudFront distribution will be redeployed again, that’s another 15 minute or so wait, but once done you’ll be able to check your site and see the new certificate in use.

Valid Certificate

So in summary we’ve:

  • made an S3 bucket and put a simple page in there
  • created the CloudFront distribution
  • pointed a domain at the distribution for an incredibly easy way to host a static website, https://easyas.info
  • created a real TLS certificate with ACM to make it all legit.

A note on pricing, the cost of using these services is almost non-existent with the S3 bucket and the CloudFront distribution costing a few cents per 1000s of requests, ACM is free.

TLS Certificate Basics

Over the last few years we’ve seen a series of high profile SSL fails with names like Heartbleed, BEAST and POODLE which are based on various flaws in SSL and its various algorithm/cipher implementations. SSL (otherwise formerly known as SSLv3) is dead and has now all but been abandoned as essentially “not fixable”.

TLS is now the only show in town with three active versions TLSv1, TLSv1.1 and TLSv1.2 (and TLSv1.3 is on its way in hopefully 2016). Given the age of TLSv1, it probably doesn’t hurt to disable it too, unless you’ve got some semi-ancient clients (Java 6 can only do up to TLSv1).

With an increased focus on security, I thought I’d document some simple steps to getting a new TLS certificate setup.

One can purchase a certificate from a Certificate Authority (CA) such as StartSSL, Comodo or Gandi. Alternatively, one could generate a certificate but that wouldn’t be signed by a trusted CA, a self-signed certificate, which thus wouldn’t be trusted by browsers. Self-signed certificates result in some at times scary looking browser security warnings but it depends on the target audience and your degree of annoyance.

Generate a private key

To get things started you’ll need to generate a private key file:

openssl genrsa -out dash.thecuriousdev.com.key 2048

This will produce a file dash.thecuriousdev.com.key which will contain something like this (truncated):

-----BEGIN RSA PRIVATE KEY-----
MIIEpAIBAAKCAQEAw1Fo5Ay3U/AeO0AVNieFpjcEe63S8bAN8+YPqpIwQzauXzTA
ukOOXSYXR+RrN33KFQT+AzusQ1Ska5+VNM0tV+cjgYMB2I0JsUQ6tQ==
-----END RSA PRIVATE KEY-----

Generate a CSR

As an input of the certificate creation process you’ll need a Certificate Signing Request (CSR) which you generate from your private key:

openssl req -new -key dash.thecuriousdev.com.key -out thecuriousdev.com.csr -config "C:\Program Files (x86)\Git\ssl\openssl.cnf"

That last bit with the openssl.cnf file is required otherwise OpenSSL has a whinge about not finding the configuration, the OpenSSL on my machine is the one that came with the very helpful Git Bash tools package, so I just tell it where the config is.

The output file dash.thecuriousdev.com.csr will look something like this (truncated):

-----BEGIN CERTIFICATE REQUEST-----
MIICsjCCAZoCAQAwbTELMAkGA1UEBhMCQVUxGjAYBgNVBAgMEVdlc3Rlcm4gQXVz
CWHCQjHlWoTnuKeRpXuEVhF3Dt5RHiD9G4c3TQ4207V4azsvEt/7HWH1An6F6RRv
-----END CERTIFICATE REQUEST-----

When buying a certificate there will typically be a text area to input the contents of this file.

Purchasing the Certificate

Upon purchasing the certificate from your CA, you’ll typically be provided with a certificate file to download or a block of text to select, it’ll have the form similar to this (truncated):

-----BEGIN CERTIFICATE-----
MIIGMjCCBRqgAwIBAgIHBjI4n6fkszANBgkqhkiG9w0BAQsFADCBjDELMAkGA1UE
9YVDp+AnN/0tLTS0MxVVtJJZHwDCF3lRrcnuNm6tzufSwQlFLnsoqQWEufFLls8a
-----END CERTIFICATE-----

In addition to your certificate you’ll likely be provided an “intermediate” CA certificate which links your certificate up to the global certificate used by the CA (which is typically included in the various browsers etc). Depending on the CA, there may be multiple intermediate certificates.

Save the contents of all of these certificates to the same file, i.e. concatenated one after another and name it accordingly, such as dash.thecuriousdev.com.pem.

Note: I’m not sure whether the order of the certificate blocks makes a difference.

This looks something like this truncated example:

 -----BEGIN CERTIFICATE-----
 MIIGMjCCBRqgAwIBAgIHBjI4n6fkszANBgkqhkiG9w0BAQsFADCBjDELMAkGA1UE
 9YVDp+AnN/0tLTS0MxVVtJJZHwDCF3lRrcnuNm6tzufSwQlFLnsoqQWEufFLls8a
 -----END CERTIFICATE-----
 -----BEGIN CERTIFICATE-----
 MIIF2TCCA8GgAwIBAgIHFxU9nqs/vzANBgkqhkiG9w0BAQsFADB9MQswCQYDVQQG
 EwJJTDEWMBQGA1UEChMNU3RhcnRDb20gTHRkLjErMCkGA1UECxMiU2VjdXJlIERp
 -----END CERTIFICATE-----
 

Create a PKCS12 keystore

Next up we’ll generate a PKCS12 keystore from the certificates (pem file) and private key.

Input files needed:

  • pem file - dash.thecuriousdev.com.pem (containing the new certificate and any intermediate certificates)
  • private key - dash.thecuriousdev.com.key (from your original steps to generate the CSR)

Using OpenSSL with the required certificates and key it can generate a PKCS12 keystore (typically with a .p12 extension). You’ll be prompted for a password, for this example I’ll stick with the default changeit that the Java keytool uses.

openssl pkcs12 -export -in dash.thecuriousdev.com.pem -inkey dash.thecuriousdev.com.key -out dash.thecuriousdev.com.p12 -name dash.thecuriousdev.com

Create a JKS keystore

Now that we’ve got a PKCS12 keystore, we can easily produce our JKS keystore from it.

keytool -importkeystore -deststorepass changeit -destkeypass changeit -destkeystore dash.thecuriousdev.com.jks -srckeystore dash.thecuriousdev.com.p12 -srcstoretype PKCS12 -srcstorepass changeit -alias dash.thecuriousdev.com

Note: I believe there can be problems if a srcstorepass is not provided.

Summary

You should now have a JKS keystore dash.thecuriousdev.com.jks that will contain one entry, keytool -list -keystore dash.thecuriousdev.com.jks, will produce something like this:

Keystore type: JKS
Keystore provider: SUN

Your keystore contains 1 entry

dash.thecuriousdev.com, 08/07/2015, PrivateKeyEntry,
Certificate fingerprint (SHA1): 76:E4:54:8B:71:72:F6:1F:CB:80:47:D5:A9:C6:1C:46:0E:91:96:2E

You can now use this keystore in your web/application server or even for AWS CloudFront, more on this in another post perhaps.

SSH Tunnels With PuTTY

In a previous post I covered how I’ve been using PuTTY to get through the day, I decided to expand on it a little further and document a couple steps to bring a particular server’s ports closer to you … even if they’re firewalled away.

An ssh tunnel can be explained quite simply as:

an encrypted connection that is used to transport another protocol

Single Server

sshing into a server is one thing, but what if port 22 is the only practical accessible port due to firewall restrictions? To connect to a database on the server one can use a tunnel to “sneak” through.

To expand on this example further, let’s say there’s a postgres server running on port 5432 but the firewall is blocking access to it (as it probably should be). It is trivial to setup an ssh tunnel to effectively bring the port to the local machine.

Using PuTTY, configure the connection as per normal, for example:

SSH Session

Then in the SSH settings, add a port value for the Source port field, this is the port you’ll need to use in your database tool of choice, pgAdminIII perhaps. This can be any port really, except for anything already in use locally, i.e. if you’ve got postgres running locally, don’t choose 5432.

In the Destination field, this is the important bit, set the host:port combination as though you were directly sitting at the remote server, i.e. localhost:5432 for the server’s local database.

SSH > Tunnels configuration

Once you open the session, the tunnel is live too. One can now go into pgAdminIII and setup a connection to a local database:

Create a new connection

Note the port in use and that the host is localhost as it is connecting to the local end of the SSH Tunnel on the configured port 15432.

pgAdminIII has an option to configure an SSH Tunnel in the connection creation dialog but I haven’t worked out how that fits in to the picture just yet.

So there’s a connection, does it work?

Sure does!

As a side note: if you close your PuTTY session, your tunnel will go with it.

Included file 'facebook_like.html' not found in _includes directory