# NODA2 Security Hardening Guide
**MacBook Pro M4 Max — мінімальний безпечний пакет**  
**Дата:** 2026-02-27

---

## 1. SSH — перехід з пароля на ключ (ED25519)

### Статус: ВИКОНАНО (2026-02-27)

#### Що зроблено
- Згенеровано ED25519 ключ: `~/.ssh/noda1_ed25519`
- Public key доданий на NODA1 (`/root/.ssh/authorized_keys`)
- Private key: `secrets/noda1_id_ed25519` (chmod 600)
- `NODES_NODA1_SSH_PASSWORD` видалено з `.env`
- `sofiia-console` читає ключ через `/run/secrets/noda1_ssh_key` (file mount)

#### Як відновити (якщо ключ загублено)

```bash
# Генерація нового ключа
ssh-keygen -t ed25519 -C "sofiia-console@noda2" -f ./secrets/noda1_id_ed25519 -N ""
chmod 600 ./secrets/noda1_id_ed25519

# Додати public key на NODA1
cat ./secrets/noda1_id_ed25519.pub | ssh root@144.76.224.179 "cat >> ~/.ssh/authorized_keys"

# Перевірка
ssh -i ./secrets/noda1_id_ed25519 root@144.76.224.179 "echo 'key auth works'"
```

#### Перевірка що пароль не використовується
```bash
# Локально (repo)
grep -r 'SSH_PASSWORD' . --include='*.yml' --include='*.yaml' --include='*.env' --include='*.py' 2>/dev/null | grep -v '.git\|venv\|__pycache__\|ops/\|docs/'

# В запущеному контейнері
docker inspect sofiia-console --format '{{range .Config.Env}}{{println .}}{{end}}' | grep SSH_PASSWORD
# Очікуємо: 0 результатів
```

---

## 2. Port Exposure — bind на 127.0.0.1

### Статус: ВИКОНАНО (2026-02-27)

| Порт | Сервіс | Bind | Причина |
|------|--------|------|---------|
| 9102 | dagi-router-node2 | 127.0.0.1 | тільки локальне використання |
| 8890 | swapper-service-node2 | 127.0.0.1 | internal inference, не зовні |
| 8002 | sofiia-console | 127.0.0.1 | web UI тільки локально |
| 9300 | dagi-gateway-node2 | 0.0.0.0 | Telegram webhook потребує зовнішній доступ |
| 4222 | dagi-nats-node2 | 0.0.0.0 | NATS leafnode від NODA1 |
| 8222 | dagi-nats-node2 | 0.0.0.0 | NATS monitoring (обмежити якщо не потрібно) |

#### Рекомендація: обмежити NATS HTTP monitoring
Якщо 8222 не потрібен зовні:
```yaml
# docker-compose.node2-sofiia.yml — dagi-nats:
ports:
  - "0.0.0.0:4222:4222"      # leafnode — потрібен зовні
  - "127.0.0.1:8222:8222"    # monitoring — тільки локально
```

---

## 3. macOS Application Firewall

### Перевірка поточних слухаючих портів
```bash
lsof -iTCP -sTCP:LISTEN -P | grep -E '(LISTEN)' | awk '{print $9, $1}' | sort
```

### Мінімальний macOS pf firewall (Application Firewall)

Увімкнення через System Preferences → Security → Firewall:
1. Firewall → Turn On
2. Firewall Options → Block all incoming connections (крім Telegram і NATS)

Або через pfctl (більш гнучко):

```bash
# /etc/pf.conf (додати правила)
# Дозволити тільки NATS (4222) і Telegram gateway (9300) зовні
# Решту блокувати
pass in on en0 proto tcp to any port 4222  # NATS leafnode
pass in on en0 proto tcp to any port 9300  # Telegram gateway
block in on en0 proto tcp to any port 8890  # swapper — тільки localhost
block in on en0 proto tcp to any port 9102  # router — тільки localhost
block in on en0 proto tcp to any port 8002  # sofiia-console — тільки localhost
block in on en0 proto tcp to any port 8222  # nats monitoring — тільки localhost

# Apply:
sudo pfctl -f /etc/pf.conf -e
```

### Швидка перевірка що порти не відкриті назовні
```bash
# Ці команди мають повернути "refused" від зовнішньої машини:
# router, swapper, sofiia-console
nc -zv <NODA2_IP> 9102  # має бути Connection refused
nc -zv <NODA2_IP> 8890  # має бути Connection refused
nc -zv <NODA2_IP> 8002  # має бути Connection refused

# Ці мають бути доступні:
nc -zv <NODA2_IP> 4222  # NATS
nc -zv <NODA2_IP> 9300  # Gateway
```

---

## 4. Secrets Directory

`secrets/` — локальна директорія для чутливих файлів (НЕ в git):

```
secrets/
├── noda1_id_ed25519    # SSH private key → NODA1 (chmod 600)
└── README.md           # опис (ок в git)
```

Правила:
- `secrets/noda1_id_ed25519` і `secrets/*.key` — в `.gitignore`
- Всі файли `chmod 600`
- НЕ зберігати паролі в env docker-compose — тільки через file mount

---

## 5. Checklist фінальний

- [x] SSH пароль видалено з `.env`
- [x] SSH key-based auth налаштований і перевірений
- [x] `secrets/noda1_id_ed25519` в `.gitignore`
- [x] Router (9102) bind на 127.0.0.1
- [x] Swapper (8890) bind на 127.0.0.1  
- [x] Sofiia-console (8002) bind на 127.0.0.1
- [ ] NATS monitoring (8222) обмежити до 127.0.0.1 (optional)
- [ ] macOS Firewall увімкнений
- [ ] pf rules додані для зовнішніх портів
