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:
~/.netrc— standard Unix location storing data for logging in to remote hosts
~/.aws/credentials— Amazon Web Services credentials are used by a number of tools, Serverless.js being the most notable one
~/.eyrc— Engine Yard
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
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:
- Regular account the does all the development work. This account would also install all dependencies.
- 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
1 2 3 4 5 6 sudo dscl . create /Groups/homebrew sudo dscl . create /Groups/homebrew RealName "brew.sh 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:
1 2 3 4 5 6 7 8 9 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
su deploy and edit
1 2 3 4 5 6 7 8 # 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.comments powered by Disqus