En este documento, se describen las dependencias de las aplicaciones y las prácticas recomendadas para administrarlas, lo que incluye la supervisión de vulnerabilidades, la verificación de artefactos, la reducción de la huella de las dependencias y la compatibilidad con compilaciones reproducibles.
Una dependencia de software es un componente de 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 creas, software propietario de terceros y 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 específicos para implementar las prácticas recomendadas pueden variar según el formato del artefacto y las herramientas que uses, pero los principios generales siguen siendo válidos.
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 de forma funcional. Cada dependencia puede tener sus propias dependencias directas e indirectas, lo que crea un árbol recursivo de dependencias transitivas que afectan la aplicación.
Los diferentes lenguajes de programación ofrecen distintos 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 las dependencias para compilar un módulo y las versiones de las dependencias que descarga un administrador de paquetes para una instalación específica del módulo. En otros ecosistemas de lenguajes, como Java, la introspección de dependencias tiene un soporte más limitado. Además, los sistemas de compilación deben usar administradores de dependencias específicos para administrar las dependencias de forma sistemática.
Por ejemplo, considera el módulo npm glob
versión 8.0.2. En el archivo package.json
, declaras las dependencias directas de los módulos de npm. En el archivo package.json para glob, la sección dependencies
enumera las dependencias directas del paquete publicado.
La sección devDepdencies
enumera 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 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 las dependencias de
glob
en el sitio de Open Source Insights. La lista de dependencias para glob incluye dependencias directas y dependencias indirectas (transitivas).Una dependencia transitiva puede tener varias capas de profundidad en el árbol de dependencias. Por ejemplo:
glob
8.0.2 tiene una dependencia directa enminimatch
5.0.1.minimatch
5.0.1 tiene una dependencia directabrace-expression
2.0.1.brace-expression
2.0.1 tiene una dependencia directa enbalanced-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 recuperarán las mismas versiones.
Herramientas para obtener estadísticas sobre las 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.
- 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 Google Cloud servicios también proporcionan funciones que mejoran tu postura de seguridad en todo 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, incluidas las siguientes:
Open Source Insights: Es un sitio web que proporciona información sobre las dependencias conocidas directas e indirectas, las vulnerabilidades conocidas y la información de licencias para el software de código abierto. El proyecto Open Source Insights también pone estos datos a disposición como un Google Cloud conjunto de datos. 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 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 riesgosas de la cadena de suministro de software en tus proyectos de GitHub. Realiza verificaciones en los repositorios y asigna 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 garantizar el cumplimiento de las políticas configuradas. Por ejemplo, puedes aplicar una política a tu organización de GitHub que verifique si hay 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:
- Instala 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 los ataques a 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 desde un repositorio público durante tus compilaciones, la descargas y la copias en el árbol de origen de tu proyecto. Tienes más control sobre las dependencias incluidas que usas, pero hay varias desventajas:
- Las dependencias de vendoring aumentan el tamaño de tu repositorio de código fuente y generan más rotación.
- Debes incluir 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 código fuente reutilizables, es posible que debas mantener varias copias de tus dependencias.
- Actualizar las dependencias del proveedor puede ser más difícil.
- Almacena 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 tus artefactos de compilación y dependencias para todas tus aplicaciones.
- Configura tus clientes de Docker y paquetes de idiomas para que interactúen con los repositorios privados en Artifact Registry de la misma manera en que lo hacen con los repositorios públicos.
Tener 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é las dependencias de fuentes públicas ascendentes 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 se descarga o instala un artefacto (versión preliminar privada).
Usa Artifact Registry con otros Google Cloud servicios, incluidos 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 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 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 fijadas
Fijar la versión significa 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 sean reproducibles. Sin embargo, también significa que tus compilaciones no incluyen actualizaciones de la dependencia, como correcciones de errores, mejoras o correcciones de seguridad.
Puedes mitigar este problema con herramientas automatizadas de administración de dependencias que supervisan las dependencias en tus repositorios de código fuente para detectar nuevas versiones. Estas herramientas actualizan tus 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 restringe las versiones del software del que my-library
tiene una dependencia. Puedes restringir el árbol de dependencias de un paquete en algunos lenguajes 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 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 a través de un ataque de intermediario o una vulneración del repositorio de artefactos.
Para usar la verificación de hash, debes confiar en que el hash que recibes del repositorio de artefactos no está comprometido.
- Verificación de la firma
La verificación de firma agrega seguridad adicional al proceso de verificación. Los artefactos pueden estar firmados por el repositorio de artefactos, los mantenedores del software o ambos.
Servicios como sigstore proporcionan una forma para que los mantenedores 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, los archivos de bloqueo se producen automáticamente con las herramientas de instalación y combinan la fijación de versiones y la verificación de firmas o hashes 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 de nivel inferior 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.
Combinación de dependencias privadas y públicas
Las aplicaciones nativas de la nube modernas suelen depender de código de código abierto y de terceros, así 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, cuando se mezclan dependencias privadas y públicas, la 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:
- Verifica la firma o los hashes de tus dependencias incluyéndolos en un archivo de bloqueo.
- Separa la instalación de dependencias internas y de terceros en dos pasos distintos.
- Duplica explícitamente las dependencias de terceros que necesites 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 repositorios remotos y estándar de Artifact Registry detrás de 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 confiables para los paquetes públicos y las imágenes base.
- Usa Assured Open Source Software para acceder a imágenes populares de Java y Python que Google probó y verificó.
- Usa imágenes base proporcionadas por Google o una canalización de imágenes segura para generar tus propias imágenes base.
Cómo quitar dependencias que no se usan
A medida que cambien tus necesidades y evolucione tu aplicación, es posible que dejes de usar algunas de tus dependencias o las modifiques. Si sigues instalando dependencias no utilizadas con tu aplicación, aumentarás la huella de tus dependencias y el riesgo de que una vulnerabilidad en esas dependencias ponga en peligro tu aplicación.
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 la aplicación implementada funcione, pero también es probable que introduzca dependencias que no necesitas en la 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 control completo. Como parte de tu canalización de pruebas y linting habitual, 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 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 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 de los paquetes del SO en las imágenes de contenedor. Puede analizar imágenes cuando se suben a Artifact Registry y supervisarlas continuamente 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 localmente las imágenes de contenedores en busca de vulnerabilidades de SO, Go y Java. Esto te permite identificar vulnerabilidades de forma anticipada para que puedas abordarlas antes de almacenarlas en Artifact Registry.
¿Qué sigue?
- Obtén información sobre los componentes de la seguridad de la cadena de suministro de software y cómo los servicios deGoogle Cloud te ayudan a proteger tu software.
- Obtén más información sobre Artifact Registry.
- Obtén más información sobre Artifact Analysis y los tipos de análisis.