Capítulo 28 — Seguridad de TO, IoT y sistemas ciberfísicos: implementación I
Mapa de la lección
Lección 28 publicada. Mapa, ejemplo, EX082.
flowchart TD
N0["Resultados de aprendizaje"]
N1["28.1 Forma de la implementación"]
N2["28.2 Pruebas por cláusula"]
N3["28.3 Registro"]
N4["Nota práctica"]
N5["28.1b Catálogo de motivos y fuzzing"]
N6["Nota de campo: hallazgos del fuzzing"]
N7["Síntesis del capítulo"]
N0 --> N1 --> N2 --> N3 --> N4 --> N5 --> N6 --> N7
N7 --> EX["Ejemplo resuelto"]
EX --> Q["Ejercicio EX082"]
Dibujo lógico
flowchart LR
N0["Resultados de aprendizaje"]
N1["28.1 Forma de la implementación"]
N2["28.2 Pruebas por cláusula"]
N3["28.3 Registro"]
N4["Nota práctica"]
N5["28.1b Catálogo de motivos y fuzzing"]
N6["Nota de campo: hallazgos del fuzzing"]
N7["Síntesis del capítulo"]
N0 --> N1 --> N2 --> N3 --> N4 --> N5 --> N6 --> N7 --> Q["EX082"]
[Resultados de aprendizaje]
|
v
[28.1 Forma de la implementación]
|
v
[28.2 Pruebas por cláusula]
|
v
[28.3 Registro]
|
v
[Nota práctica]
|
v
[28.1b Catálogo de motivos y fuzzing]
|
v
[Nota de campo: hallazgos del fuzzing]
|
v
[Síntesis del capítulo]
|
v
[Ejemplo resuelto] --> [Ejercicio EX082]
Resultados de aprendizaje
Usted podrá (1) implementar permit() con códigos de motivo explícitos; (2) añadir pruebas para cada cláusula; (3) registrar sin almacenar secretos.
28.1 Forma de la implementación
Función pura: entradas origen, código de función, registro y AllowListVersion, y salida (allow: bool, reason: str). Ninguna E/S de red dentro del predicado. Eso mantiene las pruebas rápidas y libres de contacto con la planta. La función de permiso de Lena en ABP sigue exactamente esta forma: recibe los campos de la trama decodificada y la versión de la lista de permitidos y devuelve una decisión con un motivo. La carga de la lista de permitidos y la escritura de los registros ocurren fuera de la función, de modo que sus pruebas se ejecutan en milisegundos con datos sintéticos y nunca corren el riesgo de tocar la planta.
28.2 Pruebas por cláusula
Un archivo de pruebas por cláusula: estación permitida, código de función, rango de registros, política no disponible. Solo datos de prueba sintéticos. Mutación: rompa una cláusula y observe cómo falla la prueba.
"""Lab: Modbus function-code allow-list on synthetic, already-decoded requests (no network)."""
ALLOW = {("HMI-1", 3), ("HMI-1", 6), ("HIST-1", 3), ("HIST-1", 4)} # (source, function code)
WRITE_REGS = {("HMI-1", 6): range(40001, 40011)} # HMI may write setpoints only
reqs = [("HMI-1", 3, 40001), ("HMI-1", 6, 40003), ("HMI-1", 6, 40050),
("HIST-1", 3, 30001), ("HIST-1", 16, 40001), ("ENG-WS", 8, 0), ("HIST-1", 4, 30010)]
for src, fc, reg in reqs:
if (src, fc) not in ALLOW:
d = "BLOCK fc-not-allowed"
elif (src, fc) in WRITE_REGS and reg not in WRITE_REGS[(src, fc)]:
d = "BLOCK register-out-of-range"
else:
d = "PASS"
print(f"{src:7} fc={fc:2} reg={reg:5} -> {d}")
Salida esperada:
HMI-1 fc= 3 reg=40001 -> PASS
HMI-1 fc= 6 reg=40003 -> PASS
HMI-1 fc= 6 reg=40050 -> BLOCK register-out-of-range
HIST-1 fc= 3 reg=30001 -> PASS
HIST-1 fc=16 reg=40001 -> BLOCK fc-not-allowed
ENG-WS fc= 8 reg= 0 -> BLOCK fc-not-allowed
HIST-1 fc= 4 reg=30010 -> PASS
Lena mantiene un archivo de pruebas por cláusula para la función de permiso de ABP, con datos de prueba que llevan el nombre del requisito que comprueban. Las pruebas de mutación confirmaron que cada archivo falla cuando se rompe su cláusula: borrar la comprobación del rango de registros, por ejemplo, hizo fallar de inmediato las pruebas fuera de rango. Una cláusula cuyas pruebas sobreviven a una mutación se considera no probada.
28.3 Registro
Registre la decisión, el motivo, la versión de la política y el actor. No registre contraseñas ni cargas útiles completas de las tramas que puedan contener datos propietarios de recetas. Oculte lo que haga falta.
Caso sintético — Ashford Batch Plant. La implementación I entregó códigos de motivo que muestra la interfaz de la cronología: los operadores vieron por fin por qué se bloqueó una escritura a las 03:12.
Los registros de Lena muestran qué versión de la política tomó cada decisión. Las cargas útiles de las tramas se dejan fuera porque pueden contener valores de recetas. Lena revisa una muestra de registros después de cada versión para confirmar que no se ha colado nada sensible.
28.4 Errores y límites
Los motivos tipados como cadenas libres derivan; use una enumeración compartida con el tablero de cobertura. La implementación de Lena usa una lista compartida de códigos de motivo, como register-out-of-range y policy-unavailable, publicada como documento con versión. El tablero de cobertura y la vista de cronología leen la misma lista, de modo que un código nuevo no puede aparecer en los registros sin entenderse en otros lugares. Los consumidores que ven un código desconocido lo tratan como una denegación y generan una alerta para el responsable de la herramienta. El documento se revisa con cada cambio del predicado.
Nota práctica
Someta el predicado a fuzzing con estaciones y códigos aleatorios y UTF-8 roto. Debe DENEGAR o fallar de forma cerrada, nunca PERMITIR ante un error de análisis.
28.1b Catálogo de motivos y fuzzing
El catálogo enumera, para cada código de motivo, la cláusula que lo emite y la prueba que lo fija: fc-not-allowed, register-out-of-range, policy-unavailable, station-unknown. Las entradas de fuzzing que alguna vez encontraron un error se suman a una lista de regresión que se reproduce en cada versión, incluido el nombre de estación UTF-8 que una vez hizo caer el decodificador. La lista de comprobación de Lena para un cambio del predicado del monitor cubre la cláusula nueva, las pruebas nuevas, la documentación de los códigos de motivo, un caso de fuzzing y el fallo cerrado ante errores de análisis. Un revisor que aprueba un cambio del predicado sin la lista ha comprobado al autor, no el cambio.
La lista de comprobación de Lena se adjunta a cada revisión del predicado.
Nota de campo: hallazgos del fuzzing
Un arnés de fuzzing de cinco minutos lanzó un nombre de estación UTF-8 que antes hacía caer el decodificador en una excepción no capturada, que el llamador trataba como ALLOW. La corrección devolvió DENY parse-error, y el arnés se ejecuta ahora en la integración continua con cada cambio del predicado.
28.5 Ejemplo resuelto: una prueba de cláusula que detectó un error real (ABP sintética)
Cada cláusula de la función de permiso tiene su propia prueba: origen permitido, código de función permitido, registro dentro de rango, denegación predeterminada. El arnés de fuzzing de Lena alimenta después 10.000 ternas aleatorias (origen, función, registro) tomadas de un generador con semilla y comprueba que cada PASS cumple todas las cláusulas. En la primera ejecución descubrió que el código de función 23 (lectura/escritura de múltiples registros) pasaba para HMI-1 porque la implementación solo comprobaba la mitad de lectura de la solicitud. La corrección rechaza el 23 salvo que figure explícitamente, una prueba de cláusula lo fija ahora y la nota del incidente registra qué semilla reprodujo el fallo, para que cualquiera pueda volver a ejecutarlo.
Ejercicios — capítulo 28
- EX082 — Firma. Firma de tipos de permit y por qué AllowListVersion es un argumento. Rúbrica: 3 = firma + razón de fijar la versión; 2 = firma; 1 = vago; 0 = nada.
- EX083 — Mutación. ¿Qué prueba falla si se ignora el rango de registros? Rúbrica: 3 = 40050 espera BLOCK; 2 = vago; 1 = incorrecto; 0 = nada.
- EX084 — Ocultación. Dos campos de registro que deben ocultarse. Rúbrica: 3 = p. ej., contraseñas, cargas útiles completas con datos de recetas; 2 = uno; 1 = vago; 0 = nada.
Síntesis del capítulo
Predicado puro, pruebas por cláusula, registros seguros.
Siga estudiando — capítulo 28 de 50
← Capítulo 27 (borrador) · Índice de capítulos del curso · Capítulo 29 (borrador) →