Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .env.example
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# Copie para .env localmente. A aplicação lê as variáveis de ambiente do processo;
# ela nunca grava segredos na configuração ou nos relatórios.
PROD_EXAMPLE_USERNAME=replace-me
PROD_EXAMPLE_KEY_PASSPHRASE=replace-me
PROD_EXAMPLE_PASSWORD=replace-me
TEST_DB_EXAMPLE_USERNAME=replace-me
TEST_DB_EXAMPLE_PASSWORD=replace-me
7 changes: 4 additions & 3 deletions config.example.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,11 @@ servers:
port: 22
environment: production
username_secret: PROD_EXAMPLE_USERNAME
authentication: key
private_key: /path/to/private/key
authentication: password
password_secret: PROD_EXAMPLE_PASSWORD
host_key_policy: strict
# Opcional: arquivo adicional no formato OpenSSH. Sem esta opção, usa ~/.ssh/known_hosts.
# known_hosts_file: /etc/wp-modernizer/known_hosts
databases:
test-db-example:
host: db-test.example.invalid
Expand Down Expand Up @@ -38,4 +40,3 @@ observability:
json_stdout: true
log_file: null
otel_enabled: false

8 changes: 7 additions & 1 deletion docs/architecture.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,13 @@ modelos imutáveis, enums, invariantes, análise de caminhos, nomenclatura e pla
importar APIs de processos externos. `application` contém os casos de uso e as portas `Protocol`.
`pipeline` contém etapas independentes e o executor que preserva o estado em caso de falha.
`infrastructure` fornece adaptadores de subprocessos, estado local, YAML/ambiente, MySQL,
SSH/rsync, WP-CLI, sistema de arquivos e Git. `cli` trata apenas da composição.
SSH/rsync por chave, SSH/SFTP por senha, WP-CLI, sistema de arquivos e Git. `cli` trata apenas da
composição.

A composition root em `cli.main.build_service` liga a configuração ao
`EnvironmentSecretProvider`, cria os adaptadores SSH/MySQL/WP-CLI, injeta um roteador de
transporte que escolhe chave ou senha explicitamente em
`RuntimeOperations` pelas portas da aplicação e, por fim, constrói `ModernizerService`.

As dependências apontam para dentro. Objetos falsos implementam os mesmos `Protocol`s e permitem
testar todas as regras de segurança sem WordPress. Dataclasses modelam valores estáveis do
Expand Down
27 changes: 27 additions & 0 deletions docs/configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,3 +9,30 @@ Cada instalação informa um servidor de origem, um ambiente de origem (`product
um caminho absoluto de origem, um destino de TESTE absoluto e IDs permitidos de endpoints de
banco de dados de teste. Apelidos e substituições exatas de bancos são explícitos. Por padrão,
não é permitida a criação de bancos inexistentes.

## Transporte SSH

Cada servidor escolhe o transporte explicitamente em `authentication`. O cenário principal de
implantação usa `password`, com `username_secret` e `password_secret` apontando para entradas do
`SecretProvider`:

```yaml
servers:
source-example:
host: source.example.org
port: 22
environment: production
username_secret: PROD_EXAMPLE_USERNAME
authentication: password
password_secret: PROD_EXAMPLE_PASSWORD
host_key_policy: strict
```

O valor da senha não pertence ao YAML. O adapter SFTP obtém usuário e senha somente no momento da
conexão e os fornece à API do Paramiko, sem shell ou subprocesso. `authentication: key` continua
disponível com `private_key` e usa OpenSSH/rsync; os dois mecanismos são adapters separados.

Com `host_key_policy: strict`, o transporte carrega o `~/.ssh/known_hosts` da conta que executa a
aplicação e rejeita chaves desconhecidas ou alteradas. `known_hosts_file` pode indicar um arquivo
OpenSSH adicional, por exemplo `/etc/wp-modernizer/known_hosts`. O arquivo deve ser provisionado
antes do preflight por um canal confiável. Não use `accept-new` em produção.
19 changes: 18 additions & 1 deletion docs/deployment-requirements.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,5 +12,22 @@
| Quais pontos de controle do núcleo são aceitos por site? | compatibilidade controlada de atualização | lista ordenada de versões | apenas etapas genéricas configuradas são executadas |
| Quais plugins gerenciados e qual política para árvore suja se aplicam? | evitar perder trabalho local | repositório público/acessível, branch e política | atualização gerenciada ignorada |
| Onde o estado externo é mantido e copiado? | durabilidade da retomada e auditoria | diretório absoluto e política de retenção/criptografia | apenas estado local configurado |
| É necessária compatibilidade com senha no SSH? | escolha de adaptador/segurança | sim/não e mecanismo de transporte seguro | apenas autenticação por chave |
| Quais referências de usuário e senha SSH serão provisionadas? | autenticação do transporte SFTP | nomes das entradas no `SecretProvider`; nunca os valores | cópia remota indisponível |
| Qual destino de telemetria e política de dados estão aprovados? | exportação OTLP opcional | endpoint, referências de ambiente para TLS/autenticação e retenção | apenas logs JSON locais |

## Preflight SSH por senha

Antes de liberar uma origem, confirme que:

1. o DNS e a porta do servidor são alcançáveis pela conta de serviço;
2. `username_secret` e `password_secret` existem no `SecretProvider`;
3. a chave pública do host foi validada fora de banda e instalada em `~/.ssh/known_hosts` ou no
`known_hosts_file` configurado;
4. a entrada usa o formato `[host]:porta` quando a porta não é 22;
5. a conta possui leitura e travessia sobre toda a árvore de origem;
6. o destino local possui espaço e permissões para criar a cópia;
7. um teste em infraestrutura descartável confirma as exclusões do plano e os timeouts.

Uma chave ausente ou diferente deve interromper o preflight. Não altere `host_key_policy: strict`
para resolver falhas de autenticação: confiança do host e credenciais do usuário são verificações
independentes.
11 changes: 7 additions & 4 deletions docs/operations.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,10 @@ adaptador. Uma simulação ainda sonda capacidades e grava seu manifesto externo
que o trabalho proposto possa ser auditado. Intencionalmente, não existe comando de publicação
em produção.

O adaptador público de execução é conservador: a migração real permanece desabilitada até que a
implantação forneça e teste por contrato a origem SSH, a descoberta do banco de origem, a
retenção de cópias de segurança e o gravador protegido de `wp-config`. Ele falha antes de alterar
o estado quando a infraestrutura não está definida.
O adaptador público de execução delega cópias à porta de transporte remoto. Um roteador usa
SSH/rsync para autenticação por chave e SSH/SFTP (Paramiko) para autenticação por senha. A
descoberta e transferência de bancos é delegada
ao MySQL e operações WordPress ao WP-CLI. Uma migração de banco exige endpoints de origem e de
TESTE permitidos e resolução não ambígua. Credenciais do `wp-config` são entregues ao WP-CLI por
entrada padrão, e não por `argv`. A retenção de uma cópia de teste já existente continua falhando
antes de alterar estado até que um adaptador específico seja configurado.
8 changes: 5 additions & 3 deletions docs/security.md
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
# Modelo de segurança

As fronteiras de segurança incluem destinos somente de TESTE, caminhos canônicos e endpoints em
listas de permissão, chaves de host SSH estritas por padrão, preferência por autenticação por
chave, execução de subprocessos por `argv` sem shell, limites de tempo, segredos apenas no
ambiente, ocultação centralizada de saída/`argv` e estado externo de execução. Dumps de bancos de
listas de permissão, chaves de host SSH estritas por padrão, adapters separados para autenticação
por chave e senha, execução de subprocessos por `argv` sem shell, limites de tempo, segredos
apenas no provedor, ocultação centralizada de saída/`argv` e estado externo de execução. O adapter
SFTP entrega a senha diretamente à API Paramiko em memória; não usa `sshpass`, shell, `expect`,
variável de subprocesso ou linha de comando. Dumps de bancos de
dados e arquivos compactados são ignorados e exigem criptografia/retenção no nível da implantação.

Antes da publicação, execute a varredura de segredos/topologia documentada em `development.md`,
Expand Down
11 changes: 10 additions & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,12 @@ readme = "README.md"
requires-python = ">=3.10"
license = {text = "Seleção de licença pendente de aprovação organizacional"}
authors = [{name = "Colaboradores do wp-modernizer"}]
dependencies = ["click>=8.1,<9", "pydantic>=2,<3", "PyYAML>=6,<7"]
dependencies = [
"click>=8.1,<9",
"paramiko>=5.0,<6",
"pydantic>=2,<3",
"PyYAML>=6,<7",
]

[project.optional-dependencies]
otel = ["opentelemetry-api>=1.27", "opentelemetry-sdk>=1.27", "opentelemetry-exporter-otlp>=1.27"]
Expand Down Expand Up @@ -58,3 +63,7 @@ mypy_path = "src"
[[tool.mypy.overrides]]
module = "yaml"
ignore_missing_imports = true

[[tool.mypy.overrides]]
module = ["paramiko", "paramiko.*"]
ignore_missing_imports = true
51 changes: 50 additions & 1 deletion src/wp_modernizer/application/ports.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,12 @@
from pathlib import Path
from typing import Any, Dict, Mapping, Optional, Protocol, Sequence, Set, Tuple

from wp_modernizer.domain.models import CapabilityReport, RunManifest, StepResult
from wp_modernizer.domain.models import (
CapabilityReport,
DatabaseProbeResult,
RunManifest,
StepResult,
)
from wp_modernizer.domain.widgets import WidgetSnapshot


Expand All @@ -22,6 +27,50 @@ def get_database(self, endpoint_id: str) -> Any: ...
def list_schemas(self, endpoint_id: str) -> Set[str]: ...


class DatabaseProbePort(Protocol):
def probe_database(self, endpoint_id: str, database: str) -> DatabaseProbeResult: ...


class FileTransferPort(ServerRegistry, Protocol):
def copy_from(
self,
server_id: str,
source: Path,
destination_parent: Path,
excludes: Sequence[Path],
run_id: str,
) -> int: ...


class DatabasePort(DatabaseRegistry, Protocol):
def dump(self, endpoint_id: str, database: str, output: Path, run_id: str) -> None: ...

def import_dump(self, endpoint_id: str, database: str, source: Path, run_id: str) -> None: ...

def snapshot_widgets(self, endpoint_id: str, database: str) -> WidgetSnapshot: ...

def wordpress_configuration(self, endpoint_id: str, database: str) -> Mapping[str, str]: ...


class WordPressPort(Protocol):
def get_config(self, path: Path, name: str, run_id: str) -> str: ...

def search_replace(
self,
path: Path,
old_url: str,
new_url: str,
*,
dry_run: bool,
multisite: bool,
run_id: str,
) -> str: ...

def set_config(self, path: Path, values: Mapping[str, str], run_id: str) -> None: ...

def update(self, path: Path, arguments: Sequence[str], run_id: str) -> str: ...


@dataclass(frozen=True)
class CommandResult:
argv: Tuple[str, ...]
Expand Down
Loading