Cómo firmar archivos con Google Cloud Storage y Django-Storages

Un tema que puede parecer muy simple en concepto pero no tanto así en ejecución es el de proveer archivos de manera pública y a la vez segura, en este caso utilizando Google Cloud Storage (GCS).

En nuestro caso estamos utilizando:

  • Django==1.11.4
  • google-cloud-storage==1.10.0
  • django-storages==1.6.5

¿Cómo empezamos?

La librería django-storages nos permite trabajar con diferentes proveedores de almacenamiento de ficheros, así como FTP y SFTP. Entre los proveedores se encuentra Google Cloud Storage, que deberemos instalar con

pip install django-storages[google]

Con esto ya podríamos acceder a la plataforma de GCS, ahora nos falta añadirle la funcionalidad que nos permita servir ficheros con un grado

Bien, para empezar tenemos que sobreescribir la clase GoogleCloudStorage para poder añadirle la función signed_url, la cual nos va a servir la url ya firmada:

storages.py

from storages.backends.gcloud import GoogleCloudStorage


class SignedGoogleCloudStorage(GoogleCloudStorage):
    """ Generate signed url using google cloud integrated in django-storages"""
    def signed_url(self, name):
        # Preserve the trailing slash after normalizing the path.
        name = self._normalize_name(clean_name(name))
        blob = self._get_blob(self._encode_name(name))
        signed_url = blob.generate_signed_url(expiration=datetime.timedelta(3))
        return signed_url

expiration define el tiempo de vida para el objeto firmado.

A continuación creamos un FileField custom en el cual sobreescribimos la función url y hacemos que llame directamente a la función signed_url, creada anteriormente. Esto lo hacemos así para que afecte lo mínimo posible al código ya existente.

fields.py

class GCSFieldFile(FieldFile):
    """ Override property to provide signed_url"""
    @property
    def url(self):
        self._require_file()
        return self.storage.signed_url(self.name)


class GCSSignedFileField(FileField):
    attr_class = GCSFieldFile

Y finalmente utilizamos nuestro Filefield:

zip = GCSSignedFileField(upload_to='zip', max_length=100, blank=True, null=True)

Después de todo esto, es tan sencillo como escribir zip.url y obtenemos la url firmada.

Configuración de GCS

En este caso es prácticamente como comenta la documentación https://django-storages.readthedocs.io/en/latest/backends/gcloud.html: tenemos que definir las variables de entorno para introducir el nombre del bucket y el Id del proyecto.

settings.py

    GS_BUCKET_NAME = opts.get('GS_BUCKET_NAME', '')
    GS_PROJECT_ID = opts.get('GS_PROJECT_ID', '')

    GS_CREDENTIALS = service_account.Credentials.from_service_account_file(opts.get('GS_CREDENTIALS_PATH', '')

Ahora viene lo bueno. Las credenciales para GCS se almacenan en un archivo json, lo cual puede ser un problema para subirlo al entorno de producción, por lo que podemos utilizar el siguiente recurso: generamos un string en base64 del fichero json y el resultado lo ponemos en la variable GS_CREDENTIALS_PATH. Para que el json exista en el entorno hacemos lo siguiente:

entrypoint.sh

echo ${GCS_CREDENTIALS} | sed 's/ //g' | base64 -d > ${GS_CREDENTIALS_PATH}

lo cual creará el json donde indiquemos en GS_CREDENTIALS_PATH y voilà un problema menos.

Acontinuación podemos ver un ejemplo de configuración del Dockerfile en el cual se asignan valores para GS_CREDENTIALS_PATH

dockerfile:

ENV GS_CREDENTIALS_PATH=/tmp/key.json
blog comments powered by Disqus