Securing deployment credentials stored in home directory

If I were a cybercriminal, software developers would be my favorite target. First of all, if they are open source maintainers, they almost always store package repository access tokens as plain unsecured files. Gaining access to these files would allow an attacker to publish a new version of an open source library with an addition of malicious code that will be run on all machines that download the new version and to which the code using the new version is deployed. This is not just a theoretical threat – such attack was launched on numerous occasions, eslint-scope being the most prominent one.

Of course not everyone is a maintainer of a notable open source library, but they can still be an attractive target. Developers tend to have access to production systems – if the systems are managed via the command-line utilities (such as Heroku), the credentials are stored as unsecured files, and stealing the files would result in gaining access to their systems.

The problem is serious and well-known. This is one of the reasons you added a passphrase to our SSH keys. NPM already implemented two-factor authentication in their CLI, while RubyGems maintainers actively work on adding 2FA to the platform.

What about the other files? Here are typical files with access credentials:

  1. ~/.netrc — standard Unix location storing data for logging in to remote hosts
  2. ~/.aws/credentials — Amazon Web Services credentials are used by a number of tools, Serverless.js being the most notable one
  3. ~/.heroku/
  4. ~/.eyrc — Engine Yard
  5. ~/.s3cfg — s3cmd
  6. ~/.gmvault/gmvault_defaults.conf

How exactly is it a threat?

As developers, we run third-party code all the time. I'm pretty sure you did not audit every single line of your dependencies. Even running yarn / bundle install to install dependencies is potentially dangerous as one of the libraries could have a malicious post-install script that is automatically executed by package managers. One doesn't even need to publish a malicious version of a notable open-source library – typosquatting a popular library is another way to execute arbitrary code on developer's machines.

Is there a solution?

Waiting for all CLI utilities to implement two-factor authentication is unrealistic so we have to take matters to our own hands. My recommendation is to have two accounts on your operating system:

  1. Regular account the does all the development work. This account would also install all dependencies.
  2. Separate account that would only store credentials and deploy. This account should never install anything (the deployment tools should be installed by your regular account) or do anything other than deployment.

The rest of the article assumes using macOS but the idea is the same on other operating systems.

Step 1: multi-user homebrew setup

The deployer should have access to homebrew to run all utilities installed by the regular user. The most elegant solution is to create a separate homebrew group:

sudo dscl . create /Groups/homebrew
sudo dscl . create /Groups/homebrew RealName " users"
sudo dscl . create /Groups/homebrew gid 599
sudo dscl . create /Groups/homebrew GroupMembership $(whoami)
sudo chgrp -R homebrew $(brew --prefix)/*
sudo chmod -R g+rwX $(brew --prefix)/*

The number 599 is just an example, run dscl . list /Groups PrimaryGroupID and pick a number that is not used by any other group.

Step 2: create the user

Make sure to run dscl . list /Users UniqueID to select a number that is not taken by any other user. In the below example, we'll use 499:

sudo dscl . create /Users/deploy UniqueID 499
sudo dscl . create /Users/deploy PrimaryGroupID 20 # staff
sudo dscl . create /Users/deploy UserShell /bin/bash

# optional, if you prefer a non-standard location
sudo dscl . create /Users/deploy NFSHomeDirectory /var/deploy 

sudo dscl . append /Groups/homebrew GroupMembership deploy
sudo chown -R deploy /var/deploy

Step 3: shell configuration

Run su deploy and edit ~/.bashrc:

# Add homebrew to PATH
export PATH=/usr/local/bin:$PATH
# Let's have a distinct prompt
export PS1="\e[1;30m\][\[\e[1;34m\]\u@\h\[\e[1;30m\] \[\e[1;33m\]\w\[\e[0;37m] \n\$ "
# We'll be using Rubies and gems installed by the regular user
export RBENV_ROOT=/Users/---your-regular-user-name---/.rbenv
# Initialize rbenv
eval "$(rbenv init -)"

Step 4: move your credentials to deployer's home directory.

It wouldn't hurt to test the credentials. Also, try to access the credentials from the regular user's account: you should have permission denied error.

Step 5: use deployer account to deploy

From now on, use only the deployer account to deploy, sync files etc.


As developers, we install and run third-party code all the time. Don't let a malicious post-install script steal your production access credentials.

Posted on February 24, 2019 in security by Jacek Galanciak

comments powered by Disqus