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
45 changes: 45 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
name: CI

on:
push:
branches: [ main, feature/** ]
pull_request:
branches: [ main ]

jobs:
test:
runs-on: ubuntu-latest
strategy:
matrix:
python-version: [3.11]

steps:
- name: Checkout repository
uses: actions/checkout@v4

- name: Set up Python
uses: actions/setup-python@v4
with:
python-version: ${{ matrix.python-version }}

- name: Create virtualenv (.venv)
run: |
python -m venv .venv
.venv/bin/python -m pip install --upgrade pip

- name: Install project and test dependencies
run: |
# Install package in editable mode and test extras from pyproject
.venv/bin/pip install -e '.[test]'
# Pillow is optional in pyproject; ensure it is present for renderer tests
.venv/bin/pip install pillow

- name: Run tests
run: .venv/bin/pytest -v

- name: Upload pytest JUnit result (optional)
if: always()
uses: actions/upload-artifact@v4
with:
name: pytest-log
path: .pytest_cache
36 changes: 34 additions & 2 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,27 @@

All notable changes to this project will be documented in this file.

## [3.0.0] - 2025-11-09
### Added
- Dynamic WAN selection and runtime orchestration via `azctl wan-manager`:
- Evaluates candidate uplink interfaces and selects the healthiest WAN at boot and runtime.
- Writes health snapshots to `runtime/wan_state.json` (production path `/var/run/azazel/wan_state.json`); path can be overridden with `AZAZEL_WAN_STATE_PATH`.
- Candidate precedence: explicit CLI `--candidate` → `AZAZEL_WAN_CANDIDATES` env var (comma-separated) → `configs/network/azazel.yaml` (`interfaces.external`/`interfaces.wan`) → safe fallbacks.
- On WAN change, the manager reapplies traffic control (`bin/azazel-traffic-init.sh`), refreshes NAT, and restarts dependent services (Suricata, `azctl-unified`).

- Universal runtime interface resolution for consumers:
- CLI/TUI, scripts, and services now prefer explicit CLI args → environment variables (`AZAZEL_WAN_IF` / `AZAZEL_LAN_IF`) → WAN manager state → configuration values → final fallback.
- Added `AZAZEL_WAN_CANDIDATES` and `AZAZEL_WAN_STATE_PATH` environment variables for operational control and testing.

### Changed
- Scripts and documentation updated to use parameterized interface references (`${AZAZEL_WAN_IF:-<fallback>}` and `${AZAZEL_LAN_IF:-<fallback>}`) in help text and examples. Where safe, runtime resolution now uses the WAN manager helper instead of hard-coded interface names.

### Notes
- Backwards-compatible: explicit CLI flags and environment variables still override runtime selection. Existing deployments should continue to work; review scripts that assume literal interface names before automating deployment.
- Tests and shell syntax checks were run after edits; no regressions detected in the unit test suite.
- QoS features are opt-in via systemd service enablement.
- All changes maintain backward compatibility with existing configurations.

## [2.2.0] - 2025-11-07
### Added
- **Internal Network QoS Control**: Comprehensive privilege-based traffic shaping and security enforcement for LAN devices.
Expand All @@ -20,6 +41,7 @@ All notable changes to this project will be documented in this file.
### Changed
- QoS scripts support DRY_RUN mode (print commands without execution, no root required).
- All QoS scripts are idempotent (safe to re-run).
- Dynamic WAN selection: `wan-manager` now determines the active WAN interface at runtime and writes runtime/wan_state.json. Consumers (CLI, TUI, scripts) will use that selection by default when `--wan-if` is omitted. Environment variables `AZAZEL_WAN_IF` and `AZAZEL_LAN_IF` may be used to override defaults where needed.

### Security
- MAC address verification prevents ARP spoofing for privileged devices.
Expand All @@ -34,8 +56,6 @@ All notable changes to this project will be documented in this file.

### Notes
- Minor version bump (2.1.0 → 2.2.0) adds significant new QoS feature without breaking existing functionality.
- QoS features are opt-in via systemd service enablement.
- All changes maintain backward compatibility with existing configurations.

## [2.1.0] - 2025-11-07
### Added
Expand Down Expand Up @@ -84,6 +104,18 @@ Semantic versioning: MAJOR.MINOR.PATCH. Deprecations queued for removal after at
## [1.0.0] - 2025-10-05
### Initial release
- Initial public baseline of Azazel-Pi with core features:

## [3.1.0] - 2025-11-09
### Added
- Display: clear and force a full E-Paper refresh when the active WAN interface changes (e.g. eth0 -> wlan1) to avoid ghosting and show the updated interface/IP immediately. (commit 478b8ee)
- Status collection: prefer kernel default route when runtime WAN state is missing and provide a `wan_state_path` injection point for testing/overrides.
- Renderer: improve network line formatting by removing the redundant "WAN" prefix and suppressing non-actionable "[WAN] unknown" messages; reserve footer area to prevent text overlap.

### Changed
- Backwards-compatible `StatusCollector` initialization handling in `epd_daemon` — older installs without the new `wan_state_path` parameter are tolerated.

### Notes
- These are backward-compatible improvements (minor release). See commit 478b8ee for details and files changed: `azazel_pi/core/display/status_collector.py`, `epd_daemon.py`, `renderer.py`.
- Suricata integration for network threat detection
- AI-based threat evaluation pipeline and scoring
- Basic TUI and CLI utilities for status and control
Expand Down
89 changes: 82 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -145,8 +145,18 @@ Lightweight configuration optimized for Raspberry Pi, enabling rapid deployment
After cloning the repository or downloading a release, run the complete automated installer:

```bash
cd Azazel-Pi
# Complete installation with all dependencies and configurations
# Launch TUI menu. If you omit --wan-if the CLI will dynamically resolve the WAN
# interface using the WAN manager (recommended). You can also force an interface
# via the AZAZEL_WAN_IF / AZAZEL_LAN_IF environment variables.
# Example: prefer runtime selection — WAN will be resolved automatically when omitted.
# You can override the detected interfaces with environment variables:
# export AZAZEL_LAN_IF=${AZAZEL_LAN_IF:-wlan0}
# export AZAZEL_WAN_IF=${AZAZEL_WAN_IF:-wlan1}
# then run the CLI without the --wan-if flag if you want the runtime helper to pick the WAN.
python3 -m azctl.cli menu --lan-if ${AZAZEL_LAN_IF:-wlan0}
# or: omit --wan-if to let the system choose the active WAN interface
python3 -m azctl.cli menu --lan-if ${AZAZEL_LAN_IF:-wlan0}
```
sudo scripts/install_azazel_complete.sh --start

# Or step-by-step installation:
Expand Down Expand Up @@ -203,6 +213,35 @@ sudo systemctl enable --now azazel-epd.service

See [`docs/en/EPD_SETUP.md`](docs/en/EPD_SETUP.md) for complete E-Paper configuration instructions.

## Running tests (developer)

This project uses a local virtual environment at `.venv` for development tests. To run the unit tests that exercise E-Paper rendering in emulation mode, do the following:

1. Activate or create the virtual environment (example):

```bash
python3 -m venv .venv
source .venv/bin/activate
pip install -U pip
pip install -r requirements-dev.txt
```

2. Install optional dependencies used by E-Paper rendering (Pillow) if not included in `requirements-dev.txt`:

```bash
pip install pillow
```

3. Run tests (example):

```bash
.venv/bin/pytest tests/core/test_epd_daemon.py -q
```

Notes:
- The E-Paper renderer supports `--emulate` which avoids hardware access and writes a PNG file when run in `--mode test`.
- Use `--wan-state-path` to point the renderer/collector at a custom WAN state file for integration testing.

### Optional: Front Mattermost with Nginx

To serve Mattermost via Nginx reverse proxy (recommended), use the provided template and setup script:
Expand All @@ -229,7 +268,7 @@ The interactive Terminal User Interface (TUI) menu provides comprehensive system
python3 -m azctl.cli menu

# With specific interface configuration
python3 -m azctl.cli menu --lan-if wlan0 --wan-if wlan1
python3 -m azctl.cli menu --lan-if ${AZAZEL_LAN_IF:-wlan0} --wan-if ${AZAZEL_WAN_IF:-wlan1}
```

**Modular Architecture:**
Expand Down Expand Up @@ -293,11 +332,16 @@ echo '{"mode": "lockdown"}' | azctl events --config -
The modular TUI menu provides comprehensive system management:

```bash
# Launch modular TUI menu
# Launch modular TUI menu. If --wan-if is omitted, azctl will consult the
# WAN manager to select the active WAN interface. To override selection use
# the CLI flags or environment variables described below.
python3 -m azctl.cli menu

# Specify custom interfaces
python3 -m azctl.cli menu --lan-if wlan0 --wan-if wlan1
# Specify custom interfaces (explicit override)
python3 -m azctl.cli menu --lan-if ${AZAZEL_LAN_IF:-wlan0} --wan-if ${AZAZEL_WAN_IF:-wlan1}

# Or let the system choose WAN automatically:
python3 -m azctl.cli menu --lan-if ${AZAZEL_LAN_IF:-wlan0}
```

**Menu Features:**
Expand Down Expand Up @@ -386,7 +430,12 @@ python3 -m azctl.cli menu --lan-if wlan0 --wan-if wlan1
### Configuration Workflow

1. **Edit Core Configuration**: Modify `/etc/azazel/azazel.yaml` to adjust delay values, bandwidth controls, and lockdown allowlists (template at `configs/network/azazel.yaml`).
- By default, `wlan0` is treated as the internal LAN (AP), and both `wlan1` and `eth0` are considered external (WAN/uplink) interfaces. See `interfaces.external: ["eth0", "wlan1"]` in `configs/network/azazel.yaml` and adjust as needed.
- Interface defaults: `${AZAZEL_LAN_IF:-wlan0}` is typically treated as the internal LAN (AP); `${AZAZEL_WAN_IF:-wlan1}` and `${AZAZEL_WAN_IF:-eth0}` are common external (WAN/uplink) candidates and are listed under `interfaces.external` in `configs/network/azazel.yaml`.
Note: Azazel now prefers a runtime WAN selection produced by the WAN manager when `--wan-if` is not provided. To explicitly override the chosen interfaces, set the environment variables `AZAZEL_WAN_IF` and/or `AZAZEL_LAN_IF` before running commands or scripts.
- Override options:
- CLI: pass `--lan-if` and/or `--wan-if` to `azctl` commands to explicitly set interfaces.
- Environment: set `AZAZEL_LAN_IF` or `AZAZEL_WAN_IF` to change defaults for scripts and services.
- Dynamic: if `--wan-if` is omitted, `azctl` will query the WAN manager (recommended) to pick the active WAN interface based on runtime health checks.

2. **Generate Suricata Rules**: Use `scripts/suricata_generate.py` to render environment-specific IDS configurations

Expand All @@ -396,6 +445,32 @@ python3 -m azctl.cli menu --lan-if wlan0 --wan-if wlan1

5. **Monitor Operations**: Analyze scoring results in `decisions.log` and use `azctl` for manual mode switching during incidents

### Dynamic WAN Selection (NEW)

- The `azctl wan-manager` service evaluates all candidate WAN interfaces (from `interfaces.external`) after boot and continuously during runtime.
- Health snapshots (link status, IP presence, estimated speed) are written to `runtime/wan_state.json` (or `/var/run/azazel/wan_state.json` on deployed systems) and surfaced on the E-Paper display. You can override the default path with the `AZAZEL_WAN_STATE_PATH` environment variable when testing or for non-standard deployments.
- The WAN manager reads candidate lists in order of precedence: explicit CLI `--candidate` arguments, the `AZAZEL_WAN_CANDIDATES` environment variable (comma-separated), values declared in `configs/network/azazel.yaml` (`interfaces.external` or `interfaces.wan`), then safe fallbacks. Use `AZAZEL_WAN_CANDIDATES` to force a specific candidate ordering without changing config files.
- When the active interface changes, the manager reapplies `bin/azazel-traffic-init.sh`, refreshes NAT (`iptables -t nat`), and restarts dependent services (Suricata and `azctl-unified`) so they immediately consume the new interface.
- Suricata now launches through `azazel_pi.core.network.suricata_wrapper`, which reads the same WAN state file, so restarting the service is sufficient to follow the latest selection.

Developer note — non-root testing and fallback behavior

- The WAN manager will attempt to write the runtime state file to a system runtime path (for example `/var/run/azazel/wan_state.json`) when running as a system service. On systems where the process does not have permission to create `/var/run/azazel`, the manager now falls back automatically to a repository-local path `runtime/wan_state.json` so developers can run and test `azctl wan-manager` without root.
- For explicit control in tests or non-standard deployments, set `AZAZEL_WAN_STATE_PATH` to a writable path before running the manager. Example (development):

```bash
# write state into the repository runtime directory (no root required)
AZAZEL_WAN_STATE_PATH=runtime/wan_state.json python3 -m azctl.cli wan-manager --once
```

- For production systems, run the WAN manager via systemd (root) so that traffic-init, iptables/nft, and service restarts run with the required privileges. Example (recommended for deployed systems):

```bash
sudo systemctl enable --now azazel-wan-manager.service
```

These options allow safe developer testing while preserving the intended privileged behavior in production.

### Defensive Mode Operations

- **Portal Mode**: Baseline monitoring with minimal network impact
Expand Down
38 changes: 35 additions & 3 deletions README_ja.md
Original file line number Diff line number Diff line change
Expand Up @@ -202,7 +202,11 @@ sudo systemctl enable --now azazel-epd.service
python3 -m azctl.cli menu

# 特定のインターフェース設定で起動
python3 -m azctl.cli menu --lan-if wlan0 --wan-if wlan1
# 例: 実行時のWAN選択を優先します。`--wan-if` を省略した場合、WANマネージャが既定値を選択します。
# 必要に応じて環境変数で上書きできます:
# export AZAZEL_LAN_IF=${AZAZEL_LAN_IF:-wlan0}
# export AZAZEL_WAN_IF=${AZAZEL_WAN_IF:-wlan1}
python3 -m azctl.cli menu --lan-if ${AZAZEL_LAN_IF:-wlan0}
```

**モジュラーアーキテクチャ:**
Expand Down Expand Up @@ -283,13 +287,14 @@ echo '{"mode": "lockdown"}' | azctl events --config -
python3 -m azctl.cli menu

# カスタムインターフェースを指定
python3 -m azctl.cli menu --lan-if wlan0 --wan-if wlan1
python3 -m azctl.cli menu --lan-if ${AZAZEL_LAN_IF:-wlan0} --wan-if ${AZAZEL_WAN_IF:-wlan1}
```

**メニュー機能:**

1. **コア設定の編集**: `/etc/azazel/azazel.yaml` を修正して遅延値、帯域制御、ロックダウン許可リストを調整(テンプレートは `configs/network/azazel.yaml`)。
- 既定では `wlan0` を内部LAN(AP)、`wlan1` と `eth0` を外部(WAN/アップリンク)として扱います。`configs/network/azazel.yaml` の `interfaces.external` に `["eth0", "wlan1"]` を定義済みです(必要に応じて変更可能)。
- 既定では `wlan0` を内部LAN(AP)、`wlan1` と `eth0` を外部(WAN/アップリンク)として扱います。`configs/network/azazel.yaml` の `interfaces.external` に `["eth0", "wlan1"]` を定義済みです(必要に応じて変更可能)。
注: `--wan-if` を指定しない場合、WAN 管理コンポーネントがランタイムで最適な WAN インターフェイスを選択します。明示的に指定したい場合は `AZAZEL_WAN_IF` / `AZAZEL_LAN_IF` を環境変数で設定してください。

2. **Suricataルール生成**: `scripts/suricata_generate.py` を使用して環境固有のIDS設定をレンダリング

Expand Down Expand Up @@ -412,6 +417,33 @@ azctl/menu/
- [`docs/ja/API_REFERENCE.md`](docs/ja/API_REFERENCE.md) — Pythonモジュールとスクリプトリファレンス
- [`docs/ja/SURICATA_INSTALLER.md`](docs/ja/SURICATA_INSTALLER.md) — Suricataインストールと設定詳細

#### 動的WAN切り替え(新機能)

- `azctl wan-manager` サービスが `interfaces.external` に列挙されたインターフェースを順番にヘルスチェックし、起動直後と運用中の両方で最も安定した WAN を自動選択します。
- 選定結果と各インターフェースの状態は `runtime/wan_state.json`(本番では `/var/run/azazel/wan_state.json`)に記録され、E-Paper 画面にも「再設定中」「WAN切替完了」といったメッセージで表示されます。テストやカスタム配置では `AZAZEL_WAN_STATE_PATH` 環境変数で状態ファイルの場所を上書きできます。
- WAN マネージャは候補の読み取り順序(優先順位)を持ちます: 明示的な CLI の `--candidate` → `AZAZEL_WAN_CANDIDATES` 環境変数(カンマ区切り)→ `configs/network/azazel.yaml` の `interfaces.external` / `interfaces.wan` → フォールバック。設定ファイルを直接編集せずに候補順序を制御したい環境では `AZAZEL_WAN_CANDIDATES` を利用してください。
- 切り替え時には `bin/azazel-traffic-init.sh`、NAT (`iptables -t nat`) を再適用し、Suricata と `azctl-unified` を順次再起動して即座に新しいインターフェースを利用させます。
- Suricata は `azazel_pi.core.network.suricata_wrapper` を経由して起動するため、サービス再起動だけで常に最新の WAN 状態を参照できます。

開発者向けメモ — 非 root 環境でのテストとフォールバック動作

- WAN マネージャは通常システムのランタイムパス(例: `/var/run/azazel/wan_state.json`)へ状態を書き込みます。systemd 等で root 権限で実行される本番環境ではこれが期待どおりに動作します。
- 一方で開発や CI 環境などでプロセスに `/var/run/azazel` を作成する権限が無い場合、現在の実装は自動的にリポジトリ内の `runtime/wan_state.json` にフォールバックするため、非 root ユーザーでも `azctl wan-manager` を実行して動作確認ができます。
- 明示的に書き込み先を指定したい場合は `AZAZEL_WAN_STATE_PATH` 環境変数を設定してください(開発例):

```bash
# リポジトリ内の runtime ディレクトリに状態を書き込む(root 不要)
AZAZEL_WAN_STATE_PATH=runtime/wan_state.json python3 -m azctl.cli wan-manager --once
```

- 本番運用では systemd(root)経由で WAN マネージャを動かすことを推奨します。これにより `tc`/`iptables`(または `nft`)やサービス再起動など、特権を要する処理が正しく行われます。例(推奨):

```bash
sudo systemctl enable --now azazel-wan-manager.service
```

これにより、開発者は権限に縛られずにローカルで動作確認ができ、本番では特権を持った実行で期待どおりの自動適用が行われます。

## 開発の背景

現代のサイバー攻撃はますます高速化・自動化されており、従来のハニーポットでは不十分です。このシステムは **観察やブロックではなく戦術的遅延** を目的として設計されており、時間を防御資産として活用します。
Expand Down
Loading
Loading