Python Encryption
Public-key cryptography made easy - a DevOps perspective on securing deployment parameters
Photo by Towfiqu barbhuiya on Unsplash. Read or listen to this article on Medium.
A DevOps perspective on securing deployment parameters
Code builds and deployment automation has paved the way for many great things. Long gone are the days of monthly, manual code releases; modern CI/CD pipelines now have the ability to release every code commit that passes automated testing in the development and staging environments to production.
In order to make this magic happen, DevOps teams must build code that is environment agnostic. The exact same code must be deployed in DEV, TEST, STAGE, and then in PROD, passing the proper parameters at deployment time to make the solution behave appropriately. Microservices architecture and the decoupling of independent subcomponents that have to discover each other after being deployed to work together make this a very challenging task. Keeping track of these deployment parameters, the “glue” that will make all these naive components work together in any given environment, has become a new challenge in itself.
While it is common knowledge, It probably can’t hurt to repeat it one more time: you should never, under any circumstances, embed credentials of any kind, including API keys, in your code or a container image. Credentials uploaded to a code repository or image registry should be considered to have been compromised and changed immediately.
But, you may ask: if you can’t keep track of these precious deployment parameters in a source code repository, what can you do? Keeping these critical values in a version-controlled repository is a good idea, but as long as they are encrypted. Public/private key cryptography provides the best solution because the encrypted secret cannot be decrypted without the private key, so they can safely be uploaded to a private GitHub repository as long as the decryption key is not.
Good old configuration files or “.INI” files are surprisingly well suited to store deployment parameters because they allow for storing the same key name with different values in multiple sections representing the various environments. For Instance:
[DEFAULT]
db_user = dbuser
[DEV]
db_pwd = devpassword
[PROD]
db_pwd = prodpassword
The value of my database username db_user will be the same for all sections (DEFAULT) but the value for db_pwd can be different in DEV than in PROD.
The rsa_crypto Python library and command-line utility allow you to combine the flexibility of storing values in configuration files with the security of private/public key encryption. Self-contained binary files for Windows, Mac, and Linux are also available as a convenience.
The first step is to generate new keys:
rsa_crypto create
Enter key password:
Re-enter key password:
Creating key...
Created password-protected private/public keys file /Users/me/rsa_key.bin
Use the "extract" keyword to create public and private key files.
By default, the tool creates a new 4096-bit RSA key pair (secret) and saves it into a single file, protected by a password meeting the PKCS#8 standard and using the scrypt key derivation function to thwart brute-force dictionary attacks. The key is safe with a good password, but it should still never be stored in a code repository.
Having yet another password doesn’t lend itself well to deployment automation. The tool also provides a way to extract that password-protect file into separate public and private key files that don’t require a password.
rsa_crypto extract
Using key: /Users/me/rsa_key.bin
Opening encrypted key.
Enter key password:
Created private key file /Users/me/rsa_private.pem (File can decrypt data and is not password-protected, keep it safe!)
Created public key file /Users/me/rsa_public.pem (distribute this one to anyone who needs to encrypt data, it cannot be used for decryption!)
The public key file can safely be distributed to anyone who needs to be able to encrypt values, it cannot be used for decryption. This is a great option as credentials are usually managed by a separate group in most organizations.
Conversely, the private key must be guarded with extreme caution as it can be used to decrypt any values without any password. The tool will look for these key files in the current directory (the one you are executing the command from), the directory where the script is located, as well as in the user’s home directory. It will also look for the key files as environment variables, which might be a better option in many situations.
export rsa_private=$(cat /Users/me/rsa_private.pem)
The private key file can then safely be deleted, it can always be re-generated from the password-encrypted key file:
rsa_crypto clear
Using key: /Users/me/rsa_private.pem
Private key deleted: /Users/me/rsa_private.pem
The tool can also be used with the -k option to specify a different key name prefix, allowing users to have multiple keys for different environments or purposes or credentials managed by different teams:
rsa_crypto create -k dev
Enter key password:
Re-enter key password:
Creating key...
Created password-protected private/public keys file /Users/me/dev_key.bin
Use the "extract" keyword to create public and private key files.
This get and set parameters of the script can also be used to store and retrieve encrypted values in a configuration file. The configuration file must already exist:
touch ~/.rsa_values.conf
The structure of the file is fairly simple, it contains sections delimited by brackets [MY_SECTION]. Note that the section name can be case-sensitive. The [DEFAULT] section is used by default.
Each section will then contain several "options" and values (key/value pairs). Only the value will be encrypted, the option name will remain in cleartext.
This structure is very well suited for keeping track of values that need to be different by environment, such as to keep track of database passwords in a DEV, TEST, and PRODUCTION environments.
To save a value of an option named database_password in the DEV section using the default encryption key:
rsa_crypto set -s DEV -o database_password
Using key: /Users/me/Documents/rsa_public.pem
Enter value:
DEV my_password
set
Updated /Users/me/.rsa_values.conf
If you don't specify a value, the script will prompt you for the value so that it is not visible in the command line history. Optionally, if you prefer, you can also specify the value as a command-line parameter by using the -v parameter.
rsa_crypto set -s DEV -o database_password -v my_secret_password
Using key: /Users/me/Documents/rsa_public.pem
DEV my_secret_password
set
Updated /Users/me/.rsa_values.conf
To decrypt the value:
rsa_crypto get -s DEV -o database_password
Using key: /Users/me/rsa_private.pem
get
Reading from /Users/me/.rsa_values.conf
DEV database_password my_secret_password
my_secret_password
Note that in the above example the rsa_private.pem private key allowed us to decrypt the data without prompting for a password. Once again, be extremely careful because anyone with that file can decrypt the data. If the private key is not present, the script would then prompt for a password to open the protected key-pair file rsa_key.bin.
rsa_crypto clear
Using key: /Users/me/rsa_private.pem
Private key deleted: /Users/me/rsa_private.pem
rsa_crypto get -s DEV -o database_password
Using key: /Users/me/rsa_key.bin
Opening encrypted key.
Enter key password:
get
Reading from /Users/me/.rsa_values.conf
DEV database_password my_secret_password
my_secret_password
Now let's set and get the PROD password:
rsa_crypto set -s PROD -o database_password -v super-secret
Using key: /Users/me/Documents/workspaces/rsa_public.pem
PROD super-secret
set
Updated /Users/me/.rsa_values.conf
rsa_crypto get -s PROD -o database_password
Using key: /Users/me/rsa_key.bin
Opening encrypted key.
Enter key password:
get
Reading from /Users/me/.rsa_values.conf
PROD database_password super-secret
super-secret
The content of the .rsa_values.conf configuration file will look something like this:
cat ~/.rsa_values.conf
[DEFAULT]
test = VvXy8NcqL94lBDYS56EnQm03vq9Kvg17VNU1Tu0T1j_hn-OxOTmXv_NoQHWcvWZuJto4awbq1Y_yvi_MKYE5uXOv15iVBZAuHO_xlUmujrL9pdUfxnBe8SAzH7sy2GTx42tLkb2MB9E-49GmKYqbx9dBzTNRDJj8D8LDZku6CJeSDPGy9l6UzG2vl53V3GY97an4Gb4UJ7XYEeEqMsZFRqaxgdWd_IMA_L5FtAlEaU3j4SYvqq-9QDxuab0vv8ZgzP6KuR05jXcLTrEZdrfmy_zRHuLiThu5_-ofsUNoXGNByGWAdBuuMONQj1s2QiI7qsqbFw66RBh0zUMzF2XFtSHY4AklF6uiDkieAhjBldbIjGEhrt3eMVBRBtRIDQ-LlYMcP8HnMPjBe-FBn8rYNscDrOWJIcqyTXwspfnyI6iSjEfTNQilMG6V17NXaJNipbJpoFm0aiKokZXawgav9yWWXAjRitMBtCGbqeEXVw704uY2s2K0m8XQhBLuwtCSS2Q616e4CgBxhEZOHNC0FPDpLWgvUwSFJ9vLphYSEQXeak3GRPDUfzxnjIUi8uLtifJGVEUycyRf8PV_Zf-0i8SAxFbB9OYawAKBpwwTGt4B8Pir351AcID4-s-9TG7LwrOxDvDiGxTH6Kho0SnMubXdpfDESFlwb61KzD3Yap0=
[dev]
test = Nfe5yc-FegHyEODNAX-ndIs8kf6Tjn1V3fjy7PSZ4J4NuOq7bOHCfooVb2mK4KS7Q0U8MSIIo_JmAZVqY__CvWR4zaczr4Es1d64YNX8CyVKvfIK7sPVSfv-v54-edcdtKHEj6dJRo1Pdbvc8ESgMxEUK9J64lS0FloZoXJGE1NVdkgf19IX4ZlHm2XjhyQ1pgfEg0cJPqDukM6cHfXwqexVjWGGF9-eYw6jeUFm59O3_D5Z44ull9HCdEtG85Hv99R4lpQJWYRLF1b7-HPPnyAoXwnCuR-mKi7KdiZw4q_bTruuKYltTKIYbMxXzW5m-kjNUcHSaYdOxSGVbOYdhMiBOnvTRZS5KVVpJCfS5fkQG2HLRlkRhW2PWwaG8ieP-bXvK7jvImKqRbGryPNHtdNBSv3yIXhQKqHfs6JxVXg_pBJywv7q-oymxw4hk3jf11CyZaDmAS-XQQU0KxnnkJ7Cm1h1KYVSfFMZWw9teEd3fRsiBktqPaIOnw0U5liK5WG52uBN_hdoAM73aOpsuDLPy1fYEL5Wuw3nuSZt48Gf7q6AFWS8WRCwIXa0oJJjudObfkeCw7jA0-ufIEHa4wBk2X93D2Mjr-nLOSayLM71UOdT94B4-2oVV-44Djuo-iY3iKQQkllFvuQmZr2Ozs68knOA70qbIYewVBcO4fY=
[DEV]
database_password = YrUidTfrK3w-y2KneYUSWugR0IVjmPvBpjqlBZ_5Oic5td0rO2aeWOuyeiujSe7G9YoBnKLxtIkGGzeOs0EQ6kEJCmLCq2MVNOECj4__majFlmp3De_ypebwq0SbRn4UGRzrGSV6oO93jnoHpH8Rky2L5yeJMAqjwsMAgOQBrfdpdcgqLRATUumoaRkvfMafdTYKjhJj5m6EUB9-la8YPK9kxMKu2-l9GlEnqug5S91xAOXb2laX2b3T5KCeQxxAZ2L9KtUG0NJulmaEtUFoRNWSVyBLsAvDdRkXoYXuHLSIhD-8x1RwxOjPyJ-t4cc7uNJF8ZfCsLHuesQj4jauXSmiVe5wI718rC4PS9kYH5Z5IKgl19d7jRaUI2jx-lPs4Rues1SZpHQKpH8GG3Id7z5RAtJ3OdlCrx7b3uOPL5GinlG4QdyiF7ROIsShAZPTdrQybRDDCU8ju-6R0RkMO8Qds7VNRANSwXYQAn2D62IQx3cAr40TMOHy697QgklomfT-k52GCQsfyJqFASYJ4DDhnjZB8uXzx1eHLhmVTikVt1yYXjoPOD0HPX0uYTd8L-TNwU9OnU36Q8m9dTez9rUHrX2xKapkYf0SIRPeSiLRbey_h9tnynoQYXtKsca-jxdBUDZvw2t_KbU__z_zuRWv65CrJpkcBGMvYkqeXj4=
[PROD]
database_password = g1G_zRij0D6nerTHn1bJ7fr_HiWLFv4Qi-a2Y9QfjeWy5lRCO4L_9ENuiG4hbqyJj2NtbLJ7-NCpN3wd_i8djTGcY2yTcgsFZQEceco-n1bK9yX3Fq8Go1r2D82ccdlNSASeFwA5XDEiBbjpDmsgeawYQNJJUC84oAdv52cFIqTVHecYXGp8cr93eUI3Cpj8Q67zoMH3bJNXkF1KIcFCdrlFfwOQA3RsVuoYdw_JXztVAGaUBWfnBKWjDuTcM9WJyB2-Zfw8Pv0W4Dd2YkJvjvMcCJakxoVEz1OGFBlLyBwleTXBQVKLxGBkK7Xfr7s0FArM6yBAe5BFOfd-vfNeoR38X-Rc00ojUTpbsforLKTWuvHGx1tXi8F5b7TAhNKsICptmBn52ZZmYQjCyIktgL_v0Trngk0Y3uYiaAZpFJyvNHcebjSJ445c_knbcFdn158tud9WX8dHOcXcx5LXFrfh-hU1Vc0U6MUVXgja7T_-O5N59Hob4DIyb4sHF8x-FGFiBvZK-dvIY_FDt82Z0Bk-AETPCykdkmtTx4eg-_o2eEb9ewKHlgLpnBjUs1FajMcfGYiQnaRQNfubBRHY34nmdJtfqQVqVIcQkD0N19qI-8Mg0RSwLaxKSDPlK06JdZew1Nrli-l7U5wYZV4zLdIzXG4tqy6qIb_8Y5yMXm0=
The tool can also be used to encrypt entire files, such as a Kubernetes Parameter Store .yaml configuration file, by using the -f option.