Apps web aisladas

La Web es una plataforma de aplicaciones verdaderamente única. Las apps creadas en él son accesibles al instante en cualquier sistema operativo sin necesidad de realizar cambios en el código ni compilaciones. Cada vez que un usuario ingresa a tu app, siempre tiene la versión más actualizada. Son instalables, pueden funcionar sin conexión, son altamente capaces y se pueden compartir fácilmente con un vínculo. Compila una aplicación web y funcionará en cualquier lugar.

Dado que la Web pretende ser segura y protegida de forma predeterminada, su modelo de seguridad debe ser muy conservador. Todas las capacidades nuevas que se agreguen deben ser seguras para que un usuario ocasional las encuentre accidentalmente a través de una URL. A este modelo de seguridad lo llamamos Web de paso. Si bien esto es ideal para muchas aplicaciones y se puede hacer más seguro con las políticas de seguridad del contenido y el aislamiento de origen cruzado, no funciona para todos los casos de uso. Varias APIs muy importantes y potentes, como Direct Sockets y Controlled Frame, que los desarrolladores necesitan, no se pueden proteger lo suficiente para la Web basada en unidades.

En el caso de estas aplicaciones, actualmente no hay una opción para crear en la Web. Para otros, el modelo de seguridad de la Web puede no ser lo suficientemente conservador; es posible que no compartan la suposición de que el servidor es confiable y, en cambio, prefieran aplicaciones independientes discretamente versionadas y firmadas. Se necesita un modelo de seguridad nuevo y de alta confianza. Las apps web aisladas (IWA) proporcionan un modelo de aplicación aislado, empaquetado, con versiones, firmado y de confianza creado sobre la plataforma web existente para habilitar a estos desarrolladores.

Un espectro de confianza en la Web

Puedes pensar en la seguridad y las capacidades en la Web en términos de un espectro.

Ilustración que muestra el espectro de confianza en la Web. A la izquierda, un globo terráqueo que representa la Web de recorrido. En el medio, las apps web progresivas. A la derecha, una pecera con un pez dorado en su interior, que representa las apps web aisladas. Una línea negra continua conecta los tres íconos de forma horizontal, y una línea roja discontinua separa las apps web progresivas de las apps web aisladas.

El sitio web de paso, a la izquierda, tiene el modelo de seguridad de confianza más bajo porque debe ser el más accesible y, por lo tanto, tiene el menor acceso al sistema de un usuario. Las apps web instaladas en el navegador, en el medio, obtienen un poco más de confianza y pueden integrarse un poco más en el sistema del usuario. En general, los usuarios pueden cambiar entre las versiones web de las apps y las versiones instaladas en el navegador sin problemas.

Luego, están las aplicaciones web aisladas de alta confianza.

Se sienten y funcionan más como apps nativas, y pueden acceder a integraciones profundas del sistema y a capacidades potentes. Los usuarios no pueden cambiar entre ellos y el ataque drive-by-web. Si necesitas este nivel de seguridad o estas capacidades, no hay vuelta atrás.

Cuando intentes decidir a qué punto del espectro debes apuntar, elige el modelo de seguridad de menor confianza que puedas, como una app web progresiva. Esto te brindará el mayor alcance, requerirá que administres la menor cantidad de problemas de seguridad por tu cuenta y será lo más flexible para tus desarrolladores y usuarios.

Diseño de seguridad integral

Las apps web aisladas proporcionan un modelo de seguridad de alta confianza para las aplicaciones web. Sin embargo, para habilitar esa función, se deben reconsiderar algunas de las suposiciones que la conducción por la Web hace sobre la confianza. Los componentes básicos de la Web, como los servidores y el DNS, ya no se pueden considerar confiables de forma explícita. Los vectores de ataque que pueden parecer más relevantes para las apps nativas de repente se vuelven importantes. Por lo tanto, para acceder al nuevo modelo de seguridad de alta confianza que proporcionan las IWA, las apps web deben empaquetarse, aislarse y protegerse.

Empaquetado

Las páginas y los recursos de las apps web aisladas no se pueden publicar desde servidores activos ni recuperar a través de la red como las aplicaciones web normales. En cambio, para acceder al nuevo modelo de seguridad de alta confianza, las apps web deben empaquetar todos los recursos que necesitan para ejecutarse en un WebBundle firmado. Los paquetes web firmados toman todos los recursos necesarios para ejecutar un sitio y los empaquetan en un archivo .swbn, concatenándolos con un bloque de integridad. Esto permite que la app web se descargue de forma segura en su totalidad y que incluso se comparta o instale sin conexión.

Sin embargo, esto plantea un problema para verificar la autenticidad del código de un sitio: las claves de TLS requieren una conexión a Internet para funcionar. En lugar de claves TLS, las IWA se firman con una clave que se puede mantener de forma segura sin conexión. La buena noticia es que, si puedes reunir todos tus archivos de producción en una carpeta, puedes convertirla en un IWA sin muchas modificaciones.

Genera claves de firma

Las claves de firma son pares de claves Ed25519 o ECDSA P-256, en los que la clave privada se usa para firmar el paquete y la clave pública se usa para verificarlo. Puedes usar OpenSSL para generar y encriptar una clave Ed25519 o ECDSA P-256:

# Generate an unencrypted Ed25519 key
openssl genpkey -algorithm Ed25519 -out private_key.pem

# or generate an unencrypted ECDSA P-256 key
openssl ecparam -name prime256v1 -genkey -noout -out private_key.pem

# Encrypt the generated key. This will ask for a passphrase, make sure to use a strong one
openssl pkcs8 -in private_key.pem -topk8 -out encrypted_key.pem

# Delete the unencrypted key
rm private_key.pem

Las claves de firma también tienen un propósito secundario. Como un dominio puede ser vulnerable a la pérdida de control, al igual que un servidor, no se puede usar para identificar la IWA instalada. En cambio, se identifica por la clave pública del paquete, que forma parte de su firma y está vinculada a la clave privada. Este es un cambio significativo en el funcionamiento de la Web de paso, por lo que, en lugar de usar HTTPS, las IWA también usan un esquema nuevo: isolated-app://.

Crea el paquete de tu app

Con tu clave de firma disponible, es hora de agrupar tu app web. Para ello, puedes usar los paquetes oficiales de NodeJS para agrupar y, luego, firmar tus IWA (las herramientas de línea de comandos de Go también están disponibles). Primero, usa el paquete wbn y apunta a la carpeta que contiene todos los archivos de producción de tu IWA (aquí, dist) para empaquetarlos en un paquete sin firmar:

npx wbn --dir dist

Esto generará un paquete web sin firmar de ese directorio en out.wbn.. Una vez generado, usa la clave Ed25519 o ECDSA P-256 encriptada que creaste anteriormente para firmarlo con wbn-sign:

npx wbn-sign -i out.wbn -k encrypted_key.pem -o signed.swbn

Esto generará un paquete web firmado a partir del paquete web sin firmar llamado signed.swbn. Una vez que se firme, la herramienta también generará el ID del paquete web y el origen de la app web aislada. El origen de la app web aislada es la forma en que se identifica tu IWA en el navegador.

Web Bundle ID: ggx2sheak3vpmm7vmjqnjwuzx3xwot3vdayrlgnvbkq2mp5lg4daaaic
Isolated Web App Origin: isolated-app://ggx2sheak3vpmm7vmjqnjwuzx3xwot3vdayrlgnvbkq2mp5lg4daaaic/

Si usas Webpack, Rollup o una herramienta que admita sus complementos (como Vite), puedes usar uno de los complementos del agrupador (Webpack, Rollup) que encapsula estos paquetes en lugar de llamarlos directamente. Si lo haces, se generará un paquete firmado como resultado de tu compilación.

Prueba tu app

Puedes probar tu IWA de dos maneras: ejecutando tu servidor de desarrollo a través del proxy para desarrolladores de IWA integrado en Chrome o instalando tu IWA empaquetada. Para ello, deberás usar Chrome o ChromeOS 120 o versiones posteriores, habilitar las marcas de IWA y, luego, instalar la app a través de la página Internals de la app web de Chrome:

  1. Habilita la marca chrome://flags/#enable-isolated-web-app-dev-mode
  2. Para probar tu IWA, ve a la página Internals de la app web de Chrome en chrome://web-app-internals.

Una vez que estés en la página Web App Internals, tendrás dos opciones: Install IWA with Dev Mode Proxy o Install IWA from Signed Web Bundle.

Si realizas la instalación a través de un proxy en modo de desarrollo, puedes instalar cualquier URL, incluidos los sitios que se ejecutan desde un servidor de desarrollo local, como una IWA, sin agruparlos, siempre que cumplan con los demás requisitos de instalación de IWA. Una vez instalada, se agregará una IWA para esa URL a tu sistema con las políticas de seguridad y las restricciones correctas, y acceso a las APIs exclusivas para IWA. Se le asignará un identificador aleatorio. Las Herramientas para desarrolladores de Chrome también están disponibles en este modo para ayudarte a depurar tu aplicación. Si instalas desde un paquete web firmado, subirás tu IWA firmado y empaquetado, y se instalará como si la hubiera descargado un usuario final.

En la página Web App Internals, también puedes forzar las verificaciones de actualización para cualquier aplicación instalada a través de Dev Mode Proxy o desde un paquete web firmado para probar también el proceso de actualización.

Aislado

La confianza es clave para las apps web aisladas. Esto comienza con la forma en que se ejecutan. Los usuarios tienen diferentes modelos mentales sobre lo que una app puede y debería poder hacer, según si se ejecuta en un navegador o en una ventana independiente, y, en general, creen que las apps independientes tienen más acceso y son más potentes. Dado que las IWA pueden acceder a APIs de alta confianza, deben ejecutarse en una ventana independiente para alinearse con este modelo mental. Esto los separa visualmente del navegador. Pero va más allá de la separación visual.

Las apps web aisladas se ejecutan en un protocolo independiente de los sitios web integrados en el navegador (isolated-app en comparación con http o https). Esto significa que cada IWA está completamente separada de los sitios web que se ejecutan en el navegador, incluso si los creó la misma empresa, gracias a la política del mismo origen. El almacenamiento de IWA también está separado. En conjunto, esto garantiza que el contenido de origen cruzado no se pueda filtrar entre diferentes IWA o entre los IWA y el contexto de navegación normal de un usuario.

Sin embargo, ni el aislamiento ni el empaquetado y la firma del código de un sitio son útiles para establecer la confianza si una IWA puede descargar y ejecutar código arbitrario después de la instalación. Para garantizar esto y, al mismo tiempo, permitir que las IWA se conecten a otros sitios para obtener contenido, las IWA aplican un conjunto riguroso de políticas de seguridad del contenido:

  • Solo permite JavaScript del paquete, pero sí permite que se ejecute Wasm sin importar su fuente. (script-src)
  • Permite que JavaScript recupere datos de dominios seguros de origen cruzado que no sean localhost, conecte extremos de WebSocket y WebTransport, y URLs de blob y data (connect-src)
  • Protege contra ataques de inyección de secuencias de comandos entre sitios (XSS) en el DOM regulando cómo se pueden usar las funciones de manipulación del DOM (require-trusted-types-for).
  • Permite marcos, imágenes, audio y video de cualquier dominio HTTPS (frame-src, img-src, media-src)
  • Permite fuentes del paquete y BLOBs (font-src)
  • Permite CSS intercalado o CSS del paquete (style-src)
  • No se pueden usar los elementos <object>, <embed> y <base> (object-src y base-uri)
  • Solo permite recursos del paquete para cualquier otra solicitud cubierta por el CSP (default-src).
Content-Security-Policy: script-src 'self' 'wasm-unsafe-eval';
  connect-src 'self' https: wss: blob: data:;
  require-trusted-types-for 'script';
  frame-src 'self' https: blob: data:;
  img-src 'self' https: blob: data:;
  media-src 'self' https: blob: data:;
  font-src 'self' blob: data:;
  style-src 'self' 'unsafe-inline';
  object-src 'none';
  base-uri 'none';
  default-src 'self';

Estos CSP no son suficientes para proteger completamente contra el código de terceros potencialmente malicioso. Las IWA también están aisladas de origen cruzado, y establecen encabezados para reducir la capacidad de los recursos de terceros de afectarlas:

  • Solo permite recursos del paquete o recursos de origen cruzado marcados explícitamente como compatibles con CORS con un encabezado de política de recursos de origen cruzado (CORP) establecido o el atributo crossorigin. (Cross-Origin-Embedder-Policy)
  • No permitir solicitudes entre dominios sin CORS (Cross-Origin-Resource-Policy)
  • Aislamiento del proceso del contexto de navegación de documentos de origen cruzado, lo que evita referencias a window.opener y el acceso a objetos globales (Cross-Origin-Opener-Policy)
  • Evita que el sitio se incorpore en un marco o iframe (CSP, frame-ancestors).
Cross-Origin-Opener-Policy: same-origin
Cross-Origin-Embedder-Policy: require-corp
Cross-Origin-Resource-Policy: same-origin
Content-Security-Policy: frame-ancestors 'self'

Incluso con estas restricciones, hay un ataque potencial más contra el que protegen las IWA: los ataques de ruptura de secuencia. Un ataque de secuencia interrumpida ocurre cuando contenido malicioso de terceros intenta crear una experiencia del usuario confusa y potencialmente explotable navegando a una página de una manera inesperada, como navegar directamente a una página de configuración interna. Las IWA evitan esto, ya que no permiten la vinculación directa arbitraria desde sitios externos y solo permiten que se abran las aplicaciones navegando a puntos de entrada bien definidos, como un start_url, un controlador de protocolos, un destino de uso compartido o a través de un controlador de inicio.

Bloqueado

El empaquetado y el aislamiento proporcionan un conjunto de garantías sobre lo que se permite ejecutar y de dónde proviene, pero la naturaleza dinámica de los permisos en la Web significa que, por sí solos, no pueden garantizar que una aplicación web solo use las capacidades que necesita. Dado que las diferentes capacidades tienen diferentes consideraciones de seguridad, un usuario o administrador querrá auditar qué permisos puede usar una IWA, al igual que puede hacerlo con otras apps nativas, como las de Android y iOS, antes de instalar o actualizar una app.

Para facilitar esto, las apps web aisladas bloquean todas las solicitudes de permisos de forma predeterminada. Luego, los desarrolladores pueden habilitar el permiso que necesitan agregando un campo permissions_policy a su manifiesto de la app web. Este campo contiene pares clave-valor de directivas de la política de permisos y listas de entidades permitidas de la política de permisos para cada permiso que la IWA, o cualquier iframe secundario, como un Controlled Frame o un iframe, puede solicitar. Agregar un permiso aquí no lo otorga automáticamente, sino que lo habilita para que se solicite cuando se realice una solicitud para esa capacidad.

Considera que estás creando una IWA de seguimiento de flota como ejemplo. Es posible que tu IWA necesite poder solicitar la ubicación del usuario, y que un mapa integrado también lo haga. También es posible que desees que cualquier sitio incorporado pueda mostrarse en pantalla completa para brindar una vista envolvente al usuario. Para ello, deberás configurar la siguiente política de permisos en el manifiesto de tu app web:

"permissions_policy": {
   "geolocation": [ "self", "https://map.example.com" ],
   "fullscreen": [ "*" ]
}

Dado que los WebBundles también pueden especificar encabezados de Permissions Policy, solo se permitirán los permisos declarados en ambos, y luego solo se permitirán los orígenes de las listas de entidades permitidas que estén en ambos.

Con nombre y versión

Las apps web normales dependen de su nombre de dominio para identificarse ante los usuarios y se pueden actualizar cambiando el código que se publica en ese dominio. Sin embargo, debido a las restricciones de seguridad en torno a las Isolated Web Apps, la identidad y las actualizaciones deben controlarse de manera diferente. Al igual que las apps web progresivas, las apps web aisladas necesitan un archivo de manifiesto de la app web para identificarlas ante los usuarios.

Manifiesto de la app web

Las apps web aisladas comparten las mismas propiedades clave del manifiesto para su manifiesto de la app web que las AWP, con algunas pequeñas variaciones. Por ejemplo, display funciona de manera un poco diferente: tanto browser como minimal-ui se fuerzan a mostrarse como minimal-ui, y tanto fullscreen como standalone se fuerzan a mostrarse como standalone (las opciones adicionales de display_override funcionan según lo esperado). Además, hay dos campos más que se deben incluir, version y update_manifest_url:

  • version: Obligatorio para las apps web aisladas. Es una cadena que consta de uno o más números enteros separados por un punto (.). Tu versión puede ser algo simple, como 1, 2, 3, etcétera, o algo complejo, como SemVer (1.2.3). El número de versión debe coincidir con la expresión regular ^(\d+.?)*\d$.
  • update_manifest_url: Campo opcional, pero recomendado, que apunta a una URL HTTPS (o localhost para pruebas) en la que se puede recuperar un manifiesto de actualización de la aplicación web.

Un manifiesto de app web mínimo para una app web aislada podría verse de la siguiente manera:

{
  "name": "IWA Kitchen Sink",
  "version": "0.1.0",
  "update_manifest_url": "https://example.com/updates.json",
  "start_url": "/",
  "icons": [
    {
      "src": "/images/icon.png",
      "type": "image/png",
      "sizes": "512x512",
      "purpose": "any"
    },
    {
      "src": "/images/icon-mask.png",
      "type": "image/png",
      "sizes": "512x512",
      "purpose": "maskable"
    }
  ]
}

Manifiesto de actualización de la aplicación web

Un manifiesto de actualización de la aplicación web es un archivo JSON que describe cada versión de una aplicación web determinada. El objeto JSON contiene un campo obligatorio, version, que es una lista de objetos que contienen version, src y channels:

  • version: Es el número de versión de la aplicación, igual que el campo version del manifiesto de la app web.
  • src: Es la URL HTTPS (o localhost para pruebas) que apunta al paquete alojado para esa versión (el archivo .swbn). Las URLs relativas son relativas al archivo de manifiesto de actualización de la aplicación web.
  • channels: Es una lista de cadenas para identificar el canal de actualización del que forma parte esta versión. Se usa un canal default especial para describir el canal principal que se usará si no se selecciona ningún otro canal.

También puedes incluir un campo channels, un objeto de los IDs de tu canal con una propiedad name opcional para cada ID que proporcione un nombre legible por humanos (incluido el canal default). Un canal que no incluye la propiedad name o que no se incluye en el objeto channels usa su ID como nombre.

Un manifiesto de actualización mínimo podría verse de la siguiente manera:

{
  "versions": [
    {
      "version": "5.2.17",
      "src": "https://cdn.example.com/app-package-5.2.17.swbn",
      "channels": ["next", "5-lts", "default"]
    },
    {
      "version": "5.3.0",
      "src": "v5.3.0/package.swbn",
      "channels": ["next", "default"]
    },
    {
      "version": "5.3.1",
      "src": "v5.3.1/package.swbn",
      "channels": ["next"]
    },
  ],
  "channels": {
    "default": {
      "name": "Stable"
    },
    "5-lts": {
      "name": "5.x Long-term Stable"
    }
  }
}

En este ejemplo, hay tres canales: default, que se etiquetará como Stable; 5-lts, que se etiquetará como 5.x Long-term Stable, y next, que se etiquetará como next. Si un usuario está en el canal 5-lts, obtendrá la versión 5.2.17; si está en el canal default, obtendrá la versión 5.3.0; y si está en el canal next, obtendrá la versión 5.3.1.

Los manifiestos de actualización de aplicaciones web se pueden alojar en cualquier servidor. Las actualizaciones se verifican cada 4 a 6 horas.

Gestionado por el administrador

En el lanzamiento inicial, las apps web aisladas solo se podrán instalar en Chromebooks administrados por Chrome Enterprise a través del panel de administrador.

Para comenzar, en el panel de administrador, ve a Dispositivos > Chrome > Apps y extensiones > Usuarios y navegadores. En esta pestaña, puedes agregar apps y extensiones de Chrome Web Store, Google Play y la Web para los usuarios de tu organización. Para agregar elementos, abre el botón flotante amarillo de agregar (+) en la esquina inferior derecha de la pantalla y selecciona el tipo de elemento que deseas agregar.

Cuando se abra, verás un ícono de un cuadrado dentro de otro cuadrado, etiquetado como Agregar una app web aislada. Si haces clic en él, se abrirá un modal para agregar una IWA a tu OU. Para ello, necesitarás dos datos: el ID del paquete web de la IWA (que se genera a partir de la clave pública de tu app y se muestra después de que se empaqueta y firma la app) y la URL del manifiesto de actualización de la app web para la IWA. Una vez que se instale, tendrás el conjunto estándar de opciones del panel de administración para administrarlo:

  • Política de instalación: Cómo quieres que se instale la IWA, ya sea instalada de manera automática, instalada de manera automática y fijada en la biblioteca de ChromeOS, o bien impedir la instalación.
  • Launch on login: Indica cómo deseas que se inicie la IWA. Puedes permitir que el usuario la inicie manualmente, forzar el inicio de la IWA cuando el usuario acceda, pero permitir que la cierre, o forzar el inicio cuando el usuario acceda y evitar que la cierre.

Una vez que se guarde, la app se instalará la próxima vez que se aplique una actualización de política a las Chromebooks de esa OU. Una vez instalada, el dispositivo del usuario buscará actualizaciones del manifiesto de actualización de la app web cada 4 a 6 horas.

Además de forzar la instalación de las IWA, también puedes otorgar automáticamente algunos permisos para ellas de manera similar a como lo haces con otras aplicaciones web. Para ello, ve a Dispositivos > Chrome > Capacidades web y haz clic en el botón Agregar origen. En Origin / site pattern field, pega el ID del paquete web de la IWA (isolated-app:// se agregará automáticamente como su protocolo). Desde allí, puedes establecer niveles de acceso a diferentes APIs (permitido, bloqueado o no establecido), incluidas la administración de ventanas, la administración de fuentes locales y la API de monitoreo de pantalla. En el caso de las APIs que pueden requerir la habilitación adicional por parte de un administrador, como la API obligatoria de supervisión de pantalla, aparecerá un diálogo adicional para confirmar tu selección. Cuando termines, guarda los cambios y tus usuarios podrán comenzar a usar tu IWA.

Trabaja con extensiones

Si bien las apps web aisladas no funcionan con extensiones de forma predeterminada, puedes conectar las extensiones que posees a ellas. Para ello, deberás editar el archivo de manifiesto de la extensión. La sección externally_connectable del manifiesto define con qué páginas web externas o con qué otras extensiones de Chrome puede interactuar tu extensión. Agrega el origen de tu IWA en el campo matches dentro de externally_connectable (asegúrate de incluir el esquema isolated-app://):

{
  "externally_connectable": {
    "matches": ["isolated-app://79990854-bc9f-4319-a6f3-47686e54ed29/*"]
  }
}

Si bien esto permitirá que tu extensión se ejecute en la IWA aislada, no le permitirá insertar contenido en ella. Solo podrás pasar mensajes entre la extensión y tu IWA.