
Um framework Pythonico para modelagem de ameaças
A modelagem de ameaças tradicional muitas vezes chega tarde à festa, ou às vezes nem chega. Além disso, criar fluxos de dados e relatórios manualmente pode ser extremamente demorado. O objetivo do pytm é deslocar a modelagem de ameaças para a esquerda, tornando a modelagem de ameaças mais automatizada e centrada no desenvolvedor.
Com base na sua entrada e definição do design arquitetônico, o pytm pode gerar automaticamente os seguintes itens:
O tm.py é um modelo de exemplo. Você pode executá-lo para gerar o relatório e os arquivos de imagem de diagrama que ele 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
Há também um exemplo de `Makefile` que agrupa tudo isso em targets que podem ser facilmente compartilhados para vários modelos. Se você tem [GNU make](https://www.gnu.org/software/make/) instalado (disponível por padrão em distribuições Linux, mas não no OSX), simplesmente execute:```
make MODEL=the_name_of_your_model_minus_.py
Você deve ter o plantuml.jar no mesmo diretório do seu modelo, ou definir PLANTUML_PATH. Para evitar instalar todas as dependências, como pandoc ou Java, o script pode ser executado dentro de um container:```
export USE_DOCKER=true make image
make
### Primeiros Passos - Variante Devbox
Para simplificar o uso de `pytm`, as dependências do host podem ser completamente isoladas usando [`Devbox`](https://github.com/jetify-com/devbox). Esta é geralmente uma alternativa de menor sobrecarga e mais conveniente em comparação com a abordagem de contêiner OCI.
- Instalar Devbox no Linux/MacOS: `curl -fsSL https://get.jetify.com/devbox | bash`
- Instalar Devbox no [Windows/WSL](https://www.jetify.com/docs/devbox/installing-devbox/index#installing-wsl2)
- Atualizar para a versão mais recente do devbox: `devbox version update`
- Defina seu token de acesso do GitHub no arquivo `~/.config/nix/nix.conf`: `access-tokens = github.com=YOUR_TOKEN_HERE`
- Crie um novo ambiente de shell isolado que inclua todas as ferramentas e pacotes especificados no arquivo `devbox.json` do projeto: `devbox shell`
- Exiba o caminho completo para o executável Python que será usado quando você digitar simplesmente `python` no terminal, usando o comando which python. A saída deve ser o seguinte caminho: `.devbox/nix/profile/default/bin/python`
- Teste executando o seguinte comando, que deve gerar um DFD como um arquivo PNG chamado `sample.png`: `./tm.py --dfd | dot -Tpng -o sample.png`
- Saia do ambiente de shell Devbox: `exit`
## Uso
Todos os argumentos disponíveis:```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
O argumento stale_days tenta determinar a distância em dias entre o script do modelo (que você está escrevendo) e o código que implementa o sistema sendo modelado. Idealmente, eles devem estar bem próximos na maioria dos casos de um sistema em desenvolvimento ativo. Você pode executar isso periodicamente para medir o pulso do seu projeto e a 'frescura' do seu modelo de ameaças.
Os elementos atualmente disponíveis são: TM, Element, Server, ExternalEntity, Datastore, Actor, Process, SetOfProcesses, Dataflow, Boundary, Lambda, LLM e Agent.
As propriedades disponíveis de um elemento podem ser listadas usando --describe seguido pelo nome de um 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
O argumento *colormap*, usado em conjunto com *dfd*, gera um DFD codificado por cores onde os elementos são pintados de vermelho, amarelo ou verde dependendo do seu nível de risco (conforme identificado pela execução das regras).
## Uso - Variante Devbox
- `devbox shell`
- `pytm` uso como de costume
- `exit`
## Criando um Modelo de Ameaças
O seguinte é um arquivo de exemplo `tm.py` que descreve uma aplicação simples onde um Usuário faz login na aplicação e publica comentários no app. O servidor da aplicação armazena esses comentários no banco de dados. Existe uma AWS Lambda que periodicamente limpa o banco de dados.```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
)
my_lambda = Lambda("cleanDBevery6hours")
my_lambda.hasAccessControl = True
my_lambda.inBoundary = Web_DB