Security for dummies: Protecting application secrets made easy

Using Vault to secure, encrypt and manage secrets (and some scare stories to drive you with fear)

March 19, 2018
Contact Us
Weekly Shorts are topics we discuss in our weekly remote meeting related to recent work we have done with our customers
Security for dummies: Protecting application secrets made easy

Last year, I’ve found my client’s AWS account access and secret keys pushed to git, and visible in 23 different locations in the application’s codebase. After requesting an immediate removal, I used the awesome GitLeaks tool to scan the project's history. GitLeaks allows you scan a project’s entire history, rather than it’s current state, so that it provides a very reliable output of the sensitive data hiding in your code.

The process yielded a 4.3MB of sensitive data, listing the same keys in 309 commits throughout the project’s history, along with private keys, private secrets used to communicate with cloud providers and other gems.

In the absence of tools, engineers tend to find their own solutions to everything. Whether it’s for delivery, storage, build-pipelines and unfortunately secrets and keys, developers require certain tools to surround them and provide solutions to their basic application needs.

A secret manager is a tool that allows safe storage and encryption of sensitive data, providing access to strict and secure methods.
Without it, keys are being stored on shared locations, hardcoded, committed and pushed to VCS. Sometimes it’s even publicly visible in the code downloaded to the client’s browser. #truestoryRight, it’s not even remotely safe or done with the minimum level of security in mind, we can blame the responsible ones as much as we want, but educating is worthless without providing a proper alternative.
Common sense should be trusted, but not with your company’s life, and yes, I mean that in the very literal way.

In order to illustrate how destructive a data leak can be, I used a few python lines exploiting AWS root keys as described in the first paragraph; I’m sure you understand this can be done just as easily with any language or the CLI.
Please do not copy this code, let alone run it.

#gist:<link rel="stylesheet" href="https://assets-cdn.github.com/assets/gist-embed-87673c31a5b37b5e6556b63e1081ebbc.css"><div id=\"gist87914714\" class=\"gist\">\n <div class=\"gist-file\">\n <div class=\"gist-data\">\n <div class=\"js-gist-file-update-container js-task-list-container file-box\">\n <div id=\"file-dont_run_me-py\" class=\"file\">\n \n\n <div itemprop=\"text\" class=\"blob-wrapper data type-python\">\n <table class=\"highlight tab-size js-file-line-container\" data-tab-size=\"8\">\n <tr>\n <td id=\"file-dont_run_me-py-L1\" class=\"blob-num js-line-number\" data-line-number=\"1\"><\/td>\n <td id=\"file-dont_run_me-py-LC1\" class=\"blob-code blob-code-inner js-file-line\"><span class=\"pl-k\">import<\/span> boto3<\/td>\n <\/tr>\n <tr>\n <td id=\"file-dont_run_me-py-L2\" class=\"blob-num js-line-number\" data-line-number=\"2\"><\/td>\n <td id=\"file-dont_run_me-py-LC2\" class=\"blob-code blob-code-inner js-file-line\">\n<\/td>\n <\/tr>\n <tr>\n <td id=\"file-dont_run_me-py-L3\" class=\"blob-num js-line-number\" data-line-number=\"3\"><\/td>\n <td id=\"file-dont_run_me-py-LC3\" class=\"blob-code blob-code-inner js-file-line\"><span class=\"pl-c\"><span class=\"pl-c\">#<\/span> Listing all stacks in the AWS account that are currently active<\/span><\/td>\n <\/tr>\n <tr>\n <td id=\"file-dont_run_me-py-L4\" class=\"blob-num js-line-number\" data-line-number=\"4\"><\/td>\n <td id=\"file-dont_run_me-py-LC4\" class=\"blob-code blob-code-inner js-file-line\"><span class=\"pl-k\">def<\/span> <span class=\"pl-en\">list_stacks<\/span>(<span class=\"pl-smi\">client<\/span>):<\/td>\n <\/tr>\n <tr>\n <td id=\"file-dont_run_me-py-L5\" class=\"blob-num js-line-number\" data-line-number=\"5\"><\/td>\n <td id=\"file-dont_run_me-py-LC5\" class=\"blob-code blob-code-inner js-file-line\"> response <span class=\"pl-k\">=<\/span> client.list_stacks(<\/td>\n <\/tr>\n <tr>\n <td id=\"file-dont_run_me-py-L6\" class=\"blob-num js-line-number\" data-line-number=\"6\"><\/td>\n <td id=\"file-dont_run_me-py-LC6\" class=\"blob-code blob-code-inner js-file-line\"> <span class=\"pl-v\">StackStatusFilter<\/span><span class=\"pl-k\">=<\/span>[<span class=\"pl-s\"><span class=\"pl-pds\">'<\/span>CREATE_COMPLETE<span class=\"pl-pds\">'<\/span><\/span>, <span class=\"pl-s\"><span class=\"pl-pds\">'<\/span>UPDATE_COMPLETE<span class=\"pl-pds\">'<\/span><\/span>]<\/td>\n <\/tr>\n <tr>\n <td id=\"file-dont_run_me-py-L7\" class=\"blob-num js-line-number\" data-line-number=\"7\"><\/td>\n <td id=\"file-dont_run_me-py-LC7\" class=\"blob-code blob-code-inner js-file-line\"> )<\/td>\n <\/tr>\n <tr>\n <td id=\"file-dont_run_me-py-L8\" class=\"blob-num js-line-number\" data-line-number=\"8\"><\/td>\n <td id=\"file-dont_run_me-py-LC8\" class=\"blob-code blob-code-inner js-file-line\"> <span class=\"pl-k\">return<\/span> response<\/td>\n <\/tr>\n <tr>\n <td id=\"file-dont_run_me-py-L9\" class=\"blob-num js-line-number\" data-line-number=\"9\"><\/td>\n <td id=\"file-dont_run_me-py-LC9\" class=\"blob-code blob-code-inner js-file-line\">\n<\/td>\n <\/tr>\n <tr>\n <td id=\"file-dont_run_me-py-L10\" class=\"blob-num js-line-number\" data-line-number=\"10\"><\/td>\n <td id=\"file-dont_run_me-py-LC10\" class=\"blob-code blob-code-inner js-file-line\"><span class=\"pl-c\"><span class=\"pl-c\">#<\/span> Iterate through the list and delete everything<\/span><\/td>\n <\/tr>\n <tr>\n <td id=\"file-dont_run_me-py-L11\" class=\"blob-num js-line-number\" data-line-number=\"11\"><\/td>\n <td id=\"file-dont_run_me-py-LC11\" class=\"blob-code blob-code-inner js-file-line\"><span class=\"pl-k\">def<\/span> <span class=\"pl-en\">make_the_world_burn<\/span>(<span class=\"pl-smi\">client<\/span>, <span class=\"pl-smi\">response<\/span>):<\/td>\n <\/tr>\n <tr>\n <td id=\"file-dont_run_me-py-L12\" class=\"blob-num js-line-number\" data-line-number=\"12\"><\/td>\n <td id=\"file-dont_run_me-py-LC12\" class=\"blob-code blob-code-inner js-file-line\"> <span class=\"pl-k\">for<\/span> stack <span class=\"pl-k\">in<\/span> response[<span class=\"pl-s\"><span class=\"pl-pds\">'<\/span>StackSummaries<span class=\"pl-pds\">'<\/span><\/span>]:<\/td>\n <\/tr>\n <tr>\n <td id=\"file-dont_run_me-py-L13\" class=\"blob-num js-line-number\" data-line-number=\"13\"><\/td>\n <td id=\"file-dont_run_me-py-LC13\" class=\"blob-code blob-code-inner js-file-line\"> client.delete_stack(<\/td>\n <\/tr>\n <tr>\n <td id=\"file-dont_run_me-py-L14\" class=\"blob-num js-line-number\" data-line-number=\"14\"><\/td>\n <td id=\"file-dont_run_me-py-LC14\" class=\"blob-code blob-code-inner js-file-line\"> <span class=\"pl-v\">StackName<\/span><span class=\"pl-k\">=<\/span>stack[<span class=\"pl-s\"><span class=\"pl-pds\">'<\/span>StackName<span class=\"pl-pds\">'<\/span><\/span>]<\/td>\n <\/tr>\n <tr>\n <td id=\"file-dont_run_me-py-L15\" class=\"blob-num js-line-number\" data-line-number=\"15\"><\/td>\n <td id=\"file-dont_run_me-py-LC15\" class=\"blob-code blob-code-inner js-file-line\"> )<\/td>\n <\/tr>\n <tr>\n <td id=\"file-dont_run_me-py-L16\" class=\"blob-num js-line-number\" data-line-number=\"16\"><\/td>\n <td id=\"file-dont_run_me-py-LC16\" class=\"blob-code blob-code-inner js-file-line\">\n<\/td>\n <\/tr>\n <tr>\n <td id=\"file-dont_run_me-py-L17\" class=\"blob-num js-line-number\" data-line-number=\"17\"><\/td>\n <td id=\"file-dont_run_me-py-LC17\" class=\"blob-code blob-code-inner js-file-line\"><span class=\"pl-k\">def<\/span> <span class=\"pl-en\">main<\/span>():<\/td>\n <\/tr>\n <tr>\n <td id=\"file-dont_run_me-py-L18\" class=\"blob-num js-line-number\" data-line-number=\"18\"><\/td>\n <td id=\"file-dont_run_me-py-LC18\" class=\"blob-code blob-code-inner js-file-line\"> client <span class=\"pl-k\">=<\/span> boto3.client(<span class=\"pl-s\"><span class=\"pl-pds\">'<\/span>cloudformation<span class=\"pl-pds\">'<\/span><\/span>)<\/td>\n <\/tr>\n <tr>\n <td id=\"file-dont_run_me-py-L19\" class=\"blob-num js-line-number\" data-line-number=\"19\"><\/td>\n <td id=\"file-dont_run_me-py-LC19\" class=\"blob-code blob-code-inner js-file-line\"> make_the_world_burn(list_stacks(client))<\/td>\n <\/tr>\n <tr>\n <td id=\"file-dont_run_me-py-L20\" class=\"blob-num js-line-number\" data-line-number=\"20\"><\/td>\n <td id=\"file-dont_run_me-py-LC20\" class=\"blob-code blob-code-inner js-file-line\"> <\/td>\n <\/tr>\n<\/table>\n\n\n <\/div>\n\n <\/div>\n<\/div>\n\n <\/div>\n <div class=\"gist-meta\">\n <a href=\"https://gist.github.com/omerxx/f0869cfb1a24bcb4215eef2759d94a3d/raw/a41d5291720afd2405af942f99dcc5710f1a90cd/dont_run_me.py\" style=\"float:right\">view raw<\/a>\n <a href=\"https://gist.github.com/omerxx/f0869cfb1a24bcb4215eef2759d94a3d#file-dont_run_me-py\">dont_run_me.py<\/a>\n hosted with ❤ by <a href=\"https://github.com\">GitHub<\/a>\n <\/div>\n <\/div>\n<\/div>\n

This code can be altered and used on any AWS resource the keys allow access to. Deleting instances, databases, users, DNS records and everything in its path!

To be very clear about the risk of such a key pair falling in the wrong hands, and without disclosing any names, it could have completely ruined the company with its 65 engineers and $50 Million in the bank, turning it to ashes within a single command.
A tool like aws-nuke can also be handy for the bad guys. The destructive power of such a leak cannot be stressed enough.

Not convinced?

Here are some real-world examples:

#1

A small startup in the retail business, discovering and comparing retail prices of premium brand top sellers, had been hacked four times in 90 days at the end of 2017. A private SSH key used by the production cluster was part of a library open sourced by the organization and aggressively marketed to create traction and buzz around their product.
After each hack they rotated all AWS and GCP keys, and 3rd party tools access credentials including taking a security audit of their internal code base. Only a complete scan over the public code base exposed the leaked secret keys.

#2

Another small company in the mobile gaming field, used AWS S3 to store XML files. Upon launching their Android app, XML data from their AWS account was downloaded, using an AWS key.
Only retrospectively, it turned out to be lacking any kind of monitoring on resources and monthly bills alike.
In order to fetch the data, under the hood, the application used to authenticate with S3 using the account’s root credentials.
When their new iOS application was launched, they were disapproved by Amazon’s Appstore, and communicated by Amazon describing the leak. Only then, they found some public unfamiliar instances running on their expense. Luckily, except for money loss, no real harm was done.

#3

One of the biggest known digital media and advertising companies in the world had discovered a very suspicious activity in their account on March 2017: instead of running their normal cluster ranging from 500 to 1000 instances every day, they were constantly growing and requesting Amazon to raise their instance count limits, assuming they were experiencing a natural growth, they thanked their marketing division for their fruit-bearing efforts. The instance count curve went very steep in a single 24 hour period reaching a peak of 8000 R3 (intensive memory) instances. The activity was so out of the ordinary they were contacted by AWS, and found a Bitcoin mining cluster running on their account resulting in over $600,000 in charges, taking into account the entire month’s activity since the exploit started.
In the post-mortem investigation, it was discovered that one of their publicly accessible instances was left open, and contained a file you may be familiar with in the path ~/.aws/credentials allowing the use of all EC2 permissions, which was exploited to launch a huge cluster of machines by an opportunist hacker.

How can you mitigate the situation?

Based on the Pareto principle (a.k.a the 20–80 rule), by managing secrets within a single, secure, encrypted, yet reachable location, you can take care of the 20% that can potentially impose 80% damage.

The best example of such a solution is Hashicorp’s Vault:

Vault is a tool for securely accessing secrets. A secret is anything that you want to tightly control access to, such as API keys, passwords, certificates, and more. Vault provides a unified interface to any secret, while providing tight access control and recording a detailed audit log.
HashiCorp Vault logo

With Vault, solving the problem is easy. It is a secret manager that literally seals your keys in a safe. Every unplanned change in the vault system (such as crashing, multiple bad authentications, etc…) seals the system. It moves to a “frozen” state and requires a minimum number of pre-configured personal keys to be inserted, in a very specific way to unseal the system and allow access.

Visibility to secrets is granted based on configured “paths” and user-based policies. So, for example, an application called “UserAccounts” can be attached to a policy that allows the service to only read i.e vault.company.io/secrets/UserAccounts from its own path. This actively applies compartmentalization between the different services, so that even if one of them is compromised, it has no access to others’ keys. Such a process comes hand-in-hand with a general rule to create dedicated and slim roles and policies to each service, providing it with only the minimum permissions required to complete its job.

sys/auth

Vault allows different authentication methods like user/pass, tokens, LDAP, AWS, GitHub tokens and more. This way developers can easily interact with the system, reading and writing secrets, and so can applications interacting with Vault natively using wrapper-libraries.

* Security TIP: If you use GitHub as an organization, allowing GitHub tokens as backend authentication is very comfortable for developers to interact with Vault directly. But allow it only after enforcing 2FA on the GitHub organization.

GDPR Compliancy

As the world heads toward the GDPR due date, companies and organizations plan their information compliancy according to the new rules. Vault can help with strengthening personal data using roles and access policies. Learn more about it in a recorded webinar, from Feb/2018 — Preparing for GDPR Compliance with HashiCorp Vault.

A much simpler integration with application code

In order to maintain strict security, Vault requires each application to send an authentication request and only then it allows API interaction.

Since developers wouldn’t normally jump on the boat when it comes to adding code which doesn’t contribute to an applicative feature, we have to ease the on-boarding process and make it as seamless as possible; this can be simplified using Vault-Get.

Vault-get is a free and open source CLI tool which we developed in-house to interact with Vault in a single request that 1. Authenticates 2. Pulls secrets, and 3. Injects them to the local environment. This way, when the instance fires up, it pulls the secrets from vault and then launches the application with secrets injected into its environment.

Vault-get is as easy as:

#gist:<link rel="stylesheet" href="https://assets-cdn.github.com/assets/gist-embed-87673c31a5b37b5e6556b63e1081ebbc.css">'<div id=\"gist90398222\" class=\"gist\">\n    <div class=\"gist-file\">\n      <div class=\"gist-data\">\n        <div class=\"js-gist-file-update-container js-task-list-container file-box\">\n  <div id=\"file-sec4dummies1\" class=\"file\">\n    \n\n  <div itemprop=\"text\" class=\"blob-wrapper data type-text\">\n      <table class=\"highlight tab-size js-file-line-container\" data-tab-size=\"8\">\n      <tr>\n        <td id=\"file-sec4dummies1-L1\" class=\"blob-num js-line-number\" data-line-number=\"1\"><\/td>\n        <td id=\"file-sec4dummies1-LC1\" class=\"blob-code blob-code-inner js-file-line\">eval "$(vault-get —vault_host https://vault.example.com<\/td>\n      <\/tr>\n      <tr>\n        <td id=\"file-sec4dummies1-L2\" class=\"blob-num js-line-number\" data-line-number=\"2\"><\/td>\n        <td id=\"file-sec4dummies1-LC2\" class=\"blob-code blob-code-inner js-file-line\">—vault_token mytoken —vault_path secret/my-secret-path)"<\/td>\n      <\/tr>\n<\/table>\n\n\n  <\/div>\n\n  <\/div>\n<\/div>\n\n      <\/div>\n      <div class=\"gist-meta\">\n        <a href=\"https://gist.github.com/seanroisentul/6333bfc49ba5e73f9c6b76a207ac73b3/raw/73819c32d21ad338384cc61568a83bde918facd9/Sec4Dummies1\" style=\"float:right\">view raw<\/a>\n        <a href=\"https://gist.github.com/seanroisentul/6333bfc49ba5e73f9c6b76a207ac73b3#file-sec4dummies1\">Sec4Dummies1<\/a>\n        hosted with ❤ by <a href=\"https://github.com\">GitHub<\/a>\n      <\/div>\n    <\/div>\n<\/div>\n

This line is the command that is fired before running the application. For more information visit github.com/devops-israel/vault-get. That’s all it takes, secrets have been returned and set as environment variables, ready to be used.

Lessons learned

Vault is just an example, it has alternatives and competitors, some may be better or more fitting for each specific use case. It’s not the tool that matters, but the change in methods of work, mindset and understanding.It’s important to understand that organizational secrets and keys have to be concealed and protected, and that a software code base should be consistently monitored and scanned for leaks.Investing in education is far more effective than just announcing new strict rules; let your developers and infrastructure employees learn about the environment they work in, and the risks and threats that surround them, in order to motivate them to improve security, and apply what they’ve just been taught.

So, as I like to say, don’t wait for the s**t to hit the fan, because the s**t is there and the fan is on:

Be proactive; be a professional; protect yourself.

Some additional information to complete the security process of concealing your secrets

Disaster Recovery

Even with the best security measures, accidents, and exploits still happen. Make sure you are ready for any scenario, even the one where your entire resource stack is being terminated or deleted in a non-retrievable way.In order to recover, you want to make sure your infrastructure is templated and backed-up in git. Recommended read: Infrastructure as code

Concealing visible sensitive data at all times

As presented earlier, Vault has many ways of authenticating, some include roles, some require token as a string delivered. Try working with authentication methods that don’t require any additional steps that may expose data. A very good example is using the already assumed IAM role that the instance or service use. This way, you’re not using keys or generating any unnecessary points of failure in your security flow.I’ll use a quote by Ryan Kroonenberg from acloud.guru:

“If you don’t have keys, you can’t lose them.”

It’s funny, cause it’s true 😉. Keeping this quote in mind is a very good way to keep your data out of unwanted hands.

Using IAM

Follow AWS’s / GCP’s best IAM practices, in order to learn about slim policies, roles creation and assuming by resources. Knowing in-depth IAM internals may very well be your saviour in terms of access management.

Let me know your thoughts in the comments below, or connect with me directly on Twitter @omergsr.Clap if you liked it, it helps me focus my future writings!

Security for dummies: Protecting application secrets made easy
Omer Hamerman
Senior Software Operations Architect
Omer is an experienced software operations engineer and an open source contributor. He is always willing to go the extra mile to help our clients improve their software delivery. He is known for getting the job done very quickly and is clear-cut and very sharp, delivering almost any job on the spot. When he’s not helping our clients achieve scalable and resilient infrastructure, you’ll find him rock climbing and bouldering. He is passionate about beautiful code, cybersecurity and doing things right the first time. He is a keen writer of blog posts and a speaker at meetups.