Thursday, January 15, 2015

Using IAM Roles and S3 to Securely Load Application Credentials

Many applications require certain information to be provided to them at run-time in order to utilize additional services. For example, applications that connect to a database require the database connection URL, a port, a username, and a password. Developers frequently utilize environment variables, which are set on the machine on which the application is running, to provide these credentials to the underlying code. Some developers, against all recommendations, will hard-code the credentials into an application, which then gets checked into git, distributed, etc.

Ideally, the credentials required by an application should not be hard-coded at all, or even accessible to processes outside of the one running the application itself. To achieve this, the application must determine what additional credentials it needs and load them prior to starting its main command.

Many applications that run on Amazon's Web Services platform have the added advantage of being hosted on EC2 instances that can assume specific IAM roles. An IAM role is essentially a definition of access rights that are provided to a particular AWS resource (an EC2 instance in this case). AWS takes care of generating temporary credentials for that instance, rotating them, and ensuring they are provided only to the assigned instance. Additionally, the AWS command line tools and various AWS language-specific SDKs will detect that they are being run on an instance using an IAM role and automatically load the necessary credentials.

As developers, we can take advantage of IAM roles to provide access to credentials that are stored in a private S3 bucket. When the application loads, it will use its IAM role to download the credentials and load them into the environment variables of the process. Then, wherever they are needed, they can simply be called by accessing the environment variable that has been defined.

As an example of this setup, here are the steps I would take to run a Node.js web server that requires some database credentials:

1. Create an S3 bucket called "organization-unique-name-credentials".

2. If you plan to have multiple applications, create a new folder for each within the bucket: "organization-unique-name-credentials/web-app," "organization-unique-name-credentials/app-two," etc. Ensure the proper access rights to each for your existing AWS users.

3. Set encryption on the bucket (you can use either AWS' key or your own).

4. Create a file called credentials.json that looks like this:

{
    "DB_URL" : "some-database-connection-string.com",
    "DB_PORT" : 3306,
    "DB_USER" : "app_user",
    "DB_PASS" : "securepass"
}

5. Upload the file to the right S3 bucket and folder (be sure to enable encryption or the upload will fail, assuming you required it for the bucket)

6. Create an IAM role for your instance. In the IAM console, click "Roles," then create a new role, enter a name, select "EC2 Service Role," and give it the following policy (add any other rights the app may need if it accesses other AWS resources):

{
  "Statement": [
    {
      "Effect": "Allow",
      "Action": [
        "s3:GetObject"
      ],
      "Resource": [
        "arn:aws:s3:::organization-unique-name-credentials/web-app/credentials.json"
      ]
    }
  ]
}

7. Launch your EC2 instance, selecting the role you just created.

8. In your code, do the following (node pseudo-code):

var AWS = require('aws-sdk);
AWS.config.region = 'us-east-1';
var s3 = new AWS.S3();

var params = {
    Bucket: 'organization-unique-name-credentials',
    Key: 'web-app/credentials.json'
}

s3.getObject(params, function(err, data) {
    if (err) {
        console.log(err);
    } else {
        data = JSON.parse(data.Body.toString());
        for (i in data) {
            console.log('Setting environment variable: ' + i);
            process.env[i] = data[i];
        }

        // Load database via db.conn({user:process.env['DB_USER'], password:process.env[
'DB_PASS']}); etc...
    }
});

9. Run the app, and you will notice that the environment variables are downloaded from S3 and are set before the database connection is attempted.

If you're using Node.js, I made a module that does exactly this: https://www.npmjs.com/package/secure-credentials

If you're not using Node.js, this technique can be applied to any language that AWS has an SDK for. While it isn't 100% hacker-proof (if someone managed to log into your instance as root, he or she could still modify the source code to display the credentials), but combined with other AWS security mechanisms such as security groups, VPCs with proper network ACLs, etc. it can certainly help. Additionally, it keeps credentials out of the source code.

One final note: if you're running this app locally to test, your machine will obviously not have an EC2 IAM role. When testing locally, it's okay to use AWS keys and secrets, but be sure to keep them in a separate file that is excluded with .gitignore.

Tuesday, January 13, 2015

AWS Cross-Account IAM Roles in CloudFormation

The AWS documentation is relatively sparse when it comes to creating specific IAM role types using CloudFormation. It describes the process of setting up standard roles, attaching roles to instances, etc. but doesn't mention that all of the other role types can also be created using CloudFormation.

For example, when you log into the AWS console and click on "IAM," you see a number of different roles you can create:

AWS Service Roles
Role for Cross-Account Access
Role for Identity Provider Access

However, these role types are merely just different adaptations of the same concept. In the following steps, I'll show how to create a Cross-Account Role using CloudFormation.

1. Add the following to the "Resources" section of your CloudFormation template:

"CrossAccountRole" : {
"Type" : "AWS::IAM::Role",
"Properties" : {
"AssumeRolePolicyDocument" : {
"Statement" : [
{
"Effect" : "Allow",
"Principal" : {
"AWS": "arn:aws:iam::ACCOUNT_NUMBER_HERE:root"
},
"Action" : [
"sts:AssumeRole"
]
}
]
}
}
},

2. Add another resource for the policy:

"CrossAccountPolicy" : {
"Type" : "AWS::IAM::Policy",
"Properties" : {
"PolicyName" : "IAMInstancePolicy",
"PolicyDocument" : {
"Statement" : [
{
"Effect" : "Allow",
"Action" : [
"*"
],
"Resource" : [
"*"
]
}
]
},
"Roles" : [
{ "Ref" : "CrossAccountRole" }
]
}
},

3. Adjust the account number and resources as needed:

This policy gives admin access to any account you specify. To restrict permissions, change the statement section of the policy document as desired.

Monday, January 5, 2015

Quickly Find What is Using Disk Space on Linux

Here's a quick command to find out what folder is consuming the most space on Linux. This will also sort the results to show the most space-consuming folders at the bottom:

du -sh * | sort -h

Run this in the current directory to find the most consuming subdirectory.

Thursday, October 30, 2014

Node.js "Error: too many parameters at queryparse"

If you've been using Express' body-parser and attempting to process large data submissions (aka forms with more than 1000 elements or 1000 sub-elements, you may have run into the following error:

Error: too many parameters
    at queryparse (/project/node_modules/body-parser/lib/types/urlencoded.js:120:17)
    at parse (/project/node_modules/body-parser/lib/types/urlencoded.js:64:9)

This is due to the fact the urlencode defaults to 1000 parameters by default. If you have a large form or just an abnormally large JSON submission, you'll need to increase this limit by doing the following:

var bodyParser = require('body-parser');

app.use(bodyParser.urlencoded({
        extended: false,
    parameterLimit: 10000,
    limit: 1024 * 1024 * 10
}));
app.use(bodyParser.json({
        extended: false,
    parameterLimit: 10000,
    limit: 1024 * 1024 * 10
}));


This will allow you to provide up to 10,000 parameters (increase as needed) and 10 MB of data (also adjustable).

Tuesday, October 14, 2014

How to Disable SSLv3 on AWS Elastic Load Balancers

In a blog post today, Google announced that a vulnerability in SSLv3 had been found that could allow attackers to intercept data that had previously been assumed to be secured. Luckily, a very small portion of the web (IE6 users on Windows XP) still use SSLv3, so it can safely, for the most part, be disabled to mitigate the risk from this issue.

http://googleonlinesecurity.blogspot.com/2014/10/this-poodle-bites-exploiting-ssl-30.html

UPDATE 10/15: As Andrew and Julio point out in the comments below, AWS has since updated their default cipher security policies. Replace steps 5 and 6.

To modify the ciphers on AWS ELBs, follow the following steps:

1) Log into the AWS console and click on "Load Balancers."
2) Find the load balancer that handles your site's traffic (you shouldn't need to worry about internal VPC LBs, etc.)
3) Click the "Listeners" tab
4) Find the HTTPS/443 listener and click "Edit" under the cipher column
5) Change the option to "Custom"
6) Uncheck the SSLv3 option
5) Change the policy to "ELBSecurityPolicy-2014-10" which disables SSLv3 for you.
6) Save.

This should be sufficient to mitigate this risk with the information that is currently known.

Redirect HTTP to HTTPS Behind AWS Elastic Load Balancer - Node.js and Apache

Apache

When you enable HTTPS for your website, you should enforce that HTTPS is being used by automatically redirecting users who access your site over HTTP to the HTTPS version. When using Apache, this is done via a host redirect entry with mod_rewrite:

<VirtualHost *:80>

# Beginning declarations here

RewriteEngine On
RewriteCond %{HTTPS} off
RewriteRule ^.*$ https://%{SERVER_NAME}%{REQUEST_URI}

# Rest of entry here...
</VirtualHost>

Apache Behind an ELB

However, in the Amazon Web Services world, most applications utilize the load balancer as an SSL-termination point. This means that the traffic is decrypted at the load balancer and all traffic being sent to the final destination instances is actually sent over HTTP (from a security standpoint, this is generally an accepted practice as the load balancer and instance are in the same network, and thus secure from eavesdropping). If you used the rule above, your users would wind up in an endless redirect loop.

To fix this, the "RewriteCond" needs to be changed to:

RewriteCond %{HTTP:X-Forwarded-Proto} !https

This rule will check to see which protocol the user was using before he or she hit the load balancer - which is what HTTPS redirection should use.

Node.js

When using Node.js, the same thing can be accomplished by inserting some middleware that listens for all traffic and redirects the non-HTTPS requests to their HTTPS equivalents.

var myApp = express () ,
myServer = http.createServer(myApp);

myApp.use(function redirectHTTP(req, res, next) {
  if (!req.secure) {
    return res.redirect('https://' + req.headers.host + req.url);
  }
  next();
});

Node.js Behind an ELB

Just as in our first Apache example, this works great until it is placed behind a load balancer. If you're using a load balancer, you need to make some modifications. Instead of "req.secure", do the following:

if (req.headers['x-forwarded-proto'] && req.headers['x-forwarded-proto'].toLowerCase() === 'http') {
return res.redirect('https://' + req.headers.host + req.url);
}

Using these techniques, you should be able to enforce the new HTTPS version of your website. Remember - if you serve both an HTTPS and an HTTP version of your site, the HTTPS is rendered pointless unless you also redirect the HTTP requests.

Sunday, September 28, 2014

Bringing Mint's Interface into the 21st Century

If you use the online financial planning app, Mint, you've probably wondered what decade it was designed in. Just browsing through the site presents a clutter of different styles, an incredible use of box-shadow, and more border-radius than should be permissible by law. It's time for an upgrade (Intuit, if you're reading this, please hire some designers!).

Now I am by no means a graphic designer. In fact, I'd argue that my understanding of colors, patterns, and other design principles is on par with most grade schoolers. However, in about three hours of work on a lazy Sunday afternoon, I was able to turn this:


Into this:



As you can see, not a ton has changed; my goal was not to completely design a new site. However, as I'll show in the next screenshots, I've tried to unify the interface a bit, get rid of the overzealous use of shadows, gradients, and rounded borders, as well as add a bit of white space to spread things out a bit. I also tried to flatten some of the progress bars to get rid of the outdated "Web 2.0" gloss effect.

For the transactions page, I've simply added white space to make things seem less cramped (pardon the gray box, I had to box out some of my own personal info):


Within the budgets page, I've gotten rid of all sorts of rounded glossy boxes and replaced them with whitespace and flat progress bars.


Not much has changed in the "Trends" page, but I did get rid of a lot of the cramped feeling and spread the content out a bit.


Finally, the "Investments" page was given more whitespace as well and a wider width for content:


Here is a list of other little changes I've made:


  • Increased the overall width to 1200px from the original 900. This gives us more room to work and added whitespace.
  • Changed all the popups to have sharp edges rather than rounded corners
  • Replaced the shadows with clean lines
  • Made all the menus square with straight hover edges rather than rounded corners
  • All the progress bars are flat now
  • Removed the bulky hover effects on the budgets page
  • Removed ads on the homepage and investments page
  • Removed the annoying Norton Certified seal in the footer
  • Widened the transactions and their details
  • A bunch of other small touches
I did not:
  • Get rid of the "Ways to Save" page. This is how Mint makes money and I don't want to destroy their revenue stream.
  • Modify the "Goals" page. It is so poorly designed right now with background images and borders and popups I wasn't brave enough to touch it.
  • Change the chart or graph styles for trends
Now, for a disclaimer: I do not guarantee that this will work for everyone's account. I tried to test it as much as possible, but there are a lot of hidden pieces of functionality within Mint that I may have missed. Finally, this code is all written out below. It's entirely CSS, so you can decide which parts you want to use. This code will not steal your account info or do anything else malicious.

I also don't make any guarantees about supporting this in the future. Mint could change their site tomorrow and break all of this. Fortunately, it's very easy to undo (just erase the user styles).

So how can you get this?


It's all CSS! I use an extension called "StyleBot" for Chrome (download link), but Firefox has similar extensions as well. Simply copy and paste the following styles into the extension by opening the extension while on mint.com, then clicking "Edit CSS" at the bottom, and then pasting it in.


#brokerage-link {
    display: none;
}

#filters-wrapper {
    border-radius: 0px;
    box-shadow: none;
    margin-left: 40px;
}

#filters-wrapper.closed {
    border: 0px;
    border-bottom: 0px solid lightgray;
    margin-bottom: 25px;
}

#filters-wrapper.open {
    border: 0px;
    border-bottom: 1px solid lightgray;
}

#graphSelectionNav {
    width: 100%;
}

#graphSelectionNav ul li {
}

#ira-link {
    display: none;
}

#menu-category li.active, #menu-categoryTypeFilter li.active {
    border-radius: 0px;
}

#menu-category ul {
    border-radius: 0px;
}

#menu-compare ul {
    border-radius: 0px;
}

#menu-compare ul li.active {
    border-radius: 0px;
}

#module-accounts ul li h3 {
    background: none;
    border: none;
    border-bottom: 1px solid #DFDFDF;
    box-shadow: none;
}

#module-advice {
    display: none;
}

#module-budget table div.bar {
    background: none;
    background-color: #ededed;
    border-radius: 0px;
    height: 20px;
}

#module-budget table div.bar span {
    background: none;
    background-color: #3FAF3B;
    border: none;
    border-radius: 0px;
    font-weight: normal;
    padding-bottom: 4px;
    padding-top: 3px;
    text-shadow: none;
}

#module-budget table tr {
    height: 40px;
}

#module-budget table tr.overbudget div.bar span {
    background: none;
    background-color: #ED5D51;
}

#module-budget table tr.warning div.bar span {
    background: none;
    background-color: #EFBE2E;
}

#module-budget thead th.bar {
    width: 70%;
}

#module-budget.module table thead th.budget {
    padding: 0 11px;
}

#module-goals table th.name {
    width: 40%;
}

#module-goals table th.next-step {
    width: 40%;
}

#module-investments table tr {
    height: 40px;
}

#module-offers {
    display: none;
}

#month-line {
    display: none;
    height: 500px;
}

#overview-left-column {
    margin: 0px;
    width: 302px;
}

#planning_group {
    padding: 5px 0 10px 250px;
}

#pop-categories, #pop-rules, #pop-tags, #pop-hotspot-overlay {
    border-radius: 0px;
}

#rollover-link {
    display: none;
}

#search-filters small {
    border-radius: 0px;
}

#transaction-ad {
    display: none;
}

#trust {
    display: none;
}

#txn-column-accounts {
    min-width: 240px;
    width: 20%;
}

#txn-detail {
    background: none;
}

#txnEdit #actions {
    left: 698px;
}

#txnEdit-basic tbody td.date, #txnEdit-basic-multi tbody td.date {
    max-width: 12%;
}

#txnEdit-form fieldset, #txnEdit-form-multi fieldset {
    width: 726px;
}

#txnEdit-form, #txnEdit-form-multi {
    top: 48px;
    width: 765px;
}

#txnEdit-mt-account label.txn-edit-labels.checknumber {
    width: 90px;
}

#txnEdit-note, #txnEdit-note-multi {
    width: 588px;
}

#txnEdit-split-icon {
    margin-left: -9px;
}

#txnEdit-toggle.noattachcol, #txnEdit-toggleP {
    left: 350px;
    width: 765px;
}

#ways_to_invest_control {
    display: none;
}

#wrapper {
    width: 1200px;
}

.custom-filters-action-wrapper {
    left: -130px;
    top: 10px;
}

.filters-top-line {
    display: none;
}

.module .module-menu .menu-wrapper {
    border-radius: 0px;
}

.module-reminders-content .timeline .chart .date {
    width: 25px;
}

.module-reminders-content .timeline .chart .dates {
    width: 810px;
}

.module-reminders-content .timeline .chart .divider-line {
    width: 830px;
}

.module-reminders-content .timeline .chart .graph {
    background: none;
    background-color: #3FAF3B;
    border-radius: 0px;
}

.nav #activeMenuItemGreenBar {
    width: 238px;
}

.overview {
    width: 100%;
}

.overviewPage .column-left {
    box-shadow: none;
}

.overviewPage .column-main {
    width: 95%;
}

.overviewPage .module .module-menu {
    left: 823px;
}

.pageContents > div {
    width: 100%;
}

.planningPage #incomeEE-list ul.ee_header {
    padding-bottom: 0px;
}

.planningPage #timeline {
    padding: 15px 0 5px 260px;
}

.planningPage .allocate_to_goals a.button {
    background: none;
    border-radius: 0px;
}

.planningPage .budget-summary {
    border-radius: 0px;
    box-shadow: none;
}

.planningPage .budget_group {
    padding: 5px 0 10px 260px;
}

.planningPage .right_col {
    width: 310px;
}

.planningPage div.ee_list ul.ee_header {
    padding-bottom: 0px;
}

.planningPage div.leftcolumn {
    width: 240px;
}

.planningPage ul.planning_items .edit-details {
    padding: 8px 0 0 1px;
}

.planningPage ul.planning_items .over-under-budget-text {
    margin: 6px 4px 0 0;
}

.planningPage ul.planning_items div.status span.progress_bar {
    background: none ;
    background-color: #3FAF3B ;
    border: none ;
    border-radius: 0px ;
    font-weight: normal ;
    padding-bottom: 4px ;
    padding-top: 3px ;
    text-shadow: none ;
}

.planningPage ul.planning_items div.status span.total_bar {
    background: none ;
    background-color: #ededed ;
    border-radius: 0px ;
    height: 20px ;
}

.planningPage ul.planning_items li {
    height: 70px;
}

.planningPage ul.planning_items li div.status span.progress_bar.full, .planningPage #incomeBudget-list-body li.overbudget div.status span.progress_bar.full {
    background-color: #ED5D51 ;
}

.planningPage ul.planning_items li.hover {
    background: none;
    padding: 7px 35px 0px 12px;
}

.planningPage ul.planning_items li.warning div.status span.progress_bar {
    background-color: #EFBE2E ;
}

.productPageContent {
    width: 100%;
}

.txnEdit-btn {
    border-radius: 2px;
}

a#txnEdit-category_picker.noattachcol, a#txnEdit-category_picker-multi.noattachcol, a#txnEdit-category_picker, a#txnEdit-category_picker-multi {
    left: 600px;
    top: 15px;
}

a.find_all span.find_all_wrapper {
    background: none;
}

a.find_all, a.split {
    background: none;
    border: 1px solid #D8D8D8;
    margin-bottom: 5px;
    margin-top: 10px;
    padding-bottom: 10px;
    padding-left: 10px;
}

a.find_all:hover, a.split:hover {
    background-color: #D8D8D8;
}

body div#graph-container {
    padding-left: 80px;
}

div#column-accounts.column {
    width: 100%;
}

div#column-accounts.column ul li div.result-number {
    border-radius: 0px;
    left: 227px;
}

div#column-accounts.column ul li li {
    padding: 10px 10px;
}

div#column-transactions div.controls#controls-top a.button {
    border-radius: 0px;
}

div#flash-container {
    margin-left: 10px;
    padding-left: 90px;
}

div#main {
    width: 1200px;
}

div#main.trends-main {
    width: 100%;
}

div.column#column-accounts ul li, div.column#column-accounts ul li li {
}

div.column#column-content {
    width: 900px;
}

div.column#column-transactions {
    width: 98%;
}

div.inspector {
    border-radius: 0px;
}

div.left-nav {
    width: 240px;
}

div.pop.newpop {
    border-radius: 0px;
}

div.premium-filters {
    width: 850px;
}

div.right-column {
    box-shadow: none;
    width: 945px;
}

div.txn-edit-group {
    width: 598px;
}

table.account {
    width: 100%;
}

table.transactions {
    width: 766px
;
}

table.transactions tbody td {
    padding: 12px 4px 12px 3px;
}

td.column.accounts {
    border-right: 1px solid #E1E6DD;
    box-shadow: none;
    width: 240px;
}

td.column.details {
    background: none;
}

td.column.details-budgets {
    background: none;
}

td.column.transactions {
    width: 70%;
}

ul.horizontal-bar li {
    background: none;
    background-color: #3FAF3B;
    border-radius: 0px;
}

ul.horizontal-bar li.debt {
    background: none;
    background-color: #ED5D51;
    border-radius: 0px;
}

ul.vertical-bar li a {
    background: none;
    background-color: #3FAF3B;
    border-radius: 0px;
}

ul.vertical-bar li a.debt {
    background: none;
    background-color: #ED5D51;
    border-radius: 0px;
}