Administración de dependencias

En este documento, se describen las dependencias de la aplicación y las prácticas recomendadas para administrarlas, incluida la supervisión de vulnerabilidades, la verificación de artefactos, la reducción de tu huella de dependencia 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. La resolución de dependencias puede ocurrir cuando compilas código, compilas, ejecutas, descargas o instalas tu software.

Las dependencias pueden incluir componentes que tú crees, software de terceros propio y software de código abierto. El enfoque que adoptes para administrar dependencias puede afectar la seguridad y confiabilidad de tus aplicaciones.

Los detalles específicos para implementar las prácticas recomendadas pueden variar según el formato del artefacto y las herramientas que uses, pero se siguen aplicando los principios generales.

Dependencias directas y transitivas

Tus aplicaciones pueden incluir dependencias directas y transitivas:

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

Los diferentes lenguajes de programación ofrecen diferentes niveles de visibilidad de las dependencias y sus relaciones. Además, algunos lenguajes usan administradores de paquetes para resolver el árbol de dependencias cuando se instala o implementa un paquete.

En el ecosistema de Node.js, los administradores de paquetes de npm y yarn usan archivos de bloqueo para identificar las versiones de dependencia a fin de compilar un módulo y las versiones de dependencia que un administrador de paquetes descarga para una instalación específica del módulo. En otros ecosistemas de lenguajes, como Java, la compatibilidad con la introspección de dependencias es más limitada. Además, los sistemas de compilación deben usar administradores de dependencias específicos para administrar dependencias de manera sistemática.

Como ejemplo, considera la versión 8.0.2 del módulo de npm glob. Debes declarar las dependencias directas para los módulos de npm en el archivo package.json. En el archivo package.json para glob, la sección dependencies enumera las dependencias directas para el paquete publicado. En la sección devDepdencies, se enumeran las dependencias para el desarrollo y las pruebas locales de los encargados de mantenimiento y los colaboradores de glob.

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

  • Puedes encontrar información adicional sobre la dependencia de glob en el sitio de Open Source Insights. La lista de dependencias para glob incluye las dependencias directas y las indirectas (transitivas).

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

    1. glob 8.0.2 tiene una dependencia directa en 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 sobre 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 descargadas específicas en el archivo package.lock.json para tener un registro de todas las dependencias. Las instalaciones posteriores en el mismo entorno recuperarán las mismas versiones.

Herramientas para las estadísticas de dependencias

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

Software Delivery Shield
Una solución de seguridad de la cadena de suministro de software completamente administrada en Google Cloud que te permite ver estadísticas de seguridad para tus artefactos en Cloud Build, Cloud Run y GKE, incluidas las vulnerabilidades, la información de dependencias, la lista de materiales de software (SBOM) y la procedencia de la compilación. Software Delivery Shield también proporciona otros servicios y funciones para mejorar tu postura de seguridad durante todo el ciclo de vida del desarrollo de software.
Herramientas de código abierto

Hay una serie de herramientas de código abierto disponibles, incluidas las siguientes:

  • Estadísticas de código abierto: Es un sitio web que proporciona información sobre dependencias indirectas y directas conocidas, información sobre licencias de software de código abierto y vulnerabilidades conocidas. El proyecto de estadísticas de código abierto también hace que estos datos estén disponibles como un conjunto de datos de Google Cloud. Puedes usar BigQuery para explorar y analizar los datos.

  • Base de datos de vulnerabilidades de código abierto: Es una base de datos de vulnerabilidades que se puede buscar y que agrega vulnerabilidades de otras bases de datos en una ubicación.

  • Cuadros de evaluación: Es una herramienta automatizada que puedes usar para identificar prácticas riesgosas de la cadena de suministro de software en tus proyectos de GitHub. Realiza verificaciones en los repositorios y le otorga a cada verificación una puntuación de 0 a 10. Luego, puedes usar las puntuaciones para evaluar la postura de seguridad de tu proyecto.

  • Allstar: Es una app de GitHub que supervisa de forma continua las organizaciones o los repositorios de GitHub para cumplir con las políticas configuradas. Por ejemplo, puedes aplicar una política a la organización de GitHub que busque colaboradores fuera de la organización que tengan acceso de administrador o de envío.

Enfoques para incluir dependencias

Existen varios métodos comunes 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 enfoque es conveniente porque no necesitas mantener tus dependencias externas. Sin embargo, debido a que no controlas estas dependencias externas, la cadena de suministro de software es más propensa a sufrir ataques en la cadena de suministro de código abierto.
Almacena copias de las dependencias en tu repositorio de código fuente
Este enfoque también se conoce como vendoring. En lugar de instalar una dependencia externa de un repositorio público durante las compilaciones, debes descargarla y copiarla en el árbol de fuentes de tu proyecto. Tienes más control sobre las dependencias con proveedores que usas, pero existen varias desventajas:
  • Las dependencias veneradas aumentan el tamaño de tu repositorio de código fuente y provocan una mayor deserción.
  • Debes proporcionar las mismas dependencias en cada aplicación independiente. Si tu repositorio de código fuente o proceso de compilación no admiten módulos de origen reutilizables, es posible que debas mantener varias copias de tus dependencias.
  • Actualizar las dependencias vendidas puede ser más difícil.
Almacena las dependencias en un registro privado
Un registro privado, como Artifact Registry, proporciona la comodidad de la instalación desde un repositorio público, así como el control de las dependencias. Con Artifact Registry, puedes hacer lo siguiente:
  • Centraliza tus artefactos de compilación y las dependencias para todas tus aplicaciones.
  • Configura los clientes de Docker y de paquetes de lenguajes para interactuar con los repositorios privados en Artifact Registry de la misma manera que lo hacen con los repositorios públicos.
  • Obtén un mayor control sobre tus dependencias en repositorios privados:
  • Restringir el acceso a cada repositorio con Identity and Access Management
  • Usa repositorios remotos para almacenar en caché las dependencias de fuentes públicas ascendentes y analizarlas en busca de vulnerabilidades (versión preliminar privada).
  • Usa repositorios virtuales para agrupar los repositorios remotos y privados detrás de un solo extremo. Establece una prioridad en cada repositorio para controlar el orden de búsqueda cuando se descarga o instala un artefacto (vista previa privada).
  • Usa Artifact Registry con facilidad con otros servicios de Google Cloud en Software Delivery Shield, incluidos Cloud Build, Cloud Run y Google Kubernetes Engine. Usa el análisis automático de vulnerabilidades en todo el ciclo de vida del desarrollo de software, genera fuentes de compilación, controla las implementaciones y obtén estadísticas sobre tu postura de seguridad.

Cuando sea posible, usa un registro privado para tus dependencias. En situaciones en las que no puedes usar un registro privado, considera crear un proveedor de tus dependencias para tener control sobre el contenido de tu cadena de suministro de software.

Versiones fijadas

Fijar versión implica restringir una dependencia de la aplicación a una versión o un rango de versiones específicos. Lo ideal es fijar una sola versión de una dependencia.

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

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

La fijación de versiones solo se aplica a dependencias directas, no a dependencias 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 my-library tiene una dependencia. Puedes restringir el árbol de dependencias para un paquete en algunos lenguajes con un archivo de bloqueo.

Verificación de firma y hash

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

Verificación de hash

Un hash es un valor generado para un archivo que actúa como un 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 te ayuda a identificar el reemplazo, la manipulación o la corrupción de dependencias mediante un ataque de intermediario o un compromiso del repositorio de artefactos.

El uso de la verificación de hash requiere confiar en que el hash que recibes del repositorio de artefactos no esté comprometido.

Verificación de firma

La verificación de firma agrega seguridad adicional al proceso de verificación. El repositorio de artefactos, los encargados de mantenimiento del software o ambos pueden firmar artefactos.

Los servicios como sigstore permiten que los encargados de mantenimiento firmen artefactos de software y que los consumidores verifiquen esas firmas.

La autorización binaria puede verificar que las imágenes de contenedor implementadas en los entornos de ejecución de Google Cloud estén firmadas con certificaciones para una variedad de criterios.

Bloquea 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 para una aplicación. Por lo general, los archivos de bloqueo, que se producen automáticamente mediante las herramientas de instalación, combinan la fijación de versiones y la verificación de firma o hash con un árbol de dependencias completo para tu aplicación.

Las herramientas de instalación crean árboles de dependencias mediante la resolución completa de todas las dependencias transitivas descendentes de tus dependencias de nivel superior y, luego, incluyen el árbol de dependencias en tu archivo de bloqueo. Como resultado, solo estas dependencias se pueden instalar, lo que hace que las compilaciones sean más reproducibles y coherentes.

Combina dependencias privadas y públicas

Las aplicaciones nativas de la nube modernas suelen depender de código abierto y de código de terceros, además de bibliotecas internas de código cerrado. Artifact Registry te permite compartir tu lógica empresarial entre varias aplicaciones y reutilizar la misma herramienta para instalar bibliotecas internas y externas.

Sin embargo, cuando se combinan dependencias privadas y públicas, la cadena de suministro de software es más vulnerable a un ataque de confusión en relación con las dependencias. Si publicas proyectos con el mismo nombre que tu proyecto interno en repositorios de código abierto, los atacantes podrían aprovechar los 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 estos pasos:

  • Verifica la firma o los hashes de tus dependencias. Para ello, inclúyelos en un archivo de bloqueo.
  • Separa la instalación de dependencias internas y de terceros en dos pasos distintos.
  • Duplica de forma explícita las dependencias de terceros que necesitas en tu repositorio privado, ya sea de forma manual o con un proxy de extracción. Los repositorios remotos de Artifact Registry son proxies de extracción para los repositorios públicos ascendentes.
  • Usa repositorios virtuales para consolidar los repositorios remotos y estándar de Artifact Registry en un solo extremo. Puedes configurar prioridades para repositorios ascendentes a fin de que las versiones de tus artefactos privados siempre tengan prioridad sobre los artefactos públicos con el mismo nombre.
  • Usa fuentes confiables para las imágenes base y los paquetes públicos.

Quita dependencias sin usar

A medida que tus necesidades cambian y tu aplicación evoluciona, puedes cambiar o dejar de usar algunas de tus dependencias. Seguir instalando dependencias sin usar con tu aplicación aumenta tu huella de dependencias y aumenta el riesgo de que una vulnerabilidad en esas dependencias te comprometa.

Una vez que la aplicación funciona de manera local, una práctica común es copiar cada dependencia que instalaste durante el proceso de desarrollo en el archivo de requisitos de la aplicación. Luego, implementas la aplicación con todas esas dependencias. Este enfoque ayuda a garantizar que la aplicación implementada funcione, pero también es probable que introduzca dependencias que no necesitas en producción.

Ten cuidado cuando agregues dependencias nuevas a tu aplicación. Cada uno tiene el potencial de agregar más código sobre el que no tienes control total. Como parte de tu canalización de análisis y pruebas habituales, integra herramientas que auditen tus archivos de requisitos para determinar si realmente usas o importas tus dependencias.

Algunos lenguajes tienen herramientas para ayudarte a administrar tus dependencias. Por ejemplo, puedes usar el complemento de dependencias de Maven para analizar y administrar dependencias de Java.

Análisis de vulnerabilidades

Responder con rapidez a las vulnerabilidades en 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 coherente 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 surgen nuevas vulnerabilidades, a veces incluso con rutas de actualización sugeridas.

Por ejemplo, Artifact Analysis identifica las vulnerabilidades del paquete de SO en las imágenes de contenedor. Puede analizar imágenes cuando se suben a Artifact Registry y las supervisa de forma continua para encontrar vulnerabilidades nuevas hasta 30 días después de enviarla.

También puedes usar el Análisis a pedido para analizar imágenes de contenedores de forma local en busca de vulnerabilidades de SO, Go y Java. Esto te permite identificar las vulnerabilidades con anticipación para que puedas abordarlas antes de almacenarlas en Artifact Registry.

¿Qué sigue?