Search code examples
pythonconfigurationpython-cryptographyaumbry

Support both encrypted and non encrypted configuration with aumbry


We have a python application that loads a config.yml with aumbry. For production purpose we need to encrypt this configuration with fernet, which aumbry can load seamlessly.

We want to be able to load both unencrypted and encrypted in a transparent way, for example load unencrypted if found, and if not (production) load encrypted. So far we have implemented this.

Encryption

import cryptography.Fernet as fn
from os.path import split, splitext

def _encrypt_file(path, key):
    with open(path, 'rb') as infile:
        file_data = infile.read()
        nc_data= fn(key).encrypt(file_data)
        infile.close()

        base_path, filename = split(path)
        name, _ = splitext(filename)
        nc_name = "{}.{}".format(name, 'nc')
        with open(join(base_path, nc_name), 'wb') as outfile:
            outfile.write(nc_data)
            outfile.close()

Aumbry configuration

from aumbry.errors import LoadError

def _get_configuration():
    return aumbry.load(
        aumbry.FILE,
        AppConfig,
        options={
            'CONFIG_FILE_PATH': "config.yml"            
        }
    )

def _get_encrypted_configuration():
    return aumbry.load(
        aumbry.FERNET,
        AppConfig,
        options={
            'CONFIG_FILE_PATH': "config.nc",
            'CONFIG_FILE_FERNET_KEY': 'bZhF6nN4A6fhVBPtru2dG1_6d7i0d_B2FxmsybjtE-g='
        }
    )
def load_config():
    """General method to load configuration"""
    try:
        return _get_configuration()
    except LoadError:
        try: 
            return _get_encrypted_configuration()
        except LoadError:
            return None
  • Is there a more elegant way to achieve this behavior?

Solution

  • Depending on the framework on which your python application is built, there is usually some kind of global "mode" value that can be used.

    Flask, for example, uses FLASK_ENV environment variable that can be set to either development or production. Within the app, you can use

    app.config['DEBUG']  # True if FLASK_ENV is "development"
    

    to differentiate between the 2 modes.

    In your case, the ambury loader can be refactored to do:

    config_loader_cls = EncryptedConfigLoader if environment=='production' else PlainConfigLoader
    

    I would go even further and have EncryptedConfigLoader fail if the configuration is not encrypted for additional security.