Gestión de dependencias

En este documento se describen las dependencias de las aplicaciones y las prácticas recomendadas para gestionarlas, como la monitorización de vulnerabilidades, la verificación de artefactos, la reducción de la superficie de las dependencias y la compatibilidad con compilaciones reproducibles.

Una dependencia de software es un software que tu aplicación necesita para funcionar, como una biblioteca de software o un complemento. Las dependencias se pueden resolver al compilar, crear, ejecutar, descargar o instalar software.

Las dependencias pueden incluir componentes que crees, software de terceros propietario y software de código abierto. La estrategia que sigas para gestionar las dependencias puede afectar a la seguridad y la fiabilidad de tus aplicaciones.

Los detalles para implementar las prácticas recomendadas pueden variar en función del formato del artefacto y de las herramientas que utilices, pero los principios generales siguen siendo los mismos.

Dependencias directas y transitivas

Tus aplicaciones pueden incluir dependencias directas y transitivas:

Dependencias directas
Componentes de software a los que hace referencia una aplicación directamente.
Dependencias transitivas
Componentes de software que requieren funcionalmente las dependencias directas de una aplicación. Cada dependencia puede tener sus propias dependencias directas e indirectas, lo que crea un árbol recursivo de dependencias transitivas que afectan a la aplicación.

Los distintos lenguajes de programación ofrecen diferentes niveles de visibilidad de las dependencias y sus relaciones. Además, algunos lenguajes usan gestores de paquetes para resolver el árbol de dependencias al instalar o implementar un paquete.

En el ecosistema de Node.js, los gestores de paquetes npm y yarn usan archivos de bloqueo para identificar las versiones de las dependencias al compilar un módulo y las versiones de las dependencias que descarga un gestor de paquetes para una instalación específica del módulo. En otros ecosistemas de lenguajes, como Java, el soporte para la introspección de dependencias es más limitado. Además, los sistemas de compilación deben usar gestores de dependencias específicos para gestionar las dependencias de forma sistemática.

Por ejemplo, supongamos que tienes el módulo npm glob versión 8.0.2. Declaras las dependencias directas de los módulos npm en el archivo package.json. En el archivo package.json de glob, la sección dependencies muestra las dependencias directas del paquete publicado. En la sección devDepdencies se enumeran las dependencias para el desarrollo y las pruebas locales por parte de los mantenedores y colaboradores de glob.

  • En el sitio web de npm, la página de glob muestra las dependencias directas y las dependencias de desarrollo, pero no indica si estos módulos también tienen sus propias dependencias.

  • Puedes consultar más información sobre las dependencias de glob en el sitio de Open Source Insights. La lista de dependencias de glob incluye tanto dependencias directas como indirectas (transitivas).

    Una dependencia transitiva puede tener varias capas de profundidad en el árbol de dependencias. Por ejemplo:

    1. glob 8.0.2 tiene una dependencia directa de minimatch 5.0.1.
    2. minimatch 5.0.1 tiene una dependencia directa de brace-expression 2.0.1.
    3. brace-expression 2.0.1 tiene una dependencia directa de balanced-match 1.0.2.

Sin visibilidad de las dependencias indirectas, es muy difícil identificar y responder a las vulnerabilidades y otros problemas que se originan en un componente al que tu código no hace referencia directamente.

Cuando instalas el paquete glob, npm resuelve todo el árbol de dependencias y guarda la lista de versiones específicas descargadas en el archivo package.lock.json para que tengas un registro de todas las dependencias. Las instalaciones posteriores en el mismo entorno obtendrán las mismas versiones.

Herramientas para obtener estadísticas sobre las dependencias

Puedes usar las siguientes herramientas para entender tus dependencias de código abierto y evaluar la postura de seguridad de tus proyectos. Estas herramientas proporcionan información sobre los formatos de los paquetes.

Estadísticas de seguridad en la Google Cloud consola
Google Cloud proporciona información valiosa sobre la seguridad de tus artefactos en Cloud Build, Cloud Run y GKE, como vulnerabilidades, información sobre dependencias, listas de materiales de software (SBOM) y procedencia de compilaciones. Otros servicios Google Cloud también ofrecen funciones que mejoran tu postura de seguridad durante todo el ciclo de vida del desarrollo de software. Para obtener más información, consulte el resumen de seguridad de la cadena de suministro de software.
Herramientas de código abierto

Hay varias herramientas de código abierto disponibles, entre las que se incluyen las siguientes:

  • Open Source Insights: un sitio web que proporciona información sobre dependencias directas e indirectas conocidas, vulnerabilidades conocidas e información sobre licencias de software libre. El proyecto Open Source Insights también pone estos datos a disposición de los usuarios como Google Cloud conjunto de datos. Puedes usar BigQuery para explorar y analizar los datos.

  • Base de datos de vulnerabilidades de código abierto: una base de datos de vulnerabilidades en la que se pueden realizar búsquedas y que agrega vulnerabilidades de otras bases de datos en una sola ubicación.

  • Tarjetas de puntuación: una herramienta automatizada que puedes usar para identificar prácticas de la cadena de suministro de software arriesgadas en tus proyectos de GitHub. Realiza comprobaciones en los repositorios y asigna a cada comprobación una puntuación del 0 al 10. Después, puedes usar las puntuaciones para evaluar la postura de seguridad de tu proyecto.

  • Allstar una aplicación de GitHub que monitoriza continuamente las organizaciones o los repositorios de GitHub para comprobar que cumplen las políticas configuradas. Por ejemplo, puedes aplicar una política a tu organización de GitHub que busque colaboradores ajenos a la organización que tengan acceso de administrador o de envío.

Métodos para incluir dependencias

Hay varios métodos habituales para incluir dependencias en tu aplicación:

Instalar directamente desde fuentes públicas
Instala dependencias de código abierto directamente desde repositorios públicos, como Docker Hub, npm, PyPI o Maven Central. Este método es práctico porque no tienes que mantener tus dependencias externas. Sin embargo, como no controlas estas dependencias externas, tu cadena de suministro de software es más propensa a sufrir ataques a la cadena de suministro de código abierto.
Almacenar copias de las dependencias en tu repositorio de origen
Este enfoque también se conoce como vendoring. En lugar de instalar una dependencia externa desde un repositorio público durante las compilaciones, puedes descargarla y copiarla en el árbol de origen de tu proyecto. Tienes más control sobre las dependencias de proveedores que usas, pero hay varias desventajas:
  • Las dependencias de proveedores aumentan el tamaño de tu repositorio de origen e introducen más cambios.
  • Debes incluir las mismas dependencias en cada aplicación independiente. Si tu repositorio de origen o tu proceso de compilación no admiten módulos de origen reutilizables, es posible que tengas que mantener varias copias de tus dependencias.
  • Actualizar las dependencias de proveedores puede ser más difícil.
Almacenar dependencias en un registro privado

Un registro privado, como Artifact Registry, ofrece la comodidad de instalar desde un repositorio público, así como el control de tus dependencias. Con Artifact Registry, puedes hacer lo siguiente:

  • Centraliza tus artefactos y dependencias de compilación de todas tus aplicaciones.
  • Configura tus clientes de paquetes de Docker y de idiomas para que interactúen con los repositorios privados de Artifact Registry de la misma forma que lo hacen con los públicos.
  • Tener un mayor control sobre tus dependencias en repositorios privados:

    • Restringe el acceso a cada repositorio con Gestión de Identidades y Accesos.
    • Usa repositorios remotos para almacenar en caché las dependencias de fuentes públicas de nivel superior y analizarlas en busca de vulnerabilidades (vista previa privada).
    • Usa repositorios virtuales para agrupar repositorios remotos y privados en un único endpoint. Define una prioridad en cada repositorio para controlar el orden de búsqueda al descargar o instalar un artefacto (vista previa privada).
  • Usa Artifact Registry con otros Google Cloud servicios, como Cloud Build, Cloud Run y Google Kubernetes Engine. Usa el análisis automático de vulnerabilidades durante todo el ciclo de vida del desarrollo de software, genera la procedencia de las compilaciones, controla las implementaciones y consulta estadísticas sobre tu postura de seguridad.

Cuando sea posible, usa un registro privado para tus dependencias. En los casos en los que no puedas usar un registro privado, considera la posibilidad de vender tus dependencias para tener control sobre el contenido de tu cadena de suministro de software.

Versiones fijas

Fijar una versión significa restringir una dependencia de una aplicación a una versión o a un intervalo de versiones específicos. Lo ideal es que fijes una sola versión de una dependencia.

Fijar la versión de una dependencia ayuda a asegurar que las compilaciones de tu aplicación sean reproducibles. Sin embargo, esto también significa que tus compilaciones no incluyen actualizaciones de la dependencia, como correcciones de seguridad, correcciones de errores o mejoras.

Puedes mitigar este problema con herramientas automatizadas de gestión de dependencias que monitoricen las dependencias de tus repositorios de origen para detectar nuevas versiones. Estas herramientas actualizan los archivos de requisitos para actualizar las dependencias según sea necesario, y a menudo incluyen información del registro de cambios o detalles adicionales.

La fijación de versiones solo se aplica a las dependencias directas, no a las transitivas. Por ejemplo, si fijas la versión del paquete my-library, la fijación restringe la versión de my-library, pero no las versiones del software del que depende my-library. En algunos lenguajes, puedes restringir el árbol de dependencias de un paquete mediante un archivo de bloqueo.

Verificación de firmas y hashes

Hay varios métodos que puedes usar para verificar la autenticidad de un artefacto que estés usando como dependencia.

Verificación de hash

Un hash es un valor generado para un archivo que actúa como identificador único. Puedes comparar el hash de un artefacto con el valor de hash calculado por el proveedor del artefacto para confirmar la integridad del archivo. La verificación de hash le ayuda a identificar la sustitución, la manipulación o la corrupción de dependencias mediante un ataque de intermediario o una vulneración del repositorio de artefactos.

Para usar la verificación de hash, debe confiar en que el hash que recibe del repositorio de artefactos no se ha visto comprometido.

Verificación de firmas

La verificación de firma añade seguridad adicional al proceso de verificación. El repositorio de artefactos, los mantenedores del software o ambos pueden firmar artefactos.

Servicios como sigstore permiten a los mantenedores firmar artefactos de software y a los consumidores verificar esas firmas.

La autorización binaria puede verificar que las imágenes de contenedor desplegadas en entornos de ejecución de Google Cloud se hayan firmado con certificaciones que cumplan una serie de criterios.

Bloquear archivos y dependencias compiladas

Los archivos de bloqueo son archivos de requisitos totalmente resueltos que especifican exactamente qué versión de cada dependencia se debe instalar en una aplicación. Los archivos de bloqueo, que suelen generarse automáticamente con herramientas de instalación, combinan la fijación de versiones y la verificación de firmas o hashes con un árbol de dependencias completo de tu aplicación.

Las herramientas de instalación crean árboles de dependencias resolviendo por completo todas las dependencias transitivas de nivel inferior de tus dependencias de nivel superior y, a continuación, incluyen el árbol de dependencias en tu archivo de bloqueo. Por lo tanto, solo se pueden instalar estas dependencias, lo que hace que las compilaciones sean más reproducibles y coherentes.

Combinar dependencias privadas y públicas

Las aplicaciones nativas de la nube modernas suelen depender tanto de código de terceros y de código abierto como de bibliotecas internas de código cerrado. Artifact Registry te permite compartir tu lógica empresarial en varias aplicaciones y reutilizar las mismas herramientas para instalar bibliotecas externas e internas.

Sin embargo, al mezclar dependencias privadas y públicas, tu cadena de suministro de software es más vulnerable a un ataque de confusión de dependencias. Si publicas proyectos con el mismo nombre que tu proyecto interno en repositorios de código abierto, los atacantes podrían aprovechar instaladores mal configurados para instalar su código malicioso en lugar de tu dependencia interna.

Para evitar un ataque de confusión de dependencias, puedes seguir varios pasos:

  • Verifica la firma o los hashes de tus dependencias incluyéndolos en un archivo de bloqueo.
  • Separa la instalación de las dependencias de terceros y las internas en dos pasos distintos.
  • Replica explícitamente las dependencias de terceros que necesites en tu repositorio privado, ya sea manualmente o con un proxy pull-through. Los repositorios remotos de Artifact Registry son proxies de extracción para repositorios públicos upstream.
  • Usa repositorios virtuales para consolidar repositorios de Artifact Registry remotos y estándar en un único endpoint. Puede configurar prioridades para los repositorios upstream de forma que las versiones de sus artefactos privados siempre tengan prioridad sobre los artefactos públicos con el mismo nombre.
  • Usa fuentes de confianza para los paquetes públicos y las imágenes base.

Eliminar dependencias no utilizadas

A medida que cambien tus necesidades y tu aplicación evolucione, es posible que modifiques o dejes de usar algunas de tus dependencias. Si sigues instalando dependencias que no se usan en tu aplicación, aumentará el tamaño de las dependencias y el riesgo de que se vea afectada por una vulnerabilidad en esas dependencias.

Una vez que la aplicación funcione de forma local, lo habitual es copiar todas las dependencias que hayas instalado durante el proceso de desarrollo en el archivo requirements de la aplicación. A continuación, implementa la aplicación con todas esas dependencias. Este enfoque ayuda a asegurar que la aplicación implementada funcione, pero también es probable que introduzca dependencias que no necesites en producción.

Ten cuidado al añadir nuevas dependencias a tu aplicación. Cada una de ellas tiene el potencial de introducir más código sobre el que no tienes control total. Como parte de tu proceso habitual de linting y pruebas, integra herramientas que auditen tus archivos de requisitos para determinar si realmente usas o importas tus dependencias.

Algunos lenguajes tienen herramientas que te ayudan a gestionar tus dependencias. Por ejemplo, puedes usar el complemento de dependencias de Maven para analizar y gestionar las dependencias de Java.

Análisis de vulnerabilidades

Responder rápidamente a las vulnerabilidades de tus dependencias te ayuda a proteger tu cadena de suministro de software.

El análisis de vulnerabilidades te permite evaluar de forma automática y constante si tus dependencias introducen vulnerabilidades en tu aplicación. Las herramientas de análisis de vulnerabilidades consumen archivos de bloqueo para determinar exactamente de qué artefactos dependes y te notifican cuando aparecen nuevas vulnerabilidades, a veces incluso con rutas de actualización sugeridas.

Por ejemplo, Artifact Analysis identifica vulnerabilidades de paquetes de SO en imágenes de contenedor. Puede analizar imágenes cuando se suben a Artifact Registry y monitorizarlas continuamente para detectar nuevas vulnerabilidades hasta 30 días después de subir la imagen.

También puedes usar On-Demand Scanning para analizar imágenes de contenedor de forma local y detectar vulnerabilidades de SO, Go y Java. De esta forma, puedes identificar vulnerabilidades en una fase temprana para solucionarlas antes de almacenarlas en Artifact Registry.

Siguientes pasos