Development tips

I’m now versioning my app secrets in Git, here is why you should do the same.

It’s supposed to be a bad practice, but you might need it when you’re having a lot of config and managing secrets get complicated.

Andréas Hanss
Mar 23 · 7 min read
Article featured image
Article featured image

I know what you might think: “this is a bad practice…” and “probably the worst idea in the world”.

And I will concede that I totally agree with you.

However, to give some context, let’s start with a quick story about why I have started doing such a thing, and most importantly in which manner.

Why I switched from environments secrets to a versioned JSON configuration with secrets?

While developing, my friend was using the company bank card to power some AWS EC2 sandboxes. For those who don’t know about that, it’s a kind of VPS machine that can be populated on demand and for which you will pay a certain amount based on usage each hour as long as you keep it powered on. It’s really cool in the way that you can adjust your network infrastructure depending on the server load.

But one Friday, before the weekend after a long week, my friend committed inadvertently on a public GitHub repository the private API key that allows anyone who owns it to control the AWS account and populate EC2 or other services. It’s kind of a god mode.

Back on Monday, the company received several emails, mentioning that the billing as ignited and with the maximum restrictions they reached 15 000€ in just 2 days. That’s not so cool for a small commit of fail like this… The thing is that it’s happening to many people every year.

You have to know that those keys have a specific pattern and there is a bunch of bots that are crawling the web searching for that pattern.

What happened is that someone had populated several of the biggest CPU optimized VM in order to crypto mine some Bitcoin (BTC).

The moral of this story is: Never commit any sensitive data anywhere to prevent any accidental leak, even on the small repositories you might think that they will never become public.

For that, most cloud provider services allow managing secrets in a secure way, that will be injected on runtime directly in cloud functions or the VM through Environment variables. This is a really cool thing and starting that day, I was applying that anywhere, even on my own private repository, just in case…

…Until last week, where I wanted to add to my already not so small collection of secret a serialized version of a Firebase Service account, which is a JSON file with amongst others a full RSA string key.

There are some limitations on some provider about secrets management

And on Zeit Now, which is kind of a wrapper around AWS, there is an upstream limit in terms of size of secrets…

Env vars are limited to 4kb maximum on zeit now
Env vars are limited to 4kb maximum on zeit now

This means for those who are base64'ing complexes' secrets such as Firebase/ Google credentials and service account, you will probably exceed the limit. This happened to me and to many others.

All of this leads me to another technique: Encryption.

From 99 secrets to 1 secret with a versioned encrypted JSON configuration

The only thing that remains in the cloud secrets storage is the encryption key and —potentially— the initiation vector (IV) that is used to cipher this configuration object.

This practice as some benefits:

  • All the configuration stands in one place.
  • You can easily store JSON structures and namespace your config parameters properly.
  • It is safe.
  • It’s IDE and developer-friendly, with statics typings and auto-completion.

Generally speaking, if you’re not a rookie and apply the basics of security, there isn’t many chances that someone successfully breaks through your security and steals your code repository without having you get notified.

But even if it happens here, using the following strategy you’ll be safe because, like your database passwords, your whole configuration is encrypted.

That way you have far enough time to invalidate and generate new access keys for any of your services and secure your repository again.

But wait… What is symmetric encryption?

A quick reminder about symmetric encryption

Symmetric encryption diagram
Symmetric encryption diagram
Symmetric Encryption in a nutshell — Wikipedia

Symmetric encryption is the action of encoding a text (here, a stringified JSON) using a randomly generated key. Using the same key, the program is able to decrypt this chunk of data later and restore it.

In addition to the key, each time we encrypt the stringified configuration object, we use a random chunk of data called an Initialization Vector (IV). This prevents having the same encryption results when ciphering several times the same document. Which increases security.

All of this is done using a technique called Cipher Block Chaining (CBC) if you want to read more about it feel free to Google about it or Wikipedia.

Good news, it’s pretty easy to use a CBC cipher in NodeJS. It’s supported out-of-the-box using the crypto module on top OpenSSL.

Using Cipher IV CBC to encrypt a JSON configuration for your cloud projet

  1. First: encryption. We will encrypt on the developer computer the desired JSON configuration object. We could have done this on some websites such as this one but I respectfully disagree with that practice. You should never take any risks and expose your configuration on an external website and/or letting someone else generate your encryption key. Otherwise, it’s useless to encrypt something. Once we have the encrypted string, we just add it to our repository and commit it with our source code.
  2. Second: decryption. We will decrypt the configuration when the code is bootstrapping using our encryption key injected by the secrets provider in our environment variables and make it available in our application.

Ready? Let’s code!

Here is the code for the of the demo we will study in this article, feel free to open it on another screen and keep reading the explanation. All encrypted values provided in the example are stubs without meaning for security purposes. It’s up to you to reproduce and specify your encrypted data that match your project instead of the given stubs.

Code samples

JSON configuration encryption in NodeJS

To cipher the configuration, I made a quick shell script (you could improve it or I may someday) that take only 1 argument: the encryption key that will be used to cipher the config.

The following example is using aes-256-cbc algorithm, which requires a 256 bits encryption key (i.e 32 characters) and a 16 bytes IV.

  • To get an encryption key, you need a true random number. Personally I generally use this website which is based on atmospheric noise. And ask for 4 strings of 8 alphanumeric-upper-lower chars that you will concatenate in the order you want to gives a 32 chars true random string.
  • The resulting encrypted version of the JSON configuration will be a HEX string.

The code is self-explained, so just use your encryption key to encrypt your config.json file usingnode encrypt.js <encryption_key>.and paste the encrypted content in the placeholder defined in config.ts snippet.

JSON configuration decryption in NodeJS

Here I’m using Typescript, which is a well-known Javascript superset that brings static typings and interfaces to Javascript files. That way I can type my configuration to leverage autocomplete in my IDE.

Nothing complicated here: at my application startup, the config.ts file will be required and the code will be interpreted decrypting our configuration.

The encryption key that has been used to encrypt the data above is injected in the application using environments variable, thanks to the secrets functionality of my cloud provider. The encrypted JSON configuration stands in the code as plain text.

Depending on the NODE_ENV variable, the production or the staging config is lazy-loaded and exposed as APP_CONFIG constant, which is Object.freeze to prevent accidental mutation during the program execution. If you use a bad encryption key, you won’t be able to decrypt the configuration, nor will potential hackers.

Keep the encryption key safe and avoid sharing it as it gives access to your whole configuration. The IV, on the other hand, doesn’t need to be a secret and is publicly shared in the encrypted string.

Feel free to adapt the above example and it’s behavior to your needs.

And there we are, we just replaced good old 99 environments variables by two: NODE_ENV and ENCRYPT_KEY, all of this in a secure way.

Bonus: Inline an RSA / PEM key to get it embedded in a JSON file

To put an RSA .pem in a JSON string, you need to put it on one line and transform the hidden linefeeds, in escaped linefeeds character \n.

To achieve that behavior, you might use this command:

awk 'NF {sub(/\r/, ""); printf "%s\\n",$0;}' <your_key_file.pem>

And for those who are lazy, here is the version to have it in the clipboard directly using pbcopy on Mac/Linux. Based that rsa_priv.pem is our file name and in the current directory.

awk 'NF {sub(/\r/, ""); printf "%s\\n",$0;}' rsa_priv.pem | pbcopy

JavaScript in Plain English

Learn the web's most important programming language.

Andréas Hanss

Written by

👨🏻‍💻Javascript’ Tech Lead 🇫🇷 — react | react-native | modern js |🔥 Firebase — — Passionated about learning.

JavaScript in Plain English

Learn the web's most important programming language.

More From Medium

More from JavaScript in Plain English

More from JavaScript in Plain English

More from JavaScript in Plain English

More from JavaScript in Plain English

32 funny Code Comments that people actually wrote


Welcome to a place where words matter. On Medium, smart voices and original ideas take center stage - with no ads in sight. Watch
Follow all the topics you care about, and we’ll deliver the best stories for you to your homepage and inbox. Explore
Get unlimited access to the best stories on Medium — and support writers while you’re at it. Just $5/month. Upgrade