feat: protect production Snipe-IT operations
This commit is contained in:
@@ -77,11 +77,15 @@ Os relatórios usam consultas de leitura com período máximo de 366 dias. A exp
|
||||
3. `AssetSyncService` faz upsert por `snipe_asset_id`, calcula `content_hash` e marca cada ativo com `last_seen_sync_run_id`.
|
||||
4. Apenas após todas as páginas serem recebidas com sucesso, ativos cujo marcador não corresponde à execução atual ficam `active = false`; não são apagados para preservar histórico.
|
||||
5. Antes de aprovação ou entrega, o ativo é validado novamente.
|
||||
6. Checkout/check-in usam um token operacional separado e guardam apenas a resposta necessária para auditoria.
|
||||
6. Imediatamente antes de cada checkout, o portal faz um `GET /hardware/{id}` com o token de leitura e bloqueia a operação se existir qualquer atribuição remota.
|
||||
7. Checkout/check-in usam um token operacional separado e guardam apenas a resposta necessária para auditoria.
|
||||
8. Antes de um check-in, o portal exige um checkout confirmado em `handover_records` para o mesmo requisitante e confirma por `GET` que o ativo continua atribuído a esse utilizador Snipe-IT.
|
||||
|
||||
`HandoverService` mantém a ordem explícita “Snipe-IT primeiro, confirmação local depois” para cada equipamento. Uma requisição com vários itens não é apresentada como concluída se apenas parte dos movimentos for aceite: os itens confirmados preservam o seu `HandoverRecord`, os restantes mantêm o estado anterior e a requisição passa a `incident`. As escritas externas não têm retry automático porque um timeout pode ocorrer depois de o Snipe-IT executar a operação; repetir sem reconciliação poderia criar um resultado enganador.
|
||||
|
||||
O checkout associa o ativo a um utilizador Snipe-IT. O operador confirma o ID remoto na primeira entrega e o portal guarda-o em `users.snipe_user_id` para utilizações futuras. O check-in repõe o estado e a localização habitual sincronizados do ativo. As respostas externas ficam em `handover_records.snipe_response`; tokens nunca são persistidos.
|
||||
O checkout associa o ativo a um utilizador Snipe-IT. O operador confirma o ID remoto na primeira entrega e o portal guarda-o em `users.snipe_user_id` para utilizações futuras. Um ativo já atribuído nunca recebe um checkout do portal. O check-in repõe o estado e a localização habitual sincronizados do ativo, mas apenas quando o movimento de saída foi confirmado pelo portal e a atribuição remota ainda corresponde ao mesmo destinatário. As respostas externas ficam em `handover_records.snipe_response`; tokens nunca são persistidos.
|
||||
|
||||
A instância Snipe-IT ligada ao projeto é de produção e é tratada como inventário imutável. O portal não cria, edita nem elimina ativos, modelos, produtos ou outra informação mestre. Os únicos pedidos de escrita autorizados no cliente são os endpoints estritos de checkout e check-in, invocados por uma ação operacional legítima. Diagnósticos, sincronizações e validações técnicas usam exclusivamente `GET`.
|
||||
|
||||
Uma falha intermédia pode deixar upserts válidos das páginas anteriores, mas nunca executa a desativação global. A sincronização seguinte converge o cache. Um lock distribuído e um job único impedem duas sincronizações simultâneas.
|
||||
|
||||
|
||||
@@ -110,3 +110,12 @@ Por defeito, uma identidade Microsoft só pode ser associada a partir de uma con
|
||||
Separar liveness, que prova apenas resposta do processo, de readiness, que confirma PostgreSQL e cache. As respostas públicas contêm apenas o estado agregado. O Nginx usa readiness no Docker Compose.
|
||||
|
||||
Disponibilizar scripts independentes para `pg_dump` custom e restauro numa base temporária validada. Os scripts usam nomes resolvidos do contentor, restringem os identificadores aceites, geram checksum e nunca substituem a base principal. Encriptação, armazenamento externo, retenção e alertas permanecem responsabilidades explícitas da plataforma de produção.
|
||||
|
||||
## ADR-016 — Snipe-IT de produção imutável salvo movimentos controlados
|
||||
|
||||
**Data:** 2026-08-05
|
||||
**Estado:** aceite
|
||||
|
||||
A instância Snipe-IT configurada é de produção. Ficam proibidas, tanto na aplicação como em diagnóstico, operações de criação, edição ou eliminação de ativos, modelos, produtos e restantes dados mestres. Os únicos pedidos remotos de escrita admitidos são checkout e check-in resultantes de uma ação operacional legítima no portal; testes de conectividade e sincronizações são exclusivamente de leitura.
|
||||
|
||||
Cada checkout faz primeiro uma leitura atual de `hardware/{id}` e é recusado sem qualquer escrita se o ativo estiver atribuído. Cada check-in exige um `HandoverRecord` de checkout criado pelo portal para o mesmo requisitante e uma leitura atual que confirme a atribuição ao respetivo `snipe_user_id`. Uma divergência é tratada como incidente para reconciliação manual. Mantém-se a decisão de não repetir escritas automaticamente e de nunca expor tokens, URL privada ou respostas com dados de inventário em documentação, logs de diagnóstico ou mensagens de agentes.
|
||||
|
||||
@@ -21,7 +21,10 @@ Referências oficiais: [Authorization Code Flow com PKCE](https://learn.microsof
|
||||
- [ ] Criar conta/token de leitura com menor privilégio.
|
||||
- [ ] Criar conta/token operacional separado, apenas com checkout/check-in necessários.
|
||||
- [ ] Executar sincronização integral e comparar amostra de etiquetas, categorias, estados e localizações.
|
||||
- [ ] Exercitar checkout/check-in com equipamentos de teste e reconciliar `handover_records`.
|
||||
- [ ] Confirmar por inspeção e testes simulados que o portal não contém endpoints de criação, edição ou eliminação de inventário.
|
||||
- [ ] Validar tecnicamente a produção apenas com pedidos `GET`; nunca usar um movimento artificial como teste de conectividade.
|
||||
- [ ] No primeiro movimento institucional legítimo, usar um ativo existente e não atribuído, verificar o preflight e reconciliar `handover_records`.
|
||||
- [ ] Confirmar que um ativo já atribuído é bloqueado sem qualquer pedido de escrita.
|
||||
- [ ] Confirmar resposta a indisponibilidade, timeout e falha parcial sem retries de escrita.
|
||||
|
||||
## 3. Email, domínio e TLS
|
||||
@@ -72,7 +75,7 @@ Referências oficiais: [Authorization Code Flow com PKCE](https://learn.microsof
|
||||
| Área | Responsável | Data | Resultado | Evidência externa |
|
||||
|---|---|---|---|---|
|
||||
| Entra ID | Por atribuir | — | Pendente | — |
|
||||
| Snipe-IT | Por atribuir | — | Pendente | — |
|
||||
| Snipe-IT | Codex (validação técnica) | 2026-08-05 | Parcial: dois tokens distintos responderam a `GET`; sincronização de 2367 ativos; zero escritas | Registo interno em [PROJECT_STATUS.md](PROJECT_STATUS.md); validação institucional pendente |
|
||||
| SMTP/TLS | Por atribuir | — | Pendente | — |
|
||||
| Monitorização | Por atribuir | — | Pendente | — |
|
||||
| Restauro | Por atribuir | — | Pendente | — |
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
# Estado do projeto e handoff
|
||||
|
||||
**Atualizado em:** 4 de agosto de 2026
|
||||
**Fase:** Fases 1 a 6 concluídas; Fase 7 em curso (base técnica de identidade, saúde e recuperação concluída)
|
||||
**Atualizado em:** 5 de agosto de 2026
|
||||
**Fase:** Fases 1 a 6 concluídas; Fase 7 em curso (identidade, saúde, recuperação e ligação de leitura ao Snipe-IT de produção concluídas)
|
||||
**Último agente:** Codex
|
||||
|
||||
## Resultado disponível
|
||||
@@ -35,6 +35,9 @@
|
||||
- Substituição de equipamento apenas por ativo livre da mesma categoria, mantendo a pré-reserva e todo o histórico.
|
||||
- Fila de aprovação com filtros por referência, requisitante, categoria e período, incluindo histórico de ações.
|
||||
- Cliente Snipe-IT operacional com token separado, checkout para utilizador, check-in para a localização habitual e validação de erros transportados em HTTP 200.
|
||||
- Preflight remoto obrigatório antes de checkout; ativos já atribuídos são recusados sem qualquer pedido de escrita.
|
||||
- Check-in condicionado a checkout confirmado pelo portal para o mesmo requisitante e a atribuição remota atual ao respetivo utilizador Snipe-IT.
|
||||
- Snipe-IT de produção formalmente limitado a leitura, checkout e check-in: criar, editar ou eliminar inventário é proibido no código, diagnóstico e procedimentos de agentes.
|
||||
- Área diária do operador protegida pelo perfil `inventory_operator`/`administrator`, com leitura por scanner-teclado ou pesquisa de referência, requisitante, etiqueta e ID Snipe-IT.
|
||||
- Preparação, entrega, devolução e conclusão com máquina de estados, condição física, observações, operador, destinatário e resposta externa em `handover_records`.
|
||||
- Associação persistente entre o utilizador do portal e o respetivo ID no Snipe-IT, confirmada durante uma entrega bem-sucedida.
|
||||
@@ -59,7 +62,7 @@
|
||||
|
||||
## Limites conhecidos
|
||||
|
||||
- A integração está implementada e testada contra respostas simuladas; falta configurar e validar URL, versão e token da instância institucional. Até lá, o catálogo utiliza dados fictícios do seeder.
|
||||
- A URL e os dois tokens distintos estão configurados localmente e responderam com sucesso a pedidos `GET` em 5 de agosto de 2026. A sincronização integral de leitura importou 2367 ativos para o cache local, dos quais 716 estavam atribuídos e 144 marcados como requisitáveis. Não foi executada qualquer escrita real. Falta confirmar institucionalmente a versão, o menor privilégio efetivo dos tokens e as categorias/estados autorizados.
|
||||
- Microsoft Entra ID está implementado e testado com tokens assinados simulados; falta registar/configurar a aplicação no tenant institucional e validar Conditional Access/MFA real.
|
||||
- As notificações estão implementadas com Mailpit em desenvolvimento; falta validar SMTP institucional antes do piloto.
|
||||
- O calendário atual é a vista mensal pessoal do MVP; vistas diária, semanal, global e por equipamento/categoria/localização ficam para as áreas operacional e administrativa.
|
||||
@@ -72,7 +75,7 @@
|
||||
Implementar a **Fase 7 — hardening e entrada em produção**:
|
||||
|
||||
1. configurar e validar Microsoft Entra no tenant institucional, incluindo Conditional Access/MFA;
|
||||
2. validar a integração contra o Snipe-IT institucional e configurar tokens reais de menor privilégio;
|
||||
2. confirmar com o responsável Snipe-IT a versão, o âmbito de menor privilégio dos tokens e as categorias/estados requisitáveis; validar checkout/check-in apenas no primeiro movimento institucional legítimo;
|
||||
3. configurar SMTP institucional, domínio, HTTPS e gestão externa de segredos;
|
||||
4. ligar os health checks a monitorização/alertas e agendar backups cifrados externos;
|
||||
5. executar testes de segurança, concorrência, desempenho e end-to-end com equipamentos reais;
|
||||
@@ -81,12 +84,12 @@ Implementar a **Fase 7 — hardening e entrada em produção**:
|
||||
|
||||
## Testes da última sessão
|
||||
|
||||
Validação concluída em 4 de agosto de 2026:
|
||||
Validação concluída em 5 de agosto de 2026:
|
||||
|
||||
- `docker compose exec -T app composer test`: passou;
|
||||
- Laravel Pint: 141 ficheiros sem desvios;
|
||||
- PHPStan/Larastan nível 7: 107 ficheiros, zero erros;
|
||||
- Pest: 103 testes passaram, 585 asserções e 2 testes de registo ignorados por a funcionalidade estar deliberadamente desativada;
|
||||
- Pest: 108 testes passaram, 609 asserções e 2 testes de registo ignorados por a funcionalidade estar deliberadamente desativada;
|
||||
- TypeScript (`tsc --noEmit`): passou;
|
||||
- ESLint: passou;
|
||||
- Prettier: passou;
|
||||
@@ -109,6 +112,12 @@ Validação concluída em 4 de agosto de 2026:
|
||||
- smoke tests via Nginx: `/health/live` devolveu `ok` e `/health/ready` devolveu `ready`.
|
||||
- diagnóstico de login local: a base de desenvolvimento estava vazia apesar de migrada; `db:seed --force` repôs 2 utilizadores ativos, 5 perfis e 18 ativos fictícios, sem substituir dados existentes;
|
||||
- login real no browser validado com `demo@escola.pt`: redirecionou para `/dashboard` e não produziu erros de consola.
|
||||
- testes focados das salvaguardas Snipe-IT: 17 passaram, com 106 asserções, incluindo prova de ausência de `POST` em ativos atribuídos e check-in sem proveniência ou destinatário válidos;
|
||||
- tokens Snipe-IT confirmados como configurados e distintos; ambos responderam a uma consulta `GET` com sucesso, sem revelar credenciais ou URL;
|
||||
- sincronização real exclusivamente de leitura concluída: 2367 ativos processados e criados no cache local, sem qualquer pedido de escrita no Snipe-IT;
|
||||
- cache local após sincronização: 2367 ativos ativos, 716 atribuídos e 144 marcados como requisitáveis;
|
||||
- frontend: TypeScript, ESLint, Prettier e build Vite passaram novamente; Composer Audit sem avisos conhecidos;
|
||||
- smoke tests após a sincronização: liveness `ok`, readiness `ready` e `/login` HTTP 200.
|
||||
|
||||
## Decisões pendentes do contexto institucional
|
||||
|
||||
@@ -124,4 +133,4 @@ Estas decisões não bloqueiam o desenvolvimento com defaults seguros, mas devem
|
||||
|
||||
## Próximo ficheiro a abrir
|
||||
|
||||
Começar em `docs/PRODUCTION_CHECKLIST.md`. A próxima alteração de código pode preparar testes de concorrência/desempenho e observabilidade local; as validações Entra, Snipe-IT e SMTP só ficam concluídas quando existirem serviços institucionais concretos, sem guardar os respetivos segredos no repositório.
|
||||
Começar em `docs/PRODUCTION_CHECKLIST.md`. Para Snipe-IT, confirmar versão, categorias/estados e permissões efetivas com o responsável institucional; não executar testes de escrita. A próxima alteração de código pode preparar testes de concorrência/desempenho e observabilidade local. Entra, SMTP e o primeiro movimento Snipe-IT só ficam concluídos com validação institucional, sem guardar segredos no repositório.
|
||||
|
||||
@@ -74,6 +74,8 @@ docker compose restart queue scheduler
|
||||
|
||||
## Integração Snipe-IT
|
||||
|
||||
> **Instância de produção:** é proibido criar, editar ou eliminar produtos, ativos ou quaisquer dados mestres. Diagnósticos e testes técnicos fazem apenas pedidos `GET`. Checkout e check-in só podem ocorrer como movimentos operacionais legítimos iniciados por um operador no portal. Um ativo que já esteja atribuído não pode ser usado para testar ou repetir um checkout.
|
||||
|
||||
Definir no ambiente, sem colocar tokens no repositório:
|
||||
|
||||
```dotenv
|
||||
@@ -88,6 +90,8 @@ SNIPE_IT_RETRY_DELAY_MS=250
|
||||
|
||||
O token de leitura deve pertencer a uma conta técnica com acesso apenas de leitura aos ativos. O token operacional deve ser separado e ter apenas as permissões necessárias para checkout/check-in. A aplicação acrescenta `/api/v1` automaticamente quando a URL não o contém.
|
||||
|
||||
Nunca validar o token operacional através de um checkout/check-in artificial. A validade de leitura pode ser confirmada pelo teste de ligação administrativo ou por uma sincronização. A permissão operacional é confirmada apenas no primeiro movimento institucional legítimo, sobre um ativo existente, atualmente não atribuído e previamente reservado no portal.
|
||||
|
||||
Operações:
|
||||
|
||||
```bash
|
||||
@@ -103,6 +107,8 @@ docker compose exec app php artisan schedule:list
|
||||
|
||||
Administradores podem testar a ligação, iniciar uma execução e consultar as últimas dez sincronizações em `http://localhost:8000/administracao/snipe-it`. Uma execução falhada mantém o último catálogo válido e não desativa registos ausentes. Consultar `sync_runs`, os logs do worker e o painel antes de repetir.
|
||||
|
||||
O teste de ligação e `snipe:sync-assets` consultam a API e alteram apenas o cache local do portal. Não executar comandos improvisados com `POST`, `PUT`, `PATCH` ou `DELETE` contra a URL configurada. Nunca copiar para terminais partilhados, tickets ou documentação os tokens, a URL privada ou corpos integrais devolvidos pela API.
|
||||
|
||||
## Microsoft Entra ID
|
||||
|
||||
Registar uma aplicação **single tenant** do tipo Web. O redirect URI tem de corresponder exatamente ao domínio do portal e ao caminho `/auth/microsoft/callback`. Em produção é obrigatório HTTPS.
|
||||
@@ -139,6 +145,8 @@ Procedimento de entrega:
|
||||
4. registar a entrega e verificar a mensagem de sucesso;
|
||||
5. se existir erro, não voltar a submeter antes de consultar o estado do ativo no Snipe-IT.
|
||||
|
||||
Antes do `POST` de checkout, o portal consulta o estado remoto atual. Se o ativo já estiver atribuído — ao destinatário esperado ou a qualquer outro — a entrega é bloqueada sem escrita no Snipe-IT. Não contornar este bloqueio nem alterar o ativo diretamente para prosseguir.
|
||||
|
||||
Procedimento de devolução:
|
||||
|
||||
1. localizar a requisição ou ler a etiqueta;
|
||||
@@ -147,6 +155,8 @@ Procedimento de devolução:
|
||||
4. criar um incidente e bloqueio temporal quando o ativo não deva voltar ao catálogo;
|
||||
5. concluir o processo apenas depois de todos os itens estarem devolvidos.
|
||||
|
||||
O check-in só é enviado se existir um checkout confirmado pelo portal para esse item e requisitante, e se a atribuição atual no Snipe-IT corresponder ao mesmo utilizador. Se alguma destas provas falhar, não efetuar o check-in manualmente a partir do portal; reconciliar o histórico com o responsável do inventário.
|
||||
|
||||
Uma operação parcial fica em `incident`. Consultar `handover_records` para movimentos confirmados e os eventos `reservation.delivery_failed`/`reservation.return_failed` em `audit_logs`. Comparar cada item com o estado atual no Snipe-IT; corrigir apenas os itens ainda `ready` ou `delivered`. Não alterar estados diretamente na base de dados nem repetir em bloco, porque as escritas externas são deliberadamente não idempotentes e não têm retry automático.
|
||||
|
||||
## Ciclo de vida das pré-reservas
|
||||
|
||||
Reference in New Issue
Block a user