In December 2014, Slashdot ran an alarming story Bots Scanning GitHub To Steal Amazon EC2 Keys, based on developer and blogger Andrew Hoffman's experience trying out Ruby on Rails on Amazon with AWS S3. He inadvertently committed an application.yml
file with his AWS keys. Even though he noticed it and deleted them quickly, hackers running bots managed to spend $2,375 of services before Amazon intervened. Fortunately, for Hoffman, they didn't hold him responsible for the fees.
But this wasn't exactly a new story. Back in January 2014, Forbes' Runa Sandvik reported: Attackers Scrape GitHub For Cloud Service Credentials, Hijack Account To Mine Virtual Currency, based on security blogger Rich Mogull's own experience. Hackers ran up $500 in charges on his account.
Who can't relate to his telling:
I was on the couch, finishing up an episode of Marvel’s Agents of S.H.I.E.L.D. Anyway… after the show I checked my email before heading to bed. This is what I saw: "Dear AWS Customer, Your security is important to us. We recently became aware that your AWS Access Key (ending with 3KFA) along with your Secret Key are publicly available on github.com." Crap. I bolted off the couch, mumbling to my wife, “my Amazon’s been hacked”, and disappeared into my office. I immediately logged into AWS and GitHub to see what happened.
It's an easy mistake and most of us have probably done a similar thing at one point or another. And it's not just AWS keys that are at risk. As our use of cloud-based services increases, the expanding use of a broad variety of service API keys can be leveraged by hackers and spammers alike.
In this tutorial, I'll walk you through one solution I use in my own applications to separate my repository code from my keys. While this may not be appropriate for larger environments, it's a practice that I regularly employ that's helped me limit these types of mistakes—yeah, I've made them too.
Can't We Just Use Git's Ignore?
Sure. Theoretically, that should work. But I've had experiences where Git's ignore hasn't worked the way I expected (probably due to my own error). Furthermore, if you rely on gitignore
and you make an error, you may not discover it until it's too late.
Another approach is to pay for private repositories. If you have a flexible budget, that works well. But if you work on open source projects or ever decide to open up a legacy project, it's not a foolproof approach either.
Programmable Web's Why Exposed API Keys And Sensitive Data Are Growing Cause For Concern lists some best practices and suggestions such as:
- When using AWS, set up billing alerts and max budgets.
- Never use root credentials with AWS—use limited access keys through IAM instead.
- Use git-crypt. Git-crypt enables transparent encryption and decryption of files in a Git repository.
They also recommend:
Don't embed API keys, passwords, etc., directly into code, always keep these separate. Put API keys, passwords, etc., in a separate configuration file. If a configuration file is part of a project in an IDE, remove sensitive data before distributing or sharing.
This is the approach that I use for my own projects and will review with you below.
Using a Configuration File
I find it's best to place keys in a configuration file hosted outside the publicly accessible web server directories—and managed manually, apart from my Git repository.
I began employing this solution when I began working with Yii 1.1. Yii 2.0 provides configurable environments as part of its Advanced Application Template, but doesn't fix the vulnerability to .gitignore
errors.
My approach is relatively simple. Instead of embedding service keys and credentials into my coding files directly, I place my keys in a separate configuration file which is parsed during initialization scripts.
For example, here's what the traditional approach might look like. Yii provides a configuration script that loads on every page request—notice that all my user names, passwords and API keys are vulnerable to mistaken check-ins:
<?php ... // This is the main Web application configuration. $options = array( ... 'db'=>array( 'connectionString' => 'mysql:host=localhost;dbname=mydatabase', 'emulatePrepare' => true, 'username' => 'jeff', 'password' => 'whitefoxesjumpfences', 'charset' => 'utf8', 'tablePrefix'=>'fox_', ), // application-level parameters that can be accessed // using Yii::app()->params['paramName'] 'params'=>array( 'pushover'=> array( 'key'=> 'H75EAC19M3249!X2', ), 'google'=> array( 'maps_api_key' => 'W69uHsUJZBPhsFNExykbQceK', ), ), ... ); ... ?>
Rather than run this risk, I create an app-config.ini
file and place it outside the github repository directory. It might look something like this:
mysql_host="http://host33663.rds.amazon-aws.com" mysql_un="amzn-app-db7293" mysql_db="rds-foxesandfences-db" mysql_pwd="K*$1x7B32auiWX91" pushover_key = "H75EAC19M3249!X2" google_maps_api_key="W69uHsUJZBPhsFNExykbQceK"
Then, I modify the configuration script to include the file using parse_ini_file and replace the hardcoded settings with variables from the ini
file:
<?php $config = parse_ini_file('/outside/webserver/app-config.ini', true); ... // This is the main Web application configuration. $options = array( ... 'db'=>array( 'connectionString' => 'mysql:host='.$config['mysql_host'].';dbname='.$config['mysql_db'], 'emulatePrepare' => true, 'username' => $config['mysql_un'], 'password' => $config['mysql_pwd'], 'charset' => 'utf8', 'tablePrefix'=>'fox_', ), // application-level parameters that can be accessed // using Yii::app()->params['paramName'] 'params'=>array( 'pushover'=> array( 'key'=> $config['pushover_key'], ), 'google'=> array( 'maps_api_key' => $config['google_maps_api_key'], ), ), ... ); >?
This approach has proved quite simple and manageable for me.
WordPress Plugin Vulnerabilities
If you're a WordPress administrator, you should also be aware of your API keys employed by common plugins; these are stored in your WordPress database. Keep up with your OS, WordPress and plugin updates to avoid having keys compromised by other vulnerabilities.
Closing Thoughts
My approach is meant to offer a basic alternative, but it's certainly not the last word. I'd love to hear your ideas and suggestions. Please post any comments and ideas below. You can browse my other Tuts+ tutorials on my instructor page or follow me on Twitter @reifman.
Comments