
Un framework pitónico para el modelado de amenazas
El modelado de amenazas tradicional a menudo llega tarde a la fiesta, o a veces no llega en absoluto. Además, crear flujos de datos e informes manuales puede ser extremadamente lento. El objetivo de pytm es desplazar el modelado de amenazas hacia la izquierda, haciéndolo más automatizado y centrado en el desarrollador.
Basado en su entrada y definición del diseño arquitectónico, pytm puede generar automáticamente los siguientes elementos:
El archivo tm.py es un modelo de ejemplo. Puede ejecutarlo para generar el informe y los archivos de imagen del diagrama a los que hace referencia:```
mkdir -p tm
./tm.py --report docs/basic_template.md | pandoc -f markdown -t html > tm/report.html
./tm.py --dfd | dot -Tpng -o tm/dfd.png
./tm.py --seq | java -Djava.awt.headless=true -jar $PLANTUML_PATH -tpng -pipe > tm/seq.png
También hay un ejemplo `Makefile` que envuelve todo esto en objetivos que pueden ser fácilmente compartidos para múltiples modelos. Si tienes [GNU make](https://www.gnu.org/software/make/) instalado (disponible por defecto en distribuciones Linux pero no en OSX), simplemente ejecuta:```
make MODEL=the_name_of_your_model_minus_.py
Debe tener plantuml.jar en el mismo directorio que su modelo, o establecer PLANTUML_PATH. Para evitar instalar todas las dependencias, como pandoc o Java, el script se puede ejecutar dentro de un contenedor:```
export USE_DOCKER=true make image
make
### Primeros pasos - Variante Devbox
Para simplificar el uso de `pytm`, las dependencias del host pueden aislarse completamente usando [`Devbox`](https://github.com/jetify-com/devbox). Esto suele ser una alternativa de menor sobrecarga y más conveniente que el enfoque de contenedor OCI.
- Instala Devbox en Linux/MacOS: `curl -fsSL https://get.jetify.com/devbox | bash`
- Instala Devbox en [Windows/WSL](https://www.jetify.com/docs/devbox/installing-devbox/index#installing-wsl2)
- Actualiza a la última versión de devbox: `devbox version update`
- Establece tu token de acceso de GitHub en el archivo `~/.config/nix/nix.conf`: `access-tokens = github.com=YOUR_TOKEN_HERE`
- Crea un nuevo entorno de shell aislado que incluya todas las herramientas y paquetes especificados en el archivo `devbox.json` del proyecto: `devbox shell`
- Muestra la ruta completa al ejecutable de Python que se usará cuando simplemente escribas `python` en tu terminal usando el comando `which python`. La salida debe ser la siguiente ruta: `.devbox/nix/profile/default/bin/python`
- Prueba ejecutando el siguiente comando, que debería generar un DFD como un archivo PNG llamado `sample.png`: `./tm.py --dfd | dot -Tpng -o sample.png`
- Sal del entorno de shell de Devbox: `exit`
## Uso
Todos los argumentos disponibles:```text
usage: tm.py [-h] [--debug] [--dfd] [--report REPORT]
[--exclude EXCLUDE] [--seq] [--list] [--describe DESCRIBE]
[--list-elements] [--json JSON] [--levels LEVELS [LEVELS ...]]
[--stale_days STALE_DAYS]
optional arguments:
-h, --help show this help message and exit
--debug print debug messages
--dfd output DFD
--report REPORT output report using the named template file (sample
template file is under docs/template.md)
--exclude EXCLUDE specify threat IDs to be ignored
--seq output sequential diagram
--list list all available threats
--colormap color the risk in the diagram
--describe DESCRIBE describe the properties available for a given element
--list-elements list all elements which can be part of a threat model
--json JSON output a JSON file
--levels LEVELS [LEVELS ...]
Select levels to be drawn in the threat model (int
separated by comma).
--stale_days STALE_DAYS
checks if the delta between the TM script and the code
described by it is bigger than the specified value in
days
El argumento stale_days intenta determinar qué tan separados en días están el script del modelo (que estás escribiendo) del código que implementa el sistema modelado. Idealmente, deberían estar bastante cerca en la mayoría de los casos de un sistema en desarrollo activo. Puedes ejecutar esto periódicamente para medir el pulso de tu proyecto y la 'frescura' de tu modelo de amenazas.
Los elementos actualmente disponibles son: TM, Element, Server, ExternalEntity, Datastore, Actor, Process, SetOfProcesses, Dataflow, Boundary, Lambda, LLM y Agent.
Las propiedades disponibles de un elemento se pueden listar usando --describe seguido del nombre de un elemento:```text
(pytm) ➜ pytm git:(master) ✗ ./tm.py --describe Element Element class attributes: OS definesConnectionTimeout default: False description handlesResources default: False implementsAuthenticationScheme default: False implementsNonce default: False inBoundary inScope Is the element in scope of the threat model, default: True isAdmin default: False isHardened default: False name required onAWS default: False
El argumento *colormap*, usado junto con *dfd*, genera un DFD codificado por colores donde los elementos se pintan de rojo, amarillo o verde según su nivel de riesgo (identificado mediante la ejecución de las reglas).
## Uso - Variante Devbox
- `devbox shell`
- `pytm` uso como de costumbre
- `exit`
## Creación de un Modelo de Amenazas
El siguiente es un archivo `tm.py` de ejemplo que describe una aplicación simple donde un Usuario inicia sesión en la aplicación y publica comentarios en ella. El servidor de la aplicación almacena esos comentarios en la base de datos. Hay una función AWS Lambda que limpia periódicamente la base de datos.```python
#!/usr/bin/env python3
from pytm import TM, Server, Datastore, Dataflow, Boundary, Actor, Lambda, LLM, Data, Classification
tm = TM("my test tm")
tm.description = "another test tm"
tm.isOrdered = True
User_Web = Boundary("User/Web")
Web_DB = Boundary("Web/DB")
user = Actor("User")
user.inBoundary = User_Web
web = Server("Web Server")
web.OS = "CloudOS"
web.isHardened = True
web.sourceCode = "server/web.cc"
db = Datastore("SQL Database (*)")
db.OS = "CentOS"
db.isHardened = False
db.inBoundary = Web_DB
db.isSql = True
db.inScope = False
db.sourceCode = "model/schema.sql"
comments = Data(
name="Comments",
description="Comments in HTML or Markdown",
classification=Classification.PUBLIC,
isPII=False,
isCredentials=False,
# credentialsLife=Lifetime.LONG,
isStored=True,
isSourceEncryptedAtRest=False,
isDestEncryptedAtRest=True
)
results = Data(
name="results",
description="Results of insert op",
classification=Classification.SENSITIVE,
isPII=False,
isCredentials=False,
# credentialsLife=Lifetime.LONG,
isStored=True,
isSourceEncryptedAtRest=False,
isDestEncryptedAtRest=True
)