Almacenamiento de objetos con MinIO

En ocasiones almacenar objetos como imágenes en un sitio centralizado. Si no quieres gastarte dinero en S3, tienes MinIO, el almacenamiento self-hosted
Almacenamiento de objetos con MinIO

En ocasiones cuando estamos trabajando en algún software (generalmente se da mucho en los microservicios) necesitamos almacenar objetos (imágenes, documentos, o ficheros en general) en algún sitio. En estos casos, siempre puedes optar por un almacenamiento en la nube con S3 de Amazon o alguno similar pero, ¿y si no quiero gastar dinero? En ese caso podías reinventar la rueda o utilizar el almacenamiento en tu propia nube con MinIO

Bueno, esto es una verdad a medias. Tu almacenamiento en la nube mientras tú mismo mantengas la infraestructura. Pero si solo necesitas guardar información no crítica, hagas copias de seguridad y no te importe no tener un 100% de SLA, Minio puede echarte una mano.

La primera vez que me encontré con MinIO fue mientras desarrollaba un microservicio en docker. Al generarse las imágenes por los propios usuarios no las podía tener repositadas y al estar basado en docker (swarm, concretamente) no me valía montar un volumen compartido entre el microservicio y el proxy. Así que buscando soluciones low-cost encontré MinIO, un servidor de alojamiento compatible con S3.

Instalas la aplicación (o construyes un contenedor de docker, aunque si los datos son persistentes no deberías usar docker. Regla que también aplica a bases de datos), la configuras y ya estás sirviendo ficheros. Es genial para que tus programas puedan usarlo "como" un ftp pero mucho más cómodo: subir ficheros, bajártelos, hacerlos públicos (como imágenes de usuario, etc).

Aviso: la configuración no es que digamos muy sencilla e intuitiva. Se basa en S3, por lo tanto la gestión de los usuarios, buckets (o carpetas) y políticas de acceso son un poco complicadas, pero vamos a intentar explicarlas en este post.

Instalación de MinIO

Instala MinIO en tu sistema o en un contenedor siguiendo las instrucciones oficiales. No son especialmente complejas: basta con que sepas qué sistema operativo tienes.  Una vez hecho, ya tendrás el servidor de MinIO corriendo.

Pero para interactuar con la plataforma necesitamos un cliente. Para crear buckets (que asi se llaman las carpetas en MinIO y Amazon S3) y para subir algunos ficheros a mano, tiene una simple interfaz gráfica que es muy útil para cosas rápidas. Pero si quieres personalizar usuarios y cositas más avanzadas, necesitas el cliente de MinIO: mc. Instalalo siguiendo estas instrucciones, también oficiales.

Hecho esto, tiene que quedarte claro. En el primer párrafo instalamos el servidor y en el segundo paso el cliente: la aplicación que nos permite interactuar con el servidor sin navegador, tan solo desde consola.

Preparando nuestro primer usuario

Cuando termina la instalación de MinIO, tendrás acceso completo con el usuario "root" y, como bien sabrás, NUNCA debes usar este usuario para casi nada. Por ello, vamos a crear un usuario que tenga todos los permisos sobre su propio bucket pero no pueda crear ni entrar en otros buckets de otros usuarios (o proyectos que tengas).

Creando un Alias: conectando el cliente y el server

En primer lugar debemos configurar el cliente para que sepa donde tiene que dirigir las peticiones. Esto lo conseguimos creando un alias: un alias contiene todos los datos necesarios para que el cliente pueda conectarse y autenticarse con el servidor: la dirección del servidor, nombre de usuario y contraseña:

mc alias set <nombre_del_alias> http://localhost <usuario> <contraseña> --api S3v4
#por ejemplo
mc alias set prueba http://localhost root securepassword --api S3v4

Como seguramente hayas configurado el servidor en local para hacer pruebas, la dirección es localhost (dirección que tendrás que cambiar cuando lo muevas a un servidor en Internet). Así cada vez que le digamos a mc que debe hacer algo en pruebas, el cliente sabe donde tiene que conectar. Esto nos permite, usando el mismo cliente, gestionar más de un servidor de MinIO

Creando un bucket: la carpeta raíz de tu usuario

Cuando instalaste el sistema operativo de tu ordenador, el sistema te creo una carpeta: nunca usas /root o c:\windows\ como tu carpeta principal, ¿verdad? Pues en MinIO es igual. El usuario root tiene acceso a todo y esto es un problema de seguridad. Así que vamos a crear un bucket donde el usuario (que puede ser un programa que estés desarrollando) pueda subir ficheros. En este caso vamos a crear la carpeta avatar en el servidor con el alias prueba

#Para crear un alias:
mc mb <nombre_del_alias>/<nombre_del_bucket>
# Por ejemplo
mc mb pruebas/avatar

Creando un usuario y su política de acceso

¡Guay! Ya tenemos una carpeta. Ahora es el momento de crear un usuario. Esto es fácil con el cliente de MinIO. Vamos a crear en el servidor con alias pruebas un usuario con nombre pepito y con contraseña constraseñasecreta.

#Para crear un bucket
mc admin user add <nombre_del_alias> <username> <password>
#Por ejemplo
mc admin user add pruebas pepito contraseñasecreta

Genial. Ya puedes entrar a MinIO con estos credenciales: tanto a nivel de consola creando un nuevo alias (por ejemplo pruebas-pepito para diferenciarlo de pruebas que incluye el usuario root) o a través del navegador web. Pero, un momento, algo está fallando: el usuario puede acceder a todos los bucket. Esto no es lo que queríamos.

Aquí entra en juego la política de acceso de MinIO (y que de paso, es la misma que S3). Una política de acceso es una serie de directivas qué indica qué puede hacer un usuario. Una vez creada la política de acceso, se la tenemos que asignar al usuario y, desde ese momento, el usuario estará limitado a hacer lo que la política de acceso les permita.

Vamos a crear una política de acceso que solamente permita a los usuarios a los que se le asigne acceder a un bucket concreto, en este caso, nuestro bucket avatar. No te asustes, las políticas de acceso no son bonitas, pero te dejo este  modelo por aquí. En ella se indica que el usuario puede hacer lo que quiera dentro del bucket llamado <nombre_del_bucket>. En tu caso, cambia esto por el del bucket que estés usando (en nuestro ejemplo avatar)

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Principal": {
        "AWS": [
          "*"
        ]
      },
      "Action": [
        "s3:GetBucketLocation",
        "s3:ListBucket",
        "s3:ListBucketMultipartUploads"
      ],
      "Resource": [
        "arn:aws:s3:::<nombre del bucket>"
      ]
    },
    {
      "Effect": "Allow",
      "Principal": {
        "AWS": [
          "*"
        ]
      },
      "Action": [
        "s3:AbortMultipartUpload",
        "s3:DeleteObject",
        "s3:GetObject",
        "s3:ListMultipartUploadParts",
        "s3:PutObject"
      ],
      "Resource": [
        "arn:aws:s3:::<nombre del bucket>/*"
      ]
    }
  ]
}

Copia y edita el código anterior y crea un archivo JSON con este contenido. Llámalo como quieras pero recuerda bien dónde lo estás guardando, pues necesitaremos la dirección más tarde. Como consejo, lo puedes llamar <nombre_del_bucket>-full.json para indicar que estás dando acceso completo al bucket. Y una vez guardado, vete a la consola y crearemos la política de acceso (ojo, la política de acceso como tal, todavía no está asignada al usuario)

mc admin policy add <nombre_del_alias> <nombre_de_politica> <direccion_del_fichero>
#Por ejemplo
mc admin policy add pruebas avatar-full /home/user/avatar-full.json

Ya tenemos el modelo de política de acceso. Ahora, vamos a asignárselo a un usuario

mc admin policy set <nombre_del_alias> <nombre_de_politica> user=<nombre_de_usuario>
#Por ejemplo
mc admin policy set pruebas avatar-full user=pepito

Genial! Ahora el usuario pepito solo puede acceder a su bucket y no a los bucket de los demás. A partir de este punto, puedes empezar a añadir, eliminar y compartir ficheros sin tener que programarlo tú.

Llegados a este punto, MinIO ya te sirve para almacenar ficheros privados. Imagina que los usuarios de tu aplicación pueden subir contratos o balances a tu app. Pues desde Java, PHP o Node (entre otros) ya pueden entrar al bucket y subir el fichero o bajárselo (pero siempre a través del sistema, no a través de la URL). Cómo hacerlo te lo cuento un poco más abajo.

Hacer ficheros públicos

Siguiendo nuestro ejemplo, en nuestro bucket habrá imágenes de perfil de los usuarios. No es óptimo que tengan que pasar por nuestro software para poder bajarse estas imágenes; sería genial que se pudiera acceder por url (algo así como http://localhost/avatar/fulanito.jpg.

Por defecto y cómodamente desde el navegador puedes compartir un fichero a través de un token hasta un máximo de 7 días. Pero esto no óptimo para nuestro caso porque no queremos estar renovando cada 7 días el token. Aquí es cuando entra la política de acceso DEL BUCKET en juego. Un momento! Otra vez las políticas de acceso? Sí, pero no del usuario: del bucket.

Es decir, hemos hecho que el usuario pepitotenga acceso ilimitado al bucket pero por el hecho de ser pepito el usuario que está accediendo. En nuestro caso debemos configurar el acceso anónimo (que será el navegador del usuario de tu app).

Por defecto, el usuario anónimo no tiene permisos de ningún tipo. Es normal, ¿acaso puedes entrar en los ficheros de S3 de cualquier usuario sin permiso? No. Pero asignando permisos de descarga para el usuario anónimo sobre el bucket, puede hacerlo. Para asignarle permisos de descarga al usuario anónimo basta con lanzar esta sentencia:

#Asignar permisos de descarga al usuario anónimo
mc policy set download <nombre_del_alias>/<nombre_del_bucket>
#En nuestro caso,
mc policy set download pruebas/avatar

Desde este momento, puedes obtener sin límite los ficheros dentro del bucket avatar con la siguiente url: http://localhost/avatar/<nombre_del_fichero>.

Perfecto! Tenemos un almacenamiento donde guardar ficheros y ahora son públicos. Nuestra misión ha terminado. Enhorabuena!

Hacer ficheros públicos dentro de una carpeta

La política "download" anterior da acceso completo a todos los ficheros dentro de un bucket, incluso las subcarpetas. Esto puede no ser óptimo en tu caso, que necesitas que solo una parte del bucket sea accesible.

Esto lo podemos conseguir aplicando una política personalizada al bucket, parecida a "download", pero evitando a toda costa listar documentos y hacer solo accesible cierta carpeta dentro del bucket. Te dejo aquí la política de privacidad (recuerda el nombre que le das al fichero que contiene la política porque luego lo necesitarás)

{
    "Statement": [{
        "Action": ["s3:GetBucketLocation"],
        "Effect": "Allow",
        "Principal": {
            "AWS": ["*"]
        },
        "Resource": ["arn:aws:s3:::jfs"]
    }, {
        "Action": ["s3:GetObject"],
        "Effect": "Allow",
        "Principal": {
            "AWS": ["*"]
        },
        "Resource": ["arn:aws:s3:::avatar/myfolder/*"]
    }],
    "Version": "2012-10-17"
}

En este ejemplo, estamos dando acceso público a la carpeta myfolder dentro del bucket avatar. Todo lo que pongas ahí, sera accesible desde el navegador; pero, por contra, lo que esté en la carpeta documentos dentro del bucket no será accesible. Así mismo, evitaremos listar el resto de ficheros, pues hemos eliminado la acción "ListBucket" en el primer statement.

Una vez que hemos guardado esta política en un fichero, solo debemos aplicarlo al bucket

#Asignar permisos de descarga a una parte del bucket
mc policy set-json ./read_bucket_policy.json <alias>/<bucket>
#En nuestro caso
mc policy set-json ./read_bucket_policy.json pruebas/avatar

Interactuar con un lenguaje de programación

Lo bueno de MinIO es que es completamente compatible con otros sistemas de almacenamiento como Amazon S3. Eso quiere decir que es posible que mientras estés en el entorno de desarrollo uses MinIO para no generar costes pero en producción uses S3. O no, puede que quieras usar MinIO para todo.

En cualquier caso, yo siempre he usado la librería oficial de AWS para manipular MinIO desde mi código. Si luego cambiar de opinión, basta con cambiar los datos de entorno y seguirá funcionando.

A continuación te dejo algunos enlaces de cómo usar MinIO desde tu código fuente si usas:

Otros comandos útiles de MinIO cliente

#Listar los bucket de un servidor
mc ls <nombre_del_alias>

#Listar los contenidos de un bucket
mc ls <nombre_del_alias>/<nombre_del_bucket>

#Listar los contenidos creados hace más de tres días
mc ls --rewind 3d <nombre_del_alias>/<nombre_del_bucket>

#Copiar de tu local al bucket
mc cp <fichero local> <alias>/<bucket>

#Vaciar de ficheros un bucket sin eliminarlo
mc rm <nombre_del_alias>/<nombre_del_bucket>

#Elimianr un bucket Y TODO LO ASOCIADO A ÉL (ficheros, configuraciones)
mc rb <nombre_del_alias>/<nombre_del_bucket>

MinIO es un completo sistema de almacenamiento y aquí solo te he dejado algunos comandos para que puedas empezar. Tienes toda la documentación de los comandos que admite en este enlace.

Espero que hayas aprendido algo sobre almacenamiento y que te pueda sacar del apuro en alguna ocasión.

Que tengas un feliz coding 😀