Capítulo 4. Compromisos de diseño

Este trabajo se ha traducido utilizando IA. Agradecemos tus opiniones y comentarios: translation-feedback@oreilly.com

¡Así que vas a construir un producto (de software)! Tendrás muchas cosas en las que pensar en este complejo viaje, desde la concepción de planes de alto nivel hasta la implementación del código.

Normalmente, empezarás con una idea aproximada de lo que va a hacer el producto o servicio. Por ejemplo, esto podría adoptar la forma de un concepto de alto nivel para un juego, o un conjunto de requisitos empresariales de alto nivel para una aplicación de productividad basada en la nube. También desarrollarás planes de alto nivel sobre cómo se financiará la oferta de servicios.

A medida que te adentras en el proceso de diseño y tus ideas sobre la forma del producto se hacen más específicas, tienden a surgir requisitos y limitaciones adicionales sobre el diseño y la implementación de la aplicación. Habrá requisitos específicos para la funcionalidad del producto, y limitaciones generales, como los costes de desarrollo y operativos. También te encontrarás con requisitos y limitaciones de seguridad y fiabilidad: es probable que tu servicio tenga ciertos requisitos de disponibilidad y fiabilidad, y puede que tengas requisitos de seguridad para proteger los datos sensibles de los usuarios que maneja tu aplicación.

Algunos de estos requisitos y limitaciones pueden entrar en conflicto entre sí, y tendrás que hacer concesiones y encontrar el equilibrio adecuado entre ellos.

Objetivos y requisitos del diseño

Los requisitos de funcionalidad de tu producto tenderán a tener características significativamente diferentes de los requisitos de seguridad y fiabilidad. Echemos un vistazo más de cerca a los tipos de requisitos a los que te enfrentarás al diseñar un producto.

Requisitos de las funciones

Los requisitos de características, también conocidos como requisitos funcionales1 identifican la función principal de un servicio o aplicación y describen cómo un usuario puede realizar una tarea concreta o satisfacer una necesidad particular. A menudo se expresan en términos de casos de uso, historias de usuario o viajes de usuario, es decir, secuenciasde interacciones entre un usuario y el servicio o la aplicación. Los requisitos críticos son el subconjunto de requisitos de características que son esenciales para el producto o servicio. Si un diseño no satisface un requisito crítico o una historia de usuario crítica, no tienes un producto viable.

Los requisitos de las características suelen ser los principales impulsores de tus decisiones de diseño. Al fin y al cabo, estás intentando construir un sistema o servicio que satisfaga un conjunto concreto de necesidades para el grupo de usuarios que tienes en mente. A menudo tienes que tomar decisiones de compromiso entre los distintos requisitos. Teniendo esto en cuenta, es útil distinguir los requisitos críticos de otros requisitos de características.

Normalmente, una serie de requisitos se aplican a toda la aplicación o servicio. Estos requisitos no suelen aparecer en las historias de usuario ni en los requisitos de las características individuales. En su lugar, se enuncian una vez en la documentación centralizada de requisitos, o incluso se asumen implícitamente. He aquí un ejemplo:

Todas las vistas/páginas de la interfaz de usuario web de la aplicación deben:
  • Sigue las pautas comunes de diseño visual
  • Cumplir las directrices de accesibilidad
  • Tener un pie de página con enlaces a la política de privacidad y a las ToS (Condiciones del servicio)

Requisitos no funcionales

Varias categorías de requisitos se centran en atributos o comportamientos generales del sistema, más que en comportamientos específicos. Estos requisitos no funcionales son relevantes para nuestro objetivo: la seguridad y la fiabilidad. Por ejemplo:

  • ¿Cuáles son las circunstancias exclusivas en las que alguien (un usuario externo, un agente de atención al cliente o un ingeniero de operaciones) puede tener acceso a determinados datos?

  • ¿Cuáles son los objetivos de nivel de servicio (SLO) para métricas como el tiempo de actividad o la latencia de respuesta de los percentiles 95 y 99? ¿Cómo responde el sistema bajo carga por encima de un determinado umbral?

Al equilibrar los requisitos, puede ser útil considerar simultáneamente los requisitos en áreas más allá del propio sistema, ya que las elecciones en esas áreas pueden tener un impacto significativo en los requisitos básicos del sistema. Entre esas áreas más amplias se incluyen las siguientes

Eficacia y velocidad de desarrollo
Teniendo en cuenta el lenguaje de implementación elegido, los marcos de aplicación, los procesos de prueba y los procesos de creación, ¿con qué eficacia pueden los desarrolladores iterar sobre nuevas funciones? ¿Con qué eficacia pueden los desarrolladores comprender y modificar o depurar el código existente?
Velocidad de Implementación
¿Cuánto tiempo pasa desde que se desarrolla una función hasta que está disponible para los usuarios/clientes?

Características frente a propiedades emergentes

Los requisitos de las características suelen mostrar una conexión bastante directa entre los requisitos, el código que satisface esos requisitos y las pruebas que validan la implementación. Por ejemplo:

Especificación
Una historia de usuario o requisito puede estipular cómo un usuario que ha iniciado sesión en una aplicación puede ver y modificar los datos personales asociados a su perfil de usuario (como su nombre e información de contacto).
Aplicación

Una aplicación web o móvil basada en esta especificación tendría normalmente un código que se refiriera muy específicamente a ese requisito, como el siguiente:

  • Tipos estructurados para representar los datos del perfil

  • Código UI para presentar y permitir la modificación de los datos del perfil

  • Manejadores de acciones RPC o HTTP del lado del servidor para consultar los datos del perfil del usuario que ha iniciado sesión desde un almacén de datos, y para aceptar información actualizada que se escribirá en el almacén de datos.

Validación
Normalmente, habría una prueba de integración que recorriera paso a paso la historia de usuario especificada. La prueba podría utilizar un controlador de pruebas de interfaz de usuario para rellenar y enviar el formulario "editar perfil" y, a continuación, verificar que los datos enviados aparecen en el registro de base de datos esperado. Es probable que también haya pruebas unitarias para los pasos individuales de la historia de usuario.

En cambio, los requisitos no funcionales -como los de fiabilidad y seguridad- suelen ser mucho más difíciles de precisar. Estaría bien que tu servidor web tuviera una bandera --enable_high_reliability_mode, y que para que tu aplicación fuera fiable sólo tuvieras que darle la vuelta a esa bandera y pagar a tu proveedor de alojamiento o de la nube una tarifa de servicio premium. Pero no existe tal bandera, ni ningún módulo o componente específico en el código fuente de ninguna aplicación que "implemente" la fiabilidad.

Ejemplo: Documento de diseño de Google

Google utiliza una plantilla de documento de diseño para orientar el diseño de nuevas funciones y recoger las opiniones de las partes interesadas antes de iniciar un proyecto de ingeniería.

Las secciones de la plantilla relativas a las consideraciones de fiabilidad y seguridad recuerdan a los equipos que deben pensar en las implicaciones de su proyecto y poner en marcha los procesos de preparación para la producción o de revisión de la seguridad, si procede. Las revisiones de diseño a veces se producen varios trimestres antes de que los ingenieros empiecen a pensar oficialmente en la fase de lanzamiento.

Requisitos de equilibrio

Dado que los atributos de un sistema que satisfacen las preocupaciones de seguridad y fiabilidad son en gran medida propiedades emergentes, tienden a interactuar tanto con las implementaciones de los requisitos de las características como entre sí. Como resultado, es especialmente difícil razonar sobre las compensaciones que implican seguridad y fiabilidad como tema independiente.

Esta sección presenta un ejemplo que ilustra los tipos de compensaciones que podrías tener que considerar. Algunas partes de este ejemplo profundizan bastante en detalles técnicos, que no son necesariamente importantes en sí mismos. Todas las consideraciones de cumplimiento, normativas, legales y empresariales que intervienen en el diseño de los sistemas de procesamiento de pagos y su funcionamiento tampoco son importantes para este ejemplo. En su lugar, el propósito es ilustrar las complejas interdependencias entre requisitos. En otras palabras, la atención no se centra en los detalles esenciales sobre la protección de los números de las tarjetas de crédito, sino en el proceso de reflexión que conlleva el diseño de un sistema con complejos requisitos de seguridad y fiabilidad.

Ejemplo: Procesamiento de pagos

Imagina que estás construyendo un servicio online que vende widgets a los consumidores.2 La especificación del servicio incluye una historia de usuario que estipula que un usuario puede elegir widgets de un catálogo online utilizando una aplicación móvil o web. A continuación, el usuario puede comprar los widgets elegidos, para lo que debe proporcionar los datos de un método de pago.

Consideraciones sobre seguridad y fiabilidad

Aceptar información de pago introduce importantes consideraciones de seguridad y fiabilidad para el diseño del sistema y los procesos organizativos. Los nombres, direcciones y números de tarjetas de crédito son datos personales sensibles que requieren salvaguardas especiales3 y pueden someter tu sistema a normas reguladoras, dependiendo de la jurisdicción aplicable. Aceptar información de pago también puede hacer que el servicio esté sujeto al cumplimiento de normas de seguridad reguladoras o del sector, como la PCI DSS.

Un compromiso de esta información sensible del usuario, especialmente la información personal identificable (IPI), puede tener graves consecuencias para el proyecto e incluso para toda la organización/empresa. Podrías perder la confianza de tus usuarios y clientes y, como consecuencia, perder su negocio. En los últimos años, las legislaturas han promulgado leyes y reglamentos que imponen obligaciones potencialmente largas y costosas a las empresas afectadas por violaciones de datos. Algunas empresas incluso han quebrado por completo a causa de un grave incidente de seguridad, como se indica en el Capítulo 1.

En determinados casos, un compromiso de mayor nivel en el diseño del producto podría liberar a la aplicación del procesamiento de pagos; por ejemplo, quizá el producto pueda refundirse en un modelo basado en la publicidad o financiado por la comunidad. A efectos de nuestro ejemplo, nos ceñiremos a la premisa de que aceptar pagos es un requisito crítico.

Utilizar un proveedor de servicios externo para gestionar datos sensibles

A menudo, la mejor manera de mitigar las preocupaciones de seguridad sobre los datos sensibles es no almacenar esos datos en primer lugar (para más información sobre este tema, consulta el Capítulo 5). Es posible que puedas hacer que los datos sensibles nunca pasen por tus sistemas, o al menos diseñar los sistemas para que no almacenen los datos de forma persistente.4 Puedes elegir entre varias API comerciales de servicios de pago para integrarlas con la aplicación, y descargar al proveedor la gestión de la información de pago, las transacciones de pago y las cuestiones relacionadas (como las contramedidas contra el fraude).

Beneficios

Dependiendo de las circunstancias, utilizar un servicio de pago puede reducir el riesgo y el grado en que necesitas crear experiencia interna para abordar los riesgos en este ámbito, confiando en cambio en la experiencia del proveedor:

  • Tus sistemas ya no contienen los datos sensibles, lo que reduce el riesgo de que una vulnerabilidad en tus sistemas o procesos pueda poner en peligro los datos. Por supuesto, un compromiso del proveedor externo podría comprometer los datos de tus usuarios.

  • Dependiendo de las circunstancias específicas y de los requisitos aplicables, tus obligaciones contractuales y de cumplimiento de las normas de seguridad del sector de pagos pueden simplificarse.

  • No tienes que construir y mantener una infraestructura para proteger los datos en reposo en los almacenes de datos de tu sistema. Esto podría eliminar una cantidad significativa de desarrollo y esfuerzo operativo continuo.

  • Muchos proveedores de pagos externos ofrecen contramedidas contra las transacciones fraudulentas y servicios de evaluación del riesgo de pago. Puedes utilizar estas funciones para reducir tu riesgo de fraude en los pagos, sin tener que construir y mantener tú mismo la infraestructura subyacente.

Por otro lado, confiar en un proveedor de servicios externo introduce costes y riesgos propios.

Costes y riesgos no técnicos

Obviamente, el proveedor cobrará comisiones. El volumen de transacciones probablemente influirá en tu elección: a partir de cierto volumen, probablemente sea más rentable procesar las transacciones internamente.

También tienes que tener en cuenta el coste de ingeniería de depender de un tercero: tu equipo tendrá que aprender a utilizar la API del proveedor, y puede que tengas que hacer un seguimiento de los cambios/lanzamientos de la API según el calendario del proveedor.

Riesgos de fiabilidad

Al externalizar el procesamiento de pagos, añades una dependencia adicional a tu aplicación, en este caso, un servicio de terceros. Las dependencias adicionales suelen introducir modos de fallo adicionales. En el caso de las dependencias de terceros, estos modos de fallo pueden estar parcialmente fuera de tu control. Por ejemplo, tu historia de usuario "el usuario puede comprar los widgets que elija" puede fallar si el servicio del proveedor de pagos no funciona o no se puede acceder a él a través de la red. La importancia de este riesgo depende del cumplimiento por parte del proveedor de pago de los SLA que tengas con él.

Puedes abordar este riesgo introduciendo redundancia en el sistema (véase el Capítulo 8), en este caso, añadiendo un proveedor de pagos alternativo al que tu servicio pueda recurrir en caso de fallo. Esta redundancia introduce costes y complejidad: lo más probable es que los dos proveedores de pago tengan API diferentes, por lo que deberás diseñar tu sistema para que pueda comunicarse con ambos, además de todos los costes operativos y de ingeniería adicionales, así como una mayor exposición a errores o problemas de seguridad.

También podrías mitigar el riesgo de fiabilidad mediante mecanismos de reserva por tu parte. Por ejemplo, podrías insertar un mecanismo de cola en el canal de comunicación con el proveedor de pagos para almacenar los datos de la transacción si el servicio de pagos no está disponible. Esto permitiría que la historia de usuario del "flujo de compra" continuara durante una interrupción del servicio de pago.

Sin embargo, añadir el mecanismo de cola de mensajes introduce una complejidad adicional y puede introducir sus propios modos de fallo. Si la cola de mensajes no está diseñada para ser fiable (por ejemplo, sólo almacena datos en memoria volátil), puedes perder transacciones: una nueva superficie de riesgo. En términos más generales, los subsistemas que sólo se ejercitan en circunstancias raras y excepcionales pueden albergar fallos ocultos y problemas de fiabilidad.

Podrías optar por utilizar una implementación de cola de mensajes más fiable. Esto probablemente implique o bien un sistema de almacenamiento en memoria que se distribuya a través de múltiples ubicaciones físicas, introduciendo de nuevo la complejidad, o bien el almacenamiento en disco persistente. Almacenar los datos en disco, aunque sólo sea en situaciones excepcionales, reintroduce las preocupaciones sobre el almacenamiento de datos sensibles (riesgo de compromiso, consideraciones de cumplimiento, etc.) que intentabas evitar en primer lugar. En particular, algunos datos de pago ni siquiera llegan nunca al disco, lo que hace que una cola de reintento que se base en el almacenamiento persistente sea difícil de aplicar en este escenario.

En este sentido, puede que tengas que considerar ataques (en particular, ataques de iniciados) que rompan a propósito el enlace con el proveedor de pagos para activar la puesta en cola local de los datos de la transacción, que puede verse comprometida.

En resumen, ¡acabas encontrándote con un riesgo de seguridad que surgió de tu intento de mitigar un riesgo de fiabilidad, que a su vez surgió porque intentabas mitigar un riesgo de seguridad!

Riesgos de seguridad

La elección del diseño de confiar en un servicio de terceros también plantea consideraciones de seguridad inmediatas.

En primer lugar, estás confiando datos sensibles de clientes a un proveedor externo. Querrás elegir un proveedor cuya postura de seguridad sea al menos igual a la tuya, y tendrás que evaluar cuidadosamente a los proveedores durante la selección y de forma continua. No es una tarea fácil, y existen complejas consideraciones contractuales, normativas y de responsabilidad que quedan fuera del alcance de este libro y que deberías remitir a tu abogado.

En segundo lugar, la integración con el servicio del proveedor puede requerir que vincules en tu aplicación una biblioteca suministrada por el proveedor. Esto introduce el riesgo de que una vulnerabilidad en esa biblioteca, o en una de sus dependencias transitivas, pueda dar lugar a una vulnerabilidad en tus sistemas. Puedes considerar mitigar este riesgo aislando la biblioteca5 y estando preparado para implementar rápidamente versiones actualizadas de la misma (consulta el Capítulo 7). Puedes evitar en gran medida este problema utilizando un proveedor que no te exija vincular una biblioteca propietaria a tu servicio (véase el Capítulo 6). Las bibliotecas propietarias pueden evitarse si el proveedor expone su API utilizando un protocolo abierto como REST+JSON, XML, SOAP o gRPC.

Puede que necesites incluir una biblioteca JavaScript en el cliente de tu aplicación web para integrarte con el proveedor. Hacerlo te permite evitar pasar los datos de pago a través de tus sistemas, aunque sea temporalmente; en su lugar, los datos de pago pueden enviarse desde el navegador del usuario directamente al servicio web del proveedor. Sin embargo, esta integración plantea problemas similares a los de incluir una biblioteca del lado del servidor: el código de la biblioteca del proveedor se ejecuta con plenos privilegios en el origen web de tu aplicación.6 Una vulnerabilidad en ese código o un compromiso del servidor que sirve esa biblioteca puede hacer que tu aplicación se vea comprometida. Podrías considerar mitigar ese riesgo aislando la funcionalidad relacionada con el pago en un origen web separado o en un iframe aislado. Sin embargo, esta táctica significa que necesitas un mecanismo seguro de comunicaciones entre orígenes, lo que introduce de nuevo complejidad y modos de fallo adicionales. Otra posibilidad es que el proveedor de pagos ofrezca una integración basada en redireccionamientos HTTP, pero esto puede dar lugar a una experiencia de usuario menos fluida.

Las elecciones de diseño relacionadas con requisitos no funcionales pueden tener implicaciones de bastante alcance en áreas de conocimientos técnicos específicos del dominio: empezamos discutiendo un compromiso relacionado con la mitigación de riesgos asociados a la gestión de datos de pago, y acabamos pensando en consideraciones que se adentran en el ámbito de la seguridad de la plataforma web. Por el camino, también nos encontramos con preocupaciones contractuales y normativas .

Gestionar las tensiones y alinear los objetivos

Con un poco de planificación previa, a menudo puedes satisfacer importantes requisitos no funcionales, como la seguridad y la fiabilidad, sin tener que renunciar a características, y a un coste razonable. Al dar un paso atrás para considerar la seguridad y la fiabilidad en el contexto de todo el sistema y del flujo de trabajo de desarrollo y operaciones, a menudo se hace evidente que estos objetivos están muy alineados con los atributos generales de calidad del software.

Ejemplo: Microservicios y el Marco de Aplicaciones Web de Google

Considera la evolución de un marco interno de Google para microservicios y aplicaciones web. El objetivo principal del equipo que creó el marco era agilizar el desarrollo y funcionamiento de aplicaciones y servicios para grandes organizaciones. Al diseñar este marco, el equipo incorporó la idea clave de aplicar comprobaciones de conformidad estáticas y dinámicas para garantizar que el código de la aplicación se adhiere a diversas directrices de codificación y buenas prácticas. Por ejemplo, una comprobación de conformidad verifica que todos los valores pasados entre contextos de ejecución concurrentes sean de tipos inmutables, una práctica que reduce drásticamente la probabilidad de errores de concurrencia. Otro conjunto de comprobaciones de conformidad impone restricciones de aislamiento entre componentes, lo que hace mucho menos probable que un cambio en un componente/módulo de la aplicación provoque un error en otro componente.

Dado que las aplicaciones creadas en este marco tienen una estructura bastante rígida y bien definida, el marco puede proporcionar automatización inmediata para muchas tareas comunes de desarrollo e implementación: desde andamiaje para nuevos componentes hasta configuración automatizada de entornos de integración continua (CI), pasando por implementaciones de producción automatizadas en gran medida. Estas ventajas han hecho que este marco sea muy popular entre los desarrolladores de Google.

¿Qué tiene que ver todo esto con la seguridad y la fiabilidad? El equipo de desarrollo del marco colaboró con los equipos de SRE y seguridad durante las fases de diseño e implementación, asegurándose de que las buenas prácticas de seguridad y fiabilidad se entretejieran en el tejido del marco, y no se añadieran al final. El marco se responsabiliza de muchos problemas comunes de seguridad y fiabilidad. Del mismo modo, configura automáticamente el monitoreo de las métricas operativas e incorpora funciones de fiabilidad como la comprobación del estado y el cumplimiento de los SLA.

Por ejemplo, el soporte para aplicaciones web del marco de trabajo gestiona los tipos más comunes de vulnerabilidades de las aplicaciones web.7 Mediante una combinación de diseño de API y comprobaciones de conformidad del código, evita eficazmente que los desarrolladores introduzcan accidentalmente muchos tipos comunes de vulnerabilidades en el código de las aplicaciones.8 Con respecto a estos tipos de vulnerabilidades, el marco va más allá de la "seguridad por defecto", sino que asume toda la responsabilidad de la seguridad y garantiza activamente que ninguna aplicación basada en él se vea afectada por estos riesgos. En los Capítulos 6 y 12 tratamos con más detalle cómo se consigue esto.

Alinear los requisitos de Emergencia-Propiedad

El ejemplo del marco ilustra que, contrariamente a la percepción común, los objetivos relacionados con la seguridad y la fiabilidad suelen estar bien alineados con otros objetivos del producto, especialmente con la salud y la mantenibilidad del código y del proyecto, y con la velocidad sostenida y a largo plazo del proyecto. Por el contrario, intentar adaptar los objetivos de seguridad y fiabilidad como un añadido tardío suele conllevar mayores riesgos y costes.

Las prioridades en materia de seguridad y fiabilidad también pueden alinearse con las prioridades en otros ámbitos:

  • Como se expone en el Capítulo 6, un diseño del sistema que permita a las personas razonar con eficacia y precisión sobre las invariantes y los comportamientos del sistema es crucial para la seguridad y la fiabilidad. La comprensibilidad es también un atributo clave del código y de la salud del proyecto, y un apoyo clave para la velocidad de desarrollo: un sistema comprensible es más fácil de depurar y de modificar (sin introducir errores en primer lugar).

  • Diseñar para la recuperación (véase el Capítulo 9) nos permite cuantificar y controlar el riesgo introducido por los cambios y las implantaciones. Normalmente, los principios de diseño aquí expuestos permiten un ritmo de cambio (es decir, una velocidad de implementación) mayor del que podríamos conseguir de otro modo.

  • La seguridad y la fiabilidad exigen que diseñemos para un panorama cambiante (véase el Capítulo 7). Hacerlo hace que el diseño de nuestro sistema sea más adaptable y nos coloca no sólo en posición de abordar con rapidez las nuevas vulnerabilidades y escenarios de ataque que surjan, sino también de acomodarnos más rápidamente a los cambiantes requisitos empresariales.

Velocidad inicial frente a velocidad sostenida

Existe una tendencia natural, especialmente en los equipos más pequeños, a aplazar los problemas de seguridad y fiabilidad hasta algún momento en el futuro ("Añadiremos la seguridad y nos preocuparemos de la ampliación cuando tengamos algunos clientes"). Los equipos suelen justificar el hecho de ignorar la seguridad y la fiabilidad como impulsores iniciales y principales del diseño en aras de la "velocidad": les preocupa que dedicar tiempo a pensar y abordar estas cuestiones ralentice el desarrollo e introduzca retrasos inaceptables en su primer ciclo de lanzamiento.

Es importante distinguir entre velocidad inicial y velocidad sostenida. Optar por no tener en cuenta requisitos críticos como la seguridad, la fiabilidad y la mantenibilidad al principio del ciclo del proyecto puede, en efecto, aumentar la velocidad de tu proyecto al principio de su vida útil. Sin embargo, la experiencia demuestra que hacerlo también suele ralentizarte considerablemente más adelante.10 El coste tardío de adaptar un diseño para dar cabida a requisitos que se manifiestan como propiedades emergentes puede ser muy considerable. Además, hacer cambios invasivos en una fase tardía para abordar los riesgos de seguridad y fiabilidad puede, en sí mismo, introducir aún más riesgos de seguridad y fiabilidad. Por tanto, es importante integrar la seguridad y la fiabilidad en la cultura de tu equipo desde el principio (para más información sobre este tema, consulta el Capítulo 21).

La historia temprana de Internet11 y el diseño y evolución de los protocolos subyacentes como IP, TCP, DNS y BGP, ofrece una perspectiva interesante sobre este tema. Fiabilidad -en particular, la capacidad de supervivencia de la red incluso ante interrupciones de los nodos12 y la fiabilidad de las comunicaciones a pesar de los enlaces propensos a fallos13-eran objetivos de diseño explícitos y prioritarios de los primeros precursores de la Internet actual, como ARPANET.

La seguridad, sin embargo, no se menciona mucho en los primeros documentos y documentos de Internet. Las primeras redes eran esencialmente cerradas, con nodos operados por instituciones de investigación y gubernamentales de confianza. Pero en la Internet abierta de hoy, este supuesto no se cumple en absoluto: en la red participan muchos tipos de actores maliciosos (véase el Capítulo 2).

Los protocolos fundacionales de Internet -IP, UDP y TCP- no tienen ninguna disposición para autenticar al originador de las transmisiones, ni para detectar la modificación intencionada y maliciosa de los datos por parte de un nodo intermedio de la red. Muchos protocolos de nivel superior, como HTTP o DNS, son intrínsecamente vulnerables a diversos ataques de participantes malintencionados en la red. Con el tiempo, se han desarrollado protocolos seguros o extensiones de protocolo para defenderse de tales ataques. Por ejemplo, HTTPS aumenta HTTP mediante la transferencia de datos a través de un canal autenticado y seguro. En la capa IP, IPsec autentica criptográficamente a los pares a nivel de red y proporciona integridad y confidencialidad de los datos. IPsec puede utilizarse para establecer VPN sobre redes IP no fiables.

Sin embargo, la implementación generalizada de estos protocolos seguros ha resultado bastante difícil. Llevamos aproximadamente 50 años de historia de Internet, y el uso comercial significativo de Internet comenzó quizás hace 25 o 30 años, pero todavía hay una fracción sustancial del tráfico web que no utiliza HTTPS.14

Otro ejemplo de la compensación entre velocidad inicial y sostenida (en este caso fuera del ámbito de la seguridad y la fiabilidad) son los procesos de desarrollo ágiles. Uno de los principales objetivos de los flujos de trabajo de desarrollo ágil es aumentar la velocidad de desarrollo e implementación, en concreto, reducir la latencia entre la especificación de las características y la implementación. Sin embargo, los flujos de trabajo Ágiles suelen basarse en prácticas de pruebas unitarias y de integración razonablemente maduras y en una sólida infraestructura de integración continua, que requieren una inversión inicial para su establecimiento, a cambio de beneficios a largo plazo para la velocidad y la estabilidad.

En términos más generales, puedes optar por dar prioridad a la velocidad inicial del proyecto por encima de todo: puedes desarrollar la primera iteración de tu aplicación web sin pruebas y con un proceso de publicación que consista en copiar archivos tar a hosts de producción. Es probable que consigas sacar tu primera demo relativamente rápido, pero para tu tercera versión, es muy posible que tu proyecto esté retrasado y cargado de deuda técnica.

Ya hemos hablado de la alineación entre fiabilidad y velocidad: invertir en un flujo de trabajo y una infraestructura maduros de integración continua/implantación continua (CI/CD) permite realizar lanzamientos de producción frecuentes con un riesgo de fiabilidad gestionado y aceptable (véase el Capítulo 7). Pero establecer un flujo de trabajo de este tipo requiere cierta inversión inicial; por ejemplo, necesitarás lo siguiente:

  • Cobertura de pruebas unitarias y de integración lo suficientemente robusta como para garantizar un riesgo de defectos aceptablemente bajo para las versiones de producción, sin requerir un gran trabajo humano de cualificación de la versión

  • Una canalización CI/CD que sea en sí misma fiable

  • Una infraestructura fiable que se ejercita con frecuencia para realizar rollouts y rollbacks de producción escalonados

  • Una arquitectura de software que permita el despliegue desacoplado de código y configuraciones (por ejemplo, "banderas de características").

Esta inversión suele ser modesta cuando se realiza al principio del ciclo de vida de un producto, y sólo requiere un esfuerzo incremental por parte de los desarrolladores para mantener una buena cobertura de pruebas y "construcciones verdes" de forma continuada. En cambio, un flujo de trabajo de desarrollo con escasa automatización de las pruebas, dependencia de pasos manuales en la implementación y largos ciclos de lanzamiento tiende a empantanar un proyecto a medida que aumenta su complejidad. En ese momento, adaptar la automatización de pruebas y lanzamientos suele requerir mucho trabajo de golpe y puede ralentizar aún más el proyecto. Además, las pruebas readaptadas a un sistema maduro pueden caer a veces en la trampa de ejercitar más el comportamiento actual con errores que el comportamiento correcto y previsto.

Estas inversiones son beneficiosas para proyectos de todos los tamaños. Sin embargo, las grandes organizaciones pueden disfrutar aún más de las ventajas de la escala, ya que pueden amortizar el coste entre muchos proyectos: la inversión de un proyecto individual se reduce entonces al compromiso de utilizar marcos y flujos de trabajo mantenidos centralmente.

Cuando se trata de tomar decisiones de diseño centradas en la seguridad que contribuyan a una velocidad sostenida, recomendamos elegir un marco y un flujo de trabajo que proporcionen una defensa segura por construcción contra las clases de vulnerabilidades pertinentes. Esta elección puede reducir drásticamente, o incluso eliminar, el riesgo de introducir dichas vulnerabilidades durante el desarrollo y mantenimiento continuos de la base de código de tu aplicación (consulta los Capítulos 6 y 12). Por lo general, este compromiso no implica una inversión inicial significativa, sino un esfuerzo incremental y normalmente modesto para adherirse a las restricciones del marco. A cambio, reduces drásticamente el riesgo de interrupciones imprevistas del sistema o de simulacros de respuesta de seguridad que desorganicen los calendarios de implementación. Además, es mucho más probable que tus revisiones de seguridad y preparación para la producción en el momento del lanzamiento se desarrollen sin problemas.

Conclusión

No es fácil diseñar y construir sistemas seguros y fiables, sobre todo porque la seguridad y la fiabilidad son principalmente propiedades emergentes de todo el flujo de trabajo de desarrollo y operaciones. Esta empresa implica pensar en un montón de temas bastante complejos, muchos de los cuales, al principio, no parecen tan relacionados con abordar los requisitos de las características principales de tu servicio.

Tu proceso de diseño implicará numerosas compensaciones entre requisitos de seguridad, fiabilidad y prestaciones. En muchos casos, estas compensaciones parecerán al principio estar en conflicto directo. Puede parecer tentador evitar estas cuestiones en las primeras fases de un proyecto y "ocuparse de ellas más tarde", pero hacerlo a menudo conlleva un coste y un riesgo significativos para tu proyecto: una vez que tu servicio está activo, la fiabilidad y la seguridad no son opcionales. Si tu servicio se cae, puedes perder negocio; y si tu servicio se ve comprometido, responder requerirá que todos se pongan manos a la obra. Pero con una buena planificación y un diseño cuidadoso, a menudo es posible satisfacer estos tres aspectos. Es más, puedes hacerlo con un coste inicial adicional modesto, y a menudo con un esfuerzo total de ingeniería reducido a lo largo de la vida útil del sistema.

1 Para un tratamiento más formal, véase The MITRE Systems Engineering Guide e ISO/IEC/IEEE 29148-2018(E).

2 A efectos del ejemplo, no es relevante qué se está vendiendo exactamente: un medio de comunicación puede exigir pagos por los artículos, una empresa de movilidad puede exigir pagos por el transporte, un mercado online puede permitir la compra de bienes físicos que se envían a los consumidores, o un servicio de pedidos de comida puede facilitar la entrega de pedidos para llevar de restaurantes locales.

3 Véase, por ejemplo, McCallister, Erika, Tim Grance y Karen Scarfone. 2010. Publicación Especial 800-122 del NIST, "Guía para proteger la confidencialidad de la información de identificación personal (IIP) ", https://oreil.ly/T9G4D.

4 Ten en cuenta que si esto es o no apropiado puede depender de los marcos normativos a los que esté sujeta tu organización; estas cuestiones normativas quedan fuera del alcance de este libro.

5 Véase, por ejemplo, el proyecto Sandboxed API.

6 Para más información sobre este tema, véase Zalewski, Michał. 2011. La Red Enredada: Guía para asegurar las aplicaciones web modernas. San Francisco, CA: No Starch Press.

7 Véase, por ejemplo, el Top 10 de OWASP y el TOP 25 de CWE/SANS de los errores de software más peligrosos.

8 Véase Kern, Christoph. 2014. "Asegurar la Web enmarañada". Comunicaciones de la ACM 57(9): 38-47. doi:10.1145/2643134.

9 En Google, el software se construye normalmente desde la HEAD de un repositorio común, lo que hace que todas las dependencias se actualicen automáticamente con cada compilación. Véase Potvin, Rachel, y Josh Levenberg. 2016. "Por qué Google almacena miles de millones de líneas de código en un único repositorio". Comunicaciones de la ACM 59(7): 78-87. https://oreil.ly/jXTZM.

10 Véase el debate sobre programación táctica frente a programación estratégica en Ousterhout, John. 2018. Una filosofía del diseño de software. Palo Alto, CA: Yaknyam Press. Martin Fowler hace observaciones similares.

11 Véase RFC 2235 y Leiner, Barry M. et al. 2009. "Breve historia de Internet". ACM SIGCOMM Computer Communication Review 39(5): 22–31. doi:10.1145/1629607.1629613.

12 Baran, Paul. 1964. "Sobre las redes de comunicaciones distribuidas". IEEE Transactions on Communications Systems 12(1): 1–9. doi:10.1109/TCOM.1964.1088883.

13 Roberts, Lawrence G., y Barry D. Wessler. 1970. "Desarrollo de redes informáticas para conseguir compartir recursos". Actas de la Conferencia Conjunta de Informática de Primavera de 1970: 543–549. doi:10.1145/1476936.1477020.

14 Felt, Adrienne Porter, Richard Barnes, April King, Chris Palmer, Chris Bentzel y Parisa Tabriz. 2017. "Medición de la adopción de HTTPS en la Web". Actas de la 26ª Conferencia USENIX sobre el Simposio de Seguridad: 1323–1338. https://oreil.ly/G1A9q.

Get Construir sistemas seguros y fiables now with the O’Reilly learning platform.

O’Reilly members experience books, live events, courses curated by job role, and more from O’Reilly and nearly 200 top publishers.