Buena práctica de Django: sacar valores de configuración fuera del código

Cuando generamos un nuevo proyecto en Django, normalmente partimos ya de una situación que en sí misma constituye un antipatrón: valores de configuración como el SECRET_KEY, las AWS keys, las API keys y otros valores específicos del servidor aparecer "hardcodeados" en el fichero de configuración settings.py.

Ésto puede generar problemas de seguridad y un código más complicado de mantener. Sin contar con el hecho de que si utilizamos un control de versiones en la nube estamos subiendo código sensibles a internet, que después pueden ser utiliazados por un atacante.

La respuesta a ésto está en utilizar variables de entorno en nuestro servidor para almacenar estos valores.

Para ello, si trabajamos en Linux necesitamos conocer bien cómo funcionan las variables de entorno en bash. Hay que matizar que ésto no funcionará con Apache, puesto que este servidor no permite utilizar variables de entorno del sistema operativo. Sin embargo en este caso podéis investigar la documentación de Apache ya que éste servidor permite gestionar sus propias variables de entorno. No obstante, al final también daré una solución alternativa que funciona perfectamente en Apache.

Dar de alta las variables localmente

En Linux se utiliza bash. Dependiendo de la distribución las variables de entorno se pueden almacenar en .bashrc, .bash_profile, o .profile. Si trabajas en Mac ésto debería ser muy semejante. En Windows también es posible utilizar este patrón pero el funcionamieno de las variales de entorno es muy diferente y no entraré aquí al detalle.

export SOME_SECRET_KEY=1e2-c323d-15-sdsd
export AUDREY_FREEZER_KEY=fdsfsd-f-d-f-d-fd-fdfd-fd232dd

Evidentemente, desplegar en producción a través de un (PaaS) puede ser ago diferente. Habrá que investigar la documetación de cada servicio concreto, pero lo normal es que sea posible.

Por ejemplo, en heroku sería algo así:

heroku config:set SOME_SECRET_KEY=1e2-c323d-15-sdsd

Implementar un manejador de excepciones en el caso de que la variable de entorno no se encuentre

Ésto es muy recomendable, ya que en caso de que ocurra el mensaje de error por defecto no nos va a dar demasiadas pistas de lo que ocurre y nos podemos volver locos.

Para ello vamos a definir una función que capture el valor de las variables de entorno y lance una excepción en caso de error:

# settings/base.py
import os

from django.core.exceptions import ImproperlyConfigured

def get_env_variable(var_name):
    """aquí se captura la variable o se genera excepción"""
    try:
        return os.environ[var_name]
    except KeyError:
        error_msg = 'Error capturando la variable {}'.format(var_name)
        raise ImproperlyConfigured(error_msg)

La llamada a ésta función se realiza así desde nuestro fichero de configuración:

SOME_SECRET_KEY = get_env_variable('SOME_SECRET_KEY')

Qué hacer cuando el servidor no nos permite hacer ésto

Un caso particular (aunque hay más) es cuando usamos Apache, que no nos permite acceder a variables de entorno del S.O. En este caso puede resolverse dejando los valores de las variables en un fichero JSON:

{
    "FILENAME": "secrets.json",
    "SECRET_KEY": "4324--42-rr424no",
    "DATABASES_HOST": "127.0.0.1",
    "PORT": "5432"
}

En este caso, la función para capturar los valores de las variables de entorno es muy parecida a la anterior:

# settings/base.py

import json
from django.core.exceptions import ImproperlyConfigured

with open('secrets.json') as f:
    secrets = json.loads(f.read())

def get_secret(setting, secrets=secrets):
    try:
        return secrets[setting]
    except KeyError:
        error_msg = 'Error capturando la variable {}'.format(var_name)

Y la llamada a esta función:

SECRET_KEY = get_secret('SECRET_KEY')