Instituto Nacional de ciberseguridad. Sección Incibe
Instituto Nacional de Ciberseguridad. Sección INCIBE-CERT

Seguridad en el pipeline CI/CD

Fecha de publicación 14/11/2024
Autor
INCIBE (INCIBE)
Portada de blog Seguridad en el pipeline CI/CD

Un pipeline CI/CD es un conjunto de pasos automatizados que se utilizan en el desarrollo de software para llevar el código desde su creación hasta su despliegue en producción. Se compone de tres fases clave: la Integración Continua (CI), la Entrega Continua (CD) y el Despliegue Continuo (CD).

  • Integración Continua (CI): se centra en la integración regular del código de los desarrolladores en un repositorio compartido. Cada cambio en el código se integra y se verifica automáticamente mediante la ejecución de pruebas unitarias y de integración. Esto permite detectar errores de manera temprana, evitando que problemas en el código se acumulen a lo largo del desarrollo. La CI automatiza la verificación del código, asegurando que cada nueva pieza de código que se integra en la base de código existente funcione correctamente con los cambios anteriores.
  • Entrega Continua (CD): en esta etapa, el código que ha pasado con éxito por la CI es empaquetado y preparado para ser entregado a un entorno de producción o preproducción. La Entrega Continua automatiza la entrega de este código listo para producción, pero suele requerir una aprobación manual antes de que se realice el despliegue final. Esto garantiza que el software está en un estado listo para desplegarse en cualquier momento, pero mantiene un control manual sobre el momento exacto de su implementación.
  • Despliegue Continuo (CD): esta fase lleva la automatización un paso más allá, eliminando la necesidad de una intervención manual en el despliegue. En un pipeline con Despliegue Continuo, el software que pasa por todas las pruebas y validaciones es automáticamente desplegado en el entorno de producción. Esto permite un ciclo de vida de desarrollo completamente automatizado, desde la codificación hasta la entrega final, lo que reduce los tiempos de entrega y permite una rápida iteración y respuesta a los cambios en los requisitos.

Un pipeline CI/CD seguro ofrece numerosas ventajas en el desarrollo de software, ya que automatiza la integración de prácticas de seguridad en cada etapa del ciclo de vida del desarrollo, como el análisis de código y las pruebas automatizadas, lo que permite detectar y mitigar vulnerabilidades de manera proactiva. 

Estas prácticas, alineadas con la filosofía DevSecOps, aseguran que la seguridad se mantenga como una prioridad desde el desarrollo hasta el despliegue, fomentando la colaboración entre los equipos de desarrollo, operaciones y seguridad para garantizar la integridad y seguridad del software a lo largo de todo su ciclo de vida.

- Diagrama de pipeline CI/CD. -

Integración continua

Esta etapa se centra en producir un código fuente libre de errores, vulnerabilidades y problemas de calidad, manteniendo así un desarrollo estable y eficiente.

Gestión segura del repositorio de código fuente

El almacenamiento de código fuente y el control de versiones son fundamentales en el desarrollo de software. Es necesario un lugar, o repositorio, donde almacenar el código y mantener múltiples versiones, ya que el código se actualiza constantemente con nuevas características y mejoras. Para elegir una solución de gestión de repositorios de código, es crucial mantener la integridad de los cambios desde la primera versión, lo que asegura que todas las modificaciones queden registradas y se puedan rastrear en cualquier momento. Las soluciones de control de versiones son esenciales para gestionar cambios frecuentes y permitir que múltiples desarrolladores trabajen en el mismo código simultáneamente. Herramientas comunes basadas en Git (como Gitlab o Github) y SVN (como TortoiseSVN) facilitan la gestión del código fuente; Git, por ejemplo, es un sistema distribuido donde cada colaborador tiene su copia del código, mientras que SVN es un sistema centralizado.

Una configuración comúnmente insegura es permitir que cualquier usuario en una red interna se registre en una instancia de un sistema de versionado y acceda a repositorios públicos, lo que amplía la superficie de ataque. En grandes organizaciones, esto puede resultar en la exposición de información confidencial debido a la creencia equivocada de que la red interna es una barrera de seguridad suficiente. Herramientas automatizadas, como scripts que usan la API del gestor de versiones, pueden explotar esta vulnerabilidad para enumerar y descargar repositorios públicos completos. Y, además, no hay que perder de vista que, si el desarrollo incluye código inseguro o información sensible en cualquier punto, este puede seguir siendo visible o accesible para los usuarios del sistema, incluso si se ha corregido en versiones posteriores. Este riesgo se amplifica en el caso de repositorios abiertos, donde cualquier usuario con acceso puede revisar el historial completo de cambios y encontrar vulnerabilidades o datos confidenciales, como credenciales, que puedan comprometer la seguridad del proyecto.

Para asegurar la seguridad del código fuente en un entorno de desarrollo de forma general, es fundamental implementar varias prácticas de seguridad:

  • La protección de ramas impide que los cambios se realicen directamente en ramas críticas, como la principal, sin pasar por revisiones de código y pruebas automatizadas, garantizando que solo el código revisado y aprobado se fusione. 
  • Las variables de entorno permiten almacenar datos sensibles, como contraseñas o claves API, de manera segura, fuera del código fuente, evitando que se expongan en el repositorio. Además, si se utiliza una solución de gestión de secretos, es importante revisar sus mecanismos de cifrado.
  • Los controles de acceso granulares definen permisos específicos para cada usuario o grupo, asegurando que solo personas autorizadas puedan acceder o modificar partes sensibles del código.
  • Herramientas como GittyLeaks, gitleaks o trufflehog pueden ayudar a auditar el nivel de información sensible de versiones anteriores almacenadas en el repositorio.

Gestión segura de dependencias

La gestión de dependencias es un aspecto crucial del desarrollo de software, ya que muchas veces el código que escribimos es solo una pequeña parte del total, apoyándonos en gran medida en bibliotecas y kits de desarrollo de software (SDKs). Estas dependencias permiten a los desarrolladores reutilizar código existente, acelerar el desarrollo y reducir la necesidad de reinventar la rueda, lo que mejora la eficiencia general. Sin embargo, el uso extensivo de dependencias también introduce riesgos de seguridad significativos que deben gestionarse cuidadosamente. Las dependencias pueden ser externas, o internas, desarrolladas y mantenidas por una organización para su propio uso.

Las dependencias externas como las bibliotecas disponibles públicamente en gestores como PyPi para Python, NuGet para .NET, o Gems para Ruby; presentan riesgos de seguridad porque no tenemos control total sobre su mantenimiento y evolución; estas bibliotecas podrían contener vulnerabilidades o verse comprometidas a través de ataques a la cadena de suministro. Por ejemplo, si un gestor de paquetes o un CDN que distribuye estas bibliotecas es comprometido, un atacante podría insertar código malicioso en una biblioteca ampliamente utilizada, afectando potencialmente a todas las aplicaciones que la utilizan. Como ejemplo, podemos citar el caso del incidente de seguridad con la biblioteca 'event-stream' en 2018.

Para mitigar estos riesgos, es crucial:

  • Actualizar y aplicar parches a las dependencias de manera regular, realizando parcheos de emergencia ante vulnerabilidades graves. 
  • Además, copiar y alojar estas dependencias externamente desarrolladas de forma interna puede reducir la superficie de ataque, limitando la exposición a fuentes externas, pero es esencial sincronizarlas regularmente con las versiones más recientes de los repositorios públicos para asegurarse de que se apliquen los parches de seguridad más recientes. 
  • El uso de Sub-resource Integrity (SRI) ayuda a proteger las bibliotecas de JavaScript contra manipulaciones durante la carga. Complementar SRI con auditorías de seguridad regulares garantiza que las bibliotecas utilizadas no contengan vulnerabilidades desde el origen.

Por otro lado, las dependencias internas, también conllevan riesgos. Aunque son desarrolladas y mantenidas internamente, pueden volverse obsoletas si dejan de recibir actualizaciones o si el equipo que las desarrolló ya no está disponible. Además, una vulnerabilidad en una biblioteca interna podría afectar a todas las aplicaciones que la utilizan dentro de la organización, generando un riesgo generalizado. Para proteger estas dependencias, es esencial:

  • Mantenerlas activamente, asegurando que no se conviertan en código legado sin soporte y que cualquier vulnerabilidad en ellas no afecte a múltiples aplicaciones.
  • Además, la infraestructura de alojamiento de estas dependencias debe estar protegida, proteger los paquetes mediante el control de ámbitos para restringir el uso de las dependencias a aplicaciones o servicios específicos dentro de la organización asegurando que solo las aplicaciones que realmente necesitan una determinada dependencia tengan acceso a ella, y utilizar características de verificación del lado del cliente como el bloqueo de versiones para detectar y evitar la ejecución de código malicioso. Cabe destacar que este bloqueo de versiones evita la instalación de dependencias no aprobadas, pero debe ser complementado con escaneos de seguridad regulares para asegurar que las versiones bloqueadas no tengan vulnerabilidades conocidas

Pruebas de seguridad SAST

La integración de SAST (Static Application Security Testing) en CI permite detectar problemas de seguridad de manera temprana, durante el desarrollo del software, y proporciona retroalimentación inmediata a los desarrolladores. Sin embargo, aunque son eficaces para ahorrar tiempo en comparación con las revisiones manuales, estas herramientas pueden generar falsos positivos y, aunque muchas ofrecen configuraciones avanzadas para reducir su frecuencia, es importante ajustar las reglas de escaneo y realizar validaciones manuales, asegurando que las vulnerabilidades reales sean tratadas de manera eficiente. Sistemas como Gitlab y GitHub integran el SAST de forma nativa. Algunas pautas para mejorar la integración de SAST en CI incluyen:

  • Integrar herramientas como Semgrep en los IDEs de los desarrolladores permite realizar análisis de seguridad durante la fase de codificación, mejorando la calidad del código antes de las fases de integración. Para alertas en tiempo real, se pueden utilizar otras herramientas como SonarLint, que ofrecen detección de problemas mientras el desarrollador escribe el código. Además, estas herramientas permiten definir umbrales de severidad que determinen cuándo se debe detener el proceso de integración continua.
  • Además del SAST, se pueden realizar análisis de flujo de datos y detección de contaminación de datos, mediante herramientas complementarias, proporcionando una capa adicional de seguridad antes del despliegue del software.
  • También se pueden integrar herramientas de gestión de vulnerabilidades, como Jira, GitLab Issues o GitHub Issues, para hacer un seguimiento detallado de las vulnerabilidades detectadas por SAST y gestionar su corrección de manera sistemática dentro del pipeline de desarrollo. Estas herramientas permiten crear tickets o tareas de forma automática cuando se detectan problemas de seguridad, asignándolos a los desarrolladores correspondientes para su revisión y corrección. Esto asegura que cada vulnerabilidad sea monitorizada desde su detección hasta su resolución.

Entrega continua

Esta fase asegura que las versiones del software que han pasado las pruebas iniciales se entreguen de manera rápida y segura, permitiendo revisiones adicionales y asegurando que el producto esté listo para ser lanzado bajo aprobación.

Pruebas de seguridad DAST

A diferencia del pentesting manual de aplicaciones, DAST (Dynamic Application Security Testing) en un pipeline de CI/CD suele implicar la ejecución automática de pruebas de seguridad en diferentes etapas del proceso de desarrollo y despliegue, que complementadas con pruebas manuales permite detectar vulnerabilidades en aplicaciones dinámicas.

La integración de DAST puede parecer sencilla, pero requiere decisiones clave, como gestionar adecuadamente los falsos positivos, determinar en qué etapas se ejecutarán los escaneos, qué eventos los desencadenarán y la intensidad de cada escaneo para evitar ralentizar el equipo de desarrollo. Es esencial que estas decisiones se tomen en colaboración con el equipo de desarrollo para asegurar que los requisitos de seguridad se cumplan sin interrumpir significativamente los procesos establecidos:

  • Por ejemplo, se puede utilizar una herramienta automatizada como OWASP ZAP, ejecutada mediante Zap2docker, que permite ejecutar escaneos de seguridad de forma integrada en contenedores Docker durante el proceso de construcción del pipeline
  • Si usamos orquestadores, como Jenkins, también es posible configurar una nueva etapa en el orquestador para ejecutar estos escaneos
  • Finalmente, implementar mecanismos de monitorización y revisión para los resultados de los escaneos permite una respuesta rápida a las vulnerabilidades detectadas, optimizando así la seguridad del desarrollo sin comprometer la velocidad de entrega del software.

Despliegue continuo

El despliegue continuo lleva la automatización al siguiente nivel, permitiendo que cada cambio aprobado en el código se despliegue automáticamente en el entorno deseado. Esta etapa elimina la intervención manual en el proceso de despliegue, garantizando una entrega rápida y eficiente del software al usuario final, minimizando los riesgos de errores humanos y tiempos de inactividad.

Riesgos en las configuraciones de entornos

Es común tener varios entornos para el desarrollo, las pruebas o la producción en un pipeline de CD para diferentes etapas, pero la configuración incorrecta de estos entornos también introduce factores de riesgo que son potenciados por la automatización. Por ejemplo, el uso compartido de runners entre entornos puede permitir que una vulnerabilidad en un entorno menos seguro comprometa uno más crítico, como PROD, facilitando ataques transversales y escaladas de privilegios que podrían comprometer la integridad del pipeline. Además, si no se implementan controles de acceso adecuados, existe el riesgo de manipulación no autorizada de los runners, lo que podría llevar a la ejecución de código malicioso o acceso no autorizado a entornos sensibles. La falta de aislamiento de recursos entre entornos y las configuraciones de seguridad inconsistentes aumentan la exposición a posibles ataques internos y externos, poniendo en riesgo la seguridad de todo el pipeline de desarrollo.

Algunas pautas para mejorar la seguridad en los entornos incluyen: 

  • Aislar los entornos: se debe asegurar que los pipelines de DEV y PRO estén aislados, utilizando runners de compilación separados o etiquetas de runners específicas para cada entorno. Esto previene que cualquier compromiso en el entorno DEV afecte directamente al entorno PRO. Cada entorno debe contar con recursos dedicados para evitar vulnerabilidades compartidas.
  • Restringir el acceso a los trabajos de CD: es importante limitar el acceso a las máquinas que ejecutan los runners de compilación (como los GitLab Runners) únicamente al personal autorizado. Se deben implementar controles de acceso estrictos y utilizar la función de ‘entornos de CD Protegidos’ de GitLab para definir quién puede desplegar en entornos específicos. Esta función permite establecer permisos sobre quién puede modificar las configuraciones de CD, incluido el archivo .gitlab-ci.yml, garantizando que solo usuarios autorizados puedan realizar cambios.
  • Monitorización y alertas: se recomienda configurar sistemas de alertas para el pipeline de CD y los runners de compilación. Estos sistemas deben incluir alertas para cualquier actividad sospechosa, como intentos de acceso no autorizado o compilaciones fallidas que puedan indicar un compromiso. Es fundamental revisar y auditar regularmente los permisos de acceso a los entornos y configuraciones de CD para asegurar que solo el personal necesario tenga acceso. También se debe revocar el acceso a cualquier usuario o rol que ya no lo requiera, manteniendo un control estricto sobre quién puede afectar los procesos de construcción y despliegue.

Conclusión

Con la introducción de herramientas y procesos automatizados, la superficie de ataque se amplía considerablemente. Cada componente del pipeline CI/CD, como los runners, repositorios y registros de imágenes, se convierte en un posible vector de ataque si no se gestionan adecuadamente los controles de seguridad, por lo que un atacante ya no necesita centrarse únicamente en la aplicación final.

Si un pipeline automatizado no está bien protegido, puede ser explotado para inyectar código malicioso, modificar configuraciones o extraer datos sensibles antes de que el software llegue al entorno de producción. Esta posibilidad aumenta el riesgo de comprometer la aplicación final, ya que cualquier vulnerabilidad en el pipeline puede facilitar ataques a la producción si no existen controles de seguridad robustos. La segmentación adecuada de entornos y la validación continua del pipeline pueden reducir significativamente este riesgo.

Por lo tanto, es fundamental implementar una automatización segura dentro del pipeline para asegurarse de que no contribuya al riesgo de compromiso de la aplicación. Esto implica incorporar controles de seguridad en cada etapa del pipeline, desde la integración del código y las pruebas hasta el despliegue y la entrega continua. Es necesario establecer políticas de acceso estrictas, como la autenticación multifactor (MFA), asegurar los runners y entornos de ejecución, gestionar adecuadamente los secretos y variables sensibles mediante la rotación automática y monitorizar continuamente las actividades en el pipeline para detectar posibles anomalías o intentos de acceso no autorizado. Además, capacitar al equipo de desarrollo sobre las mejores prácticas de seguridad y mantener una vigilancia constante a través de auditorías y revisiones periódicas son pasos esenciales para garantizar que la automatización, en lugar de ser una vulnerabilidad, se convierta en una fortaleza que impulse tanto la eficiencia como la seguridad del desarrollo de software.