Safeguarding Secrets in Python

Python is a terrific language for quick development. Being able to throw together a quick Python script to interface with data is, in my opinion, going to be on par with solid Excel skills.

One of my most common use cases for Python is interacting with some sort of API. Usually it starts with something like this:

1
2
3
4
5
6
7
8
9
import requests

headers = {
  'api-key' = 'xxxxxxxxxxxxxxxxxxxxxxxxxxxx'
}

response = requests.get('https://api.target.com/endpoint, headers=headers')
...
print('[+] Profit!')

It works great! You pull it out ever few months to reuse it, tweak it, and then you think "Hmm... I should share my brilliance with the world!", so you throw it up in a GitHub repo and forget about it.

Oops

This seemed to be standard operating procedure for a number of people over at Deloitte, which caused much hullabaloo and egg on faces. The transition from personal hacky tool to public-facing hacky tool is so sneaky sometimes that you don't consider the secrets you're exposing.

There's a better way.

...The Better Way

This is neither groundbreaking nor is it complicated. I'm recording this not just for my own reference but because it's still pretty common. Even when tool authors haven't committed secrets, some modification of the source to input credentials is required. Mix in a PR and whammo!--now you've got eggface. Nobody likes eggface.

Enter configparser, a Python module for parsing configs. Obviously.

Config File

First, create appName.cfg in the same directory as your script. This is where we're going to define all of our configuration-related variables. The syntax is pretty straightforward. You've got sections, which organize the content, and then key/value pairs.

1
2
[app1]
api_key =

Commit this and upload it to version control. Next, we're going to make sure no changes to this file get recorded:

1
git update-index --assume-unchanged appName.cfg

Now you're free to add any configuration data you may need. If you'd like to commit some changes to version control, just use the same command with the --no-assume-unchanged flag.

Next we're going to make a few changes to our script.

Script Modifications

Continuing with our earlier example, make the following changes:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
import configparser
import requests

config = configparser.ConfigParser()
config.read('appName.cfg')

app1_api_key = config['app1']['api_key']

headers = {
  'api_key' = app1_api_key
}

response = requests.get('https://api.target.com/endpoint, headers=headers')
...
print('[+] Profit without Pwnage!!')

If you get in the habit of starting this way, you're much less likely to be in for a panicked reading of Removing sensitive data from a repository. Nobody likes panicked reading.

<<
>>