269

I've got a little Bash script that I use to access twitter and pop up a Growl notification in certain situations. What's the best way to handle storing my password with the script?

I would like to commit this script to the git repo and make it available on GitHub, but I'm wondering what the best way to keep my login/password private while doing this is. Currently, the password is stored in the script itself. I can't remove it right before I push because all the old commits will contain the password. Developing without a password isn't an option. I imagine that I should be storing the password in an external config file, but I thought I'd check to see if there was an established way to handle this before I tried and put something together.

kubi
  • 48,104
  • 19
  • 94
  • 118

9 Answers9

313

The typical way to do this is to read the password info from a configuration file. If your configuration file is called foobar.config, then you would commit a file called foobar.config.example to the repository, containing sample data. To run your program, you would create a local (not tracked) file called foobar.config with your real password data.

To filter out your existing password from previous commits, see the GitHub help page on Removing sensitive data.

Greg Hewgill
  • 951,095
  • 183
  • 1,149
  • 1,285
  • 7
    Btw, you can add an example foobar.config to the repo and then add foobar.config to the .ignore file. This way the example foobar.config will appear when cloned and your actual passwords will not get added to the repo. – Mr_Chimp Nov 03 '11 at 14:31
  • 21
    @Mr_Chimp: The `.gitignore` file does not apply to tracked files that are *already in* the repository. For example, `git add -u` will add a changed file even if it is already in `.gitignore`. – Greg Hewgill Nov 03 '11 at 18:04
  • 2
    As a complement, here is an interesting link in case you added the config file by accident and you want to delete it from the git history: https://help.github.com/articles/remove-sensitive-data – Loïc Lopes May 06 '14 at 13:48
  • 1
    Good answer! But, how would you import this config file entries (e.g. passwords) to a XML file, for example? – Felipe Mosso Sep 15 '15 at 21:53
  • 19
    How would you go about sharing those passwords with your team? One thing is having a local copy (not committed to the repo), the other is to share it with a bigger team of even with automatic tools (for deployment, etc) – blueFast May 23 '16 at 10:53
  • 2
    I have the same question as @dangonfast. This doesn't seem practical for a large team. – Jacob Stamm Jul 06 '17 at 19:04
  • 1
    Regarding making passwords available to a team, this can be done using password management tools such as 1Password and Last Pass. The README.md or setup script can prompt to copy them from such a location. – Harry B Apr 24 '18 at 08:47
  • 1
    @dragonfast Regarding sharing passwords with a larger team, there will always be a compromise. Regardless of the different methods you use to secure passwords, the entire team must understand the risks and agree to a standard and everyone will follow. – alans Jan 03 '20 at 19:04
31

An approach can be to set password (or API key) using an environment variable. So this password is out of revision control.

With Bash, you can set environment variable using

export your_env_variable='your_password'

This approach can be use with continuous integration services like Travis, your code (without password) being stored in a GitHub repository can be executed by Travis (with your password being set using environment variable).

With Bash, you can get value of an environment variable using:

echo "$your_env_variable"

With Python, you can get value of an environment variable using:

import os
print(os.environ['your_env_variable'])

PS: be aware that it's probably a bit risky (but it's a quite common practice) https://www.bleepingcomputer.com/news/security/javascript-packages-caught-stealing-environment-variables/

PS2: this dev.to article titled "How to securely store API keys" may be interesting to read.

tripleee
  • 175,061
  • 34
  • 275
  • 318
scls
  • 16,591
  • 10
  • 44
  • 55
  • 1
    How to prevent the potential "unsafe" code beeing build from reading your environment variable's contents? – gorootde Sep 15 '17 at 12:22
  • I suggest to have a look at https://blog.travis-ci.com/2013-06-10-secure-env-in-pull-requests/ and https://docs.travis-ci.com/user/pull-requests/#Pull-Requests-and-Security-Restrictions – scls Sep 16 '17 at 19:19
21

What Greg said but I'd add that it's a good idea to check in a file foobar.config-TEMPLATE.

It should contain example names, passwords or other config info. Then it is very obvious what the real foobar.config should contain, without having to look in all the code for which values must be present in foobar.config and what format they should have.

Often config values can be non obvious, like database connection strings and similar things.

Community
  • 1
  • 1
Prof. Falken
  • 24,226
  • 19
  • 100
  • 173
15

Dealing with passwords in repositories would be handled different ways depending on what your exact problem is.

1. Don't do it.

And ways to avoid doing are covered in some replies - .gitignore, config.example, etc

or 2. Make repository accessible only to authorized people

I.e. people that are allowed to know the password. chmod and user groups comes to mind; also problems like should Github or AWS employees be allowed to see things if you host your repositories or servers externally?

or 3. Encrypt the sensitive data (purpose of this reply)

If you want to store your config files containing sensitive information (like passwords) in a public location then it needs to be encrypted. The files could be decrypted when recovered from the repository, or even used straight from their encrypted form.

An example javascript solution to using encrypted configuration data is shown below.

const fs = require('fs');
const NodeRSA = require('node-rsa');

let privatekey = new NodeRSA();
privatekey.importKey(fs.readFileSync('private.key', 'utf8'));
const config = privatekey.decrypt(fs.readFileSync('config.RSA', 'utf8'), 'json');

console.log('decrypted: ', config);

Decrypted Config File

So you can recover an encrypted config file writing just a few lines of Javascript.

Note that putting a file config.RSA into a git repository would effectively make it a binary file and so it would lose many of the benefits of something like Git, e.g. ability to cherry pick changes to it.

The solution to that might be to encrypt key value pairs or perhaps just values. You could encrypt all values, for example if you have a separate file for sensitive information, or encrypt just the sensitive values if you have all values in one file. (see below)

My example above is a bit useless to anyone wanting to do a test with it, or as an example to start from as it assumes the existence of some RSA keys and an encrypted config file config.RSA.

So here's some extra lines of code added to create RSA keys and a config file to play with.

const fs = require('fs');
const NodeRSA = require('node-rsa');

/////////////////////////////
// Generate some keys for testing
/////////////////////////////

const examplekey = new NodeRSA({b: 2048});

fs.writeFileSync('private.key', examplekey.exportKey('pkcs8-private'));
fs.writeFileSync('public.key', examplekey.exportKey('pkcs8-public'));

/////////////////////////////
// Do this on the Machine creating the config file
/////////////////////////////

const configToStore = {Goodbye: 'Cruel world'};

let publickey = new NodeRSA();
publickey.importKey(fs.readFileSync('public.key', 'utf8'));

fs.writeFileSync('config.RSA', publickey.encrypt(configToStore, 'base64'), 'utf8');

/////////////////////////////
// Do this on the Machine consuming the config file
/////////////////////////////

let privatekey = new NodeRSA();
privatekey.importKey(fs.readFileSync('private.key', 'utf8'));

const config = privatekey.decrypt(fs.readFileSync('config.RSA', 'utf8'), 'json');
console.log('decrypted: ', config);

Encrypting values only

fs.writeFileSync('config.RSA', JSON.stringify(config,null,2), 'utf8');

enter image description here

You can decrypt a config file with encrypted values using something like this.

const savedconfig = JSON.parse(fs.readFileSync('config.RSA', 'utf8'));
let config = {...savedconfig};
Object.keys(savedconfig).forEach(key => {
    config[key] = privatekey.decrypt(savedconfig[key], 'utf8');
});

With each configuration item on a separate line (e.g. Hello and Goodbye above), Git will recognize better what's going on in a file and will store changes to items of information as differences rather than complete files. Git will also be able to manage merges and cherry picks etc better.

However the more you want to version control changes to sensitive information, the more you are moving towards a SAFE REPOSITORY solution (2) and away from an ENCRYPTED INFO (3) solution.

Ivan
  • 4,383
  • 36
  • 27
4

One can use HashiCorp Vault which secures, stores, and controls access to tokens, passwords, certificates, API keys, etc.

Ansible specifically has a "Vault" feature (unrelated to the HashiCorp product) for encrypting secrets at rest, as well.

El Ruso
  • 14,745
  • 4
  • 31
  • 54
  • I find Ansible Vault overly complex certainly compared to just creating an example config file. – icc97 May 07 '17 at 11:11
  • @icc97 Yes it's sad true. But we need mention this possibility. On my opinion, for tasks more complex then storing few passwords for single-user environment is better to use specialized solutions from the beginning. – El Ruso May 08 '17 at 11:42
  • 3
    To help future readers: Vault and Ansible Vault are quite different unrelated projects with similar names – bltavares Oct 14 '18 at 14:40
3

Here is a technique I use:

I create a folder in my home folder called: .config

In that folder I place the config files for any number of things that I want to externalize passwords and keys.

I typically use reverse domain name syntax such as:

com.example.databaseconfig

Then in the bash script I do this:

#!/bin/bash
source $HOME/.config/com.example.databaseconfig ||exit 1

The || exit 1 causes the script to exit if it is not able to load the config file.

I used that technique for bash, python, and ant scripts.

I am pretty paranoid and don't think that a .gitignore file is sufficiently robust to prevent an inadvertent check-in. Plus, there is nothing monitoring it, so if a check-in did happen no one would find out to deal with it.

If a particular application requires more than one file I create subfolder rather than a single file.

Be Kind To New Users
  • 9,672
  • 13
  • 78
  • 125
1

If you're using ruby on rails, the Figaro gem is very good, easy, and reliable. It has a low headache factor with the production environment too.

icc97
  • 11,395
  • 8
  • 76
  • 90
ahnbizcad
  • 10,491
  • 9
  • 59
  • 85
  • 4
    Can you give some details about what that gem does? That way it could (potentially) be considered as a 'practice' applicable across many languages. – mattumotu Sep 20 '16 at 11:01
  • https://medium.com/@MinimalGhost/the-figaro-gem-an-easier-way-to-securely-configure-rails-applications-c6f963b7e993 has an overview, it basically seems to manage pulling in stuff from a configuration file – tripleee Jan 11 '19 at 05:07
1

Trust but verify.

In .gitignore this would exclude a "secure" directory from the repo:

secure/

But I share @Michael Potter's paranoia. So to verify .gitignore, here's a Python unit test that would raise a klaxon if this "secure" directory ever gets checked in. And to check the check, a legitimate directory is tested too:

def test_github_not_getting_credentials(self):
    safety_url = 'https://github.com/BobStein/fliki/tree/master/static'
    danger_url = 'https://github.com/BobStein/fliki/tree/master/secure'

    self.assertEqual(200, urllib.request.urlopen(safety_url).status)

    with self.assertRaises(urllib.error.HTTPError):
        urllib.request.urlopen(danger_url)
Bob Stein
  • 16,271
  • 10
  • 88
  • 101
0

Is there any possibility to tell github to track the file under a different name? Example: Locally, I have a file passwords.config with real passwords, and sample-passwords.config with stubs. However, in public repo, I'd like to have only passwords.config with content from sample-passwords.config and real passwords.config ignored. I know .gitignore, which can hide my passwords.config, but I don't know is there any solution to rename sample-passwords.config while commiting to remote public repo. Of course, I'd like to avoid situation, when my local repo tracks renamed file as if something changed in git status.

michael112
  • 11
  • 4
  • If you have a new question, please ask it by clicking the [Ask Question](https://stackoverflow.com/questions/ask) button. Include a link to this question if it helps provide context. - [From Review](/review/late-answers/32709515) – user16217248 Sep 19 '22 at 23:48