Administración de dependencias

En este documento, se describen las dependencias de la aplicación y las prácticas recomendadas para administrarlas, lo que incluye la supervisión de vulnerabilidades, la verificación de artefactos, la reducción de tu espacio 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 los componentes que creas, el software de terceros propietario y el software de código abierto. El enfoque que adoptes para administrar las dependencias puede afectar la seguridad y la confiabilidad de tus aplicaciones.

Los detalles para implementar las prácticas recomendadas pueden variar según el formato del artefacto y las herramientas que uses, pero se aplican 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 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 diferentes lenguajes de programación ofrecen diferentes niveles de visibilidad en 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 npm y yarn usan archivos de bloqueo para identificar las versiones de dependencia para 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 las dependencias de forma sistemática.

A modo de ejemplo, considera el módulo npm glob versión 8.0.2. Declaras 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 del paquete publicado. En la sección devDepdencies, se enumeran las dependencias para el desarrollo y las pruebas locales de los encargados del mantenimiento y los colaboradores de glob.

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

  • Puedes encontrar información adicional sobre las dependencias de glob en el sitio de Open Source Insights. La lista de dependencias de glob incluye dependencias directas y dependencias 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 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 en balanced-match 1.0.2.

Sin visibilidad de las dependencias indirectas, es muy difícil identificar y responder a 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 que tengas 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 sobre los formatos de paquetes.

Estadísticas de seguridad en la consola de Google Cloud
Google Cloud proporciona estadísticas de seguridad para tus artefactos en Cloud Build, Cloud Run y GKE, incluidas las vulnerabilidades, la información de dependencia, la lista de materiales de software (SBOM) y la procedencia de la compilación. Otros servicios de Google Cloud también proporcionan funciones que mejoran tu postura de seguridad durante el ciclo de vida del desarrollo de software. Para obtener más información, consulta la descripción general de la seguridad de la cadena de suministro de software.
Herramientas de código abierto

Hay varias herramientas de código abierto disponibles, como las siguientes:

  • Open Source Insights: Es un sitio web que proporciona información sobre dependencias directas e indirectas conocidas, vulnerabilidades conocidas y la información de la licencia del software de código abierto. El proyecto Open Source Insights también pone estos datos 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 sola 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 les asigna a cada una 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 verificar el cumplimiento de las políticas configuradas. Por ejemplo, puedes aplicar una política a tu 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 con tu aplicación:

Cómo 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, como no controlas estas dependencias externas, tu cadena de suministro de software es más propensa a ataques de 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 proveedor. En lugar de instalar una dependencia externa desde un repositorio público durante tus compilaciones, la descargas y la copias en el árbol de fuentes de tu proyecto. Tienes más control sobre las dependencias del proveedor que usas, pero hay varias desventajas:
  • Las dependencias de proveedores aumentan el tamaño de tu repositorio de origen y generan más deserción.
  • Debes vender las mismas dependencias en cada aplicación independiente. Si tu repositorio de origen o proceso de compilación no admite módulos de origen reutilizables, es posible que debas mantener varias copias de tus dependencias.
  • La actualización de las dependencias del proveedor 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 sobre tus dependencias. Con Artifact Registry, puedes hacer lo siguiente:

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

    • Restringe el acceso a cada repositorio con Identity and Access Management.
    • Usa repositorios remotos para almacenar en caché dependencias de fuentes públicas upstream y analizarlas en busca de vulnerabilidades (versión preliminar privada).
    • Usa repositorios virtuales para agrupar repositorios remotos y privados detrás de un solo extremo. Establece una prioridad en cada repositorio para controlar el orden de búsqueda cuando descargues o instales un artefacto (versión preliminar privada).
  • Usa Artifact Registry con otros servicios de Google Cloud, como Cloud Build, Cloud Run y Google Kubernetes Engine. Usa el análisis automático de vulnerabilidades durante el ciclo de vida del desarrollo de software, genera la procedencia de la compilación, controla las implementaciones y consulta 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 vender tus dependencias para que tengas control sobre el contenido de tu cadena de suministro de software.

Versiones fijadas

La fijación de versiones consiste en restringir una dependencia de la aplicación a una versión o un rango 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 garantizar que las compilaciones de tu aplicación puedan reproducirse. Sin embargo, también significa que tus compilaciones no incluyen actualizaciones de la dependencia, incluidas correcciones de seguridad, correcciones de errores o mejoras.

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

La fijación de versiones solo se aplica a las dependencias directas, no a las 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 de software de las que depende my-library. Puedes restringir el árbol de dependencias de un paquete en algunos idiomas con un archivo de bloqueo.

Verificación de firma y hash

Existen 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 identificador único. Puedes comparar el hash de un artefacto con el valor de hash que calcula 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 a través de un ataque de intermediario o una vulneración 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 la firma

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

Servicios como sigstore proporcionan una forma para que los responsables de mantenimiento firmen artefactos de software y para 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.

Archivos de bloqueo y dependencias compiladas

Los archivos de bloqueo son archivos de requisitos completamente resueltos que especifican exactamente qué versión de cada dependencia se debe instalar para una aplicación. Por lo general, las herramientas de instalación los producen automáticamente. Los archivos de bloqueo combinan la fijación de versiones y la verificación de firmas o hash con un árbol de dependencias completo para tu aplicación.

Las herramientas de instalación crean árboles de dependencias resolviendo por completo 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 se pueden instalar estas dependencias, lo que hace que las compilaciones sean más reproducibles y coherentes.

Cómo combinar dependencias públicas y privadas

Las aplicaciones nativas de la nube modernas a menudo dependen de código de terceros de código abierto y 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, cuando se mezclan 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 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:

  • Para verificar la firma o los hash de tus dependencias, inclúyelas en un archivo de bloqueo.
  • Separa la instalación de dependencias de terceros y dependencias internas en dos pasos distintos.
  • Refleja 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 repositorios públicos upstream.
  • Usa repositorios virtuales para consolidar los repositorios remotos y estándar de Artifact Registry en un solo extremo. Puedes configurar prioridades para los repositorios upstream de modo que las versiones de tus 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.

Cómo quitar dependencias que no se usan

A medida que cambian tus necesidades y evoluciona tu aplicación, es posible que cambies o dejes de usar algunas de tus dependencias. Si continúas instalando dependencias que no se usan con tu aplicación, aumentas tu huella de dependencia y el riesgo de que se vea comprometida por una vulnerabilidad en esas dependencias.

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

Ten cuidado cuando agregues dependencias nuevas a tu aplicación. Cada uno tiene el potencial de introducir más código sobre el que no tienes un control total. Como parte de tu canalización de linting y pruebas habitual, 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 Maven Dependency para analizar y administrar 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 coherente si tus dependencias están introduciendo vulnerabilidades en tu aplicación. Las herramientas de análisis de vulnerabilidades consumen archivos de bloqueo para determinar exactamente de qué artefactos dependes y notificarte cuando aparezcan vulnerabilidades nuevas, a veces incluso con rutas de actualización sugeridas.

Por ejemplo, Artifact Analysis identifica vulnerabilidades de paquete del SO en imágenes de contenedor. Puede analizar imágenes cuando se suben a Artifact Registry y supervisarlas de forma continua para encontrar vulnerabilidades nuevas durante un máximo de 30 días después de enviar la imagen.

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 vulnerabilidades con anticipación para que puedas abordarlas antes de almacenarlas en Artifact Registry.

¿Qué sigue?