Initial release: ghost_protocol privacy toolkit
This commit is contained in:
+22
@@ -0,0 +1,22 @@
|
|||||||
|
logs/
|
||||||
|
__pycache__/
|
||||||
|
*.pyc
|
||||||
|
.env
|
||||||
|
vars.yaml
|
||||||
|
*.log
|
||||||
|
*.pem
|
||||||
|
*.key
|
||||||
|
id_rsa*
|
||||||
|
venv/
|
||||||
|
.venv/
|
||||||
|
.DS_Store
|
||||||
|
*.swp
|
||||||
|
*.swo
|
||||||
|
*~
|
||||||
|
*.tar.gz
|
||||||
|
.claude/
|
||||||
|
private/
|
||||||
|
*.retry
|
||||||
|
inventory*
|
||||||
|
.vault_pass*
|
||||||
|
hosts
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
MIT License
|
||||||
|
|
||||||
|
Copyright (c) 2026 ghost_protocol contributors
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all
|
||||||
|
copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
SOFTWARE.
|
||||||
@@ -0,0 +1,90 @@
|
|||||||
|
# ghost_protocol
|
||||||
|
|
||||||
|
A privacy toolkit for individuals who take digital autonomy seriously. Three tools, one goal: control your own infrastructure.
|
||||||
|
|
||||||
|
## Components
|
||||||
|
|
||||||
|
### opsec/
|
||||||
|
OPSEC hardening suite for Linux systems. MAC randomization, DNS leak prevention, kill switches, Tor integration, hostname randomization, and a desktop status widget. Configurable deployment levels from standard privacy to full paranoid mode.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cd opsec && sudo ./install.sh
|
||||||
|
```
|
||||||
|
|
||||||
|
### covert_sd/
|
||||||
|
Covert SD card tool for secure storage operations. Encryption, hidden volumes, and plausible deniability for portable media.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cd covert_sd && python3 covert_sd_card_tool.py
|
||||||
|
```
|
||||||
|
|
||||||
|
### phantom/
|
||||||
|
Privacy server deployer. Stand up Matrix, WireGuard, Pi-hole, and more on cloud providers or your own hardware — hardened out of the box.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cd phantom && python3 phantom.py
|
||||||
|
```
|
||||||
|
|
||||||
|
## Who This Is For
|
||||||
|
|
||||||
|
- Journalists protecting sources
|
||||||
|
- Researchers handling sensitive data
|
||||||
|
- Privacy advocates practicing what they preach
|
||||||
|
- Anyone who believes infrastructure shouldn't require trust in third parties
|
||||||
|
|
||||||
|
## Quick Start
|
||||||
|
|
||||||
|
```bash
|
||||||
|
git clone <this-repo> ghost_protocol
|
||||||
|
cd ghost_protocol
|
||||||
|
|
||||||
|
# OPSEC hardening
|
||||||
|
cd opsec && sudo ./install.sh
|
||||||
|
|
||||||
|
# Deploy a privacy server
|
||||||
|
cd ../phantom && python3 phantom.py
|
||||||
|
|
||||||
|
# Covert SD operations
|
||||||
|
cd ../covert_sd && python3 covert_sd_card_tool.py
|
||||||
|
```
|
||||||
|
|
||||||
|
## Requirements
|
||||||
|
|
||||||
|
- **opsec**: Linux (Debian/Ubuntu), root access
|
||||||
|
- **covert_sd**: Python 3.8+, cryptsetup
|
||||||
|
- **phantom**: Python 3.8+, Ansible 2.12+, SSH, pyyaml
|
||||||
|
- For AWS deployments: `ansible-galaxy collection install amazon.aws` and `pip install boto3`
|
||||||
|
|
||||||
|
## Important: Configure Before Use
|
||||||
|
|
||||||
|
**This toolkit ships with intentionally blank or generic defaults.** You must configure it for your environment before relying on it.
|
||||||
|
|
||||||
|
### opsec/
|
||||||
|
After installing, run `sudo opsec-config.sh` to set:
|
||||||
|
- **Tor exit node exclusions** (`TOR_BLACKLIST`) — empty by default. Set country codes based on your threat model (e.g., `us,gb,ca,au,nz` for Five Eyes exclusion). See `configs/opsec-country-codes.conf` for presets.
|
||||||
|
- **DNS mode** (`DNS_MODE`) — defaults to `tor`. Choose based on your needs: `tor`, `quad9`, `cloudflare`, `doh`, `dot`, or `custom`.
|
||||||
|
- **Deployment level** — run `sudo opsec-config.sh --level apply <level>` to select a preset. Review `configs/levels/` to understand what each level enables.
|
||||||
|
- **Widget theme** (`WIDGET_THEME`) — defaults to `default`. Run `sudo opsec-config.sh --theme list` for options.
|
||||||
|
- **Hostname pattern** (`HOSTNAME_PATTERN`) — defaults to `desktop`. Set to `random` if you want randomization on boot.
|
||||||
|
- **MAC randomization**, **kill switch behavior**, and **traffic blending** all need to be reviewed and enabled per your use case.
|
||||||
|
|
||||||
|
The generic defaults are safe but minimal. They will **not** protect you against a sophisticated adversary without customization. Review `/etc/opsec/opsec.conf` after install and adjust every section for your threat model.
|
||||||
|
|
||||||
|
### phantom/
|
||||||
|
Each deployment prompts for service-specific configuration (domains, credentials, DNS providers). There are no hardcoded server addresses or API keys. You supply everything at deploy time.
|
||||||
|
|
||||||
|
### covert_sd/
|
||||||
|
The tool prompts for all encryption parameters interactively. No defaults to change, but read the README for security model details.
|
||||||
|
|
||||||
|
## Responsible Use
|
||||||
|
|
||||||
|
This toolkit is intended for **legitimate privacy protection**: journalists safeguarding sources, researchers handling sensitive data, organizations protecting communications, and individuals exercising their right to privacy.
|
||||||
|
|
||||||
|
- Secure deletion features are irreversible. Understand what you are deleting.
|
||||||
|
- Network anonymization is not foolproof. No tool provides absolute anonymity.
|
||||||
|
- Comply with applicable laws in your jurisdiction.
|
||||||
|
- This software is provided as-is with no warranty. You are responsible for how you use it.
|
||||||
|
|
||||||
|
## License
|
||||||
|
|
||||||
|
MIT — see [LICENSE](LICENSE)
|
||||||
@@ -0,0 +1,334 @@
|
|||||||
|
# Covert SD Card Tool
|
||||||
|
|
||||||
|
## Introduction
|
||||||
|
|
||||||
|
The **Covert SD Card Tool** is a Python script designed to automate the process of setting up a bootable USB/SD card with either Kali Linux or Tails OS. It includes options to create encrypted persistence partitions, secure document storage, and user-friendly access scripts. This tool simplifies the complex steps involved in preparing a secure, portable operating system on a USB drive or SD card.
|
||||||
|
|
||||||
|
## Features
|
||||||
|
|
||||||
|
- **Install Kali Linux or Tails OS** on a USB/SD card
|
||||||
|
- **Create an encrypted persistence partition** for Kali Linux (LUKS encryption)
|
||||||
|
- **Create a maximum-security encrypted documents partition** with triple-cascade encryption
|
||||||
|
- **Secure drive wiping** using multi-pass shred for data sanitization
|
||||||
|
- **User-friendly access scripts** for mounting and locking secure storage
|
||||||
|
- **OPSEC-focused design** - generated scripts use generic terminology
|
||||||
|
- **Automated dependency checking and installation**
|
||||||
|
|
||||||
|
## Prerequisites
|
||||||
|
|
||||||
|
- **Operating System:** Linux (Debian-based distributions recommended)
|
||||||
|
- **Python Version:** Python 3.x
|
||||||
|
- **Root Access:** Required for disk operations
|
||||||
|
- **Dependencies:**
|
||||||
|
- `parted` - Partition management
|
||||||
|
- `cryptsetup` - LUKS encryption
|
||||||
|
- `lsblk` - Block device listing
|
||||||
|
- `dd` - Disk writing
|
||||||
|
- `sgdisk` - GPT partition manipulation
|
||||||
|
- `wipefs` - Filesystem signature removal
|
||||||
|
- `shred` - Secure data wiping
|
||||||
|
- `bc` - Calculator for partition math
|
||||||
|
- `fdisk` - Partition table manipulation
|
||||||
|
- `veracrypt` - Document partition encryption
|
||||||
|
- `lsof`, `fuser` - Process detection
|
||||||
|
- `udevadm` - Device management
|
||||||
|
|
||||||
|
**Note:** The script will automatically detect missing dependencies and offer to install them.
|
||||||
|
|
||||||
|
## Installation
|
||||||
|
|
||||||
|
1. **Clone the Repository or Download the Script:**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
git clone https://github.com/YOUR_USERNAME/ghost_protocol.git
|
||||||
|
cd ghost_protocol/covert_sd
|
||||||
|
```
|
||||||
|
|
||||||
|
2. **Make the Script Executable:**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
chmod +x covert_sd_card_tool.py
|
||||||
|
```
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
|
||||||
|
Run the script with appropriate options:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
sudo ./covert_sd_card_tool.py [options]
|
||||||
|
```
|
||||||
|
|
||||||
|
### Command-Line Options
|
||||||
|
|
||||||
|
- `-a`, `--all` : Set up both the OS bootable USB and the documents partition (defaults to Kali)
|
||||||
|
- `-k`, `--kali` : Create a Kali bootable USB and persistence partition
|
||||||
|
- `-t`, `--tails` : Create a Tails bootable USB (no persistence, mutually exclusive with `-a`)
|
||||||
|
- `-c`, `--custom` : Create a custom ISO bootable USB (uses Kali-style partitioning with persistence)
|
||||||
|
- `-d`, `--docs` : Create an encrypted documents partition
|
||||||
|
- `-i`, `--iso` : Path to the ISO file (Kali, Tails, or custom)
|
||||||
|
- `--fast` : Enable fast setup mode (weaker encryption, faster setup - not recommended)
|
||||||
|
- `--paranoid` : Enable paranoid mode (maximum security: 3-pass wipe, Argon2id, PIM 5000)
|
||||||
|
- `--debug` : Enable debug mode with verbose logging
|
||||||
|
|
||||||
|
**Note:** `--fast` and `--paranoid` are mutually exclusive. Documents partition always uses strong encryption even in fast mode.
|
||||||
|
|
||||||
|
### Examples
|
||||||
|
|
||||||
|
- **Install Kali with Encrypted Persistence and Encrypted Documents Partition:**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
sudo ./covert_sd_card_tool.py -a -i /path/to/kali.iso
|
||||||
|
```
|
||||||
|
|
||||||
|
- **Install Tails with Encrypted Documents Partition:**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
sudo ./covert_sd_card_tool.py -t -d -i /path/to/tails.iso
|
||||||
|
```
|
||||||
|
|
||||||
|
- **Install Tails Only (No Documents Partition):**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
sudo ./covert_sd_card_tool.py -t -i /path/to/tails.iso
|
||||||
|
```
|
||||||
|
|
||||||
|
- **Create Encrypted Documents Partition Only (No OS):**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
sudo ./covert_sd_card_tool.py -d
|
||||||
|
```
|
||||||
|
|
||||||
|
- **Install Custom ISO (e.g., Parrot OS, BlackArch) with Persistence and Documents:**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
sudo ./covert_sd_card_tool.py -c -d -i /path/to/custom.iso
|
||||||
|
```
|
||||||
|
|
||||||
|
- **Install Custom ISO with Persistence Only (No Documents):**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
sudo ./covert_sd_card_tool.py -c -i /path/to/custom.iso
|
||||||
|
```
|
||||||
|
|
||||||
|
- **Paranoid Mode - Maximum Security (Tails + Docs):**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
sudo ./covert_sd_card_tool.py -t -d -i /path/to/tails.iso --paranoid
|
||||||
|
```
|
||||||
|
|
||||||
|
## Security Features
|
||||||
|
|
||||||
|
### Documents Partition Encryption
|
||||||
|
|
||||||
|
The documents partition uses **strong security** in all modes:
|
||||||
|
|
||||||
|
**Standard Mode (default):**
|
||||||
|
- **Triple Cascade Encryption:** AES-Twofish-Serpent (3 layers)
|
||||||
|
- **Hash Algorithm:** SHA-512
|
||||||
|
- **Key Derivation:** PIM 2000 (strong key stretching)
|
||||||
|
- **Unlock Time:** ~2-3 seconds
|
||||||
|
- **Filesystem:** ext4
|
||||||
|
- **Full Format:** Always overwrites old data
|
||||||
|
|
||||||
|
**Paranoid Mode (`--paranoid`):**
|
||||||
|
- **Triple Cascade Encryption:** AES-Twofish-Serpent (3 layers)
|
||||||
|
- **Hash Algorithm:** SHA-512
|
||||||
|
- **Key Derivation:** PIM 5000 (maximum key stretching)
|
||||||
|
- **Unlock Time:** ~5-7 seconds
|
||||||
|
- **Security:** Designed to resist brute-force attacks when used with a strong passphrase
|
||||||
|
|
||||||
|
**Fast Mode (`--fast`):**
|
||||||
|
- Documents still use standard mode (PIM 2000) - no compromise on docs security
|
||||||
|
|
||||||
|
### Persistence Partition Encryption (Kali/Custom)
|
||||||
|
|
||||||
|
**Standard Mode (default):**
|
||||||
|
- **Algorithm:** AES-XTS-PLAIN64
|
||||||
|
- **Key Size:** 512-bit
|
||||||
|
- **Hash:** SHA-512
|
||||||
|
- **KDF:** LUKS2 PBKDF2
|
||||||
|
- **Iteration Time:** 5 seconds
|
||||||
|
|
||||||
|
**Paranoid Mode (`--paranoid`):**
|
||||||
|
- **Algorithm:** AES-XTS-PLAIN64
|
||||||
|
- **Key Size:** 512-bit
|
||||||
|
- **Hash:** SHA-512
|
||||||
|
- **KDF:** LUKS2 Argon2id (memory-hard, GPU-resistant)
|
||||||
|
- **Memory:** 1GB
|
||||||
|
- **Parallel Threads:** 4
|
||||||
|
- **Iteration Time:** 10 seconds
|
||||||
|
|
||||||
|
**Fast Mode (`--fast`):**
|
||||||
|
- **Algorithm:** AES-CBC-ESSIV:SHA256
|
||||||
|
- **Key Size:** 256-bit
|
||||||
|
- **Hash:** SHA-256
|
||||||
|
- **KDF:** LUKS1 PBKDF2
|
||||||
|
- **Iteration Time:** 1 second
|
||||||
|
|
||||||
|
### Secure Drive Wiping
|
||||||
|
|
||||||
|
**Standard Mode (default):**
|
||||||
|
- **1 pass** with zeros (`dd if=/dev/zero`)
|
||||||
|
- Fast and sufficient for most use cases
|
||||||
|
- Prevents casual data recovery
|
||||||
|
- Confirmation required (must type 'WIPE')
|
||||||
|
|
||||||
|
**Paranoid Mode (`--paranoid`):**
|
||||||
|
- **3 passes** with random data (`shred`)
|
||||||
|
- **Final pass** with zeros
|
||||||
|
- Makes data recovery virtually impossible
|
||||||
|
- Defense against forensic recovery techniques
|
||||||
|
- **Much slower** (can take hours on large drives)
|
||||||
|
- Confirmation required (must type 'WIPE')
|
||||||
|
|
||||||
|
### OPSEC (Operational Security)
|
||||||
|
|
||||||
|
The generated helper scripts use **generic terminology** to avoid disclosing encryption methods:
|
||||||
|
|
||||||
|
- Scripts renamed to `mount_storage.sh` and `lock_storage.sh` (instead of mentioning encryption types)
|
||||||
|
- README uses terms like "secure storage" instead of specific encryption names
|
||||||
|
- No algorithm disclosure in user-facing documentation on the device
|
||||||
|
- Suitable for travel scenarios where device inspection may occur
|
||||||
|
|
||||||
|
## Generated Helper Scripts
|
||||||
|
|
||||||
|
The tool creates a small unencrypted partition (TOOLS) containing:
|
||||||
|
|
||||||
|
### `mount_storage.sh`
|
||||||
|
- Interactive script to mount the encrypted documents partition
|
||||||
|
- Shows available devices and validates input
|
||||||
|
- Mounts to `/mnt/secure_storage`
|
||||||
|
- Clear error messages and success confirmations
|
||||||
|
|
||||||
|
### `lock_storage.sh`
|
||||||
|
- Safely dismounts and locks the encrypted storage
|
||||||
|
- Checks for open files before locking
|
||||||
|
- Shows warnings if applications are still using the storage
|
||||||
|
- Syncs pending writes before dismount
|
||||||
|
- Prevents data loss from improper ejection
|
||||||
|
|
||||||
|
### `README.txt`
|
||||||
|
- Simple instructions for non-technical users
|
||||||
|
- Generic terminology (no encryption disclosure)
|
||||||
|
- Step-by-step mount/lock procedures
|
||||||
|
|
||||||
|
## Security Mode Comparison
|
||||||
|
|
||||||
|
| Feature | Fast Mode | Standard Mode (Default) | Paranoid Mode |
|
||||||
|
|---------|-----------|------------------------|---------------|
|
||||||
|
| **Drive Wipe** | Partition table clear only | 1-pass zeros | 3-pass shred + zeros |
|
||||||
|
| **Wipe Time (64GB)** | Instant | ~5-10 min | ~2-3 hours |
|
||||||
|
| **Persistence KDF** | LUKS1 PBKDF2 | LUKS2 PBKDF2 | LUKS2 Argon2id |
|
||||||
|
| **Persistence Unlock** | ~1 sec | ~5 sec | ~10 sec |
|
||||||
|
| **Docs Encryption** | AES-Twofish-Serpent | AES-Twofish-Serpent | AES-Twofish-Serpent |
|
||||||
|
| **Docs PIM** | 2000 | 2000 | 5000 |
|
||||||
|
| **Docs Unlock** | ~2-3 sec | ~2-3 sec | ~5-7 sec |
|
||||||
|
| **Best For** | Testing/dev | Travel, daily use | Maximum security, high-risk scenarios |
|
||||||
|
|
||||||
|
**Recommendation:** Use **standard mode** for most cases. Use **paranoid mode** if:
|
||||||
|
- You're protecting extremely sensitive data
|
||||||
|
- You face nation-state level threats
|
||||||
|
- You have time for longer setup and unlock times
|
||||||
|
- You want defense against forensic analysis
|
||||||
|
|
||||||
|
## Important Security Notes
|
||||||
|
|
||||||
|
⚠️ **Password Strength:** Use strong passphrases (20+ characters, mixed case, numbers, symbols)
|
||||||
|
|
||||||
|
⚠️ **No Password Recovery:** If you forget your password, your data is **permanently inaccessible**
|
||||||
|
|
||||||
|
⚠️ **PIM Value:** The tool enforces PIM 2000 for documents partition - this adds 2-3 seconds to unlock time but massively increases security
|
||||||
|
|
||||||
|
⚠️ **Always Lock Before Removal:** Use `lock_storage.sh` before removing the device to prevent data corruption
|
||||||
|
|
||||||
|
⚠️ **Fast Mode:** While available for persistence partition, documents partition **always uses maximum security**
|
||||||
|
|
||||||
|
## Partition Layout Examples
|
||||||
|
|
||||||
|
### Kali + Documents (`-a`)
|
||||||
|
1. **Partition 1:** Kali Live OS (bootable)
|
||||||
|
2. **Partition 2:** LUKS encrypted persistence (configurable size, e.g., 4GB)
|
||||||
|
3. **Partition 3:** VeraCrypt encrypted documents (remaining space minus 1GB)
|
||||||
|
4. **Partition 4:** Unencrypted tools partition (1GB, FAT32, contains scripts)
|
||||||
|
|
||||||
|
### Tails Only (`-t`)
|
||||||
|
- **Entire Drive:** Tails Live OS (bootable, no additional partitions)
|
||||||
|
|
||||||
|
### Tails + Documents (`-t -d`)
|
||||||
|
1. **Partition 1:** Tails Live OS (bootable, 12MB EFI System)
|
||||||
|
2. **Partition 2:** Tails system partition (~2-3GB depending on Tails version)
|
||||||
|
3. **Partition 3:** VeraCrypt encrypted documents (remaining space minus 1GB)
|
||||||
|
4. **Partition 4:** Unencrypted tools partition (1GB, FAT32, contains scripts)
|
||||||
|
|
||||||
|
### Documents Only (`-d`)
|
||||||
|
1. **Partition 1:** VeraCrypt encrypted documents (remaining space minus 1GB)
|
||||||
|
2. **Partition 2:** Unencrypted tools partition (1GB, FAT32, contains scripts)
|
||||||
|
|
||||||
|
### Custom ISO + Documents (`-c -d`)
|
||||||
|
1. **Partition 1:** Custom Live OS (bootable)
|
||||||
|
2. **Partition 2:** LUKS encrypted persistence (configurable size, e.g., 4GB)
|
||||||
|
3. **Partition 3:** VeraCrypt encrypted documents (remaining space minus 1GB)
|
||||||
|
4. **Partition 4:** Unencrypted tools partition (1GB, FAT32, contains scripts)
|
||||||
|
|
||||||
|
**Note:** Custom ISO mode uses Kali-style partitioning. Works well with Debian-based live ISOs like Parrot OS, BlackArch, BackBox, etc.
|
||||||
|
|
||||||
|
## Using Custom ISOs
|
||||||
|
|
||||||
|
The `-c` (custom) flag allows you to use **any bootable ISO** and set it up with encrypted persistence and documents partitions. This is useful for:
|
||||||
|
|
||||||
|
### Compatible ISOs
|
||||||
|
- **Parrot Security OS** - Privacy-focused security distro
|
||||||
|
- **BlackArch Linux** - Penetration testing distro
|
||||||
|
- **BackBox** - Ubuntu-based penetration testing
|
||||||
|
- **Pentoo** - Gentoo-based security distro
|
||||||
|
- **Any Debian/Ubuntu-based live ISO**
|
||||||
|
|
||||||
|
### How It Works
|
||||||
|
Custom ISOs are treated like Kali Linux:
|
||||||
|
1. ISO is flashed to the drive
|
||||||
|
2. LUKS encrypted persistence partition is created (if you want settings to persist)
|
||||||
|
3. VeraCrypt encrypted documents partition is added (if `-d` flag is used)
|
||||||
|
4. Tools partition with mount/lock scripts
|
||||||
|
|
||||||
|
### Example: Parrot OS with Docs
|
||||||
|
```bash
|
||||||
|
sudo ./covert_sd_card_tool.py -c -d -i ~/Downloads/parrot-security.iso
|
||||||
|
```
|
||||||
|
|
||||||
|
### Compatibility Notes
|
||||||
|
- **Best for:** Debian/Ubuntu-based live ISOs
|
||||||
|
- **May not work with:** Arch-based ISOs (different partition structure), Windows ISOs
|
||||||
|
- **Persistence:** Depends on the ISO supporting LUKS persistence (Debian-based usually do)
|
||||||
|
- If persistence doesn't work with your ISO, you can still use the documents partition
|
||||||
|
|
||||||
|
## Troubleshooting
|
||||||
|
|
||||||
|
### Device Busy Errors
|
||||||
|
- The tool automatically unmounts partitions and kills processes using the drive
|
||||||
|
- If problems persist, manually unmount: `sudo umount /dev/sdX*`
|
||||||
|
- Check for processes: `sudo lsof /dev/sdX`
|
||||||
|
|
||||||
|
### VeraCrypt Not Found
|
||||||
|
- On Debian/Ubuntu: `sudo apt install veracrypt`
|
||||||
|
- Or the script will offer to install it automatically
|
||||||
|
|
||||||
|
### Permission Denied
|
||||||
|
- Always run with `sudo`
|
||||||
|
- Ensure your user has sudo privileges
|
||||||
|
|
||||||
|
### Drive Not Detected
|
||||||
|
- Check if drive is connected: `lsblk`
|
||||||
|
- Verify drive path (e.g., `/dev/sdb` not `/dev/sdb1`)
|
||||||
|
- Try unplugging and reconnecting the device
|
||||||
|
|
||||||
|
## License
|
||||||
|
|
||||||
|
This tool is provided as-is for educational and legitimate security purposes only. Use responsibly and in compliance with applicable laws.
|
||||||
|
|
||||||
|
## Contributing
|
||||||
|
|
||||||
|
Contributions, bug reports, and feature requests are welcome! Please open an issue or submit a pull request.
|
||||||
|
|
||||||
|
## Disclaimer
|
||||||
|
|
||||||
|
This tool performs destructive operations on storage devices. **Always verify you've selected the correct drive** before proceeding. The authors are not responsible for data loss.
|
||||||
Executable
+1409
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,239 @@
|
|||||||
|
======================================================================
|
||||||
|
SECURE STORAGE - MOBILE DEVICE INSTRUCTIONS
|
||||||
|
======================================================================
|
||||||
|
|
||||||
|
IMPORTANT: Mobile access has limitations compared to desktop.
|
||||||
|
Read carefully to understand what's possible on your device.
|
||||||
|
|
||||||
|
======================================================================
|
||||||
|
ANDROID DEVICES
|
||||||
|
======================================================================
|
||||||
|
|
||||||
|
REQUIREMENTS:
|
||||||
|
• Android 5.0 or newer
|
||||||
|
• EDS (Encrypted Data Store) app - REQUIRED
|
||||||
|
• USB OTG adapter (to connect USB device to phone)
|
||||||
|
• File manager app (most phones have one built-in)
|
||||||
|
|
||||||
|
REQUIRED APP:
|
||||||
|
• EDS Lite (Free & Open Source)
|
||||||
|
• Google Play: https://play.google.com/store/apps/details?id=com.sovworks.eds.android
|
||||||
|
• Direct link if Google Play doesn't work:
|
||||||
|
https://play.google.com/store/apps/details?id=com.sovworks.projecteds
|
||||||
|
|
||||||
|
----------------------------------------------------------------------
|
||||||
|
SETUP: Using EDS Lite (RECOMMENDED)
|
||||||
|
----------------------------------------------------------------------
|
||||||
|
|
||||||
|
Step 1: Install EDS Lite
|
||||||
|
• Open Google Play Store
|
||||||
|
• Search for "EDS Lite" or "EDS (Encrypted Data Store)"
|
||||||
|
• Install the app (it's FREE)
|
||||||
|
• Or use the direct link above
|
||||||
|
|
||||||
|
Step 2: Connect your USB device
|
||||||
|
• Connect USB device using OTG adapter
|
||||||
|
• Android should show "USB device connected" notification
|
||||||
|
|
||||||
|
Step 3: Open EDS Lite app
|
||||||
|
• Tap "Open container"
|
||||||
|
• Navigate to your USB device
|
||||||
|
• Look for the partition WITHOUT a filesystem/label
|
||||||
|
• Select the encrypted partition
|
||||||
|
|
||||||
|
Step 4: Enter password
|
||||||
|
• Select encryption: VeraCrypt
|
||||||
|
• Enter your password
|
||||||
|
• For PIM: enter 2000 or 5000 (whatever you set)
|
||||||
|
• Tap "OK"
|
||||||
|
|
||||||
|
Step 5: Access your files
|
||||||
|
• Files will appear in EDS Lite
|
||||||
|
• You can view, copy, or share files
|
||||||
|
• Some apps can open files directly from EDS
|
||||||
|
|
||||||
|
IMPORTANT - Safely Close:
|
||||||
|
• Tap "Close container" when done
|
||||||
|
• Wait for confirmation before unplugging
|
||||||
|
• Only then remove USB device
|
||||||
|
|
||||||
|
----------------------------------------------------------------------
|
||||||
|
ALTERNATIVE: VeraCrypt for Android (Advanced Users)
|
||||||
|
----------------------------------------------------------------------
|
||||||
|
|
||||||
|
ℹ️ NOTE: EDS Lite (above) is easier to use and recommended.
|
||||||
|
Only use VeraCrypt if you're familiar with it.
|
||||||
|
|
||||||
|
Step 1: Install VeraCrypt for Android
|
||||||
|
• Download from: https://github.com/veracrypt/veracrypt
|
||||||
|
• Or install from F-Droid store
|
||||||
|
• Enable "Unknown sources" if needed
|
||||||
|
|
||||||
|
Step 2-5: Follow similar steps as EDS Lite above
|
||||||
|
• The interface is similar to desktop VeraCrypt
|
||||||
|
• Process is the same: select partition, enter password, mount
|
||||||
|
|
||||||
|
======================================================================
|
||||||
|
iOS DEVICES (iPhone/iPad)
|
||||||
|
======================================================================
|
||||||
|
|
||||||
|
✅ SOLUTION: Crypto Disks App (Paid)
|
||||||
|
|
||||||
|
RECOMMENDED APP:
|
||||||
|
• Crypto Disks - Store Private Files Securely
|
||||||
|
• App Store: https://apps.apple.com/us/app/crypto-disks-store-private/id889549308
|
||||||
|
• Price: ~$4.99 (one-time purchase)
|
||||||
|
• Supports VeraCrypt containers!
|
||||||
|
|
||||||
|
----------------------------------------------------------------------
|
||||||
|
SETUP: Using Crypto Disks on iOS
|
||||||
|
----------------------------------------------------------------------
|
||||||
|
|
||||||
|
Step 1: Purchase and Install Crypto Disks
|
||||||
|
• Open App Store on your iPhone/iPad
|
||||||
|
• Search for "Crypto Disks"
|
||||||
|
• Or use the direct link above
|
||||||
|
• Purchase and install (~$4.99)
|
||||||
|
|
||||||
|
Step 2: Transfer Encrypted Container
|
||||||
|
⚠️ LIMITATION: iOS doesn't support direct USB access like Android
|
||||||
|
|
||||||
|
You have two options:
|
||||||
|
|
||||||
|
OPTION A: Copy via Computer (Easier)
|
||||||
|
1. Connect USB device to your Mac/PC
|
||||||
|
2. Mount the encrypted partition with VeraCrypt
|
||||||
|
3. Create a smaller VeraCrypt container file inside
|
||||||
|
4. Transfer that container to your iPhone via:
|
||||||
|
- AirDrop (Mac to iPhone)
|
||||||
|
- iCloud Drive
|
||||||
|
- iTunes File Sharing
|
||||||
|
5. Open in Crypto Disks app
|
||||||
|
|
||||||
|
OPTION B: Use Wi-Fi File Transfer
|
||||||
|
1. Use Crypto Disks' built-in file transfer
|
||||||
|
2. Transfer container file from computer
|
||||||
|
3. Open in app
|
||||||
|
|
||||||
|
Step 3: Mount in Crypto Disks App
|
||||||
|
• Open Crypto Disks app
|
||||||
|
• Select "Add Disk Image"
|
||||||
|
• Choose your transferred container
|
||||||
|
• Enter password and PIM
|
||||||
|
• Access your files
|
||||||
|
|
||||||
|
⚠️ IMPORTANT iOS LIMITATIONS:
|
||||||
|
• Cannot directly mount USB partition (Apple restriction)
|
||||||
|
• Must use container FILES, not raw partitions
|
||||||
|
• Requires copying/transferring container to iPhone
|
||||||
|
• Best for occasional access, not primary use
|
||||||
|
|
||||||
|
----------------------------------------------------------------------
|
||||||
|
iOS WORKAROUND: Emergency Access Only
|
||||||
|
----------------------------------------------------------------------
|
||||||
|
|
||||||
|
If you don't want to pay for Crypto Disks, here are free alternatives:
|
||||||
|
|
||||||
|
Option A: Use a Computer as Bridge
|
||||||
|
1. Mount the encrypted storage on your Mac/PC
|
||||||
|
2. Copy files you need to iCloud or Files app
|
||||||
|
3. Access those files from your iPhone
|
||||||
|
4. Delete copies when done (IMPORTANT for security!)
|
||||||
|
|
||||||
|
Option B: Use Remote Access
|
||||||
|
1. Mount encrypted storage on your home computer
|
||||||
|
2. Set up secure remote access (VPN + file sharing)
|
||||||
|
3. Access files remotely from iPhone
|
||||||
|
4. More complex but maintains security
|
||||||
|
|
||||||
|
Option C: Take Photos of Documents
|
||||||
|
1. If you just need to VIEW travel docs in emergency
|
||||||
|
2. Take photos/screenshots on computer
|
||||||
|
3. Store in encrypted note app (Apple Notes with lock)
|
||||||
|
4. Delete after trip
|
||||||
|
|
||||||
|
NOTE: These workarounds compromise security slightly.
|
||||||
|
Crypto Disks app is the most secure iOS solution.
|
||||||
|
|
||||||
|
======================================================================
|
||||||
|
IMPORTANT SECURITY NOTES FOR MOBILE
|
||||||
|
======================================================================
|
||||||
|
|
||||||
|
⚠ Screen Lock
|
||||||
|
• Always have a PIN/password on your phone
|
||||||
|
• If someone steals your phone while storage is mounted,
|
||||||
|
they can access your files
|
||||||
|
|
||||||
|
⚠ App Permissions
|
||||||
|
• Only grant necessary permissions to encryption apps
|
||||||
|
• Be cautious with "file access" permissions
|
||||||
|
|
||||||
|
⚠ Public WiFi
|
||||||
|
• Don't use public WiFi when accessing sensitive files
|
||||||
|
• Or use a VPN if you must
|
||||||
|
|
||||||
|
⚠ Battery Concerns
|
||||||
|
• Don't let your phone die while storage is mounted
|
||||||
|
• Always properly close/dismount before battery runs out
|
||||||
|
|
||||||
|
⚠ Background Apps
|
||||||
|
• Close the encryption app properly when done
|
||||||
|
• Don't just switch to another app
|
||||||
|
• Storage might stay mounted in background
|
||||||
|
|
||||||
|
⚠ Phone Updates
|
||||||
|
• Close encrypted storage before major OS updates
|
||||||
|
• Updates might interrupt and cause corruption
|
||||||
|
|
||||||
|
======================================================================
|
||||||
|
TROUBLESHOOTING - ANDROID
|
||||||
|
======================================================================
|
||||||
|
|
||||||
|
"Cannot find USB device"
|
||||||
|
• Try a different OTG adapter
|
||||||
|
• Check if your phone supports USB OTG
|
||||||
|
• Some phones need USB settings changed (File Transfer mode)
|
||||||
|
|
||||||
|
"Wrong password" (but password is correct)
|
||||||
|
• Make sure you selected "VeraCrypt" as encryption type
|
||||||
|
• Verify PIM value (2000 or 5000)
|
||||||
|
• Some apps don't support all VeraCrypt settings
|
||||||
|
|
||||||
|
"App crashes when mounting"
|
||||||
|
• Try a different encryption app (EDS vs VeraCrypt)
|
||||||
|
• Check if partition is already mounted
|
||||||
|
• Restart phone and try again
|
||||||
|
|
||||||
|
"Can't edit files, only view"
|
||||||
|
• This is normal for some apps
|
||||||
|
• Copy file to phone storage
|
||||||
|
• Edit it there
|
||||||
|
• Copy back when done
|
||||||
|
|
||||||
|
"Storage disconnected unexpectedly"
|
||||||
|
• Check USB connection
|
||||||
|
• Some phones cut power to USB to save battery
|
||||||
|
• Disable battery optimization for the encryption app
|
||||||
|
|
||||||
|
======================================================================
|
||||||
|
LIMITATIONS OF MOBILE ACCESS
|
||||||
|
======================================================================
|
||||||
|
|
||||||
|
Things that DON'T work well on mobile:
|
||||||
|
✗ Editing large files directly on encrypted storage
|
||||||
|
✗ Running programs/apps from encrypted storage
|
||||||
|
✗ Fast file operations (mobile is slower than desktop)
|
||||||
|
✗ iOS access (not supported at all)
|
||||||
|
|
||||||
|
Things that WORK WELL on mobile:
|
||||||
|
✓ Viewing documents (PDFs, photos, etc.)
|
||||||
|
✓ Copying individual files to phone
|
||||||
|
✓ Sharing files to other apps
|
||||||
|
✓ Emergency access when no computer available
|
||||||
|
✓ Quick lookup of information
|
||||||
|
|
||||||
|
RECOMMENDATION:
|
||||||
|
Use mobile access for emergencies and viewing only.
|
||||||
|
Use desktop (Linux/Windows) for actual file management.
|
||||||
|
|
||||||
|
======================================================================
|
||||||
@@ -0,0 +1,194 @@
|
|||||||
|
======================================================================
|
||||||
|
SECURE STORAGE - QUICK START GUIDE
|
||||||
|
======================================================================
|
||||||
|
|
||||||
|
WHAT'S ON THIS DEVICE:
|
||||||
|
• Encrypted storage partition for sensitive documents
|
||||||
|
• Portable VeraCrypt (works on ANY computer - no installation!)
|
||||||
|
• Helper scripts (Linux/Mac)
|
||||||
|
• Instructions for Windows and mobile devices
|
||||||
|
|
||||||
|
======================================================================
|
||||||
|
ACCESSING YOUR FILES - QUICK GUIDE
|
||||||
|
======================================================================
|
||||||
|
|
||||||
|
LINUX / MAC:
|
||||||
|
1. Open terminal in this folder
|
||||||
|
2. Run: sudo ./mount_storage.sh
|
||||||
|
3. Enter your password
|
||||||
|
4. Files appear in: ~/SecureStorage
|
||||||
|
|
||||||
|
WINDOWS:
|
||||||
|
1. Open "WINDOWS_INSTRUCTIONS.txt" on this device
|
||||||
|
2. Follow the step-by-step guide
|
||||||
|
3. Use portable VeraCrypt from "VeraCrypt" folder (no install needed!)
|
||||||
|
|
||||||
|
ANDROID:
|
||||||
|
1. Install "EDS Lite" from Google Play Store (FREE)
|
||||||
|
https://play.google.com/store/apps/details?id=com.sovworks.projecteds
|
||||||
|
2. Connect USB with OTG adapter
|
||||||
|
3. Open "MOBILE_INSTRUCTIONS.txt" for step-by-step guide
|
||||||
|
|
||||||
|
iOS (iPhone/iPad):
|
||||||
|
1. Install "Crypto Disks" from App Store (~$4.99)
|
||||||
|
https://apps.apple.com/us/app/crypto-disks-store-private/id889549308
|
||||||
|
2. See "MOBILE_INSTRUCTIONS.txt" for setup
|
||||||
|
⚠ iOS has limitations - requires copying files, can't mount USB directly
|
||||||
|
|
||||||
|
======================================================================
|
||||||
|
IMPORTANT: HOW TO SAFELY LOCK YOUR STORAGE
|
||||||
|
======================================================================
|
||||||
|
|
||||||
|
LINUX / MAC:
|
||||||
|
1. Close ALL programs using the secure storage
|
||||||
|
2. Run: sudo ./lock_storage.sh
|
||||||
|
3. Wait for "SUCCESS" message
|
||||||
|
4. Safe to remove device
|
||||||
|
|
||||||
|
WINDOWS:
|
||||||
|
1. Close all files
|
||||||
|
2. In VeraCrypt, select the drive
|
||||||
|
3. Click "Dismount"
|
||||||
|
4. Use "Safely Remove Hardware"
|
||||||
|
|
||||||
|
⚠ CRITICAL: ALWAYS lock/dismount before removing!
|
||||||
|
Otherwise you risk losing your data.
|
||||||
|
|
||||||
|
======================================================================
|
||||||
|
PORTABLE VERACRYPT - NO INSTALLATION REQUIRED!
|
||||||
|
======================================================================
|
||||||
|
|
||||||
|
Inside the "VeraCrypt" folder on this device:
|
||||||
|
|
||||||
|
• VeraCrypt-Portable-Windows.exe
|
||||||
|
→ Double-click to run on ANY Windows PC
|
||||||
|
→ Works without admin rights on most systems
|
||||||
|
|
||||||
|
• VeraCrypt-Portable-Linux.AppImage
|
||||||
|
→ Run on ANY Linux system (no install!)
|
||||||
|
→ Command: ./VeraCrypt-Portable-Linux.AppImage
|
||||||
|
→ AppImage = portable, works on all Linux distributions
|
||||||
|
|
||||||
|
• veracrypt-Ubuntu-22.04-amd64.deb
|
||||||
|
→ System-wide installer for Ubuntu/Debian
|
||||||
|
→ Install with: sudo dpkg -i veracrypt-Ubuntu-22.04-amd64.deb
|
||||||
|
→ Use this if you prefer installed version over AppImage
|
||||||
|
|
||||||
|
• VeraCrypt-MacOS.dmg
|
||||||
|
→ Install on Mac (one-time)
|
||||||
|
→ Works on any Mac after that
|
||||||
|
|
||||||
|
This means you can access your encrypted files on ANY computer
|
||||||
|
even if VeraCrypt is not installed!
|
||||||
|
|
||||||
|
======================================================================
|
||||||
|
YOUR PASSWORD
|
||||||
|
======================================================================
|
||||||
|
|
||||||
|
⚠ There is NO password recovery! If you forget it, data is GONE.
|
||||||
|
|
||||||
|
Password details:
|
||||||
|
• Case-sensitive (Abc123 ≠ abc123)
|
||||||
|
• Minimum 20 characters recommended
|
||||||
|
• Mix of letters, numbers, symbols
|
||||||
|
• Write it down and keep it SAFE (not on this device!)
|
||||||
|
|
||||||
|
If you set a PIM value:
|
||||||
|
• Standard setup: PIM = 2000
|
||||||
|
• Paranoid setup: PIM = 5000
|
||||||
|
• VeraCrypt will ask for this when mounting
|
||||||
|
|
||||||
|
======================================================================
|
||||||
|
TROUBLESHOOTING
|
||||||
|
======================================================================
|
||||||
|
|
||||||
|
PROBLEM: "Cannot find TOOLS partition"
|
||||||
|
FIX: Ensure device is fully connected; try different USB port
|
||||||
|
|
||||||
|
PROBLEM: "Wrong password" (but you know it's correct)
|
||||||
|
FIX: Check if CAPS LOCK is on; verify PIM value
|
||||||
|
|
||||||
|
PROBLEM: "Volume already mounted"
|
||||||
|
FIX: Close any VeraCrypt windows and run:
|
||||||
|
• Linux/Mac: sudo veracrypt -d
|
||||||
|
• Windows: Dismount all in VeraCrypt
|
||||||
|
|
||||||
|
PROBLEM: "Permission denied" when accessing files
|
||||||
|
FIX:
|
||||||
|
• Linux/Mac: Re-run mount script (it sets permissions)
|
||||||
|
• Windows: Check if you ran VeraCrypt as administrator
|
||||||
|
|
||||||
|
PROBLEM: "Files appear but I can't edit them"
|
||||||
|
FIX:
|
||||||
|
• Linux/Mac: Run: sudo chown -R $USER ~/SecureStorage
|
||||||
|
• Windows: Right-click → Properties → Security → Edit
|
||||||
|
|
||||||
|
PROBLEM: Device not detected on Android
|
||||||
|
FIX:
|
||||||
|
• Check USB OTG adapter is working
|
||||||
|
• Enable "File Transfer" mode in USB settings
|
||||||
|
• Try different OTG adapter
|
||||||
|
|
||||||
|
======================================================================
|
||||||
|
FILE LOCATIONS AFTER MOUNTING
|
||||||
|
======================================================================
|
||||||
|
|
||||||
|
Linux/Mac: ~/SecureStorage
|
||||||
|
(In your home folder)
|
||||||
|
|
||||||
|
Windows: Z:\ (or whatever drive letter you selected)
|
||||||
|
Shows in "This PC"
|
||||||
|
|
||||||
|
Android: Accessible within EDS Lite or VeraCrypt app
|
||||||
|
|
||||||
|
======================================================================
|
||||||
|
SECURITY NOTES
|
||||||
|
======================================================================
|
||||||
|
|
||||||
|
✓ Your files use strong multi-layer encryption
|
||||||
|
✓ AES-Twofish-Serpent triple cascade (strongest available)
|
||||||
|
✓ Designed to resist brute-force attacks with a strong passphrase
|
||||||
|
|
||||||
|
⚠ The password is the ONLY key
|
||||||
|
⚠ No backdoors, no recovery, no "forgot password" option
|
||||||
|
⚠ Keep password safe but separate from this device
|
||||||
|
|
||||||
|
Best practices:
|
||||||
|
• Always dismount before removing device
|
||||||
|
• Don't leave mounted when unattended
|
||||||
|
• Use strong unique password
|
||||||
|
• Don't store password on this device
|
||||||
|
• Lock your computer when storage is mounted
|
||||||
|
|
||||||
|
======================================================================
|
||||||
|
ADVANCED: MANUAL MOUNTING (if scripts fail)
|
||||||
|
======================================================================
|
||||||
|
|
||||||
|
LINUX/MAC:
|
||||||
|
1. Find encrypted partition:
|
||||||
|
lsblk -o NAME,SIZE,LABEL,FSTYPE
|
||||||
|
(Look for partition WITHOUT filesystem label)
|
||||||
|
|
||||||
|
2. Mount with VeraCrypt:
|
||||||
|
sudo veracrypt /dev/sdX2 ~/SecureStorage
|
||||||
|
(Replace sdX2 with your partition)
|
||||||
|
|
||||||
|
3. Enter password and PIM when prompted
|
||||||
|
|
||||||
|
WINDOWS:
|
||||||
|
1. Open portable VeraCrypt
|
||||||
|
2. Select any drive letter
|
||||||
|
3. Click "Select Device"
|
||||||
|
4. Choose partition WITHOUT label (not the TOOLS one)
|
||||||
|
5. Click "Mount"
|
||||||
|
6. Enter password
|
||||||
|
|
||||||
|
======================================================================
|
||||||
|
NEED MORE HELP?
|
||||||
|
======================================================================
|
||||||
|
|
||||||
|
• Windows users: See WINDOWS_INSTRUCTIONS.txt
|
||||||
|
• Mobile users: See MOBILE_INSTRUCTIONS.txt
|
||||||
|
• VeraCrypt documentation: https://www.veracrypt.fr/en/Documentation.html
|
||||||
|
|
||||||
|
======================================================================
|
||||||
@@ -0,0 +1,93 @@
|
|||||||
|
======================================================================
|
||||||
|
SECURE STORAGE - WINDOWS INSTRUCTIONS
|
||||||
|
======================================================================
|
||||||
|
|
||||||
|
REQUIREMENTS:
|
||||||
|
• This secure storage device connected
|
||||||
|
• NO SOFTWARE INSTALLATION NEEDED!
|
||||||
|
|
||||||
|
======================================================================
|
||||||
|
HOW TO ACCESS YOUR SECURE FILES (WINDOWS)
|
||||||
|
======================================================================
|
||||||
|
|
||||||
|
OPTION 1: USE PORTABLE VERACRYPT (RECOMMENDED - NO INSTALL!)
|
||||||
|
|
||||||
|
Step 1: Open the VeraCrypt folder on this USB drive
|
||||||
|
|
||||||
|
Step 2: Double-click "VeraCrypt-Portable-Windows.exe"
|
||||||
|
• It runs directly - no installation!
|
||||||
|
• Works on most Windows PCs without admin rights
|
||||||
|
• If blocked, right-click → "Run anyway"
|
||||||
|
|
||||||
|
Step 3: The VeraCrypt window opens
|
||||||
|
|
||||||
|
----------------------------------------------------------------------
|
||||||
|
|
||||||
|
OPTION 2: USE INSTALLED VERACRYPT (if you prefer)
|
||||||
|
|
||||||
|
Step 1: Install VeraCrypt (if not already installed)
|
||||||
|
• Go to https://www.veracrypt.fr/en/Downloads.html
|
||||||
|
• Download VeraCrypt for Windows
|
||||||
|
• Run the installer
|
||||||
|
|
||||||
|
Step 2: Open VeraCrypt from Start Menu
|
||||||
|
• Search for "VeraCrypt" in Start Menu
|
||||||
|
• Click "VeraCrypt" to launch
|
||||||
|
|
||||||
|
----------------------------------------------------------------------
|
||||||
|
|
||||||
|
CONTINUING (after opening VeraCrypt either way):
|
||||||
|
|
||||||
|
Step 3: Select a drive letter
|
||||||
|
• In VeraCrypt window, click any drive letter (e.g., Z:)
|
||||||
|
|
||||||
|
Step 4: Select Volume
|
||||||
|
• Click "Select Device..." button
|
||||||
|
• Find your USB device (look for the partition WITHOUT a filesystem)
|
||||||
|
• Usually it's the second-to-last partition
|
||||||
|
• Click OK
|
||||||
|
|
||||||
|
Step 5: Click "Mount" button
|
||||||
|
• Enter your password when prompted
|
||||||
|
• For PIM: leave blank or enter the PIM you set (2000 or 5000)
|
||||||
|
• Click OK
|
||||||
|
|
||||||
|
Step 6: Access your files
|
||||||
|
• Open File Explorer
|
||||||
|
• Look for new drive (e.g., Z:)
|
||||||
|
• Your files are there!
|
||||||
|
|
||||||
|
======================================================================
|
||||||
|
HOW TO SAFELY LOCK YOUR STORAGE (WINDOWS)
|
||||||
|
======================================================================
|
||||||
|
|
||||||
|
Step 1: Close ALL programs using the secure storage
|
||||||
|
• Save documents
|
||||||
|
• Close File Explorer windows
|
||||||
|
• Close any apps with open files
|
||||||
|
|
||||||
|
Step 2: In VeraCrypt window
|
||||||
|
• Select the mounted drive (e.g., Z:)
|
||||||
|
• Click "Dismount" button
|
||||||
|
|
||||||
|
Step 3: Safe to remove
|
||||||
|
• Click "Safely Remove Hardware" in system tray
|
||||||
|
• Select your USB device
|
||||||
|
• Remove when Windows says it's safe
|
||||||
|
|
||||||
|
======================================================================
|
||||||
|
IMPORTANT NOTES
|
||||||
|
======================================================================
|
||||||
|
|
||||||
|
⚠ ALWAYS dismount before removing the device!
|
||||||
|
Otherwise you risk data corruption.
|
||||||
|
|
||||||
|
⚠ If you forget your password, your data is GONE.
|
||||||
|
There is NO password recovery.
|
||||||
|
|
||||||
|
⚠ Partition to select:
|
||||||
|
• Look in VeraCrypt's "Select Device" window
|
||||||
|
• Choose the partition WITHOUT a label/filesystem
|
||||||
|
• It's usually NOT the one labeled "TOOLS"
|
||||||
|
|
||||||
|
======================================================================
|
||||||
Executable
+88
@@ -0,0 +1,88 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
# Script to safely lock secure storage
|
||||||
|
|
||||||
|
echo "======================================================================"
|
||||||
|
echo " SECURE STORAGE LOCK TOOL"
|
||||||
|
echo "======================================================================"
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
# Mount point location
|
||||||
|
MOUNT_POINT="$HOME/SecureStorage"
|
||||||
|
|
||||||
|
# Check if storage is mounted
|
||||||
|
if mountpoint -q "$MOUNT_POINT" 2>/dev/null || [ -d "$MOUNT_POINT" ] && sudo veracrypt --text --list | grep -q "$MOUNT_POINT"; then
|
||||||
|
echo "Step 1: Checking for open files..."
|
||||||
|
|
||||||
|
# Check if any processes are using the mount
|
||||||
|
OPEN_FILES=$(sudo lsof "$MOUNT_POINT" 2>/dev/null | tail -n +2)
|
||||||
|
if [ -n "$OPEN_FILES" ]; then
|
||||||
|
echo ""
|
||||||
|
echo "⚠ WARNING: Files or programs are still using the secure storage!"
|
||||||
|
echo ""
|
||||||
|
echo "Open files/processes:"
|
||||||
|
echo "$OPEN_FILES"
|
||||||
|
echo ""
|
||||||
|
echo "You should close these programs first to avoid data loss."
|
||||||
|
echo ""
|
||||||
|
read -p "Force close and lock anyway? (y/N): " FORCE
|
||||||
|
if [ "$FORCE" != "y" ] && [ "$FORCE" != "Y" ]; then
|
||||||
|
echo ""
|
||||||
|
echo "Lock cancelled. Please:"
|
||||||
|
echo " 1. Close all programs using the secure storage"
|
||||||
|
echo " 2. Save any open documents"
|
||||||
|
echo " 3. Run this script again"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
echo ""
|
||||||
|
echo "Forcing lock (files will be closed)..."
|
||||||
|
else
|
||||||
|
echo "✓ No open files detected"
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
echo "Step 2: Syncing pending writes to disk..."
|
||||||
|
sync
|
||||||
|
echo "✓ All data written to disk"
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
echo "Step 3: Dismounting encrypted volume..."
|
||||||
|
|
||||||
|
# Dismount the encrypted volume
|
||||||
|
if sudo veracrypt --text --dismount "$MOUNT_POINT" 2>/dev/null; then
|
||||||
|
rmdir "$MOUNT_POINT" 2>/dev/null
|
||||||
|
echo "✓ Volume dismounted"
|
||||||
|
echo ""
|
||||||
|
echo "======================================================================"
|
||||||
|
echo " SUCCESS!"
|
||||||
|
echo "======================================================================"
|
||||||
|
echo ""
|
||||||
|
echo "Secure storage is now locked and encrypted."
|
||||||
|
echo "Your data is safe to remove the device."
|
||||||
|
echo ""
|
||||||
|
echo "======================================================================"
|
||||||
|
else
|
||||||
|
echo ""
|
||||||
|
echo "======================================================================"
|
||||||
|
echo " DISMOUNT FAILED"
|
||||||
|
echo "======================================================================"
|
||||||
|
echo ""
|
||||||
|
echo "Possible reasons:"
|
||||||
|
echo " • Files still in use (close all programs)"
|
||||||
|
echo " • Permission denied"
|
||||||
|
echo " • Volume not mounted with this script"
|
||||||
|
echo ""
|
||||||
|
echo "Troubleshooting:"
|
||||||
|
echo " 1. Check what's using it: sudo lsof $MOUNT_POINT"
|
||||||
|
echo " 2. Force dismount all: sudo veracrypt -d"
|
||||||
|
echo " 3. Kill processes: sudo fuser -km $MOUNT_POINT"
|
||||||
|
echo ""
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
echo "ℹ Secure storage is not currently mounted."
|
||||||
|
echo ""
|
||||||
|
echo "Nothing to lock - your data is already secured."
|
||||||
|
echo ""
|
||||||
|
echo "If you want to access it, run:"
|
||||||
|
echo " sudo ./mount_storage.sh"
|
||||||
|
fi
|
||||||
Executable
+137
@@ -0,0 +1,137 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
# Script to mount secure storage partition
|
||||||
|
|
||||||
|
echo "======================================================================"
|
||||||
|
echo " SECURE STORAGE MOUNT TOOL"
|
||||||
|
echo "======================================================================"
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
# Function to find the documents partition (second-to-last partition on drive)
|
||||||
|
find_docs_partition() {
|
||||||
|
# Look for drives with TOOLS partition (our indicator)
|
||||||
|
local tools_drive=$(lsblk -ln -o NAME,LABEL | grep "TOOLS" | awk '{print $1}' | head -1)
|
||||||
|
|
||||||
|
if [ -z "$tools_drive" ]; then
|
||||||
|
echo "ERROR: Could not find TOOLS partition."
|
||||||
|
echo "Please ensure the secure storage device is connected."
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Get the base drive name (remove partition number)
|
||||||
|
local base_drive=$(echo "$tools_drive" | sed 's/[0-9]*$//' | sed 's/p$//')
|
||||||
|
|
||||||
|
# Get all partitions on this drive
|
||||||
|
local partitions=$(lsblk -ln -o NAME "/dev/$base_drive" | grep "^$base_drive" | tail -n +2)
|
||||||
|
local part_count=$(echo "$partitions" | wc -l)
|
||||||
|
|
||||||
|
if [ "$part_count" -lt 2 ]; then
|
||||||
|
echo "ERROR: Not enough partitions found."
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Get second-to-last partition (documents partition)
|
||||||
|
local docs_part=$(echo "$partitions" | tail -n 2 | head -n 1)
|
||||||
|
echo "/dev/$docs_part"
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
echo "Step 1: Detecting secure storage device..."
|
||||||
|
DOCS_PARTITION=$(find_docs_partition)
|
||||||
|
|
||||||
|
if [ $? -ne 0 ]; then
|
||||||
|
echo ""
|
||||||
|
echo "======================================================================"
|
||||||
|
echo "AUTO-DETECTION FAILED - MANUAL MODE"
|
||||||
|
echo "======================================================================"
|
||||||
|
echo ""
|
||||||
|
echo "Available partitions:"
|
||||||
|
lsblk -o NAME,SIZE,TYPE,LABEL,FSTYPE | grep -v "loop"
|
||||||
|
echo ""
|
||||||
|
echo "TIP: Look for a partition WITHOUT a filesystem (blank FSTYPE)"
|
||||||
|
echo " This is usually your encrypted documents partition."
|
||||||
|
echo ""
|
||||||
|
read -p "Enter the partition path (e.g., /dev/sdb1): " DOCS_PARTITION
|
||||||
|
|
||||||
|
if [ -z "$DOCS_PARTITION" ]; then
|
||||||
|
echo "ERROR: No partition specified."
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ ! -b "$DOCS_PARTITION" ]; then
|
||||||
|
echo "ERROR: $DOCS_PARTITION is not a valid block device."
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "✓ Found encrypted partition: $DOCS_PARTITION"
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
echo "Step 2: Creating mount point..."
|
||||||
|
# Mount in user's home directory for easy access
|
||||||
|
MOUNT_POINT="$HOME/SecureStorage"
|
||||||
|
mkdir -p "$MOUNT_POINT"
|
||||||
|
echo "✓ Mount point ready at: $MOUNT_POINT"
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
echo "Step 3: Mounting encrypted volume..."
|
||||||
|
echo "You will now be prompted for your encryption password."
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
# Mount the encrypted volume
|
||||||
|
if sudo veracrypt --text --mount "$DOCS_PARTITION" "$MOUNT_POINT"; then
|
||||||
|
echo ""
|
||||||
|
echo "Setting correct permissions..."
|
||||||
|
|
||||||
|
# Change ownership of the mount point to current user
|
||||||
|
# This allows the user to read/write files
|
||||||
|
sudo chown $USER:$USER "$MOUNT_POINT"
|
||||||
|
|
||||||
|
# If there are already files, change their ownership too
|
||||||
|
if [ "$(ls -A $MOUNT_POINT 2>/dev/null)" ]; then
|
||||||
|
sudo chown -R $USER:$USER "$MOUNT_POINT"/*
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "✓ Permissions set for user access"
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
echo "======================================================================"
|
||||||
|
echo " SUCCESS!"
|
||||||
|
echo "======================================================================"
|
||||||
|
echo ""
|
||||||
|
echo "Your secure storage is now accessible at:"
|
||||||
|
echo " $MOUNT_POINT"
|
||||||
|
echo ""
|
||||||
|
echo "Easy access:"
|
||||||
|
echo " • Open file manager and go to Home folder"
|
||||||
|
echo " • Look for 'SecureStorage' folder"
|
||||||
|
echo " • Or in terminal: cd ~/SecureStorage"
|
||||||
|
echo ""
|
||||||
|
echo "You can now:"
|
||||||
|
echo " • Drag and drop files"
|
||||||
|
echo " • Edit documents"
|
||||||
|
echo " • Create new folders"
|
||||||
|
echo ""
|
||||||
|
echo "IMPORTANT: When finished, run:"
|
||||||
|
echo " sudo ./lock_storage.sh"
|
||||||
|
echo ""
|
||||||
|
echo "======================================================================"
|
||||||
|
else
|
||||||
|
echo ""
|
||||||
|
echo "======================================================================"
|
||||||
|
echo " MOUNT FAILED"
|
||||||
|
echo "======================================================================"
|
||||||
|
echo ""
|
||||||
|
echo "Possible reasons:"
|
||||||
|
echo " • Wrong password"
|
||||||
|
echo " • Wrong partition selected"
|
||||||
|
echo " • Volume already mounted"
|
||||||
|
echo " • veracrypt not installed"
|
||||||
|
echo ""
|
||||||
|
echo "Troubleshooting:"
|
||||||
|
echo " 1. Check if already mounted: mount | grep secure_storage"
|
||||||
|
echo " 2. Try unmounting first: sudo veracrypt -d"
|
||||||
|
echo " 3. Verify veracrypt: which veracrypt"
|
||||||
|
echo ""
|
||||||
|
rmdir "$MOUNT_POINT" 2>/dev/null
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
@@ -0,0 +1,93 @@
|
|||||||
|
# Portable VeraCrypt Setup Instructions
|
||||||
|
|
||||||
|
Before running the main tool, you need to download portable VeraCrypt binaries.
|
||||||
|
These will be copied to the TOOLS partition so the device works on any computer.
|
||||||
|
|
||||||
|
## Required Downloads
|
||||||
|
|
||||||
|
Download the following files and place them in this directory (`tools/veracrypt/`):
|
||||||
|
|
||||||
|
### 1. Windows Portable (REQUIRED)
|
||||||
|
- **File**: `VeraCrypt Portable.exe`
|
||||||
|
- **URL**: https://www.veracrypt.fr/en/Downloads.html
|
||||||
|
- **Section**: "Portable" under Windows
|
||||||
|
- **Size**: ~30 MB
|
||||||
|
- **Rename to**: `VeraCrypt-Portable-Windows.exe`
|
||||||
|
|
||||||
|
### 2. Linux Portable (REQUIRED)
|
||||||
|
- **Option A - GUI version**:
|
||||||
|
- Download from: https://www.veracrypt.fr/en/Downloads.html
|
||||||
|
- Generic Installer: `veracrypt-*-setup.tar.bz2`
|
||||||
|
- Extract and place: `veracrypt-*-setup-gui-x64`
|
||||||
|
- **Rename to**: `VeraCrypt-Portable-Linux`
|
||||||
|
|
||||||
|
- **Option B - Console only**:
|
||||||
|
- Download: `veracrypt-*-setup-console-x64`
|
||||||
|
- **Rename to**: `VeraCrypt-Portable-Linux-Console`
|
||||||
|
|
||||||
|
### 3. macOS Portable (OPTIONAL)
|
||||||
|
- **File**: `VeraCrypt_*.dmg`
|
||||||
|
- **URL**: https://www.veracrypt.fr/en/Downloads.html
|
||||||
|
- **Note**: Not truly "portable" but can be included for reference
|
||||||
|
- **Rename to**: `VeraCrypt-MacOS.dmg`
|
||||||
|
|
||||||
|
### 4. Android APK (OPTIONAL but recommended)
|
||||||
|
- **Source**: https://github.com/veracrypt/VeraCrypt/releases
|
||||||
|
- **Alternative**: EDS Lite from Play Store (can't include, user must download)
|
||||||
|
- **Note**: Android apps can't be "portable" but having APK helps
|
||||||
|
|
||||||
|
## Quick Download Script
|
||||||
|
|
||||||
|
Run this to download automatically (Linux):
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cd tools/veracrypt/
|
||||||
|
|
||||||
|
# Windows Portable
|
||||||
|
wget https://launchpad.net/veracrypt/trunk/1.26.7/+download/VeraCrypt_Portable_1.26.7.exe \
|
||||||
|
-O VeraCrypt-Portable-Windows.exe
|
||||||
|
|
||||||
|
# Linux Console
|
||||||
|
wget https://launchpad.net/veracrypt/trunk/1.26.7/+download/veracrypt-console-1.26.7-Ubuntu-22.04-amd64.deb \
|
||||||
|
-O veracrypt-linux.deb
|
||||||
|
ar x veracrypt-linux.deb
|
||||||
|
tar xf data.tar.xz
|
||||||
|
cp usr/bin/veracrypt VeraCrypt-Portable-Linux
|
||||||
|
chmod +x VeraCrypt-Portable-Linux
|
||||||
|
rm -rf usr/ *.tar.* *.deb
|
||||||
|
|
||||||
|
# macOS
|
||||||
|
wget https://launchpad.net/veracrypt/trunk/1.26.7/+download/VeraCrypt_1.26.7.dmg \
|
||||||
|
-O VeraCrypt-MacOS.dmg
|
||||||
|
|
||||||
|
echo "✓ Downloads complete!"
|
||||||
|
```
|
||||||
|
|
||||||
|
## Verification
|
||||||
|
|
||||||
|
After downloading, your `tools/veracrypt/` directory should contain:
|
||||||
|
|
||||||
|
```
|
||||||
|
tools/veracrypt/
|
||||||
|
├── DOWNLOAD_INSTRUCTIONS.md (this file)
|
||||||
|
├── VeraCrypt-Portable-Windows.exe (~30 MB)
|
||||||
|
├── VeraCrypt-Portable-Linux (~3-5 MB)
|
||||||
|
└── VeraCrypt-MacOS.dmg (~40 MB) [optional]
|
||||||
|
```
|
||||||
|
|
||||||
|
## Notes
|
||||||
|
|
||||||
|
- **Total size**: ~70-80 MB for all platforms
|
||||||
|
- **License**: VeraCrypt is open source (Apache 2.0 / TrueCrypt License 3.0)
|
||||||
|
- **Updates**: Check veracrypt.fr periodically for newer versions
|
||||||
|
- These portable versions allow accessing your encrypted partition on ANY computer without installing software
|
||||||
|
|
||||||
|
## After Downloading
|
||||||
|
|
||||||
|
Once you have the files in place, run the main setup script:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
sudo python3 covert_sd_card_tool.py -d /dev/sdX -a -t /path/to/tails.iso
|
||||||
|
```
|
||||||
|
|
||||||
|
The script will automatically copy these portable versions to the TOOLS partition.
|
||||||
@@ -0,0 +1,97 @@
|
|||||||
|
# opsec — Privacy Hardening Toolkit
|
||||||
|
|
||||||
|
A comprehensive OPSEC hardening suite for Linux systems. Designed for anyone who needs strong privacy defaults without constant manual configuration.
|
||||||
|
|
||||||
|
## Features
|
||||||
|
|
||||||
|
- **Kill Switch**: iptables rules that block all non-Tor/VPN traffic when enabled
|
||||||
|
- **MAC Randomization**: Automatic MAC address spoofing on boot
|
||||||
|
- **Hostname Randomization**: Random hostname generation to prevent tracking
|
||||||
|
- **DNS Leak Prevention**: Locks DNS to privacy resolvers with immutable resolv.conf
|
||||||
|
- **Tor Integration**: Configurable Tor routing with circuit management, exit node filtering, and bridge support
|
||||||
|
- **Traffic Blending**: Decoy browsing traffic to mask real activity patterns
|
||||||
|
- **Desktop Widget**: Conky-based status HUD with theme support (7 themes included)
|
||||||
|
- **Deployment Levels**: Preset configurations from standard privacy to full paranoid mode
|
||||||
|
- **Profile System**: Save, load, and switch between configuration profiles
|
||||||
|
- **SSH Honeypot Detection**: Check SSH servers against known honeypot signatures
|
||||||
|
- **WiFi Security Auditing**: Check wireless configuration for common leaks
|
||||||
|
|
||||||
|
## Installation
|
||||||
|
|
||||||
|
```bash
|
||||||
|
sudo ./install.sh
|
||||||
|
```
|
||||||
|
|
||||||
|
This copies scripts to `/usr/local/bin/`, configs to `/etc/opsec/`, and sets up systemd services.
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Interactive configuration
|
||||||
|
sudo opsec-config.sh
|
||||||
|
|
||||||
|
# Toggle ghost mode (advanced privacy)
|
||||||
|
sudo opsec-mode.sh on
|
||||||
|
sudo opsec-mode.sh off
|
||||||
|
|
||||||
|
# Check OPSEC status
|
||||||
|
opsec-check.sh
|
||||||
|
|
||||||
|
# Pre-flight readiness check
|
||||||
|
opsec-preflight.sh
|
||||||
|
|
||||||
|
# Kill switch control
|
||||||
|
sudo opsec-killswitch.sh on|off|status
|
||||||
|
|
||||||
|
# Apply a deployment level
|
||||||
|
sudo opsec-config.sh --level apply bare-metal-standard
|
||||||
|
```
|
||||||
|
|
||||||
|
## Deployment Levels
|
||||||
|
|
||||||
|
| Level | Description |
|
||||||
|
|---|---|
|
||||||
|
| `bare-metal-standard` | Physical machine, ghost mode toggle available |
|
||||||
|
| `bare-metal-paranoid` | Physical machine, ghost mode always on |
|
||||||
|
| `cloud-normal` | Cloud VPS, standard privacy (no MAC/hostname) |
|
||||||
|
| `cloud-paranoid` | Cloud VPS, maximum security always on |
|
||||||
|
|
||||||
|
## Configuration
|
||||||
|
|
||||||
|
All settings live in `/etc/opsec/opsec.conf`. Edit via `opsec-config.sh` (interactive TUI) or manually.
|
||||||
|
|
||||||
|
Key settings:
|
||||||
|
- `TOR_BLACKLIST` — Comma-separated country codes to exclude from Tor exit nodes
|
||||||
|
- `DNS_MODE` — DNS resolution mode: `tor`, `quad9`, `cloudflare`, `doh`, `dot`, `custom`
|
||||||
|
- `HOSTNAME_PATTERN` — Hostname strategy: `desktop`, `random`, `custom`
|
||||||
|
- `LEVEL_TYPE` — `standard` (toggle) or `paranoid` (always on)
|
||||||
|
|
||||||
|
## Widget Themes
|
||||||
|
|
||||||
|
Seven color themes for the desktop status widget:
|
||||||
|
|
||||||
|
`default` `aurora` `ember` `slate` `cyberpunk` `frost` `terminal`
|
||||||
|
|
||||||
|
```bash
|
||||||
|
sudo opsec-config.sh --theme apply cyberpunk
|
||||||
|
```
|
||||||
|
|
||||||
|
## Important: Review Your Configuration
|
||||||
|
|
||||||
|
**This toolkit ships with intentionally generic defaults.** After installing, you **must** run `sudo opsec-config.sh` and review every setting.
|
||||||
|
|
||||||
|
Key items that require your input:
|
||||||
|
- **`TOR_BLACKLIST`** is empty by default. You need to set exit node exclusions based on your threat model.
|
||||||
|
- **`HOSTNAME_PATTERN`** defaults to `desktop` with no prefix. Set to `random` if you want randomization.
|
||||||
|
- **Kill switch and traffic blending** are off by default. Enable them if your threat model requires it.
|
||||||
|
- **Deployment level** should be selected to match your environment (bare metal vs cloud, standard vs paranoid).
|
||||||
|
|
||||||
|
The generic defaults are **safe but minimal**. They prevent accidental misconfiguration but do not represent a hardened posture. Customize for your needs.
|
||||||
|
|
||||||
|
## Responsible Use
|
||||||
|
|
||||||
|
This toolkit is designed for legitimate privacy protection. Secure deletion features are irreversible. Network anonymization tools have limitations and are not a guarantee of anonymity. Comply with applicable laws in your jurisdiction. This software is provided as-is.
|
||||||
|
|
||||||
|
## License
|
||||||
|
|
||||||
|
MIT
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
# /etc/cron.d/opsec-banner-cache — Update WAN IP cache for terminal banner
|
||||||
|
# Runs every 5 minutes to keep the cached external IP current
|
||||||
|
*/5 * * * * root /usr/local/bin/opsec-banner-cache.sh >/dev/null 2>&1
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
# /etc/cron.d/opsec-log-rotate — Secure log rotation
|
||||||
|
# Interval synced from LOG_ROTATION_HOURS in /etc/opsec/opsec.conf (default: 4h)
|
||||||
|
SHELL=/bin/bash
|
||||||
|
PATH=/usr/local/sbin:/usr/local/bin:/sbin:/bin:/usr/sbin:/usr/bin
|
||||||
|
|
||||||
|
0 */4 * * * root /usr/local/bin/opsec-log-rotate.sh >/dev/null 2>&1
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
[Desktop Entry]
|
||||||
|
Type=Application
|
||||||
|
Name=OPSEC Mode Toggle
|
||||||
|
Comment=Toggle between OPSEC Advanced and Standard modes
|
||||||
|
Exec=/usr/local/bin/opsec-toggle.sh
|
||||||
|
Icon=security-high
|
||||||
|
Terminal=false
|
||||||
|
Categories=System;Security;
|
||||||
|
Keywords=opsec;security;toggle;mode;
|
||||||
@@ -0,0 +1,85 @@
|
|||||||
|
# /etc/opsec/levels/bare-metal-paranoid.conf — Physical Machine (Paranoid)
|
||||||
|
# Maximum security always on. Ghost mode is the default — cannot be disabled.
|
||||||
|
# Apply via: sudo opsec-config.sh --level apply bare-metal-paranoid
|
||||||
|
|
||||||
|
# ─── LEVEL TYPE ───────────────────────────────────────────────────────────────
|
||||||
|
# paranoid = ghost mode always on, cannot disable
|
||||||
|
LEVEL_TYPE="paranoid"
|
||||||
|
|
||||||
|
# ─── ACTIVE PROFILE ────────────────────────────────────────────────────────────
|
||||||
|
PROFILE_NAME="default"
|
||||||
|
|
||||||
|
# ─── BASE STATE ───────────────────────────────────────────────────────────────
|
||||||
|
BASE_DNS="quad9"
|
||||||
|
BASE_MAC_RANDOMIZE="1"
|
||||||
|
BASE_IPV6_DISABLE="1"
|
||||||
|
|
||||||
|
# ─── TOR SETTINGS ─────────────────────────────────────────────────────────────
|
||||||
|
TOR_CIRCUIT_ROTATION="15"
|
||||||
|
TOR_BLACKLIST=""
|
||||||
|
TOR_STRICT_NODES="1"
|
||||||
|
TOR_ISOLATION="1"
|
||||||
|
TOR_PADDING="1"
|
||||||
|
TOR_SOCKS_PORT="9050"
|
||||||
|
TOR_DNS_PORT="5353"
|
||||||
|
TOR_NUM_GUARDS="3"
|
||||||
|
TOR_SAFE_LOGGING="1"
|
||||||
|
|
||||||
|
# ─── DNS SETTINGS ─────────────────────────────────────────────────────────────
|
||||||
|
DNS_MODE="tor"
|
||||||
|
DNS_CUSTOM_SERVERS=""
|
||||||
|
|
||||||
|
# ─── KILL SWITCH ───────────────────────────────────────────────────────────────
|
||||||
|
KILLSWITCH_ALLOW_DHCP="1"
|
||||||
|
KILLSWITCH_ALLOW_OPENVPN="1"
|
||||||
|
KILLSWITCH_ALLOW_WIREGUARD="1"
|
||||||
|
KILLSWITCH_EXTRA_PORTS=""
|
||||||
|
|
||||||
|
# ─── MAC ADDRESS ───────────────────────────────────────────────────────────────
|
||||||
|
MAC_INTERFACES="auto"
|
||||||
|
MAC_VENDOR_SPOOF=""
|
||||||
|
|
||||||
|
# ─── HOSTNAME ──────────────────────────────────────────────────────────────────
|
||||||
|
HOSTNAME_PATTERN="random"
|
||||||
|
HOSTNAME_CUSTOM_PREFIX=""
|
||||||
|
|
||||||
|
# ─── SYSTEM HARDENING ──────────────────────────────────────────────────────────
|
||||||
|
HARDEN_IPV6="1"
|
||||||
|
HARDEN_SWAP="1"
|
||||||
|
HARDEN_CORE_DUMPS="1"
|
||||||
|
HARDEN_CLIPBOARD_CLEAR="1"
|
||||||
|
HARDEN_SCREEN_LOCK="1"
|
||||||
|
HARDEN_SCREEN_LOCK_TIMEOUT="300"
|
||||||
|
HARDEN_TIMEZONE_SPOOF="1"
|
||||||
|
HARDEN_TIMEZONE_VALUE="UTC"
|
||||||
|
HARDEN_LOCALE_SPOOF="1"
|
||||||
|
HARDEN_LOCALE_VALUE="en_US.UTF-8"
|
||||||
|
|
||||||
|
# ─── LEAK PREVENTION ──────────────────────────────────────────────────────────
|
||||||
|
LEAK_WEBRTC_BLOCK="1"
|
||||||
|
LEAK_USB_BLOCK="1"
|
||||||
|
|
||||||
|
# ─── MONITORING ────────────────────────────────────────────────────────────────
|
||||||
|
MONITOR_PROCESSES="1"
|
||||||
|
MONITOR_LOG_ROTATION="1"
|
||||||
|
LOG_ROTATION_HOURS="1"
|
||||||
|
|
||||||
|
# ─── TRAFFIC SHAPING ──────────────────────────────────────────────────────────
|
||||||
|
TRAFFIC_JITTER_ENABLED="1"
|
||||||
|
TRAFFIC_JITTER_MS="50"
|
||||||
|
|
||||||
|
# ─── TOR BRIDGES ──────────────────────────────────────────────────────────────
|
||||||
|
TOR_BRIDGE_MODE="obfs4"
|
||||||
|
TOR_BRIDGE_RELAY=""
|
||||||
|
|
||||||
|
# ─── SECURE DELETION ──────────────────────────────────────────────────────────
|
||||||
|
WIPE_METHOD="auto"
|
||||||
|
|
||||||
|
# ─── DEPLOYMENT LEVEL ─────────────────────────────────────────────────────────
|
||||||
|
DEPLOYMENT_LEVEL="bare-metal-paranoid"
|
||||||
|
|
||||||
|
# ─── TERMINAL BANNER ──────────────────────────────────────────────────────────
|
||||||
|
OPSEC_BANNER="compact"
|
||||||
|
|
||||||
|
# ─── WIDGET THEME ─────────────────────────────────────────────────────────────
|
||||||
|
WIDGET_THEME="default"
|
||||||
@@ -0,0 +1,86 @@
|
|||||||
|
# /etc/opsec/levels/bare-metal-standard.conf — Physical Machine (Standard)
|
||||||
|
# Privacy-focused daily driver. Ghost mode available via: opsec-mode on
|
||||||
|
# Apply via: sudo opsec-config.sh --level apply bare-metal-standard
|
||||||
|
|
||||||
|
# ─── LEVEL TYPE ───────────────────────────────────────────────────────────────
|
||||||
|
# standard = privacy base, ghost mode toggle available
|
||||||
|
# paranoid = ghost mode always on, cannot disable
|
||||||
|
LEVEL_TYPE="standard"
|
||||||
|
|
||||||
|
# ─── ACTIVE PROFILE ────────────────────────────────────────────────────────────
|
||||||
|
PROFILE_NAME="default"
|
||||||
|
|
||||||
|
# ─── BASE STATE (always active in standard mode) ─────────────────────────────
|
||||||
|
BASE_DNS="quad9"
|
||||||
|
BASE_MAC_RANDOMIZE="1"
|
||||||
|
BASE_IPV6_DISABLE="1"
|
||||||
|
|
||||||
|
# ─── TOR SETTINGS (used when ghost mode is toggled ON) ───────────────────────
|
||||||
|
TOR_CIRCUIT_ROTATION="30"
|
||||||
|
TOR_BLACKLIST=""
|
||||||
|
TOR_STRICT_NODES="1"
|
||||||
|
TOR_ISOLATION="1"
|
||||||
|
TOR_PADDING="1"
|
||||||
|
TOR_SOCKS_PORT="9050"
|
||||||
|
TOR_DNS_PORT="5353"
|
||||||
|
TOR_NUM_GUARDS="3"
|
||||||
|
TOR_SAFE_LOGGING="1"
|
||||||
|
|
||||||
|
# ─── DNS SETTINGS (ghost mode DNS — base uses BASE_DNS above) ────────────────
|
||||||
|
DNS_MODE="tor"
|
||||||
|
DNS_CUSTOM_SERVERS=""
|
||||||
|
|
||||||
|
# ─── KILL SWITCH ───────────────────────────────────────────────────────────────
|
||||||
|
KILLSWITCH_ALLOW_DHCP="1"
|
||||||
|
KILLSWITCH_ALLOW_OPENVPN="1"
|
||||||
|
KILLSWITCH_ALLOW_WIREGUARD="1"
|
||||||
|
KILLSWITCH_EXTRA_PORTS=""
|
||||||
|
|
||||||
|
# ─── MAC ADDRESS ───────────────────────────────────────────────────────────────
|
||||||
|
MAC_INTERFACES="auto"
|
||||||
|
MAC_VENDOR_SPOOF=""
|
||||||
|
|
||||||
|
# ─── HOSTNAME ──────────────────────────────────────────────────────────────────
|
||||||
|
HOSTNAME_PATTERN="random"
|
||||||
|
HOSTNAME_CUSTOM_PREFIX=""
|
||||||
|
|
||||||
|
# ─── SYSTEM HARDENING ──────────────────────────────────────────────────────────
|
||||||
|
HARDEN_IPV6="1"
|
||||||
|
HARDEN_SWAP="1"
|
||||||
|
HARDEN_CORE_DUMPS="1"
|
||||||
|
HARDEN_CLIPBOARD_CLEAR="1"
|
||||||
|
HARDEN_SCREEN_LOCK="1"
|
||||||
|
HARDEN_SCREEN_LOCK_TIMEOUT="300"
|
||||||
|
HARDEN_TIMEZONE_SPOOF="0"
|
||||||
|
HARDEN_TIMEZONE_VALUE="UTC"
|
||||||
|
HARDEN_LOCALE_SPOOF="0"
|
||||||
|
HARDEN_LOCALE_VALUE="en_US.UTF-8"
|
||||||
|
|
||||||
|
# ─── LEAK PREVENTION ──────────────────────────────────────────────────────────
|
||||||
|
LEAK_WEBRTC_BLOCK="1"
|
||||||
|
LEAK_USB_BLOCK="1"
|
||||||
|
|
||||||
|
# ─── MONITORING ────────────────────────────────────────────────────────────────
|
||||||
|
MONITOR_PROCESSES="1"
|
||||||
|
MONITOR_LOG_ROTATION="1"
|
||||||
|
LOG_ROTATION_HOURS="4"
|
||||||
|
|
||||||
|
# ─── TRAFFIC SHAPING ──────────────────────────────────────────────────────────
|
||||||
|
TRAFFIC_JITTER_ENABLED="0"
|
||||||
|
TRAFFIC_JITTER_MS="50"
|
||||||
|
|
||||||
|
# ─── TOR BRIDGES ──────────────────────────────────────────────────────────────
|
||||||
|
TOR_BRIDGE_MODE="off"
|
||||||
|
TOR_BRIDGE_RELAY=""
|
||||||
|
|
||||||
|
# ─── SECURE DELETION ──────────────────────────────────────────────────────────
|
||||||
|
WIPE_METHOD="auto"
|
||||||
|
|
||||||
|
# ─── DEPLOYMENT LEVEL ─────────────────────────────────────────────────────────
|
||||||
|
DEPLOYMENT_LEVEL="bare-metal-standard"
|
||||||
|
|
||||||
|
# ─── TERMINAL BANNER ──────────────────────────────────────────────────────────
|
||||||
|
OPSEC_BANNER="compact"
|
||||||
|
|
||||||
|
# ─── WIDGET THEME ─────────────────────────────────────────────────────────────
|
||||||
|
WIDGET_THEME="default"
|
||||||
@@ -0,0 +1,85 @@
|
|||||||
|
# /etc/opsec/levels/cloud-normal.conf — Cloud VPS (Standard)
|
||||||
|
# Privacy-focused cloud deployment. Ghost mode available via: opsec-mode on
|
||||||
|
# No MAC/hostname/screen/USB (hardware-irrelevant on cloud).
|
||||||
|
# Apply via: sudo opsec-config.sh --level apply cloud-normal
|
||||||
|
|
||||||
|
# ─── LEVEL TYPE ───────────────────────────────────────────────────────────────
|
||||||
|
LEVEL_TYPE="standard"
|
||||||
|
|
||||||
|
# ─── ACTIVE PROFILE ────────────────────────────────────────────────────────────
|
||||||
|
PROFILE_NAME="default"
|
||||||
|
|
||||||
|
# ─── BASE STATE (always active in standard mode) ─────────────────────────────
|
||||||
|
BASE_DNS="quad9"
|
||||||
|
BASE_MAC_RANDOMIZE="0"
|
||||||
|
BASE_IPV6_DISABLE="1"
|
||||||
|
|
||||||
|
# ─── TOR SETTINGS (used when ghost mode is toggled ON) ───────────────────────
|
||||||
|
TOR_CIRCUIT_ROTATION="30"
|
||||||
|
TOR_BLACKLIST=""
|
||||||
|
TOR_STRICT_NODES="1"
|
||||||
|
TOR_ISOLATION="1"
|
||||||
|
TOR_PADDING="0"
|
||||||
|
TOR_SOCKS_PORT="9050"
|
||||||
|
TOR_DNS_PORT="5353"
|
||||||
|
TOR_NUM_GUARDS="3"
|
||||||
|
TOR_SAFE_LOGGING="1"
|
||||||
|
|
||||||
|
# ─── DNS SETTINGS ─────────────────────────────────────────────────────────────
|
||||||
|
DNS_MODE="tor"
|
||||||
|
DNS_CUSTOM_SERVERS=""
|
||||||
|
|
||||||
|
# ─── KILL SWITCH ───────────────────────────────────────────────────────────────
|
||||||
|
KILLSWITCH_ALLOW_DHCP="1"
|
||||||
|
KILLSWITCH_ALLOW_OPENVPN="1"
|
||||||
|
KILLSWITCH_ALLOW_WIREGUARD="1"
|
||||||
|
KILLSWITCH_EXTRA_PORTS=""
|
||||||
|
|
||||||
|
# ─── MAC ADDRESS ───────────────────────────────────────────────────────────────
|
||||||
|
MAC_INTERFACES="auto"
|
||||||
|
MAC_VENDOR_SPOOF=""
|
||||||
|
|
||||||
|
# ─── HOSTNAME ──────────────────────────────────────────────────────────────────
|
||||||
|
HOSTNAME_PATTERN="desktop"
|
||||||
|
HOSTNAME_CUSTOM_PREFIX=""
|
||||||
|
|
||||||
|
# ─── SYSTEM HARDENING ──────────────────────────────────────────────────────────
|
||||||
|
HARDEN_IPV6="1"
|
||||||
|
HARDEN_SWAP="1"
|
||||||
|
HARDEN_CORE_DUMPS="1"
|
||||||
|
HARDEN_CLIPBOARD_CLEAR="0"
|
||||||
|
HARDEN_SCREEN_LOCK="0"
|
||||||
|
HARDEN_SCREEN_LOCK_TIMEOUT="300"
|
||||||
|
HARDEN_TIMEZONE_SPOOF="0"
|
||||||
|
HARDEN_TIMEZONE_VALUE="UTC"
|
||||||
|
HARDEN_LOCALE_SPOOF="0"
|
||||||
|
HARDEN_LOCALE_VALUE="en_US.UTF-8"
|
||||||
|
|
||||||
|
# ─── LEAK PREVENTION ──────────────────────────────────────────────────────────
|
||||||
|
LEAK_WEBRTC_BLOCK="1"
|
||||||
|
LEAK_USB_BLOCK="0"
|
||||||
|
|
||||||
|
# ─── MONITORING ────────────────────────────────────────────────────────────────
|
||||||
|
MONITOR_PROCESSES="1"
|
||||||
|
MONITOR_LOG_ROTATION="1"
|
||||||
|
LOG_ROTATION_HOURS="1"
|
||||||
|
|
||||||
|
# ─── TRAFFIC SHAPING ──────────────────────────────────────────────────────────
|
||||||
|
TRAFFIC_JITTER_ENABLED="0"
|
||||||
|
TRAFFIC_JITTER_MS="50"
|
||||||
|
|
||||||
|
# ─── TOR BRIDGES ──────────────────────────────────────────────────────────────
|
||||||
|
TOR_BRIDGE_MODE="off"
|
||||||
|
TOR_BRIDGE_RELAY=""
|
||||||
|
|
||||||
|
# ─── SECURE DELETION ──────────────────────────────────────────────────────────
|
||||||
|
WIPE_METHOD="auto"
|
||||||
|
|
||||||
|
# ─── DEPLOYMENT LEVEL ─────────────────────────────────────────────────────────
|
||||||
|
DEPLOYMENT_LEVEL="cloud-normal"
|
||||||
|
|
||||||
|
# ─── TERMINAL BANNER ──────────────────────────────────────────────────────────
|
||||||
|
OPSEC_BANNER="compact"
|
||||||
|
|
||||||
|
# ─── WIDGET THEME ─────────────────────────────────────────────────────────────
|
||||||
|
WIDGET_THEME="default"
|
||||||
@@ -0,0 +1,84 @@
|
|||||||
|
# /etc/opsec/levels/cloud-paranoid.conf — Cloud VPS (Paranoid)
|
||||||
|
# Maximum security always on. Ghost mode is the default — cannot be disabled.
|
||||||
|
# Apply via: sudo opsec-config.sh --level apply cloud-paranoid
|
||||||
|
|
||||||
|
# ─── LEVEL TYPE ───────────────────────────────────────────────────────────────
|
||||||
|
LEVEL_TYPE="paranoid"
|
||||||
|
|
||||||
|
# ─── ACTIVE PROFILE ────────────────────────────────────────────────────────────
|
||||||
|
PROFILE_NAME="default"
|
||||||
|
|
||||||
|
# ─── BASE STATE ───────────────────────────────────────────────────────────────
|
||||||
|
BASE_DNS="quad9"
|
||||||
|
BASE_MAC_RANDOMIZE="0"
|
||||||
|
BASE_IPV6_DISABLE="1"
|
||||||
|
|
||||||
|
# ─── TOR SETTINGS ─────────────────────────────────────────────────────────────
|
||||||
|
TOR_CIRCUIT_ROTATION="15"
|
||||||
|
TOR_BLACKLIST=""
|
||||||
|
TOR_STRICT_NODES="1"
|
||||||
|
TOR_ISOLATION="1"
|
||||||
|
TOR_PADDING="1"
|
||||||
|
TOR_SOCKS_PORT="9050"
|
||||||
|
TOR_DNS_PORT="5353"
|
||||||
|
TOR_NUM_GUARDS="3"
|
||||||
|
TOR_SAFE_LOGGING="1"
|
||||||
|
|
||||||
|
# ─── DNS SETTINGS ─────────────────────────────────────────────────────────────
|
||||||
|
DNS_MODE="tor"
|
||||||
|
DNS_CUSTOM_SERVERS=""
|
||||||
|
|
||||||
|
# ─── KILL SWITCH ───────────────────────────────────────────────────────────────
|
||||||
|
KILLSWITCH_ALLOW_DHCP="1"
|
||||||
|
KILLSWITCH_ALLOW_OPENVPN="1"
|
||||||
|
KILLSWITCH_ALLOW_WIREGUARD="1"
|
||||||
|
KILLSWITCH_EXTRA_PORTS=""
|
||||||
|
|
||||||
|
# ─── MAC ADDRESS ───────────────────────────────────────────────────────────────
|
||||||
|
MAC_INTERFACES="auto"
|
||||||
|
MAC_VENDOR_SPOOF=""
|
||||||
|
|
||||||
|
# ─── HOSTNAME ──────────────────────────────────────────────────────────────────
|
||||||
|
HOSTNAME_PATTERN="desktop"
|
||||||
|
HOSTNAME_CUSTOM_PREFIX=""
|
||||||
|
|
||||||
|
# ─── SYSTEM HARDENING ──────────────────────────────────────────────────────────
|
||||||
|
HARDEN_IPV6="1"
|
||||||
|
HARDEN_SWAP="1"
|
||||||
|
HARDEN_CORE_DUMPS="1"
|
||||||
|
HARDEN_CLIPBOARD_CLEAR="1"
|
||||||
|
HARDEN_SCREEN_LOCK="0"
|
||||||
|
HARDEN_SCREEN_LOCK_TIMEOUT="300"
|
||||||
|
HARDEN_TIMEZONE_SPOOF="1"
|
||||||
|
HARDEN_TIMEZONE_VALUE="UTC"
|
||||||
|
HARDEN_LOCALE_SPOOF="1"
|
||||||
|
HARDEN_LOCALE_VALUE="en_US.UTF-8"
|
||||||
|
|
||||||
|
# ─── LEAK PREVENTION ──────────────────────────────────────────────────────────
|
||||||
|
LEAK_WEBRTC_BLOCK="1"
|
||||||
|
LEAK_USB_BLOCK="0"
|
||||||
|
|
||||||
|
# ─── MONITORING ────────────────────────────────────────────────────────────────
|
||||||
|
MONITOR_PROCESSES="1"
|
||||||
|
MONITOR_LOG_ROTATION="1"
|
||||||
|
LOG_ROTATION_HOURS="1"
|
||||||
|
|
||||||
|
# ─── TRAFFIC SHAPING ──────────────────────────────────────────────────────────
|
||||||
|
TRAFFIC_JITTER_ENABLED="1"
|
||||||
|
TRAFFIC_JITTER_MS="50"
|
||||||
|
|
||||||
|
# ─── TOR BRIDGES ──────────────────────────────────────────────────────────────
|
||||||
|
TOR_BRIDGE_MODE="obfs4"
|
||||||
|
TOR_BRIDGE_RELAY=""
|
||||||
|
|
||||||
|
# ─── SECURE DELETION ──────────────────────────────────────────────────────────
|
||||||
|
WIPE_METHOD="auto"
|
||||||
|
|
||||||
|
# ─── DEPLOYMENT LEVEL ─────────────────────────────────────────────────────────
|
||||||
|
DEPLOYMENT_LEVEL="cloud-paranoid"
|
||||||
|
|
||||||
|
# ─── TERMINAL BANNER ──────────────────────────────────────────────────────────
|
||||||
|
OPSEC_BANNER="compact"
|
||||||
|
|
||||||
|
# ─── WIDGET THEME ─────────────────────────────────────────────────────────────
|
||||||
|
WIDGET_THEME="default"
|
||||||
@@ -0,0 +1,100 @@
|
|||||||
|
# ─── OPSEC ALIASES ───────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
# Network checks
|
||||||
|
alias myip='curl -s ifconfig.me'
|
||||||
|
alias checkip='curl -s https://ipinfo.io/ip'
|
||||||
|
alias checkdns='cat /etc/resolv.conf'
|
||||||
|
alias ports='netstat -tulanp'
|
||||||
|
alias listen='lsof -i -P | grep LISTEN'
|
||||||
|
alias estab='lsof -i -P | grep ESTABLISHED'
|
||||||
|
|
||||||
|
# Process and connection monitoring
|
||||||
|
alias checkcon='ss -tupan | grep ESTABLISHED'
|
||||||
|
alias checklis='ss -tupan | grep LISTEN'
|
||||||
|
alias checkproc='ps auxf | grep -v grep | grep'
|
||||||
|
alias psg='ps aux | grep -v grep | grep -i'
|
||||||
|
|
||||||
|
# Emergency and cleanup
|
||||||
|
alias killcon='killall -9 openvpn ssh 2>/dev/null'
|
||||||
|
alias randmac='randomize-mac.sh'
|
||||||
|
alias opsec='opsec-check.sh'
|
||||||
|
|
||||||
|
# Cleanup operations
|
||||||
|
alias clear-logs='sudo find /var/log -name '\''opsec*'\'' -type f -exec truncate -s 0 {} \; && sudo truncate -s 0 /var/log/syslog /var/log/auth.log 2>/dev/null'
|
||||||
|
alias clear-history='history -c && > ~/.bash_history && > ~/.zsh_history'
|
||||||
|
alias shred-file='shred -vfz -n 3'
|
||||||
|
|
||||||
|
# Anonymity
|
||||||
|
alias anon-on='sudo systemctl start tor && . torsocks on'
|
||||||
|
alias anon-off='. torsocks off && sudo systemctl stop tor'
|
||||||
|
alias check-tor='curl -s https://check.torproject.org/api/ip'
|
||||||
|
|
||||||
|
# Safe OPSEC status check
|
||||||
|
alias opsec-status='echo "OPSEC Status:"; echo "VPN: $(pgrep openvpn > /dev/null && echo "Connected" || echo "Disconnected")"; echo "Tor: $(systemctl is-active tor 2>/dev/null | grep -q active && echo "Active" || echo "Inactive")"; echo "IP: $(curl -s --max-time 2 ifconfig.me || echo "Check failed")"'
|
||||||
|
|
||||||
|
# Quick OPSEC check
|
||||||
|
alias quick-opsec='opsec-status && echo "" && echo "=== Connections ===" && ss -tupln | grep -E ":443|:9050|:1080" | head -5'
|
||||||
|
|
||||||
|
# ─── ADVANCED OPSEC MODE ──────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
# Master toggle
|
||||||
|
alias opsec-on='sudo /usr/local/bin/opsec-mode.sh on'
|
||||||
|
alias opsec-off='sudo /usr/local/bin/opsec-mode.sh off'
|
||||||
|
alias opsec-advanced='sudo /usr/local/bin/opsec-mode.sh status'
|
||||||
|
|
||||||
|
# Kill switch controls
|
||||||
|
alias killswitch-on='sudo /usr/local/bin/opsec-killswitch.sh on'
|
||||||
|
alias killswitch-off='sudo /usr/local/bin/opsec-killswitch.sh off'
|
||||||
|
alias killswitch-status='sudo /usr/local/bin/opsec-killswitch.sh status'
|
||||||
|
|
||||||
|
# Hostname randomization
|
||||||
|
alias randhostname='sudo /usr/local/bin/opsec-hostname-randomize.sh'
|
||||||
|
|
||||||
|
# Tor circuit management
|
||||||
|
alias check-circuit='curl -s --socks5-hostname 127.0.0.1:9050 https://check.torproject.org/api/ip'
|
||||||
|
alias new-circuit='sudo killall -HUP tor && echo "New Tor circuit requested"'
|
||||||
|
|
||||||
|
# DNS leak check
|
||||||
|
alias check-dns-leak='echo "=== DNS Leak Check ===" && echo "resolv.conf:" && grep nameserver /etc/resolv.conf && echo "" && echo "Tor DNS test:" && dig +short @127.0.0.1 -p 5353 check.torproject.org 2>/dev/null || echo "Tor DNS not available"'
|
||||||
|
|
||||||
|
# OPSEC Config Manager
|
||||||
|
alias opsec-config='sudo /usr/local/bin/opsec-config.sh'
|
||||||
|
alias opsec-show='sudo /usr/local/bin/opsec-mode.sh status'
|
||||||
|
|
||||||
|
# Profile management
|
||||||
|
alias opsec-profile='sudo /usr/local/bin/opsec-config.sh --profile'
|
||||||
|
|
||||||
|
# Boot mode
|
||||||
|
alias opsec-boot-on='sudo /usr/local/bin/opsec-config.sh --boot on'
|
||||||
|
alias opsec-boot-off='sudo /usr/local/bin/opsec-config.sh --boot off'
|
||||||
|
|
||||||
|
# SSH honeypot check
|
||||||
|
alias ssh-safe='/usr/local/bin/opsec-ssh-check.sh'
|
||||||
|
|
||||||
|
# Connection monitor
|
||||||
|
alias opsec-monitor-start='sudo /usr/local/bin/opsec-monitor.sh start'
|
||||||
|
alias opsec-monitor-stop='sudo /usr/local/bin/opsec-monitor.sh stop'
|
||||||
|
alias opsec-monitor-status='sudo /usr/local/bin/opsec-monitor.sh status'
|
||||||
|
|
||||||
|
# WiFi security
|
||||||
|
alias opsec-wifi='sudo /usr/local/bin/opsec-wifi-check.sh'
|
||||||
|
|
||||||
|
# Conky widget controls — position: tl tc tr bl bc br (default: bl)
|
||||||
|
alias opsec-widget-kill='killall conky 2>/dev/null && echo "Widget stopped" || echo "Widget not running"'
|
||||||
|
opsec-widget() { ~/.config/conky/opsec-widget-launch.sh "${1:-bl}"; }
|
||||||
|
opsec-widget-restart() { ~/.config/conky/opsec-widget-launch.sh "${1:-bl}"; }
|
||||||
|
|
||||||
|
# Deployment level and banner control
|
||||||
|
alias opsec-level='sudo /usr/local/bin/opsec-config.sh --level apply'
|
||||||
|
alias opsec-level-list='sudo /usr/local/bin/opsec-config.sh --level list'
|
||||||
|
alias opsec-banner-set='sudo /usr/local/bin/opsec-config.sh --banner'
|
||||||
|
alias opsec-toggle='/usr/local/bin/opsec-toggle.sh'
|
||||||
|
|
||||||
|
# Preflight and break-glass
|
||||||
|
alias opsec-preflight='/usr/local/bin/opsec-preflight.sh'
|
||||||
|
alias opsec-score='/usr/local/bin/opsec-preflight.sh --score'
|
||||||
|
alias opsec-breakglass='sudo /usr/local/bin/opsec-mode.sh breakglass'
|
||||||
|
alias opsec-breakglass-off='sudo /usr/local/bin/opsec-mode.sh breakglass-off'
|
||||||
|
|
||||||
|
# ─── OPSEC TERMINAL BANNER ──────────────────────────────────────────────────
|
||||||
|
[ -x /usr/local/bin/opsec-banner.sh ] && /usr/local/bin/opsec-banner.sh
|
||||||
@@ -0,0 +1,28 @@
|
|||||||
|
# /etc/opsec/country-codes.conf — Country Code Presets for Tor Blacklisting
|
||||||
|
# Used by opsec-config.sh and opsec-lib.sh for quick preset loading
|
||||||
|
# Format: PRESET_NAME="cc1,cc2,cc3,..."
|
||||||
|
|
||||||
|
# ─── INTELLIGENCE ALLIANCES ────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
# Five Eyes — core anglosphere intelligence sharing
|
||||||
|
FIVE_EYES="us,gb,ca,au,nz"
|
||||||
|
|
||||||
|
# Nine Eyes — Five Eyes + Denmark, France, Netherlands, Norway
|
||||||
|
NINE_EYES="us,gb,ca,au,nz,dk,fr,nl,no"
|
||||||
|
|
||||||
|
# Fourteen Eyes — Nine Eyes + Germany, Belgium, Italy, Sweden, Spain
|
||||||
|
FOURTEEN_EYES="us,gb,ca,au,nz,dk,fr,nl,no,de,be,it,se,es"
|
||||||
|
|
||||||
|
# ─── HIGH-SURVEILLANCE STATES ─────────────────────────────────────────────────
|
||||||
|
|
||||||
|
# Countries with known mass surveillance / hostile SIGINT
|
||||||
|
SURVEILLANCE_STATES="cn,ru,ir,kp,il,sg,ae,sa,eg,tr,pk,th,vn,by,kz"
|
||||||
|
|
||||||
|
# ─── COMBINED PRESETS ──────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
# Maximum exclusion: Fourteen Eyes + surveillance states
|
||||||
|
MAX_EXCLUSION="us,gb,ca,au,nz,dk,fr,nl,no,de,be,it,se,es,cn,ru,ir,kp,il,sg,ae,sa,eg,tr,pk,th,vn,by,kz"
|
||||||
|
|
||||||
|
# ─── VALID COUNTRY CODES (for input validation) ───────────────────────────────
|
||||||
|
# ISO 3166-1 alpha-2 codes commonly used as Tor country codes
|
||||||
|
VALID_CODES="ad,ae,af,ag,ai,al,am,ao,ar,as,at,au,az,ba,bb,bd,be,bf,bg,bh,bi,bj,bm,bn,bo,br,bs,bt,bw,by,bz,ca,cd,cf,cg,ch,ci,cl,cm,cn,co,cr,cu,cv,cy,cz,de,dj,dk,dm,do,dz,ec,ee,eg,er,es,et,fi,fj,fm,fr,ga,gb,gd,ge,gh,gm,gn,gp,gq,gr,gt,gw,gy,hk,hn,hr,ht,hu,id,ie,il,in,iq,ir,is,it,jm,jo,jp,ke,kg,kh,ki,km,kn,kp,kr,kw,kz,la,lb,lc,li,lk,lr,ls,lt,lu,lv,ly,ma,mc,md,me,mg,mh,mk,ml,mm,mn,mo,mr,mt,mu,mv,mw,mx,my,mz,na,ne,ng,ni,nl,no,np,nr,nz,om,pa,pe,pg,ph,pk,pl,pt,pw,py,qa,ro,rs,ru,rw,sa,sb,sc,sd,se,sg,si,sk,sl,sm,sn,so,sr,ss,st,sv,sy,sz,td,tg,th,tj,tl,tm,tn,to,tr,tt,tv,tw,tz,ua,ug,us,uy,uz,va,vc,ve,vn,vu,ws,ye,za,zm,zw"
|
||||||
@@ -0,0 +1,96 @@
|
|||||||
|
# /etc/opsec/opsec.conf — Central OPSEC Configuration
|
||||||
|
# Shell-sourceable KEY="value" format. All scripts source this file.
|
||||||
|
# Edit via: sudo opsec-config.sh (interactive TUI)
|
||||||
|
# Manual edits: source /usr/local/lib/opsec/opsec-lib.sh && opsec_set_value KEY "value"
|
||||||
|
|
||||||
|
# ─── ACTIVE PROFILE ────────────────────────────────────────────────────────────
|
||||||
|
PROFILE_NAME="default"
|
||||||
|
|
||||||
|
# ─── TOR SETTINGS ──────────────────────────────────────────────────────────────
|
||||||
|
TOR_CIRCUIT_ROTATION="30"
|
||||||
|
TOR_BLACKLIST=""
|
||||||
|
TOR_STRICT_NODES="1"
|
||||||
|
TOR_ISOLATION="1"
|
||||||
|
TOR_PADDING="1"
|
||||||
|
TOR_SOCKS_PORT="9050"
|
||||||
|
TOR_DNS_PORT="5353"
|
||||||
|
TOR_NUM_GUARDS="3"
|
||||||
|
TOR_SAFE_LOGGING="1"
|
||||||
|
|
||||||
|
# ─── DNS SETTINGS ──────────────────────────────────────────────────────────────
|
||||||
|
# Mode: tor | quad9 | cloudflare | doh | dot | custom
|
||||||
|
DNS_MODE="tor"
|
||||||
|
DNS_CUSTOM_SERVERS=""
|
||||||
|
|
||||||
|
# ─── KILL SWITCH ───────────────────────────────────────────────────────────────
|
||||||
|
KILLSWITCH_ALLOW_DHCP="1"
|
||||||
|
KILLSWITCH_ALLOW_OPENVPN="1"
|
||||||
|
KILLSWITCH_ALLOW_WIREGUARD="1"
|
||||||
|
KILLSWITCH_EXTRA_PORTS=""
|
||||||
|
|
||||||
|
# ─── MAC ADDRESS ───────────────────────────────────────────────────────────────
|
||||||
|
# Interfaces: auto (all non-lo) or comma-separated list (e.g. "eth0,wlan0")
|
||||||
|
MAC_INTERFACES="auto"
|
||||||
|
# Vendor spoof: empty for random, or OUI prefix (e.g. "00:1A:2B" for specific vendor)
|
||||||
|
MAC_VENDOR_SPOOF=""
|
||||||
|
|
||||||
|
# ─── HOSTNAME ──────────────────────────────────────────────────────────────────
|
||||||
|
# Pattern: desktop | random | custom
|
||||||
|
HOSTNAME_PATTERN="desktop"
|
||||||
|
HOSTNAME_CUSTOM_PREFIX=""
|
||||||
|
|
||||||
|
# ─── SYSTEM HARDENING ──────────────────────────────────────────────────────────
|
||||||
|
HARDEN_IPV6="1"
|
||||||
|
HARDEN_SWAP="1"
|
||||||
|
HARDEN_CORE_DUMPS="1"
|
||||||
|
HARDEN_CLIPBOARD_CLEAR="0"
|
||||||
|
HARDEN_SCREEN_LOCK="1"
|
||||||
|
HARDEN_SCREEN_LOCK_TIMEOUT="300"
|
||||||
|
HARDEN_TIMEZONE_SPOOF="0"
|
||||||
|
HARDEN_TIMEZONE_VALUE="UTC"
|
||||||
|
HARDEN_LOCALE_SPOOF="0"
|
||||||
|
HARDEN_LOCALE_VALUE="en_US.UTF-8"
|
||||||
|
|
||||||
|
# ─── LEAK PREVENTION ──────────────────────────────────────────────────────────
|
||||||
|
LEAK_WEBRTC_BLOCK="1"
|
||||||
|
LEAK_USB_BLOCK="0"
|
||||||
|
|
||||||
|
# ─── MONITORING ────────────────────────────────────────────────────────────────
|
||||||
|
MONITOR_PROCESSES="0"
|
||||||
|
MONITOR_LOG_ROTATION="1"
|
||||||
|
LOG_ROTATION_HOURS="4"
|
||||||
|
|
||||||
|
# ─── TRAFFIC SHAPING ──────────────────────────────────────────────────────────
|
||||||
|
TRAFFIC_JITTER_ENABLED="0"
|
||||||
|
TRAFFIC_JITTER_MS="50"
|
||||||
|
|
||||||
|
# ─── LEVEL TYPE ───────────────────────────────────────────────────────────────
|
||||||
|
# standard = privacy base, ghost mode toggle available
|
||||||
|
# paranoid = ghost mode always on, cannot disable
|
||||||
|
LEVEL_TYPE="standard"
|
||||||
|
|
||||||
|
# ─── BASE STATE (always active in standard mode) ─────────────────────────────
|
||||||
|
BASE_DNS="quad9"
|
||||||
|
BASE_MAC_RANDOMIZE="1"
|
||||||
|
BASE_IPV6_DISABLE="1"
|
||||||
|
|
||||||
|
# ─── TOR BRIDGES ──────────────────────────────────────────────────────────────
|
||||||
|
# Mode: off | obfs4 | meek-azure | snowflake
|
||||||
|
TOR_BRIDGE_MODE="off"
|
||||||
|
TOR_BRIDGE_RELAY=""
|
||||||
|
|
||||||
|
# ─── SECURE DELETION ──────────────────────────────────────────────────────────
|
||||||
|
# Method: auto | shred | fstrim | luks
|
||||||
|
WIPE_METHOD="auto"
|
||||||
|
|
||||||
|
# ─── DEPLOYMENT LEVEL ─────────────────────────────────────────────────────────
|
||||||
|
# Level: bare-metal-standard | bare-metal-paranoid | cloud-normal | cloud-paranoid
|
||||||
|
DEPLOYMENT_LEVEL="bare-metal-standard"
|
||||||
|
|
||||||
|
# ─── TERMINAL BANNER ──────────────────────────────────────────────────────────
|
||||||
|
# Mode: compact | full | auto | off
|
||||||
|
OPSEC_BANNER="compact"
|
||||||
|
|
||||||
|
# ─── WIDGET THEME ────────────────────────────────────────────────────────────
|
||||||
|
# Theme: default | aurora | ember | slate | cyberpunk | frost | terminal
|
||||||
|
WIDGET_THEME="default"
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<!DOCTYPE policyconfig PUBLIC
|
||||||
|
"-//freedesktop//DTD PolicyKit Policy Configuration 1.0//EN"
|
||||||
|
"http://www.freedesktop.org/standards/PolicyKit/1/policyconfig.dtd">
|
||||||
|
<policyconfig>
|
||||||
|
<action id="com.opsec.mode.toggle">
|
||||||
|
<description>Toggle OPSEC Advanced Mode</description>
|
||||||
|
<message>Authentication is required to toggle OPSEC mode</message>
|
||||||
|
<defaults>
|
||||||
|
<allow_any>auth_admin</allow_any>
|
||||||
|
<allow_inactive>auth_admin</allow_inactive>
|
||||||
|
<allow_active>yes</allow_active>
|
||||||
|
</defaults>
|
||||||
|
<annotate key="org.freedesktop.policykit.exec.path">/usr/local/bin/opsec-mode.sh</annotate>
|
||||||
|
<annotate key="org.freedesktop.policykit.exec.allow_gui">true</annotate>
|
||||||
|
</action>
|
||||||
|
</policyconfig>
|
||||||
Executable
+14
@@ -0,0 +1,14 @@
|
|||||||
|
# OPSEC DNS Servers
|
||||||
|
# Quad9 (Security focused)
|
||||||
|
nameserver 9.9.9.9
|
||||||
|
nameserver 149.112.112.112
|
||||||
|
|
||||||
|
# Cloudflare (Privacy focused)
|
||||||
|
nameserver 1.1.1.1
|
||||||
|
nameserver 1.0.0.1
|
||||||
|
|
||||||
|
# DNS Options
|
||||||
|
options edns0 trust-ad
|
||||||
|
options timeout:1
|
||||||
|
options attempts:1
|
||||||
|
options rotate
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
# OPSEC Mode DNS — all resolution through Tor DNSPort
|
||||||
|
# Deployed by opsec-mode on, locked with chattr +i
|
||||||
|
nameserver 127.0.0.1
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
[Unit]
|
||||||
|
Description=OPSEC Boot-into-Advanced Mode Initializer
|
||||||
|
Documentation=man:opsec-mode(8)
|
||||||
|
DefaultDependencies=no
|
||||||
|
Before=network-pre.target NetworkManager.service networking.service
|
||||||
|
Before=opsec-mac-randomize.service opsec-hostname-randomize.service opsec-killswitch.service
|
||||||
|
After=local-fs.target sysinit.target
|
||||||
|
ConditionPathExists=/etc/opsec/boot-advanced.enabled
|
||||||
|
|
||||||
|
[Service]
|
||||||
|
Type=oneshot
|
||||||
|
ExecStart=/usr/local/bin/opsec-boot-init.sh
|
||||||
|
RemainAfterExit=yes
|
||||||
|
StandardOutput=journal
|
||||||
|
StandardError=journal
|
||||||
|
|
||||||
|
[Install]
|
||||||
|
WantedBy=multi-user.target
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
[Unit]
|
||||||
|
Description=OPSEC Hostname Randomization
|
||||||
|
Before=NetworkManager.service
|
||||||
|
Before=networking.service
|
||||||
|
After=local-fs.target
|
||||||
|
ConditionPathExists=/var/run/opsec-advanced.enabled
|
||||||
|
|
||||||
|
[Service]
|
||||||
|
Type=oneshot
|
||||||
|
ExecStart=/usr/local/bin/opsec-hostname-randomize.sh
|
||||||
|
RemainAfterExit=yes
|
||||||
|
|
||||||
|
[Install]
|
||||||
|
WantedBy=multi-user.target
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
[Unit]
|
||||||
|
Description=OPSEC Kill Switch (block non-Tor/VPN traffic)
|
||||||
|
Before=NetworkManager.service
|
||||||
|
Before=networking.service
|
||||||
|
After=local-fs.target
|
||||||
|
ConditionPathExists=/var/run/opsec-advanced.enabled
|
||||||
|
|
||||||
|
[Service]
|
||||||
|
Type=oneshot
|
||||||
|
ExecStart=/usr/local/bin/opsec-killswitch.sh on
|
||||||
|
ExecStop=/usr/local/bin/opsec-killswitch.sh off
|
||||||
|
RemainAfterExit=yes
|
||||||
|
|
||||||
|
[Install]
|
||||||
|
WantedBy=multi-user.target
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
[Unit]
|
||||||
|
Description=OPSEC MAC Address Randomization
|
||||||
|
Before=NetworkManager.service
|
||||||
|
Before=networking.service
|
||||||
|
After=local-fs.target
|
||||||
|
ConditionPathExists=/var/run/opsec-advanced.enabled
|
||||||
|
|
||||||
|
[Service]
|
||||||
|
Type=oneshot
|
||||||
|
ExecStart=/usr/local/bin/randomize-mac.sh
|
||||||
|
RemainAfterExit=yes
|
||||||
|
|
||||||
|
[Install]
|
||||||
|
WantedBy=multi-user.target
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
# Aurora theme — purple and cyan tones
|
||||||
|
# Deployed to /etc/opsec/themes/ — sourced by opsec_generate_conky()
|
||||||
|
THEME_LABEL="Aurora"
|
||||||
|
CONKY_BG="000000"
|
||||||
|
CONKY_COLOR0="B55AFC"
|
||||||
|
CONKY_COLOR1="FF63BE"
|
||||||
|
CONKY_COLOR2="85E7FF"
|
||||||
|
CONKY_COLOR3="000000"
|
||||||
|
CONKY_COLOR4="268BD2"
|
||||||
|
CONKY_COLOR5="07CAF9"
|
||||||
|
CONKY_COLOR6="85E7FF"
|
||||||
|
CONKY_COLOR7="ECDEF7"
|
||||||
|
CONKY_COLOR8="B55AFC"
|
||||||
|
CONKY_COLOR9="4A6A7A"
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
# CyberPunk theme — neon cyan/pink/green on black
|
||||||
|
# Deployed to /etc/opsec/themes/ — sourced by opsec_generate_conky()
|
||||||
|
THEME_LABEL="CyberPunk"
|
||||||
|
CONKY_BG="000000"
|
||||||
|
CONKY_COLOR0="FF3B3B"
|
||||||
|
CONKY_COLOR1="00F5A0"
|
||||||
|
CONKY_COLOR2="08F7FE"
|
||||||
|
CONKY_COLOR3="000000"
|
||||||
|
CONKY_COLOR4="08F7FE"
|
||||||
|
CONKY_COLOR5="FF2E88"
|
||||||
|
CONKY_COLOR6="00D1FF"
|
||||||
|
CONKY_COLOR7="EAEAFF"
|
||||||
|
CONKY_COLOR8="08F7FE"
|
||||||
|
CONKY_COLOR9="2A2A2A"
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
# Default theme — balanced green-on-black aesthetic
|
||||||
|
# Deployed to /etc/opsec/themes/ — sourced by opsec_generate_conky()
|
||||||
|
THEME_LABEL="Default"
|
||||||
|
CONKY_BG="000000"
|
||||||
|
CONKY_COLOR0="FF4C4C"
|
||||||
|
CONKY_COLOR1="00FF9C"
|
||||||
|
CONKY_COLOR2="49D6B6"
|
||||||
|
CONKY_COLOR3="000000"
|
||||||
|
CONKY_COLOR4="49D6B6"
|
||||||
|
CONKY_COLOR5="2AFFC6"
|
||||||
|
CONKY_COLOR6="2AFFC6"
|
||||||
|
CONKY_COLOR7="CFFFE6"
|
||||||
|
CONKY_COLOR8="49D6B6"
|
||||||
|
CONKY_COLOR9="1A1A1A"
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
# Ember theme — warm red and white tones
|
||||||
|
# Deployed to /etc/opsec/themes/ — sourced by opsec_generate_conky()
|
||||||
|
THEME_LABEL="Ember"
|
||||||
|
CONKY_BG="000000"
|
||||||
|
CONKY_COLOR0="FF2B2B"
|
||||||
|
CONKY_COLOR1="A0A0A0"
|
||||||
|
CONKY_COLOR2="B0B0B0"
|
||||||
|
CONKY_COLOR3="000000"
|
||||||
|
CONKY_COLOR4="FF2B2B"
|
||||||
|
CONKY_COLOR5="FF7A7A"
|
||||||
|
CONKY_COLOR6="CFCFCF"
|
||||||
|
CONKY_COLOR7="EDEDED"
|
||||||
|
CONKY_COLOR8="FF2B2B"
|
||||||
|
CONKY_COLOR9="A0A0A0"
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
# Frost theme — cool blues and teals
|
||||||
|
# Deployed to /etc/opsec/themes/ — sourced by opsec_generate_conky()
|
||||||
|
THEME_LABEL="Frost"
|
||||||
|
CONKY_BG="000000"
|
||||||
|
CONKY_COLOR0="DC2626"
|
||||||
|
CONKY_COLOR1="5EEAD4"
|
||||||
|
CONKY_COLOR2="00E5FF"
|
||||||
|
CONKY_COLOR3="000000"
|
||||||
|
CONKY_COLOR4="22D3EE"
|
||||||
|
CONKY_COLOR5="00E5FF"
|
||||||
|
CONKY_COLOR6="22D3EE"
|
||||||
|
CONKY_COLOR7="DDE6F3"
|
||||||
|
CONKY_COLOR8="00E5FF"
|
||||||
|
CONKY_COLOR9="1A1A1A"
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
# Slate theme — muted neutral tones with gold accent
|
||||||
|
# Deployed to /etc/opsec/themes/ — sourced by opsec_generate_conky()
|
||||||
|
THEME_LABEL="Slate"
|
||||||
|
CONKY_BG="000000"
|
||||||
|
CONKY_COLOR0="C63636"
|
||||||
|
CONKY_COLOR1="9A9A9A"
|
||||||
|
CONKY_COLOR2="B0B0B0"
|
||||||
|
CONKY_COLOR3="000000"
|
||||||
|
CONKY_COLOR4="7A7A7A"
|
||||||
|
CONKY_COLOR5="D1A954"
|
||||||
|
CONKY_COLOR6="AFAFAF"
|
||||||
|
CONKY_COLOR7="E6E6E6"
|
||||||
|
CONKY_COLOR8="B0B0B0"
|
||||||
|
CONKY_COLOR9="7A7A7A"
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
# Terminal theme — classic monochrome green CRT aesthetic
|
||||||
|
# Deployed to /etc/opsec/themes/ — sourced by opsec_generate_conky()
|
||||||
|
THEME_LABEL="Terminal"
|
||||||
|
CONKY_BG="0A0A0A"
|
||||||
|
CONKY_COLOR0="FF3333"
|
||||||
|
CONKY_COLOR1="33FF33"
|
||||||
|
CONKY_COLOR2="22BB22"
|
||||||
|
CONKY_COLOR3="0A0A0A"
|
||||||
|
CONKY_COLOR4="118811"
|
||||||
|
CONKY_COLOR5="44FF44"
|
||||||
|
CONKY_COLOR6="33DD33"
|
||||||
|
CONKY_COLOR7="AAFFAA"
|
||||||
|
CONKY_COLOR8="22CC22"
|
||||||
|
CONKY_COLOR9="1A3A1A"
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
# Minimal stock Tor configuration
|
||||||
|
# Restored by opsec-mode off
|
||||||
|
|
||||||
|
SocksPort 9050
|
||||||
|
Log notice file /var/log/tor/notices.log
|
||||||
@@ -0,0 +1,32 @@
|
|||||||
|
# Hardened Tor Configuration for OPSEC Mode
|
||||||
|
# Deployed by opsec-mode on — restored to torrc-default on off
|
||||||
|
|
||||||
|
# ─── SOCKS & DNS ────────────────────────────────────────────────────────────
|
||||||
|
SocksPort 9050 IsolateDestAddr IsolateDestPort
|
||||||
|
DNSPort 5353
|
||||||
|
|
||||||
|
# ─── FIVE EYES EXCLUSION ────────────────────────────────────────────────────
|
||||||
|
# Never use exit nodes in Five Eyes countries
|
||||||
|
ExcludeExitNodes {us},{gb},{ca},{au},{nz}
|
||||||
|
StrictNodes 1
|
||||||
|
|
||||||
|
# ─── CIRCUIT ROTATION ───────────────────────────────────────────────────────
|
||||||
|
# Rotate circuits every 30 seconds for maximum anonymity
|
||||||
|
MaxCircuitDirtiness 30
|
||||||
|
|
||||||
|
# ─── STREAM ISOLATION ───────────────────────────────────────────────────────
|
||||||
|
# Each destination gets its own circuit
|
||||||
|
IsolateSOCKSAuth 1
|
||||||
|
|
||||||
|
# ─── TRAFFIC PADDING ────────────────────────────────────────────────────────
|
||||||
|
# Pad cells to resist traffic analysis
|
||||||
|
ConnectionPadding 1
|
||||||
|
|
||||||
|
# ─── SAFE LOGGING ────────────────────────────────────────────────────────────
|
||||||
|
# Scrub sensitive info from logs
|
||||||
|
SafeLogging 1
|
||||||
|
Log notice file /var/log/tor/notices.log
|
||||||
|
|
||||||
|
# ─── PERFORMANCE ─────────────────────────────────────────────────────────────
|
||||||
|
NumEntryGuards 3
|
||||||
|
UseEntryGuards 1
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
# /etc/udev/rules.d/99-opsec-usb.rules — OPSEC USB Device Monitoring
|
||||||
|
# Logs all USB insertions and sends desktop notification
|
||||||
|
# Optionally blocks new devices when LEAK_USB_BLOCK=1 in /etc/opsec/opsec.conf
|
||||||
|
|
||||||
|
# Log all USB device insertions
|
||||||
|
ACTION=="add", SUBSYSTEM=="usb", ENV{DEVTYPE}=="usb_device", \
|
||||||
|
RUN+="/bin/bash -c 'echo \"[$(date +%%Y-%%m-%%d\\ %%H:%%M:%%S)] USB INSERT: vendor=$attr{idVendor} product=$attr{idProduct} serial=$attr{serial}\" >> /var/log/opsec-usb.log'"
|
||||||
|
|
||||||
|
# Desktop notification on USB insertion
|
||||||
|
ACTION=="add", SUBSYSTEM=="usb", ENV{DEVTYPE}=="usb_device", \
|
||||||
|
RUN+="/bin/bash -c 'REAL_USER=$(who | head -1 | awk \"{print \\$1}\"); [ -n \"$REAL_USER\" ] && su - $REAL_USER -c \"DISPLAY=:0 notify-send -u critical OPSEC\\ USB \\\"USB device inserted: $attr{idVendor}:$attr{idProduct}\\\"\" 2>/dev/null || true'"
|
||||||
|
|
||||||
|
# Log USB mass storage specifically (higher risk)
|
||||||
|
ACTION=="add", SUBSYSTEM=="block", ENV{ID_USB_DRIVER}=="usb-storage", \
|
||||||
|
RUN+="/bin/bash -c 'echo \"[$(date +%%Y-%%m-%%d\\ %%H:%%M:%%S)] USB STORAGE: $env{ID_VENDOR} $env{ID_MODEL} $env{ID_SERIAL}\" >> /var/log/opsec-usb.log'"
|
||||||
@@ -0,0 +1,69 @@
|
|||||||
|
# Double-Hop OpenVPN Configuration Template
|
||||||
|
# Chain two VPN servers for additional anonymity layer
|
||||||
|
# Replace %%VARIABLES%% with actual values before use
|
||||||
|
#
|
||||||
|
# Usage: Copy and fill in variables, then:
|
||||||
|
# sudo openvpn --config double-hop.ovpn
|
||||||
|
|
||||||
|
# ─── First Hop (Entry Server) ─────────────────────────────────────────────────
|
||||||
|
# This is the server your ISP sees you connecting to
|
||||||
|
|
||||||
|
client
|
||||||
|
dev tun
|
||||||
|
proto %%PROTO_1%%
|
||||||
|
remote %%SERVER_1%% %%PORT_1%%
|
||||||
|
resolv-retry infinite
|
||||||
|
nobind
|
||||||
|
persist-key
|
||||||
|
persist-tun
|
||||||
|
|
||||||
|
# Authentication
|
||||||
|
auth-user-pass %%AUTH_FILE_1%%
|
||||||
|
ca %%CA_FILE_1%%
|
||||||
|
|
||||||
|
# Security
|
||||||
|
cipher AES-256-GCM
|
||||||
|
auth SHA512
|
||||||
|
tls-cipher TLS-ECDHE-RSA-WITH-AES-256-GCM-SHA384
|
||||||
|
tls-version-min 1.2
|
||||||
|
remote-cert-tls server
|
||||||
|
|
||||||
|
# Routing — send all traffic through VPN
|
||||||
|
redirect-gateway def1
|
||||||
|
dhcp-option DNS 10.8.0.1
|
||||||
|
|
||||||
|
# Keepalive
|
||||||
|
keepalive 10 60
|
||||||
|
ping-timer-rem
|
||||||
|
|
||||||
|
# Logging
|
||||||
|
verb 3
|
||||||
|
mute 10
|
||||||
|
|
||||||
|
# ─── Second Hop Configuration ─────────────────────────────────────────────────
|
||||||
|
# After first VPN is established, launch second hop:
|
||||||
|
#
|
||||||
|
# sudo openvpn --config hop2.ovpn --route-nopull \
|
||||||
|
# --route %%SERVER_2%% 255.255.255.255 net_gateway
|
||||||
|
#
|
||||||
|
# Where hop2.ovpn contains:
|
||||||
|
# client
|
||||||
|
# dev tun1
|
||||||
|
# proto %%PROTO_2%%
|
||||||
|
# remote %%SERVER_2%% %%PORT_2%%
|
||||||
|
# auth-user-pass %%AUTH_FILE_2%%
|
||||||
|
# ca %%CA_FILE_2%%
|
||||||
|
# cipher AES-256-GCM
|
||||||
|
# redirect-gateway def1
|
||||||
|
|
||||||
|
# ─── Variables Reference ───────────────────────────────────────────────────────
|
||||||
|
# %%SERVER_1%% — First hop IP/hostname
|
||||||
|
# %%PORT_1%% — First hop port (e.g. 1194, 443)
|
||||||
|
# %%PROTO_1%% — First hop protocol (udp/tcp)
|
||||||
|
# %%AUTH_FILE_1%% — First hop credentials file
|
||||||
|
# %%CA_FILE_1%% — First hop CA certificate
|
||||||
|
# %%SERVER_2%% — Second hop IP/hostname
|
||||||
|
# %%PORT_2%% — Second hop port
|
||||||
|
# %%PROTO_2%% — Second hop protocol
|
||||||
|
# %%AUTH_FILE_2%% — Second hop credentials file
|
||||||
|
# %%CA_FILE_2%% — Second hop CA certificate
|
||||||
@@ -0,0 +1,72 @@
|
|||||||
|
# WireGuard Multi-Hop Configuration Template
|
||||||
|
# Chain WireGuard tunnels for additional anonymity
|
||||||
|
# Replace %%VARIABLES%% with actual values
|
||||||
|
#
|
||||||
|
# Architecture:
|
||||||
|
# You → wg0 (Hop 1) → wg1 (Hop 2) → Internet
|
||||||
|
#
|
||||||
|
# Usage:
|
||||||
|
# sudo cp this-file /etc/wireguard/wg0.conf (fill in variables)
|
||||||
|
# sudo wg-quick up wg0
|
||||||
|
# sudo wg-quick up wg1
|
||||||
|
|
||||||
|
# ═══════════════════════════════════════════════════════════════════════════════
|
||||||
|
# HOP 1: /etc/wireguard/wg0.conf — Entry tunnel (your ISP sees this)
|
||||||
|
# ═══════════════════════════════════════════════════════════════════════════════
|
||||||
|
|
||||||
|
[Interface]
|
||||||
|
PrivateKey = %%PRIVATE_KEY_1%%
|
||||||
|
Address = %%TUNNEL_IP_1%%/32
|
||||||
|
DNS = %%DNS_1%%
|
||||||
|
# MTU adjustment for encapsulation overhead
|
||||||
|
MTU = 1380
|
||||||
|
|
||||||
|
# Post-routing to allow hop 2 through hop 1
|
||||||
|
PostUp = ip rule add from %%TUNNEL_IP_2%% table 200; ip route add default via %%GATEWAY_1%% table 200
|
||||||
|
PostDown = ip rule del from %%TUNNEL_IP_2%% table 200; ip route del default via %%GATEWAY_1%% table 200
|
||||||
|
|
||||||
|
[Peer]
|
||||||
|
PublicKey = %%PUBLIC_KEY_1%%
|
||||||
|
PresharedKey = %%PSK_1%%
|
||||||
|
Endpoint = %%ENDPOINT_1%%:%%PORT_1%%
|
||||||
|
AllowedIPs = 0.0.0.0/0, ::/0
|
||||||
|
PersistentKeepalive = 25
|
||||||
|
|
||||||
|
# ═══════════════════════════════════════════════════════════════════════════════
|
||||||
|
# HOP 2: /etc/wireguard/wg1.conf — Exit tunnel (internet sees this)
|
||||||
|
# ═══════════════════════════════════════════════════════════════════════════════
|
||||||
|
#
|
||||||
|
# [Interface]
|
||||||
|
# PrivateKey = %%PRIVATE_KEY_2%%
|
||||||
|
# Address = %%TUNNEL_IP_2%%/32
|
||||||
|
# DNS = %%DNS_2%%
|
||||||
|
# MTU = 1340
|
||||||
|
# # Route this tunnel's traffic through wg0
|
||||||
|
# Table = off
|
||||||
|
# PostUp = ip rule add from %%TUNNEL_IP_2%% table 200
|
||||||
|
# PostDown = ip rule del from %%TUNNEL_IP_2%% table 200
|
||||||
|
#
|
||||||
|
# [Peer]
|
||||||
|
# PublicKey = %%PUBLIC_KEY_2%%
|
||||||
|
# PresharedKey = %%PSK_2%%
|
||||||
|
# Endpoint = %%ENDPOINT_2%%:%%PORT_2%%
|
||||||
|
# AllowedIPs = 0.0.0.0/0, ::/0
|
||||||
|
# PersistentKeepalive = 25
|
||||||
|
|
||||||
|
# ─── Variables Reference ───────────────────────────────────────────────────────
|
||||||
|
# %%PRIVATE_KEY_1%% — Your private key for hop 1 (wg genkey)
|
||||||
|
# %%PUBLIC_KEY_1%% — Hop 1 server's public key
|
||||||
|
# %%PSK_1%% — Preshared key for hop 1 (wg genpsk)
|
||||||
|
# %%ENDPOINT_1%% — Hop 1 server IP/hostname
|
||||||
|
# %%PORT_1%% — Hop 1 server port (default: 51820)
|
||||||
|
# %%TUNNEL_IP_1%% — Your assigned tunnel IP on hop 1
|
||||||
|
# %%GATEWAY_1%% — Hop 1 internal gateway IP
|
||||||
|
# %%DNS_1%% — DNS server for hop 1
|
||||||
|
#
|
||||||
|
# %%PRIVATE_KEY_2%% — Your private key for hop 2
|
||||||
|
# %%PUBLIC_KEY_2%% — Hop 2 server's public key
|
||||||
|
# %%PSK_2%% — Preshared key for hop 2
|
||||||
|
# %%ENDPOINT_2%% — Hop 2 server IP/hostname (reachable via hop 1)
|
||||||
|
# %%PORT_2%% — Hop 2 server port
|
||||||
|
# %%TUNNEL_IP_2%% — Your assigned tunnel IP on hop 2
|
||||||
|
# %%DNS_2%% — DNS server for hop 2
|
||||||
Executable
+146
@@ -0,0 +1,146 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
# conky-opsec-cache — Background IP/geo cache updater
|
||||||
|
# Runs in a loop, writes results to cache file that the status script reads.
|
||||||
|
# This keeps all slow network calls OUT of the conky render path.
|
||||||
|
|
||||||
|
CACHE_DIR="/tmp/.opsec-cache"
|
||||||
|
CACHE_FILE="${CACHE_DIR}/netinfo"
|
||||||
|
LOCK_FILE="${CACHE_DIR}/.updating"
|
||||||
|
INTERVAL=30 # seconds between updates
|
||||||
|
|
||||||
|
mkdir -p "$CACHE_DIR"
|
||||||
|
chmod 700 "$CACHE_DIR"
|
||||||
|
|
||||||
|
update_cache() {
|
||||||
|
# Prevent concurrent updates
|
||||||
|
[ -f "$LOCK_FILE" ] && return
|
||||||
|
touch "$LOCK_FILE"
|
||||||
|
|
||||||
|
local advanced=false
|
||||||
|
[ -f /var/run/opsec-advanced.enabled ] && advanced=true
|
||||||
|
|
||||||
|
local pub_ip="" pub_geo="" routed_tor=false exit_country=""
|
||||||
|
|
||||||
|
# In Advanced mode with SOCKS available: verify Tor via HTTPS, then get geo
|
||||||
|
if $advanced && ss -tln 2>/dev/null | grep -q ':9050 '; then
|
||||||
|
# Step 1: Verify Tor routing via HTTPS (encrypted, trusted endpoint)
|
||||||
|
local tor_json
|
||||||
|
tor_json=$(curl -4 -s --max-time 10 --socks5-hostname 127.0.0.1:9050 "https://check.torproject.org/api/ip" 2>/dev/null)
|
||||||
|
if echo "$tor_json" | grep -q '"IsTor":true'; then
|
||||||
|
pub_ip=$(echo "$tor_json" | grep -o '"IP":"[^"]*"' | cut -d'"' -f4)
|
||||||
|
if [ -n "$pub_ip" ] && echo "$pub_ip" | grep -qE '^[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+$'; then
|
||||||
|
routed_tor=true
|
||||||
|
else
|
||||||
|
pub_ip=""
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
# Step 2: Get geo data via ip-api.com (HTTP only — but over Tor SOCKS so exit encrypts)
|
||||||
|
# ip-api.com pro HTTPS requires a paid key; free tier is HTTP-only
|
||||||
|
# This is acceptable: traffic goes through Tor SOCKS tunnel, so local network can't see it
|
||||||
|
if [ -n "$pub_ip" ]; then
|
||||||
|
local geo_json
|
||||||
|
geo_json=$(curl -4 -s --max-time 10 --socks5-hostname 127.0.0.1:9050 "http://ip-api.com/json/${pub_ip}?fields=countryCode,city" 2>/dev/null)
|
||||||
|
local geo_country geo_city
|
||||||
|
geo_country=$(echo "$geo_json" | grep -o '"countryCode":"[^"]*"' | cut -d'"' -f4)
|
||||||
|
geo_city=$(echo "$geo_json" | grep -o '"city":"[^"]*"' | cut -d'"' -f4)
|
||||||
|
exit_country="$geo_country"
|
||||||
|
[ -n "$geo_country" ] && pub_geo="${geo_city:+${geo_city}, }${geo_country}"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Fallback to direct — only if NOT in advanced mode (kill switch would DROP it)
|
||||||
|
if [ -z "$pub_ip" ] && ! $advanced; then
|
||||||
|
local api_json
|
||||||
|
api_json=$(curl -4 -s --max-time 4 "https://check.torproject.org/api/ip" 2>/dev/null)
|
||||||
|
if [ -n "$api_json" ]; then
|
||||||
|
pub_ip=$(echo "$api_json" | grep -o '"IP":"[^"]*"' | cut -d'"' -f4)
|
||||||
|
fi
|
||||||
|
# Validate IP format
|
||||||
|
if [ -n "$pub_ip" ] && ! echo "$pub_ip" | grep -qE '^[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+$'; then
|
||||||
|
pub_ip=""
|
||||||
|
fi
|
||||||
|
# Get geo via HTTPS (direct mode — no Tor)
|
||||||
|
if [ -n "$pub_ip" ]; then
|
||||||
|
local geo_json
|
||||||
|
geo_json=$(curl -4 -s --max-time 4 "http://ip-api.com/json/${pub_ip}?fields=countryCode,city" 2>/dev/null)
|
||||||
|
local geo_country geo_city
|
||||||
|
geo_country=$(echo "$geo_json" | grep -o '"countryCode":"[^"]*"' | cut -d'"' -f4)
|
||||||
|
geo_city=$(echo "$geo_json" | grep -o '"city":"[^"]*"' | cut -d'"' -f4)
|
||||||
|
[ -n "$geo_country" ] && pub_geo="${geo_city:+${geo_city}, }${geo_country}"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Sanitize exit country — should be 2-3 letter country code
|
||||||
|
[ -n "$exit_country" ] && [ ${#exit_country} -gt 3 ] && exit_country=""
|
||||||
|
|
||||||
|
# Track when exit IP last changed (for circuit age display)
|
||||||
|
local exit_change_time=""
|
||||||
|
local prev_ip="" prev_change_time=""
|
||||||
|
if [ -f "$CACHE_FILE" ]; then
|
||||||
|
prev_ip=$(grep '^PUB_IP=' "$CACHE_FILE" 2>/dev/null | cut -d'"' -f2)
|
||||||
|
prev_change_time=$(grep '^EXIT_CHANGE_TIME=' "$CACHE_FILE" 2>/dev/null | cut -d'"' -f2)
|
||||||
|
fi
|
||||||
|
if [ -n "$pub_ip" ] && [ "$pub_ip" != "$prev_ip" ]; then
|
||||||
|
exit_change_time="$(date +%s)"
|
||||||
|
elif [ -n "$prev_change_time" ]; then
|
||||||
|
exit_change_time="$prev_change_time"
|
||||||
|
else
|
||||||
|
exit_change_time="$(date +%s)"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Tor bootstrap progress — only from current Tor session
|
||||||
|
local tor_bootstrap="" tor_phase=""
|
||||||
|
if $advanced && systemctl is-active tor >/dev/null 2>&1; then
|
||||||
|
# Get Tor start time, only read log lines after it
|
||||||
|
local tor_start
|
||||||
|
tor_start=$(systemctl show tor@default --property=ActiveEnterTimestamp 2>/dev/null | cut -d= -f2)
|
||||||
|
if [ -n "$tor_start" ]; then
|
||||||
|
local start_ts
|
||||||
|
start_ts=$(date -d "$tor_start" +%s 2>/dev/null || echo 0)
|
||||||
|
local boot_line=""
|
||||||
|
# Read bootstrap lines — -h suppresses filename prefix when checking both paths
|
||||||
|
while IFS= read -r line; do
|
||||||
|
local log_date
|
||||||
|
log_date=$(echo "$line" | grep -oP '^\w+ \d+ [\d:.]+')
|
||||||
|
if [ -n "$log_date" ]; then
|
||||||
|
local log_ts
|
||||||
|
log_ts=$(date -d "$log_date" +%s 2>/dev/null || echo 0)
|
||||||
|
[ "$log_ts" -ge "$start_ts" ] && boot_line="$line"
|
||||||
|
fi
|
||||||
|
done < <(grep -h "Bootstrapped" /run/tor/notices.log /var/log/tor/notices.log 2>/dev/null)
|
||||||
|
if [ -n "$boot_line" ]; then
|
||||||
|
tor_bootstrap=$(echo "$boot_line" | grep -oP '\d+(?=%)')
|
||||||
|
tor_phase=$(echo "$boot_line" | grep -oP '\(\K[^)]+' | head -1)
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Write atomically (write to tmp then move) — quote values for safe sourcing
|
||||||
|
local tmp="${CACHE_FILE}.tmp"
|
||||||
|
cat > "$tmp" <<EOF
|
||||||
|
PUB_IP="${pub_ip}"
|
||||||
|
PUB_GEO="${pub_geo}"
|
||||||
|
ROUTED_TOR="${routed_tor}"
|
||||||
|
EXIT_COUNTRY="${exit_country}"
|
||||||
|
EXIT_CHANGE_TIME="${exit_change_time}"
|
||||||
|
TOR_BOOTSTRAP="${tor_bootstrap}"
|
||||||
|
TOR_PHASE="${tor_phase}"
|
||||||
|
CACHE_TIME="$(date +%s)"
|
||||||
|
EOF
|
||||||
|
chmod 600 "$tmp"
|
||||||
|
mv -f "$tmp" "$CACHE_FILE"
|
||||||
|
rm -f "$LOCK_FILE"
|
||||||
|
}
|
||||||
|
|
||||||
|
# Initial update immediately
|
||||||
|
update_cache
|
||||||
|
|
||||||
|
# Loop forever — poll faster when Tor is bootstrapping
|
||||||
|
while true; do
|
||||||
|
if [ -f /var/run/opsec-advanced.enabled ] && systemctl is-active tor >/dev/null 2>&1 && ! ss -tln 2>/dev/null | grep -q ':9050 '; then
|
||||||
|
sleep 5 # Tor bootstrapping — fast poll
|
||||||
|
else
|
||||||
|
sleep "$INTERVAL"
|
||||||
|
fi
|
||||||
|
update_cache
|
||||||
|
done
|
||||||
Executable
+225
@@ -0,0 +1,225 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
# Conky OPSEC Status — OPSEC Status Widget
|
||||||
|
# Called by conky via execpi — outputs Conky color markup
|
||||||
|
# Network data is read from cache (updated by conky-opsec-cache.sh in background)
|
||||||
|
#
|
||||||
|
# Color map (managed by theme system):
|
||||||
|
# 0 = ALERT/BAD 1 = SECURE/GOOD 2 = INFO/NEUTRAL
|
||||||
|
# 3 = VOID/BG 4 = STRUCTURAL 5 = TITLE/ACCENT
|
||||||
|
# 6 = LABELS 7 = VALUES 8 = SECTION HDR
|
||||||
|
# 9 = METADATA
|
||||||
|
|
||||||
|
ADVANCED=false
|
||||||
|
[ -f /var/run/opsec-advanced.enabled ] && ADVANCED=true
|
||||||
|
|
||||||
|
# ─── READ CACHE (instant — no network calls) ──────────────────────────────
|
||||||
|
# Safe parsing: extract values via grep/cut instead of sourcing as shell code
|
||||||
|
CACHE_FILE="/tmp/.opsec-cache/netinfo"
|
||||||
|
PUB_IP="" PUB_GEO="" ROUTED_TOR=false EXIT_COUNTRY="" EXIT_CHANGE_TIME="" TOR_BOOTSTRAP="" TOR_PHASE="" CACHE_TIME=0
|
||||||
|
if [ -f "$CACHE_FILE" ]; then
|
||||||
|
_safe_read() { grep "^${1}=" "$CACHE_FILE" 2>/dev/null | head -1 | cut -d'"' -f2; }
|
||||||
|
PUB_IP=$(_safe_read PUB_IP)
|
||||||
|
PUB_GEO=$(_safe_read PUB_GEO)
|
||||||
|
ROUTED_TOR=$(_safe_read ROUTED_TOR)
|
||||||
|
EXIT_COUNTRY=$(_safe_read EXIT_COUNTRY)
|
||||||
|
EXIT_CHANGE_TIME=$(_safe_read EXIT_CHANGE_TIME)
|
||||||
|
TOR_BOOTSTRAP=$(_safe_read TOR_BOOTSTRAP)
|
||||||
|
TOR_PHASE=$(_safe_read TOR_PHASE)
|
||||||
|
CACHE_TIME=$(_safe_read CACHE_TIME)
|
||||||
|
# Sanitize: strip anything that isn't alphanumeric, dots, commas, spaces, or dashes
|
||||||
|
PUB_IP=$(echo "$PUB_IP" | tr -cd '0-9.')
|
||||||
|
EXIT_COUNTRY=$(echo "$EXIT_COUNTRY" | tr -cd 'A-Za-z')
|
||||||
|
EXIT_CHANGE_TIME=$(echo "$EXIT_CHANGE_TIME" | tr -cd '0-9')
|
||||||
|
TOR_BOOTSTRAP=$(echo "$TOR_BOOTSTRAP" | tr -cd '0-9')
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Kick off cache daemon if not running
|
||||||
|
if ! pgrep -f 'conky-opsec-cache\.sh' >/dev/null 2>&1; then
|
||||||
|
nohup ~/.config/conky/conky-opsec-cache.sh >/dev/null 2>&1 &
|
||||||
|
fi
|
||||||
|
|
||||||
|
# ─── HEADER ──────────────────────────────────────────────────────────────────
|
||||||
|
echo "\${color4}\${hr 1}"
|
||||||
|
if $ADVANCED; then
|
||||||
|
echo "\${color5}\${font JetBrains Mono:bold:size=11}\${alignc}OPSEC STATUS\${font}"
|
||||||
|
echo "\${color1}\${font JetBrains Mono:bold:size=8}\${alignc}▲ ADVANCED ▲\${font}"
|
||||||
|
else
|
||||||
|
echo "\${color5}\${font JetBrains Mono:bold:size=11}\${alignc}OPSEC STATUS\${font}"
|
||||||
|
echo "\${color2}\${font JetBrains Mono:size=8}\${alignc}── STANDARD ──\${font}"
|
||||||
|
fi
|
||||||
|
echo "\${color4}\${hr 1}"
|
||||||
|
|
||||||
|
# ─── SYSTEM ──────────────────────────────────────────────────────────────────
|
||||||
|
echo "\${color8}\${font JetBrains Mono:bold:size=9} ▌SYSTEM\${font}"
|
||||||
|
echo "\${color6} HOST \${color7}\${nodename}\${alignr}\${color6}UP \${color7}\${uptime_short}"
|
||||||
|
echo "\${color6} CPU \${color7}\${cpu}%\${alignr}\${color6}RAM \${color7}\${memperc}% \${color9}(\${mem}/\${memmax})"
|
||||||
|
|
||||||
|
# ─── NETWORK ─────────────────────────────────────────────────────────────────
|
||||||
|
echo "\${color4}\${hr 1}"
|
||||||
|
echo "\${color8}\${font JetBrains Mono:bold:size=9} ▌NETWORK\${font}"
|
||||||
|
|
||||||
|
LOCAL_IP=$(ip route get 1.1.1.1 2>/dev/null | awk '{for(i=1;i<=NF;i++) if($i=="src") print $(i+1)}' | head -1)
|
||||||
|
[ -z "$LOCAL_IP" ] && LOCAL_IP="No route"
|
||||||
|
echo "\${color6} LOCAL IP \${color7}${LOCAL_IP}"
|
||||||
|
|
||||||
|
# External IP from cache
|
||||||
|
_display_ip="${PUB_IP}"
|
||||||
|
[ -z "$_display_ip" ] && _display_ip="\${color0}UNAVAILABLE"
|
||||||
|
if [ -n "$PUB_GEO" ]; then
|
||||||
|
echo "\${color6} EXT IP \${color7}${_display_ip} \${color9}(${PUB_GEO})"
|
||||||
|
else
|
||||||
|
echo "\${color6} EXT IP \${color7}${_display_ip}"
|
||||||
|
fi
|
||||||
|
|
||||||
|
VPN_IF=$(ip -o link show 2>/dev/null | awk -F': ' '{print $2}' | grep -E '^(tun|wg)' | head -1)
|
||||||
|
if [ -n "$VPN_IF" ]; then
|
||||||
|
VPN_IP=$(ip -4 addr show "$VPN_IF" 2>/dev/null | awk '/inet / {print $2}' | cut -d/ -f1)
|
||||||
|
echo "\${color6} VPN \${color1}ACTIVE \${color7}${VPN_IP} \${color9}(${VPN_IF})"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# TOR/DNS/KSWCH status moved to TOR STATUS section below
|
||||||
|
|
||||||
|
PRIMARY_IF=$(ip route get 1.1.1.1 2>/dev/null | awk '{for(i=1;i<=NF;i++) if($i=="dev") print $(i+1)}' | head -1)
|
||||||
|
if [ -n "$PRIMARY_IF" ]; then
|
||||||
|
CUR_MAC=$(ip link show "$PRIMARY_IF" 2>/dev/null | awk '/ether/ {print $2}')
|
||||||
|
FIRST_OCTET=$(echo "$CUR_MAC" | cut -d: -f1)
|
||||||
|
FIRST_DEC=$((16#${FIRST_OCTET}))
|
||||||
|
if (( FIRST_DEC & 2 )); then
|
||||||
|
echo "\${color6} MAC \${color1}RANDOM \${color9}(${CUR_MAC})"
|
||||||
|
else
|
||||||
|
echo "\${color6} MAC \${color0}HWADDR \${color9}(${CUR_MAC})"
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
echo "\${color6} MAC \${color9}NO IFACE"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# ─── ADVANCED MODE EXTRAS ────────────────────────────────────────────────────
|
||||||
|
if $ADVANCED; then
|
||||||
|
echo "\${color4}\${hr 1}"
|
||||||
|
echo "\${color8}\${font JetBrains Mono:bold:size=9} ▌HARDENING\${font}"
|
||||||
|
|
||||||
|
IPV6_ALL=$(sysctl -n net.ipv6.conf.all.disable_ipv6 2>/dev/null || echo "0")
|
||||||
|
[ "$IPV6_ALL" = "1" ] && echo "\${color6} IPv6 \${color1}BLOCKED" || echo "\${color6} IPv6 \${color0}LEAKING"
|
||||||
|
|
||||||
|
if lsattr /etc/resolv.conf 2>/dev/null | grep -q 'i'; then
|
||||||
|
echo "\${color6} DNSLK \${color1}LOCKED \${color9}(immutable)"
|
||||||
|
else
|
||||||
|
echo "\${color6} DNSLK \${color0}UNLOCKED"
|
||||||
|
fi
|
||||||
|
|
||||||
|
ISO=$(grep "IsolateDestAddr" /etc/tor/torrc 2>/dev/null | head -1)
|
||||||
|
[ -n "$ISO" ] && echo "\${color6} ISOL \${color1}ACTIVE \${color9}(stream isolation)"
|
||||||
|
|
||||||
|
PAD=$(grep "ConnectionPadding" /etc/tor/torrc 2>/dev/null | awk '{print $2}')
|
||||||
|
[ "$PAD" = "1" ] && echo "\${color6} TPAD \${color1}ACTIVE \${color9}(traffic padding)"
|
||||||
|
|
||||||
|
BLACKLIST=$(grep "ExcludeExitNodes" /etc/tor/torrc 2>/dev/null | sed 's/ExcludeExitNodes //' | tr -d '{}' | tr ',' ' ')
|
||||||
|
[ -n "$BLACKLIST" ] && echo "\${color6} BLOCK \${color0}$(echo "$BLACKLIST" | tr ' ' ',' | sed 's/,$//')"
|
||||||
|
|
||||||
|
CORE_PAT=$(sysctl -n kernel.core_pattern 2>/dev/null)
|
||||||
|
[[ "$CORE_PAT" == *"/bin/false"* ]] && echo "\${color6} CORE \${color1}BLOCKED" || echo "\${color6} CORE \${color0}ENABLED"
|
||||||
|
|
||||||
|
SWAP_ACTIVE=$(swapon --show=SIZE --noheadings 2>/dev/null | head -1)
|
||||||
|
[ -z "$SWAP_ACTIVE" ] && echo "\${color6} SWAP \${color1}OFF" || echo "\${color6} SWAP \${color0}ACTIVE \${color9}(${SWAP_ACTIVE})"
|
||||||
|
|
||||||
|
BOOT_COUNT=0
|
||||||
|
for svc in opsec-boot-advanced opsec-mac-randomize opsec-hostname-randomize opsec-killswitch; do
|
||||||
|
systemctl is-enabled "$svc" 2>/dev/null | grep -q enabled && BOOT_COUNT=$((BOOT_COUNT + 1))
|
||||||
|
done
|
||||||
|
if [ "$BOOT_COUNT" -eq 4 ]; then
|
||||||
|
echo "\${color6} BOOT \${color1}PERSIST \${color9}(${BOOT_COUNT}/4)"
|
||||||
|
elif [ "$BOOT_COUNT" -gt 0 ]; then
|
||||||
|
echo "\${color6} BOOT \${color2}PARTIAL \${color9}(${BOOT_COUNT}/4)"
|
||||||
|
else
|
||||||
|
echo "\${color6} BOOT \${color0}NONE \${color9}(${BOOT_COUNT}/4)"
|
||||||
|
fi
|
||||||
|
|
||||||
|
[ -f /etc/opsec/opsec.conf ] && {
|
||||||
|
PROFILE=$(grep "^PROFILE_NAME=" /etc/opsec/opsec.conf 2>/dev/null | cut -d'"' -f2)
|
||||||
|
[ -n "$PROFILE" ] && echo "\${color6} PROF \${color2}${PROFILE}"
|
||||||
|
}
|
||||||
|
fi
|
||||||
|
|
||||||
|
# ─── TOR STATUS (replaces Route Chain) ──────────────────────────────────────
|
||||||
|
if $ADVANCED; then
|
||||||
|
echo "\${color4}\${hr 1}"
|
||||||
|
echo "\${color8}\${font JetBrains Mono:bold:size=9} ▌TOR STATUS\${font}"
|
||||||
|
|
||||||
|
# Determine protection state
|
||||||
|
_tor_svc=false; _tor_socks=false; _ks_armed=false; _tor_routed=false
|
||||||
|
systemctl is-active tor >/dev/null 2>&1 && _tor_svc=true
|
||||||
|
ss -tln 2>/dev/null | grep -q ':9050 ' && _tor_socks=true
|
||||||
|
# iptables -L requires root; use state file instead (kill switch is always armed when advanced is on)
|
||||||
|
$ADVANCED && _ks_armed=true
|
||||||
|
[ "$ROUTED_TOR" = "true" ] && _tor_routed=true
|
||||||
|
|
||||||
|
if $_tor_svc && $_tor_socks && $_ks_armed && $_tor_routed; then
|
||||||
|
echo "\${color6} STATE \${color1}PROTECTED"
|
||||||
|
elif $_tor_svc && ! $_tor_socks; then
|
||||||
|
# Bootstrapping
|
||||||
|
echo "\${color6} STATE \${color2}BOOTSTRAP \${color9}(${TOR_BOOTSTRAP:-0}% ${TOR_PHASE:-connecting})"
|
||||||
|
elif ! $_tor_svc; then
|
||||||
|
echo "\${color6} STATE \${color0}EXPOSED \${color9}(tor down)"
|
||||||
|
elif ! $_ks_armed; then
|
||||||
|
echo "\${color6} STATE \${color0}EXPOSED \${color9}(kill switch off)"
|
||||||
|
else
|
||||||
|
echo "\${color6} STATE \${color0}EXPOSED \${color9}(not routed)"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Exit country code only — no IP
|
||||||
|
if [ -n "$EXIT_COUNTRY" ] && [ ${#EXIT_COUNTRY} -le 3 ]; then
|
||||||
|
echo "\${color6} EXIT \${color2}${EXIT_COUNTRY}"
|
||||||
|
elif $_tor_socks; then
|
||||||
|
echo "\${color6} EXIT \${color9}resolving..."
|
||||||
|
else
|
||||||
|
echo "\${color6} EXIT \${color9}—"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Circuit age — time since exit IP last changed
|
||||||
|
if [ -n "$EXIT_CHANGE_TIME" ] && [ "$EXIT_CHANGE_TIME" -gt 0 ] 2>/dev/null; then
|
||||||
|
_now=$(date +%s)
|
||||||
|
_age=$(( _now - EXIT_CHANGE_TIME ))
|
||||||
|
if [ "$_age" -lt 60 ]; then
|
||||||
|
echo "\${color6} CIRCUIT \${color2}${_age}s ago"
|
||||||
|
elif [ "$_age" -lt 3600 ]; then
|
||||||
|
echo "\${color6} CIRCUIT \${color2}$(( _age / 60 ))m ago"
|
||||||
|
else
|
||||||
|
echo "\${color6} CIRCUIT \${color2}$(( _age / 3600 ))h $(( (_age % 3600) / 60 ))m ago"
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
echo "\${color6} CIRCUIT \${color9}—"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# DNS leak check — local checks only
|
||||||
|
_dns_server=$(awk '/^nameserver/ {print $2; exit}' /etc/resolv.conf 2>/dev/null)
|
||||||
|
_dns_immutable=false
|
||||||
|
lsattr /etc/resolv.conf 2>/dev/null | grep -q 'i' && _dns_immutable=true
|
||||||
|
_nm_locked=false
|
||||||
|
[ -f /etc/NetworkManager/conf.d/opsec-dns-lock.conf ] && _nm_locked=true
|
||||||
|
|
||||||
|
if [ "$_dns_server" = "127.0.0.1" ] && $_dns_immutable && $_nm_locked; then
|
||||||
|
echo "\${color6} DNS \${color1}SECURE \${color9}(tor + locked)"
|
||||||
|
elif [ "$_dns_server" = "127.0.0.1" ] && $_dns_immutable; then
|
||||||
|
echo "\${color6} DNS \${color2}SECURE \${color9}(tor, NM unlocked)"
|
||||||
|
elif [ "$_dns_server" = "127.0.0.1" ]; then
|
||||||
|
echo "\${color6} DNS \${color2}PARTIAL \${color9}(tor, not locked)"
|
||||||
|
elif echo "$_dns_server" | grep -qE '^(9\.9\.9\.9|149\.112\.112\.112|1\.1\.1\.1|1\.0\.0\.1)$'; then
|
||||||
|
echo "\${color6} DNS \${color2}PRIVACY \${color9}(${_dns_server})"
|
||||||
|
else
|
||||||
|
echo "\${color6} DNS \${color0}LEAKED \${color9}(${_dns_server})"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
# ─── MODE / LEVEL ───────────────────────────────────────────────────────────
|
||||||
|
echo "\${color4}\${hr 1}"
|
||||||
|
LEVEL=""
|
||||||
|
[ -f /etc/opsec/opsec.conf ] && LEVEL=$(grep "^DEPLOYMENT_LEVEL=" /etc/opsec/opsec.conf 2>/dev/null | cut -d'"' -f2)
|
||||||
|
if $ADVANCED; then
|
||||||
|
echo "\${color6} MODE \${color1}ADVANCED"
|
||||||
|
else
|
||||||
|
echo "\${color6} MODE \${color0}STANDARD"
|
||||||
|
fi
|
||||||
|
[ -n "$LEVEL" ] && echo "\${color6} LEVEL \${color7}${LEVEL}"
|
||||||
|
|
||||||
|
echo "\${color4}\${hr 1}"
|
||||||
|
echo "\${color9}\${font JetBrains Mono:size=7}\${alignc}// updated \${time %H:%M:%S} //\${font}"
|
||||||
@@ -0,0 +1,71 @@
|
|||||||
|
-- OPSEC Status Widget
|
||||||
|
-- Colors managed by theme system via opsec-config.sh (--theme apply NAME)
|
||||||
|
-- Uses execpi to parse Conky color markup from helper script
|
||||||
|
|
||||||
|
conky.config = {
|
||||||
|
-- Window settings
|
||||||
|
-- Default: upper-right on primary display
|
||||||
|
alignment = 'top_right',
|
||||||
|
gap_x = 15,
|
||||||
|
gap_y = 60,
|
||||||
|
minimum_width = 400,
|
||||||
|
minimum_height = 200,
|
||||||
|
maximum_width = 420,
|
||||||
|
|
||||||
|
-- Multi-monitor: pin to primary display (head 0)
|
||||||
|
-- Set xinerama_head to a different number to move to another monitor
|
||||||
|
xinerama_head = 0,
|
||||||
|
|
||||||
|
-- Window type
|
||||||
|
own_window = true,
|
||||||
|
own_window_type = 'desktop',
|
||||||
|
own_window_transparent = false,
|
||||||
|
own_window_argb_visual = true,
|
||||||
|
own_window_argb_value = 210,
|
||||||
|
own_window_colour = '0d1117',
|
||||||
|
own_window_hints = 'undecorated,below,sticky,skip_taskbar,skip_pager',
|
||||||
|
|
||||||
|
-- Drawing
|
||||||
|
double_buffer = true,
|
||||||
|
draw_shades = true,
|
||||||
|
default_shade_color = '000000',
|
||||||
|
draw_outline = false,
|
||||||
|
draw_borders = true,
|
||||||
|
border_inner_margin = 12,
|
||||||
|
border_outer_margin = 4,
|
||||||
|
border_width = 1,
|
||||||
|
border_colour = '1b3a5c',
|
||||||
|
stippled_borders = 0,
|
||||||
|
|
||||||
|
-- Font
|
||||||
|
use_xft = true,
|
||||||
|
font = 'JetBrains Mono:size=10',
|
||||||
|
override_utf8_locale = true,
|
||||||
|
|
||||||
|
-- Colors — OPSEC Status Widget
|
||||||
|
default_color = 'b0b0b0',
|
||||||
|
color0 = 'df2020', -- ares red (NOT SECURE)
|
||||||
|
color1 = '33ff33', -- terminal green (SECURE)
|
||||||
|
color2 = '3a8fd6', -- steel blue (info/neutral)
|
||||||
|
color3 = '0d1117', -- void (dividers)
|
||||||
|
color4 = '1f6feb', -- blue (structural lines)
|
||||||
|
color5 = '58a6ff', -- bright blue (title/accent)
|
||||||
|
color6 = '79c0ff', -- light blue (labels)
|
||||||
|
color7 = 'c9d1d9', -- silver (values)
|
||||||
|
color8 = '1f6feb', -- blue (section headers)
|
||||||
|
color9 = '484f58', -- dark grey (metadata)
|
||||||
|
|
||||||
|
-- Update interval
|
||||||
|
update_interval = 3,
|
||||||
|
total_run_times = 0,
|
||||||
|
|
||||||
|
-- Misc
|
||||||
|
cpu_avg_samples = 2,
|
||||||
|
no_buffers = true,
|
||||||
|
text_buffer_size = 8192,
|
||||||
|
short_units = true,
|
||||||
|
};
|
||||||
|
|
||||||
|
conky.text = [[
|
||||||
|
${execpi 5 ~/.config/conky/conky-opsec-status.sh}
|
||||||
|
]];
|
||||||
Executable
+127
@@ -0,0 +1,127 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
# OPSEC Widget Launcher — positions and launches the Conky widget
|
||||||
|
# Usage: opsec-widget-launch.sh [position]
|
||||||
|
#
|
||||||
|
# Positions:
|
||||||
|
# tl = top-left tc = top-center tr = top-right
|
||||||
|
# bl = bottom-left bc = bottom-center br = bottom-right
|
||||||
|
#
|
||||||
|
# Default: tr (top-right)
|
||||||
|
# Uses the theme system — regenerates config from /etc/opsec/themes/
|
||||||
|
|
||||||
|
CONKY_DIR="$HOME/.config/conky"
|
||||||
|
CONF="$CONKY_DIR/conky-opsec-widget.conf"
|
||||||
|
POS="${1:-tr}"
|
||||||
|
|
||||||
|
# Map shorthand to conky alignment + gaps
|
||||||
|
case "$POS" in
|
||||||
|
tl) ALIGN="top_left"; GAP_X=15; GAP_Y=15 ;;
|
||||||
|
tc) ALIGN="top_middle"; GAP_X=0; GAP_Y=15 ;;
|
||||||
|
tr) ALIGN="top_right"; GAP_X=15; GAP_Y=60 ;;
|
||||||
|
bl) ALIGN="bottom_left"; GAP_X=15; GAP_Y=60 ;;
|
||||||
|
bc) ALIGN="bottom_middle"; GAP_X=0; GAP_Y=60 ;;
|
||||||
|
br) ALIGN="bottom_right"; GAP_X=15; GAP_Y=60 ;;
|
||||||
|
*)
|
||||||
|
echo "Usage: $(basename "$0") [tl|tc|tr|bl|bc|br]"
|
||||||
|
echo ""
|
||||||
|
echo " tl = top-left tc = top-center tr = top-right"
|
||||||
|
echo " bl = bottom-left bc = bottom-center br = bottom-right"
|
||||||
|
exit 1
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
# Kill existing widget and stale cache daemon
|
||||||
|
killall conky 2>/dev/null
|
||||||
|
pkill -f 'conky-opsec-cache\.sh' 2>/dev/null
|
||||||
|
rm -f /tmp/.opsec-cache/netinfo 2>/dev/null
|
||||||
|
sleep 0.3
|
||||||
|
|
||||||
|
# Load theme from opsec config
|
||||||
|
OPSEC_CONF="/etc/opsec/opsec.conf"
|
||||||
|
THEME="default"
|
||||||
|
[ -f "$OPSEC_CONF" ] && THEME=$(grep "^WIDGET_THEME=" "$OPSEC_CONF" 2>/dev/null | cut -d'"' -f2)
|
||||||
|
[ -z "$THEME" ] && THEME="default"
|
||||||
|
|
||||||
|
THEME_FILE="/etc/opsec/themes/${THEME}.theme"
|
||||||
|
if [ -f "$THEME_FILE" ]; then
|
||||||
|
. "$THEME_FILE"
|
||||||
|
else
|
||||||
|
echo "Theme '${THEME}' not found, using defaults"
|
||||||
|
THEME_LABEL="Default"
|
||||||
|
CONKY_BG="0d1117"
|
||||||
|
CONKY_COLOR0="df2020"; CONKY_COLOR1="33ff33"; CONKY_COLOR2="3a8fd6"
|
||||||
|
CONKY_COLOR3="0d1117"; CONKY_COLOR4="1f6feb"; CONKY_COLOR5="58a6ff"
|
||||||
|
CONKY_COLOR6="79c0ff"; CONKY_COLOR7="c9d1d9"; CONKY_COLOR8="1f6feb"
|
||||||
|
CONKY_COLOR9="484f58"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Generate config with theme colors and chosen position
|
||||||
|
mkdir -p "$CONKY_DIR"
|
||||||
|
cat > "$CONF" << EOF
|
||||||
|
-- OPSEC Status Widget — OPSEC Status Widget
|
||||||
|
-- Theme: ${THEME} (${THEME_LABEL:-Custom})
|
||||||
|
-- Position: ${ALIGN}
|
||||||
|
|
||||||
|
conky.config = {
|
||||||
|
alignment = '${ALIGN}',
|
||||||
|
gap_x = ${GAP_X},
|
||||||
|
gap_y = ${GAP_Y},
|
||||||
|
minimum_width = 400,
|
||||||
|
minimum_height = 200,
|
||||||
|
maximum_width = 420,
|
||||||
|
|
||||||
|
own_window = true,
|
||||||
|
own_window_type = 'normal',
|
||||||
|
own_window_transparent = false,
|
||||||
|
own_window_argb_visual = true,
|
||||||
|
own_window_argb_value = 210,
|
||||||
|
own_window_colour = '${CONKY_BG:-0d1117}',
|
||||||
|
own_window_hints = 'undecorated,below,sticky,skip_taskbar,skip_pager',
|
||||||
|
|
||||||
|
xinerama_head = 0,
|
||||||
|
|
||||||
|
double_buffer = true,
|
||||||
|
draw_shades = true,
|
||||||
|
default_shade_color = '000000',
|
||||||
|
draw_outline = false,
|
||||||
|
draw_borders = true,
|
||||||
|
border_inner_margin = 12,
|
||||||
|
border_outer_margin = 4,
|
||||||
|
border_width = 1,
|
||||||
|
border_colour = '${CONKY_COLOR4:-1f6feb}',
|
||||||
|
stippled_borders = 0,
|
||||||
|
|
||||||
|
use_xft = true,
|
||||||
|
font = 'JetBrains Mono:size=10',
|
||||||
|
override_utf8_locale = true,
|
||||||
|
|
||||||
|
default_color = 'b0b0b0',
|
||||||
|
color0 = '${CONKY_COLOR0:-df2020}',
|
||||||
|
color1 = '${CONKY_COLOR1:-33ff33}',
|
||||||
|
color2 = '${CONKY_COLOR2:-3a8fd6}',
|
||||||
|
color3 = '${CONKY_COLOR3:-0d1117}',
|
||||||
|
color4 = '${CONKY_COLOR4:-1f6feb}',
|
||||||
|
color5 = '${CONKY_COLOR5:-58a6ff}',
|
||||||
|
color6 = '${CONKY_COLOR6:-79c0ff}',
|
||||||
|
color7 = '${CONKY_COLOR7:-c9d1d9}',
|
||||||
|
color8 = '${CONKY_COLOR8:-1f6feb}',
|
||||||
|
color9 = '${CONKY_COLOR9:-484f58}',
|
||||||
|
|
||||||
|
update_interval = 3,
|
||||||
|
total_run_times = 0,
|
||||||
|
|
||||||
|
cpu_avg_samples = 2,
|
||||||
|
no_buffers = true,
|
||||||
|
text_buffer_size = 8192,
|
||||||
|
short_units = true,
|
||||||
|
};
|
||||||
|
|
||||||
|
conky.text = [[
|
||||||
|
\${execpi 5 ~/.config/conky/conky-opsec-status.sh}
|
||||||
|
]];
|
||||||
|
EOF
|
||||||
|
|
||||||
|
# Launch
|
||||||
|
conky -c "$CONF" &
|
||||||
|
disown
|
||||||
|
echo "OPSEC widget launched: $ALIGN (theme: $THEME)"
|
||||||
Executable
+39
@@ -0,0 +1,39 @@
|
|||||||
|
# OPSEC Checklist
|
||||||
|
|
||||||
|
## Pre-Session Setup
|
||||||
|
- [ ] Fresh VM snapshot taken
|
||||||
|
- [ ] MAC address randomized: `randomize-mac`
|
||||||
|
- [ ] System timezone set appropriately
|
||||||
|
- [ ] DNS configured for privacy
|
||||||
|
- [ ] Verify no personal accounts logged in
|
||||||
|
- [ ] Disable WiFi/Bluetooth if not needed
|
||||||
|
- [ ] Configure VPN/proxy settings
|
||||||
|
- [ ] Test kill switches
|
||||||
|
|
||||||
|
## During Session
|
||||||
|
- [ ] Use workspace isolation (separate VM/container)
|
||||||
|
- [ ] All traffic through VPN/Tor
|
||||||
|
- [ ] Monitor connections: `opsec-monitor`
|
||||||
|
- [ ] Use encrypted communications
|
||||||
|
- [ ] No personal browsing/email
|
||||||
|
- [ ] Regular history clearing
|
||||||
|
- [ ] Avoid saving sensitive data locally
|
||||||
|
- [ ] Use in-memory tools when possible
|
||||||
|
|
||||||
|
## Post-Session
|
||||||
|
- [ ] Export necessary data
|
||||||
|
- [ ] Clear all logs: `clear-logs`
|
||||||
|
- [ ] Clear shell history: `clear-history`
|
||||||
|
- [ ] Wipe free space
|
||||||
|
- [ ] Secure delete files: `shred-file <files>`
|
||||||
|
- [ ] Revert to clean VM snapshot
|
||||||
|
|
||||||
|
## Emergency Procedures
|
||||||
|
- [ ] Kill network connections
|
||||||
|
- [ ] Emergency wipe if necessary
|
||||||
|
|
||||||
|
## Communication OPSEC
|
||||||
|
- [ ] Use encrypted channels
|
||||||
|
- [ ] Avoid real names/identifiers
|
||||||
|
- [ ] Use dedicated accounts
|
||||||
|
- [ ] Regular key rotation
|
||||||
Executable
+289
@@ -0,0 +1,289 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
# opsec-toolkit installer — standalone, no Ansible required
|
||||||
|
# Usage: sudo ./install.sh [--uninstall]
|
||||||
|
#
|
||||||
|
# Installs the OPSEC privacy/security toolkit on Debian/Kali/Ubuntu systems.
|
||||||
|
# Requires: tor, macchanger, iptables, curl, jq (auto-installed if missing)
|
||||||
|
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
# ─── COLORS ──────────────────────────────────────────────────────────────────
|
||||||
|
RED=$'\e[38;5;196m'
|
||||||
|
GRN=$'\e[38;5;49m'
|
||||||
|
YEL=$'\e[38;5;214m'
|
||||||
|
CYN=$'\e[38;5;45m'
|
||||||
|
RST=$'\e[0m'
|
||||||
|
|
||||||
|
ok() { echo "${GRN}[+]${RST} $*"; }
|
||||||
|
warn() { echo "${YEL}[*]${RST} $*"; }
|
||||||
|
err() { echo "${RED}[-]${RST} $*"; }
|
||||||
|
info() { echo "${CYN}[~]${RST} $*"; }
|
||||||
|
|
||||||
|
# ─── PREFLIGHT ───────────────────────────────────────────────────────────────
|
||||||
|
if [ "$EUID" -ne 0 ]; then
|
||||||
|
err "Please run as root: sudo ./install.sh"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
||||||
|
REAL_USER="${SUDO_USER:-$USER}"
|
||||||
|
REAL_HOME=$(eval echo "~${REAL_USER}")
|
||||||
|
|
||||||
|
# ─── UNINSTALL ───────────────────────────────────────────────────────────────
|
||||||
|
if [ "${1:-}" = "--uninstall" ]; then
|
||||||
|
echo ""
|
||||||
|
warn "Uninstalling OPSEC toolkit..."
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
# Stop services
|
||||||
|
for svc in opsec-boot-advanced opsec-killswitch opsec-hostname-randomize opsec-mac-randomize; do
|
||||||
|
systemctl stop "$svc" 2>/dev/null || true
|
||||||
|
systemctl disable "$svc" 2>/dev/null || true
|
||||||
|
done
|
||||||
|
|
||||||
|
# Remove scripts
|
||||||
|
rm -f /usr/local/bin/opsec-*.sh
|
||||||
|
|
||||||
|
# Remove library
|
||||||
|
rm -rf /usr/local/lib/opsec
|
||||||
|
|
||||||
|
# Remove configs (preserve opsec.conf as backup)
|
||||||
|
if [ -d /etc/opsec ]; then
|
||||||
|
cp /etc/opsec/opsec.conf "/tmp/opsec.conf.backup.$(date +%s)" 2>/dev/null || true
|
||||||
|
rm -rf /etc/opsec
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Remove systemd services
|
||||||
|
rm -f /etc/systemd/system/opsec-*.service
|
||||||
|
systemctl daemon-reload
|
||||||
|
|
||||||
|
# Remove cron jobs
|
||||||
|
rm -f /etc/cron.d/opsec-*
|
||||||
|
|
||||||
|
# Remove polkit, desktop, udev
|
||||||
|
rm -f /usr/share/polkit-1/actions/com.opsec.mode.policy
|
||||||
|
rm -f /usr/share/applications/opsec-toggle.desktop
|
||||||
|
rm -f /etc/udev/rules.d/99-opsec-usb.rules
|
||||||
|
|
||||||
|
ok "OPSEC toolkit uninstalled"
|
||||||
|
info "Config backup saved to /tmp/opsec.conf.backup.*"
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
# ─── BANNER ──────────────────────────────────────────────────────────────────
|
||||||
|
echo ""
|
||||||
|
echo "${CYN}╔══════════════════════════════════════════╗${RST}"
|
||||||
|
echo "${CYN}║${RST} ${RED}OPSEC TOOLKIT${RST} — Installer v1.0 ${CYN}║${RST}"
|
||||||
|
echo "${CYN}║${RST} Privacy & Security Hardening ${CYN}║${RST}"
|
||||||
|
echo "${CYN}╚══════════════════════════════════════════╝${RST}"
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
# ─── DEPENDENCY CHECK ────────────────────────────────────────────────────────
|
||||||
|
info "Checking dependencies..."
|
||||||
|
|
||||||
|
DEPS=(tor macchanger iptables ip6tables curl jq iproute2 net-tools procps)
|
||||||
|
MISSING=()
|
||||||
|
|
||||||
|
for dep in "${DEPS[@]}"; do
|
||||||
|
case "$dep" in
|
||||||
|
tor) command -v tor >/dev/null 2>&1 || MISSING+=("tor") ;;
|
||||||
|
macchanger) command -v macchanger >/dev/null 2>&1 || MISSING+=("macchanger") ;;
|
||||||
|
iptables) command -v iptables >/dev/null 2>&1 || MISSING+=("iptables") ;;
|
||||||
|
ip6tables) command -v ip6tables >/dev/null 2>&1 || MISSING+=("iptables") ;;
|
||||||
|
curl) command -v curl >/dev/null 2>&1 || MISSING+=("curl") ;;
|
||||||
|
jq) command -v jq >/dev/null 2>&1 || MISSING+=("jq") ;;
|
||||||
|
iproute2) command -v ip >/dev/null 2>&1 || MISSING+=("iproute2") ;;
|
||||||
|
net-tools) command -v netstat >/dev/null 2>&1 || MISSING+=("net-tools") ;;
|
||||||
|
procps) command -v ps >/dev/null 2>&1 || MISSING+=("procps") ;;
|
||||||
|
esac
|
||||||
|
done
|
||||||
|
|
||||||
|
# Deduplicate
|
||||||
|
MISSING=($(printf '%s\n' "${MISSING[@]}" | sort -u))
|
||||||
|
|
||||||
|
if [ ${#MISSING[@]} -gt 0 ]; then
|
||||||
|
warn "Installing missing packages: ${MISSING[*]}"
|
||||||
|
apt-get update -qq
|
||||||
|
apt-get install -y -qq "${MISSING[@]}"
|
||||||
|
ok "Dependencies installed"
|
||||||
|
else
|
||||||
|
ok "All dependencies present"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Optional: conky for desktop widget
|
||||||
|
if ! command -v conky >/dev/null 2>&1; then
|
||||||
|
warn "Conky not installed — desktop widget will not be available"
|
||||||
|
warn "Install later with: apt install conky-all"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# ─── INSTALL LIBRARY ─────────────────────────────────────────────────────────
|
||||||
|
info "Installing OPSEC library..."
|
||||||
|
mkdir -p /usr/local/lib/opsec
|
||||||
|
cp "$SCRIPT_DIR/lib/opsec-lib.sh" /usr/local/lib/opsec/
|
||||||
|
chmod 644 /usr/local/lib/opsec/opsec-lib.sh
|
||||||
|
ok "Library installed → /usr/local/lib/opsec/"
|
||||||
|
|
||||||
|
# ─── INSTALL SCRIPTS ─────────────────────────────────────────────────────────
|
||||||
|
info "Installing OPSEC scripts..."
|
||||||
|
for script in "$SCRIPT_DIR"/scripts/opsec-*.sh; do
|
||||||
|
[ -f "$script" ] || continue
|
||||||
|
cp "$script" /usr/local/bin/
|
||||||
|
chmod 755 "/usr/local/bin/$(basename "$script")"
|
||||||
|
done
|
||||||
|
ok "Scripts installed → /usr/local/bin/"
|
||||||
|
|
||||||
|
# ─── INSTALL CONFIGS ─────────────────────────────────────────────────────────
|
||||||
|
info "Installing configuration..."
|
||||||
|
|
||||||
|
# Main config directory
|
||||||
|
mkdir -p /etc/opsec/{themes,levels,vpn-templates,.harden-backup}
|
||||||
|
|
||||||
|
# Main config (don't overwrite existing)
|
||||||
|
if [ -f /etc/opsec/opsec.conf ]; then
|
||||||
|
warn "Existing opsec.conf found — preserving (new config saved as opsec.conf.new)"
|
||||||
|
cp "$SCRIPT_DIR/configs/opsec.conf" /etc/opsec/opsec.conf.new
|
||||||
|
else
|
||||||
|
cp "$SCRIPT_DIR/configs/opsec.conf" /etc/opsec/opsec.conf
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Country codes
|
||||||
|
cp "$SCRIPT_DIR/configs/opsec-country-codes.conf" /etc/opsec/country-codes.conf
|
||||||
|
|
||||||
|
# Themes
|
||||||
|
cp "$SCRIPT_DIR/configs/themes/"*.theme /etc/opsec/themes/
|
||||||
|
|
||||||
|
# Levels
|
||||||
|
for f in "$SCRIPT_DIR/configs/levels/"*.conf; do
|
||||||
|
[ -f "$f" ] || continue
|
||||||
|
# Map filenames: bare-metal-standard.conf → bare-metal.conf etc.
|
||||||
|
cp "$f" /etc/opsec/levels/
|
||||||
|
done
|
||||||
|
|
||||||
|
# VPN templates
|
||||||
|
cp "$SCRIPT_DIR/configs/vpn-templates/"*.template /etc/opsec/vpn-templates/
|
||||||
|
|
||||||
|
# Torrc templates
|
||||||
|
mkdir -p /etc/opsec/torrc
|
||||||
|
cp "$SCRIPT_DIR/configs/torrc/torrc-default" /etc/opsec/torrc/
|
||||||
|
cp "$SCRIPT_DIR/configs/torrc/torrc-opsec" /etc/opsec/torrc/
|
||||||
|
|
||||||
|
# DNS configs
|
||||||
|
[ -f "$SCRIPT_DIR/configs/resolv.conf.opsec" ] && cp "$SCRIPT_DIR/configs/resolv.conf.opsec" /etc/opsec/
|
||||||
|
[ -f "$SCRIPT_DIR/configs/resolv.conf.head" ] && cp "$SCRIPT_DIR/configs/resolv.conf.head" /etc/opsec/
|
||||||
|
|
||||||
|
ok "Configuration installed → /etc/opsec/"
|
||||||
|
|
||||||
|
# ─── INSTALL SYSTEMD SERVICES ────────────────────────────────────────────────
|
||||||
|
info "Installing systemd services..."
|
||||||
|
for svc in "$SCRIPT_DIR/configs/systemd/"*.service; do
|
||||||
|
[ -f "$svc" ] || continue
|
||||||
|
cp "$svc" /etc/systemd/system/
|
||||||
|
done
|
||||||
|
systemctl daemon-reload
|
||||||
|
|
||||||
|
# Enable MAC randomize and hostname randomize at boot
|
||||||
|
systemctl enable opsec-mac-randomize.service 2>/dev/null || true
|
||||||
|
systemctl enable opsec-hostname-randomize.service 2>/dev/null || true
|
||||||
|
ok "Systemd services installed and enabled"
|
||||||
|
|
||||||
|
# ─── INSTALL CRON JOBS ──────────────────────────────────────────────────────
|
||||||
|
info "Installing cron jobs..."
|
||||||
|
cp "$SCRIPT_DIR/configs/cron/opsec-banner-cache" /etc/cron.d/
|
||||||
|
cp "$SCRIPT_DIR/configs/cron/opsec-log-rotate" /etc/cron.d/
|
||||||
|
chmod 644 /etc/cron.d/opsec-*
|
||||||
|
ok "Cron jobs installed → /etc/cron.d/"
|
||||||
|
|
||||||
|
# ─── INSTALL POLKIT POLICY ──────────────────────────────────────────────────
|
||||||
|
info "Installing polkit policy..."
|
||||||
|
mkdir -p /usr/share/polkit-1/actions
|
||||||
|
cp "$SCRIPT_DIR/configs/polkit/com.opsec.mode.policy" /usr/share/polkit-1/actions/
|
||||||
|
ok "Polkit policy installed"
|
||||||
|
|
||||||
|
# ─── INSTALL DESKTOP ENTRY ──────────────────────────────────────────────────
|
||||||
|
info "Installing desktop entry..."
|
||||||
|
cp "$SCRIPT_DIR/configs/desktop/opsec-toggle.desktop" /usr/share/applications/
|
||||||
|
ok "Desktop entry installed"
|
||||||
|
|
||||||
|
# ─── INSTALL UDEV RULES ─────────────────────────────────────────────────────
|
||||||
|
info "Installing udev rules..."
|
||||||
|
cp "$SCRIPT_DIR/configs/udev/99-opsec-usb.rules" /etc/udev/rules.d/
|
||||||
|
udevadm control --reload-rules 2>/dev/null || true
|
||||||
|
ok "Udev rules installed"
|
||||||
|
|
||||||
|
# ─── INSTALL CONKY WIDGET (user-space) ──────────────────────────────────────
|
||||||
|
info "Installing Conky widget files..."
|
||||||
|
CONKY_DIR="${REAL_HOME}/.config/conky"
|
||||||
|
mkdir -p "$CONKY_DIR"
|
||||||
|
for f in "$SCRIPT_DIR"/conky/*; do
|
||||||
|
[ -f "$f" ] || continue
|
||||||
|
cp "$f" "$CONKY_DIR/"
|
||||||
|
chown "${REAL_USER}:${REAL_USER}" "$CONKY_DIR/$(basename "$f")"
|
||||||
|
done
|
||||||
|
chmod +x "$CONKY_DIR"/*.sh 2>/dev/null || true
|
||||||
|
ok "Conky widget installed → ${CONKY_DIR}/"
|
||||||
|
|
||||||
|
# ─── INSTALL SHELL ALIASES ──────────────────────────────────────────────────
|
||||||
|
info "Installing shell aliases..."
|
||||||
|
ALIAS_FILE="${REAL_HOME}/.opsec-aliases"
|
||||||
|
cp "$SCRIPT_DIR/configs/opsec-aliases" "$ALIAS_FILE"
|
||||||
|
chown "${REAL_USER}:${REAL_USER}" "$ALIAS_FILE"
|
||||||
|
|
||||||
|
# Add source line to .bashrc and .zshrc if not already present
|
||||||
|
for rc in "${REAL_HOME}/.bashrc" "${REAL_HOME}/.zshrc"; do
|
||||||
|
if [ -f "$rc" ]; then
|
||||||
|
if ! grep -q '.opsec-aliases' "$rc" 2>/dev/null; then
|
||||||
|
echo "" >> "$rc"
|
||||||
|
echo "# OPSEC toolkit aliases" >> "$rc"
|
||||||
|
echo "[ -f ~/.opsec-aliases ] && . ~/.opsec-aliases" >> "$rc"
|
||||||
|
chown "${REAL_USER}:${REAL_USER}" "$rc"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
ok "Aliases installed → ${ALIAS_FILE}"
|
||||||
|
|
||||||
|
# ─── TOR CONFIGURATION ──────────────────────────────────────────────────────
|
||||||
|
info "Configuring Tor..."
|
||||||
|
|
||||||
|
# Ensure tor log directory exists
|
||||||
|
mkdir -p /var/log/tor /run/tor
|
||||||
|
chown debian-tor:debian-tor /var/log/tor /run/tor 2>/dev/null || true
|
||||||
|
|
||||||
|
# Stop tor if running (we'll configure, user starts via opsec-on)
|
||||||
|
systemctl stop tor 2>/dev/null || true
|
||||||
|
|
||||||
|
ok "Tor configured (start with: opsec-on)"
|
||||||
|
|
||||||
|
# ─── TMPFS FOR LOGS ─────────────────────────────────────────────────────────
|
||||||
|
info "Setting up tmpfs for OPSEC logs..."
|
||||||
|
if ! grep -q 'opsec-logs' /etc/fstab 2>/dev/null; then
|
||||||
|
echo "" >> /etc/fstab
|
||||||
|
echo "# OPSEC: volatile log storage (RAM-only, cleared on reboot)" >> /etc/fstab
|
||||||
|
echo "tmpfs /var/log/opsec tmpfs nosuid,nodev,noexec,mode=0700,size=50M 0 0 # opsec-logs" >> /etc/fstab
|
||||||
|
fi
|
||||||
|
mkdir -p /var/log/opsec
|
||||||
|
mount /var/log/opsec 2>/dev/null || mount -t tmpfs -o nosuid,nodev,noexec,mode=0700,size=50M tmpfs /var/log/opsec 2>/dev/null || true
|
||||||
|
ok "OPSEC logs on tmpfs (RAM-only)"
|
||||||
|
|
||||||
|
# ─── CACHE DIRECTORY ─────────────────────────────────────────────────────────
|
||||||
|
mkdir -p /tmp/.opsec-cache
|
||||||
|
chown "${REAL_USER}:${REAL_USER}" /tmp/.opsec-cache 2>/dev/null || true
|
||||||
|
|
||||||
|
# ─── POST-INSTALL ────────────────────────────────────────────────────────────
|
||||||
|
echo ""
|
||||||
|
echo "${CYN}══════════════════════════════════════════${RST}"
|
||||||
|
ok "OPSEC toolkit installed successfully!"
|
||||||
|
echo "${CYN}══════════════════════════════════════════${RST}"
|
||||||
|
echo ""
|
||||||
|
info "Quick start:"
|
||||||
|
echo " ${GRN}opsec-on${RST} — Activate ghost mode (Tor + kill switch + hardening)"
|
||||||
|
echo " ${GRN}opsec-off${RST} — Deactivate ghost mode"
|
||||||
|
echo " ${GRN}opsec-config${RST} — Interactive configuration TUI"
|
||||||
|
echo " ${GRN}opsec-show${RST} — Show current status"
|
||||||
|
echo " ${GRN}killswitch-on${RST} — Activate kill switch only"
|
||||||
|
echo " ${GRN}opsec-preflight${RST} — Pre-session readiness check"
|
||||||
|
echo ""
|
||||||
|
info "Reload your shell to activate aliases:"
|
||||||
|
echo " ${GRN}source ~/.bashrc${RST} or ${GRN}source ~/.zshrc${RST}"
|
||||||
|
echo ""
|
||||||
|
info "Uninstall with: ${YEL}sudo ./install.sh --uninstall${RST}"
|
||||||
|
echo ""
|
||||||
Executable
+717
@@ -0,0 +1,717 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
# /usr/local/lib/opsec/opsec-lib.sh — Shared OPSEC function library
|
||||||
|
# Sourced by all OPSEC scripts for config management and common operations
|
||||||
|
|
||||||
|
OPSEC_CONF="/etc/opsec/opsec.conf"
|
||||||
|
OPSEC_COUNTRY_CODES="/etc/opsec/country-codes.conf"
|
||||||
|
OPSEC_PROFILES_DIR="/etc/opsec/profiles"
|
||||||
|
OPSEC_STATE_FILE="/var/run/opsec-advanced.enabled"
|
||||||
|
OPSEC_BOOT_MARKER="/etc/opsec/boot-advanced.enabled"
|
||||||
|
|
||||||
|
# ─── COLOR OUTPUT ──────────────────────────────────────────────────────────────
|
||||||
|
opsec_green() { echo -e "\033[38;5;49m[+]\033[0m \033[38;5;49m$*\033[0m"; }
|
||||||
|
opsec_red() { echo -e "\033[38;5;196m[-]\033[0m \033[38;5;196m$*\033[0m"; }
|
||||||
|
opsec_yellow() { echo -e "\033[38;5;214m[*]\033[0m \033[38;5;214m$*\033[0m"; }
|
||||||
|
opsec_info() { echo -e "\033[38;5;39m[~]\033[0m \033[38;5;75m$*\033[0m"; }
|
||||||
|
opsec_cyan() { echo -e "\033[38;5;51m[>]\033[0m \033[38;5;51m$*\033[0m"; }
|
||||||
|
opsec_mag() { echo -e "\033[38;5;201m[*]\033[0m \033[38;5;201m$*\033[0m"; }
|
||||||
|
opsec_dim() { echo -e "\033[38;5;244m $*\033[0m"; }
|
||||||
|
opsec_hdr() { echo -e "\033[38;5;51m━━━ \033[38;5;201m$*\033[38;5;51m ━━━\033[0m"; }
|
||||||
|
|
||||||
|
# ─── CONFIG MANAGEMENT ─────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
opsec_load_config() {
|
||||||
|
if [ -f "$OPSEC_CONF" ]; then
|
||||||
|
# shellcheck disable=SC1090
|
||||||
|
. "$OPSEC_CONF"
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
|
||||||
|
opsec_save_config() {
|
||||||
|
# Re-serialize all known keys back to config file
|
||||||
|
# Preserves comments and structure
|
||||||
|
local tmp
|
||||||
|
tmp=$(mktemp)
|
||||||
|
cat > "$tmp" << 'HEADER'
|
||||||
|
# /etc/opsec/opsec.conf — Central OPSEC Configuration
|
||||||
|
# Shell-sourceable KEY="value" format. All scripts source this file.
|
||||||
|
# Edit via: sudo opsec-config.sh (interactive TUI)
|
||||||
|
|
||||||
|
HEADER
|
||||||
|
|
||||||
|
cat >> "$tmp" << EOF
|
||||||
|
# ─── ACTIVE PROFILE ────────────────────────────────────────────────────────────
|
||||||
|
PROFILE_NAME="${PROFILE_NAME:-default}"
|
||||||
|
|
||||||
|
# ─── TOR SETTINGS ──────────────────────────────────────────────────────────────
|
||||||
|
TOR_CIRCUIT_ROTATION="${TOR_CIRCUIT_ROTATION:-30}"
|
||||||
|
TOR_BLACKLIST="${TOR_BLACKLIST:-}"
|
||||||
|
TOR_STRICT_NODES="${TOR_STRICT_NODES:-1}"
|
||||||
|
TOR_ISOLATION="${TOR_ISOLATION:-1}"
|
||||||
|
TOR_PADDING="${TOR_PADDING:-1}"
|
||||||
|
TOR_SOCKS_PORT="${TOR_SOCKS_PORT:-9050}"
|
||||||
|
TOR_DNS_PORT="${TOR_DNS_PORT:-5353}"
|
||||||
|
TOR_TRANS_PORT="${TOR_TRANS_PORT:-9040}"
|
||||||
|
TOR_NUM_GUARDS="${TOR_NUM_GUARDS:-3}"
|
||||||
|
TOR_SAFE_LOGGING="${TOR_SAFE_LOGGING:-1}"
|
||||||
|
|
||||||
|
# ─── DNS SETTINGS ──────────────────────────────────────────────────────────────
|
||||||
|
DNS_MODE="${DNS_MODE:-tor}"
|
||||||
|
DNS_CUSTOM_SERVERS="${DNS_CUSTOM_SERVERS:-}"
|
||||||
|
|
||||||
|
# ─── KILL SWITCH ───────────────────────────────────────────────────────────────
|
||||||
|
KILLSWITCH_ALLOW_DHCP="${KILLSWITCH_ALLOW_DHCP:-1}"
|
||||||
|
KILLSWITCH_ALLOW_OPENVPN="${KILLSWITCH_ALLOW_OPENVPN:-1}"
|
||||||
|
KILLSWITCH_ALLOW_WIREGUARD="${KILLSWITCH_ALLOW_WIREGUARD:-1}"
|
||||||
|
KILLSWITCH_EXTRA_PORTS="${KILLSWITCH_EXTRA_PORTS:-}"
|
||||||
|
|
||||||
|
# ─── MAC ADDRESS ───────────────────────────────────────────────────────────────
|
||||||
|
MAC_INTERFACES="${MAC_INTERFACES:-auto}"
|
||||||
|
MAC_VENDOR_SPOOF="${MAC_VENDOR_SPOOF:-}"
|
||||||
|
|
||||||
|
# ─── HOSTNAME ──────────────────────────────────────────────────────────────────
|
||||||
|
HOSTNAME_PATTERN="${HOSTNAME_PATTERN:-desktop}"
|
||||||
|
HOSTNAME_CUSTOM_PREFIX="${HOSTNAME_CUSTOM_PREFIX:-}"
|
||||||
|
|
||||||
|
# ─── SYSTEM HARDENING ──────────────────────────────────────────────────────────
|
||||||
|
HARDEN_IPV6="${HARDEN_IPV6:-1}"
|
||||||
|
HARDEN_SWAP="${HARDEN_SWAP:-1}"
|
||||||
|
HARDEN_CORE_DUMPS="${HARDEN_CORE_DUMPS:-1}"
|
||||||
|
HARDEN_CLIPBOARD_CLEAR="${HARDEN_CLIPBOARD_CLEAR:-0}"
|
||||||
|
HARDEN_SCREEN_LOCK="${HARDEN_SCREEN_LOCK:-1}"
|
||||||
|
HARDEN_SCREEN_LOCK_TIMEOUT="${HARDEN_SCREEN_LOCK_TIMEOUT:-300}"
|
||||||
|
HARDEN_TIMEZONE_SPOOF="${HARDEN_TIMEZONE_SPOOF:-0}"
|
||||||
|
HARDEN_TIMEZONE_VALUE="${HARDEN_TIMEZONE_VALUE:-UTC}"
|
||||||
|
HARDEN_LOCALE_SPOOF="${HARDEN_LOCALE_SPOOF:-0}"
|
||||||
|
HARDEN_LOCALE_VALUE="${HARDEN_LOCALE_VALUE:-en_US.UTF-8}"
|
||||||
|
|
||||||
|
# ─── LEAK PREVENTION ──────────────────────────────────────────────────────────
|
||||||
|
LEAK_WEBRTC_BLOCK="${LEAK_WEBRTC_BLOCK:-1}"
|
||||||
|
LEAK_USB_BLOCK="${LEAK_USB_BLOCK:-0}"
|
||||||
|
|
||||||
|
# ─── MONITORING ────────────────────────────────────────────────────────────────
|
||||||
|
MONITOR_PROCESSES="${MONITOR_PROCESSES:-0}"
|
||||||
|
MONITOR_LOG_ROTATION="${MONITOR_LOG_ROTATION:-1}"
|
||||||
|
LOG_ROTATION_HOURS="${LOG_ROTATION_HOURS:-4}"
|
||||||
|
|
||||||
|
# ─── TRAFFIC SHAPING ──────────────────────────────────────────────────────────
|
||||||
|
TRAFFIC_JITTER_ENABLED="${TRAFFIC_JITTER_ENABLED:-0}"
|
||||||
|
TRAFFIC_JITTER_MS="${TRAFFIC_JITTER_MS:-50}"
|
||||||
|
|
||||||
|
# ─── LEVEL TYPE ───────────────────────────────────────────────────────────────
|
||||||
|
LEVEL_TYPE="${LEVEL_TYPE:-standard}"
|
||||||
|
|
||||||
|
# ─── BASE STATE ───────────────────────────────────────────────────────────────
|
||||||
|
BASE_DNS="${BASE_DNS:-quad9}"
|
||||||
|
BASE_MAC_RANDOMIZE="${BASE_MAC_RANDOMIZE:-1}"
|
||||||
|
BASE_IPV6_DISABLE="${BASE_IPV6_DISABLE:-1}"
|
||||||
|
|
||||||
|
# ─── TOR BRIDGES ──────────────────────────────────────────────────────────────
|
||||||
|
TOR_BRIDGE_MODE="${TOR_BRIDGE_MODE:-off}"
|
||||||
|
TOR_BRIDGE_RELAY="${TOR_BRIDGE_RELAY:-}"
|
||||||
|
|
||||||
|
# ─── SECURE DELETION ──────────────────────────────────────────────────────────
|
||||||
|
WIPE_METHOD="${WIPE_METHOD:-auto}"
|
||||||
|
|
||||||
|
# ─── DEPLOYMENT LEVEL ─────────────────────────────────────────────────────────
|
||||||
|
DEPLOYMENT_LEVEL="${DEPLOYMENT_LEVEL:-bare-metal-standard}"
|
||||||
|
|
||||||
|
# ─── TERMINAL BANNER ──────────────────────────────────────────────────────────
|
||||||
|
OPSEC_BANNER="${OPSEC_BANNER:-compact}"
|
||||||
|
|
||||||
|
# ─── WIDGET THEME ────────────────────────────────────────────────────────────
|
||||||
|
WIDGET_THEME="${WIDGET_THEME:-default}"
|
||||||
|
EOF
|
||||||
|
mv "$tmp" "$OPSEC_CONF"
|
||||||
|
chmod 600 "$OPSEC_CONF"
|
||||||
|
}
|
||||||
|
|
||||||
|
opsec_set_value() {
|
||||||
|
local key="$1" val="$2"
|
||||||
|
if [ -z "$key" ]; then return 1; fi
|
||||||
|
# Validate key: must be a valid shell variable name (letters, digits, underscore, starts with letter/underscore)
|
||||||
|
if ! echo "$key" | grep -qE '^[A-Za-z_][A-Za-z0-9_]*$'; then
|
||||||
|
echo "[!] opsec_set_value: invalid key name '${key}'" >&2
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
# Sanitize value: strip characters that could break shell quoting
|
||||||
|
val=$(printf '%s' "$val" | tr -d '`$\\\"'"'" | tr -cd '[:print:]')
|
||||||
|
opsec_load_config
|
||||||
|
eval "${key}=\"${val}\""
|
||||||
|
opsec_save_config
|
||||||
|
}
|
||||||
|
|
||||||
|
opsec_get_value() {
|
||||||
|
local key="$1"
|
||||||
|
# Validate key: must be a valid shell variable name
|
||||||
|
if ! echo "$key" | grep -qE '^[A-Za-z_][A-Za-z0-9_]*$'; then
|
||||||
|
echo "[!] opsec_get_value: invalid key name '${key}'" >&2
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
opsec_load_config
|
||||||
|
eval "echo \"\${${key}:-}\""
|
||||||
|
}
|
||||||
|
|
||||||
|
# ─── TORRC GENERATION ──────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
opsec_generate_torrc() {
|
||||||
|
local _dbg="/run/opsec/debug.log"
|
||||||
|
echo "[$(date -Is)] [lib] opsec_generate_torrc called" >> "$_dbg" 2>/dev/null || true
|
||||||
|
|
||||||
|
opsec_load_config || return 1
|
||||||
|
|
||||||
|
local torrc="/etc/tor/torrc"
|
||||||
|
local socks_port="${TOR_SOCKS_PORT:-9050}"
|
||||||
|
local trans_port="${TOR_TRANS_PORT:-9040}"
|
||||||
|
local dns_port="${TOR_DNS_PORT:-5353}"
|
||||||
|
local rotation="${TOR_CIRCUIT_ROTATION:-30}"
|
||||||
|
local blacklist="${TOR_BLACKLIST:-}"
|
||||||
|
local strict="${TOR_STRICT_NODES:-1}"
|
||||||
|
local isolation="${TOR_ISOLATION:-1}"
|
||||||
|
local padding="${TOR_PADDING:-1}"
|
||||||
|
local guards="${TOR_NUM_GUARDS:-3}"
|
||||||
|
local safe_log="${TOR_SAFE_LOGGING:-1}"
|
||||||
|
echo "[$(date -Is)] [lib] socks=${socks_port} trans=${trans_port} dns=${dns_port} blacklist=${blacklist}" >> "$_dbg" 2>/dev/null || true
|
||||||
|
|
||||||
|
# Build SocksPort line with isolation flags
|
||||||
|
local socks_line="SocksPort ${socks_port}"
|
||||||
|
if [ "$isolation" = "1" ]; then
|
||||||
|
socks_line="${socks_line} IsolateDestAddr IsolateDestPort"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Build ExcludeExitNodes from blacklist
|
||||||
|
local exclude_line=""
|
||||||
|
if [ -n "$blacklist" ]; then
|
||||||
|
local formatted
|
||||||
|
formatted=$(echo "$blacklist" | sed 's/\([a-z][a-z]\)/{\1}/g; s/,/,/g')
|
||||||
|
exclude_line="ExcludeExitNodes ${formatted}"
|
||||||
|
fi
|
||||||
|
|
||||||
|
local bridge_mode="${TOR_BRIDGE_MODE:-off}"
|
||||||
|
local bridge_relay="${TOR_BRIDGE_RELAY:-}"
|
||||||
|
|
||||||
|
cat > "$torrc" << EOF
|
||||||
|
# Autogenerated — do not edit manually
|
||||||
|
# Regenerate via: opsec-config.sh --apply
|
||||||
|
|
||||||
|
# ─── SOCKS, TRANSPARENT PROXY & DNS ─────────────────────────────────────────
|
||||||
|
${socks_line}
|
||||||
|
TransPort ${trans_port}
|
||||||
|
DNSPort ${dns_port}
|
||||||
|
|
||||||
|
# ─── EXIT NODE EXCLUSION ────────────────────────────────────────────────────
|
||||||
|
${exclude_line}
|
||||||
|
StrictNodes ${strict}
|
||||||
|
|
||||||
|
# ─── CIRCUIT ROTATION ───────────────────────────────────────────────────────
|
||||||
|
MaxCircuitDirtiness ${rotation}
|
||||||
|
|
||||||
|
# ─── TRAFFIC PADDING ────────────────────────────────────────────────────────
|
||||||
|
ConnectionPadding ${padding}
|
||||||
|
|
||||||
|
# ─── SAFE LOGGING ────────────────────────────────────────────────────────────
|
||||||
|
SafeLogging ${safe_log}
|
||||||
|
Log notice file /run/tor/notices.log
|
||||||
|
|
||||||
|
# ─── ENTRY GUARDS ───────────────────────────────────────────────────────────
|
||||||
|
NumEntryGuards ${guards}
|
||||||
|
UseEntryGuards 1
|
||||||
|
|
||||||
|
EOF
|
||||||
|
|
||||||
|
# ─── PLUGGABLE TRANSPORTS (bridges) ──────────────────────────────────────
|
||||||
|
if [ "$bridge_mode" != "off" ] && [ "$bridge_mode" != "" ]; then
|
||||||
|
cat >> "$torrc" << 'BRIDGE_HEADER'
|
||||||
|
|
||||||
|
# ─── BRIDGE CONFIGURATION ────────────────────────────────────────────────────
|
||||||
|
UseBridges 1
|
||||||
|
BRIDGE_HEADER
|
||||||
|
|
||||||
|
case "$bridge_mode" in
|
||||||
|
obfs4)
|
||||||
|
echo "ClientTransportPlugin obfs4 exec /usr/bin/obfs4proxy" >> "$torrc"
|
||||||
|
;;
|
||||||
|
meek-azure)
|
||||||
|
echo "ClientTransportPlugin meek_lite exec /usr/bin/obfs4proxy" >> "$torrc"
|
||||||
|
;;
|
||||||
|
snowflake)
|
||||||
|
# snowflake-client location varies by distro
|
||||||
|
local sf_bin
|
||||||
|
sf_bin=$(command -v snowflake-client 2>/dev/null || echo "/usr/bin/snowflake-client")
|
||||||
|
echo "ClientTransportPlugin snowflake exec ${sf_bin}" >> "$torrc"
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
# Add user-specified bridge relay if provided
|
||||||
|
if [ -n "$bridge_relay" ]; then
|
||||||
|
echo "Bridge ${bridge_relay}" >> "$torrc"
|
||||||
|
else
|
||||||
|
# Default bridges for each transport type
|
||||||
|
case "$bridge_mode" in
|
||||||
|
meek-azure)
|
||||||
|
echo "Bridge meek_lite 192.0.2.18:80 BE776A53492E1E044A26F17306E1BC46A55A1625 url=https://meek.azureedge.net/ front=ajax.aspnetcdn.com" >> "$torrc"
|
||||||
|
;;
|
||||||
|
snowflake)
|
||||||
|
echo "Bridge snowflake 192.0.2.3:80 2B280B23E1107BB62ABFC40DDCC8824814F80A72 fingerprint=2B280B23E1107BB62ABFC40DDCC8824814F80A72 url=https://snowflake-broker.torproject.net.global.prod.fastly.net/ front=foursquare.com ice=stun:stun.l.google.com:19302,stun:stun.antisip.com:3478,stun:stun.bluesip.net:3478,stun:stun.dus.net:3478,stun:stun.epygi.com:3478,stun:stun.sonetel.com:3478,stun:stun.uls.co.za:3478,stun:stun.voipgate.com:3478,stun:stun.voys.nl:3478 utls-imitate=hellorandomizedalpn" >> "$torrc"
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
chmod 644 "$torrc"
|
||||||
|
echo "[$(date -Is)] [lib] torrc written ($(wc -l < "$torrc") lines)" >> "$_dbg" 2>/dev/null || true
|
||||||
|
echo "[$(date -Is)] [lib] torrc contents:" >> "$_dbg" 2>/dev/null || true
|
||||||
|
cat "$torrc" >> "$_dbg" 2>/dev/null || true
|
||||||
|
echo "[$(date -Is)] [lib] --- end torrc ---" >> "$_dbg" 2>/dev/null || true
|
||||||
|
}
|
||||||
|
|
||||||
|
# ─── RESOLV.CONF GENERATION ───────────────────────────────────────────────────
|
||||||
|
|
||||||
|
opsec_generate_resolv() {
|
||||||
|
local _dbg="/run/opsec/debug.log"
|
||||||
|
echo "[$(date -Is)] [lib] opsec_generate_resolv called" >> "$_dbg" 2>/dev/null || true
|
||||||
|
|
||||||
|
opsec_load_config || return 1
|
||||||
|
|
||||||
|
local mode="${DNS_MODE:-tor}"
|
||||||
|
local resolv="/etc/resolv.conf"
|
||||||
|
echo "[$(date -Is)] [lib] DNS_MODE=${mode}" >> "$_dbg" 2>/dev/null || true
|
||||||
|
|
||||||
|
# Unlock if immutable
|
||||||
|
chattr -i "$resolv" 2>/dev/null || true
|
||||||
|
|
||||||
|
case "$mode" in
|
||||||
|
tor)
|
||||||
|
echo "nameserver 127.0.0.1" > "$resolv"
|
||||||
|
;;
|
||||||
|
quad9)
|
||||||
|
cat > "$resolv" << 'EOF'
|
||||||
|
nameserver 9.9.9.9
|
||||||
|
nameserver 149.112.112.112
|
||||||
|
EOF
|
||||||
|
;;
|
||||||
|
cloudflare)
|
||||||
|
cat > "$resolv" << 'EOF'
|
||||||
|
nameserver 1.1.1.1
|
||||||
|
nameserver 1.0.0.1
|
||||||
|
EOF
|
||||||
|
;;
|
||||||
|
doh)
|
||||||
|
# DNS-over-HTTPS via dnscrypt-proxy
|
||||||
|
# Ensure dnscrypt-proxy is running
|
||||||
|
if command -v dnscrypt-proxy >/dev/null 2>&1; then
|
||||||
|
# Deploy config if not present
|
||||||
|
if [ ! -f /etc/dnscrypt-proxy/dnscrypt-proxy.toml ] && [ -f /etc/opsec/dnscrypt-proxy.toml ]; then
|
||||||
|
mkdir -p /etc/dnscrypt-proxy
|
||||||
|
cp /etc/opsec/dnscrypt-proxy.toml /etc/dnscrypt-proxy/dnscrypt-proxy.toml
|
||||||
|
fi
|
||||||
|
mkdir -p /var/log/dnscrypt-proxy /var/cache/dnscrypt-proxy
|
||||||
|
# Stop systemd-resolved if it conflicts on :53
|
||||||
|
systemctl stop systemd-resolved 2>/dev/null || true
|
||||||
|
systemctl start dnscrypt-proxy 2>/dev/null || dnscrypt-proxy -config /etc/dnscrypt-proxy/dnscrypt-proxy.toml &
|
||||||
|
fi
|
||||||
|
cat > "$resolv" << 'EOF'
|
||||||
|
nameserver 127.0.0.53
|
||||||
|
EOF
|
||||||
|
;;
|
||||||
|
dot)
|
||||||
|
# DNS-over-TLS via systemd-resolved
|
||||||
|
cat > "$resolv" << 'EOF'
|
||||||
|
nameserver 127.0.0.53
|
||||||
|
options edns0 trust-ad
|
||||||
|
EOF
|
||||||
|
;;
|
||||||
|
custom)
|
||||||
|
if [ -n "$DNS_CUSTOM_SERVERS" ]; then
|
||||||
|
: > "$resolv"
|
||||||
|
local IFS=','
|
||||||
|
for server in $DNS_CUSTOM_SERVERS; do
|
||||||
|
echo "nameserver ${server}" >> "$resolv"
|
||||||
|
done
|
||||||
|
else
|
||||||
|
echo "nameserver 9.9.9.9" > "$resolv"
|
||||||
|
fi
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
echo "nameserver 9.9.9.9" > "$resolv"
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
# Lock if in advanced mode
|
||||||
|
if opsec_is_advanced; then
|
||||||
|
chattr +i "$resolv"
|
||||||
|
echo "[$(date -Is)] [lib] resolv.conf locked (chattr +i)" >> "$_dbg" 2>/dev/null || true
|
||||||
|
fi
|
||||||
|
echo "[$(date -Is)] [lib] resolv.conf contents: $(cat "$resolv" 2>/dev/null | tr '\n' ' ')" >> "$_dbg" 2>/dev/null || true
|
||||||
|
}
|
||||||
|
|
||||||
|
# ─── STATE CHECKS ─────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
opsec_is_advanced() {
|
||||||
|
[ -f "$OPSEC_STATE_FILE" ]
|
||||||
|
}
|
||||||
|
|
||||||
|
opsec_is_boot_enabled() {
|
||||||
|
[ -f "$OPSEC_BOOT_MARKER" ]
|
||||||
|
}
|
||||||
|
|
||||||
|
# ─── PROFILE MANAGEMENT ───────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
opsec_profile_save() {
|
||||||
|
local name="$1"
|
||||||
|
if [ -z "$name" ]; then return 1; fi
|
||||||
|
mkdir -p "$OPSEC_PROFILES_DIR"
|
||||||
|
cp "$OPSEC_CONF" "${OPSEC_PROFILES_DIR}/${name}.conf"
|
||||||
|
# Tag profile name inside the saved copy
|
||||||
|
sed -i "s/^PROFILE_NAME=.*/PROFILE_NAME=\"${name}\"/" "${OPSEC_PROFILES_DIR}/${name}.conf"
|
||||||
|
}
|
||||||
|
|
||||||
|
opsec_profile_load() {
|
||||||
|
local name="$1"
|
||||||
|
local profile="${OPSEC_PROFILES_DIR}/${name}.conf"
|
||||||
|
if [ ! -f "$profile" ]; then return 1; fi
|
||||||
|
cp "$profile" "$OPSEC_CONF"
|
||||||
|
# Update active profile name
|
||||||
|
sed -i "s/^PROFILE_NAME=.*/PROFILE_NAME=\"${name}\"/" "$OPSEC_CONF"
|
||||||
|
}
|
||||||
|
|
||||||
|
opsec_profile_list() {
|
||||||
|
if [ -d "$OPSEC_PROFILES_DIR" ]; then
|
||||||
|
find "$OPSEC_PROFILES_DIR" -name '*.conf' -printf '%f\n' | sed 's/\.conf$//'
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
opsec_profile_delete() {
|
||||||
|
local name="$1"
|
||||||
|
rm -f "${OPSEC_PROFILES_DIR}/${name}.conf"
|
||||||
|
}
|
||||||
|
|
||||||
|
opsec_profile_export() {
|
||||||
|
local name="$1" dest="$2"
|
||||||
|
local profile="${OPSEC_PROFILES_DIR}/${name}.conf"
|
||||||
|
if [ ! -f "$profile" ]; then return 1; fi
|
||||||
|
cp "$profile" "$dest"
|
||||||
|
}
|
||||||
|
|
||||||
|
opsec_profile_import() {
|
||||||
|
local src="$1" name="$2"
|
||||||
|
if [ ! -f "$src" ]; then return 1; fi
|
||||||
|
mkdir -p "$OPSEC_PROFILES_DIR"
|
||||||
|
cp "$src" "${OPSEC_PROFILES_DIR}/${name}.conf"
|
||||||
|
}
|
||||||
|
|
||||||
|
# ─── COUNTRY CODE HELPERS ─────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
opsec_load_country_presets() {
|
||||||
|
if [ -f "$OPSEC_COUNTRY_CODES" ]; then
|
||||||
|
# shellcheck disable=SC1090
|
||||||
|
. "$OPSEC_COUNTRY_CODES"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
opsec_get_preset() {
|
||||||
|
local preset="$1"
|
||||||
|
opsec_load_country_presets
|
||||||
|
case "$preset" in
|
||||||
|
5eyes|fiveeyes) echo "$FIVE_EYES" ;;
|
||||||
|
9eyes|nineeyes) echo "$NINE_EYES" ;;
|
||||||
|
14eyes|fourteeneyes) echo "$FOURTEEN_EYES" ;;
|
||||||
|
surveillance) echo "$SURVEILLANCE_STATES" ;;
|
||||||
|
max) echo "$MAX_EXCLUSION" ;;
|
||||||
|
*) echo "" ;;
|
||||||
|
esac
|
||||||
|
}
|
||||||
|
|
||||||
|
# ─── DEPLOYMENT LEVEL MANAGEMENT ─────────────────────────────────────────────
|
||||||
|
|
||||||
|
OPSEC_LEVELS_DIR="/etc/opsec/levels"
|
||||||
|
|
||||||
|
opsec_level_list() {
|
||||||
|
if [ -d "$OPSEC_LEVELS_DIR" ]; then
|
||||||
|
find "$OPSEC_LEVELS_DIR" -name '*.conf' -printf '%f\n' | sed 's/\.conf$//' | sort
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
opsec_level_apply() {
|
||||||
|
local level="$1"
|
||||||
|
local level_file="${OPSEC_LEVELS_DIR}/${level}.conf"
|
||||||
|
if [ ! -f "$level_file" ]; then
|
||||||
|
opsec_red "Level '${level}' not found in ${OPSEC_LEVELS_DIR}"
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Preserve current PROFILE_NAME before overwriting
|
||||||
|
opsec_load_config 2>/dev/null || true
|
||||||
|
local saved_profile="${PROFILE_NAME:-default}"
|
||||||
|
|
||||||
|
# Copy level preset over active config
|
||||||
|
cp "$level_file" "$OPSEC_CONF"
|
||||||
|
chmod 600 "$OPSEC_CONF"
|
||||||
|
|
||||||
|
# Restore profile name and ensure deployment level is tagged
|
||||||
|
opsec_load_config
|
||||||
|
PROFILE_NAME="$saved_profile"
|
||||||
|
DEPLOYMENT_LEVEL="$level"
|
||||||
|
opsec_save_config
|
||||||
|
}
|
||||||
|
|
||||||
|
# ─── SECURE DELETION ─────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
opsec_detect_storage_type() {
|
||||||
|
# Detect storage type for a given path
|
||||||
|
# Returns: hdd | ssd | luks | unknown
|
||||||
|
local target_path="${1:-/}"
|
||||||
|
local device
|
||||||
|
|
||||||
|
# Find the block device for the path
|
||||||
|
device=$(df -P "$target_path" 2>/dev/null | tail -1 | awk '{print $1}')
|
||||||
|
[ -z "$device" ] && echo "unknown" && return
|
||||||
|
|
||||||
|
# Check for LUKS
|
||||||
|
if command -v cryptsetup >/dev/null 2>&1; then
|
||||||
|
# Check if device is on a dm-crypt layer
|
||||||
|
local dm_name
|
||||||
|
dm_name=$(basename "$device" 2>/dev/null)
|
||||||
|
if [ -e "/sys/block/${dm_name}/dm/uuid" ] 2>/dev/null; then
|
||||||
|
local dm_uuid
|
||||||
|
dm_uuid=$(cat "/sys/block/${dm_name}/dm/uuid" 2>/dev/null)
|
||||||
|
if echo "$dm_uuid" | grep -qi "CRYPT-LUKS"; then
|
||||||
|
echo "luks"
|
||||||
|
return
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
# Also check via dmsetup
|
||||||
|
if dmsetup info "$device" 2>/dev/null | grep -qi "CRYPT-LUKS"; then
|
||||||
|
echo "luks"
|
||||||
|
return
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Resolve to physical disk (strip partition number, handle /dev/mapper)
|
||||||
|
local phys_disk
|
||||||
|
phys_disk=$(lsblk -ndo PKNAME "$device" 2>/dev/null | head -1)
|
||||||
|
[ -z "$phys_disk" ] && phys_disk=$(echo "$device" | sed 's/[0-9]*$//' | sed 's|^/dev/||')
|
||||||
|
|
||||||
|
# Check rotational flag (0 = SSD, 1 = HDD)
|
||||||
|
local rotational_file="/sys/block/${phys_disk}/queue/rotational"
|
||||||
|
if [ -f "$rotational_file" ]; then
|
||||||
|
local rotational
|
||||||
|
rotational=$(cat "$rotational_file" 2>/dev/null)
|
||||||
|
if [ "$rotational" = "0" ]; then
|
||||||
|
echo "ssd"
|
||||||
|
else
|
||||||
|
echo "hdd"
|
||||||
|
fi
|
||||||
|
return
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "unknown"
|
||||||
|
}
|
||||||
|
|
||||||
|
opsec_secure_delete() {
|
||||||
|
# SSD-aware secure file deletion
|
||||||
|
# Usage: opsec_secure_delete <file_or_dir> [method]
|
||||||
|
# method: auto (default) | shred | fstrim | luks
|
||||||
|
local target="$1"
|
||||||
|
local method="${2:-${WIPE_METHOD:-auto}}"
|
||||||
|
|
||||||
|
[ -z "$target" ] && return 1
|
||||||
|
|
||||||
|
if [ "$method" = "auto" ]; then
|
||||||
|
method=$(opsec_detect_storage_type "$target")
|
||||||
|
fi
|
||||||
|
|
||||||
|
case "$method" in
|
||||||
|
hdd|shred)
|
||||||
|
# Traditional shred for spinning disks
|
||||||
|
if [ -d "$target" ]; then
|
||||||
|
find "$target" -type f -exec shred -fuz -n 1 {} \; 2>/dev/null
|
||||||
|
rm -rf "$target" 2>/dev/null
|
||||||
|
elif [ -f "$target" ]; then
|
||||||
|
shred -fuz -n 1 "$target" 2>/dev/null
|
||||||
|
fi
|
||||||
|
;;
|
||||||
|
ssd|fstrim)
|
||||||
|
# For SSDs: overwrite with zeros, delete, then fstrim
|
||||||
|
# shred is ineffective on SSDs due to wear leveling
|
||||||
|
if [ -d "$target" ]; then
|
||||||
|
find "$target" -type f -exec dd if=/dev/zero of={} bs=4k count=1 conv=notrunc 2>/dev/null \;
|
||||||
|
find "$target" -type f -delete 2>/dev/null
|
||||||
|
rm -rf "$target" 2>/dev/null
|
||||||
|
elif [ -f "$target" ]; then
|
||||||
|
dd if=/dev/zero of="$target" bs=4k count=1 conv=notrunc 2>/dev/null
|
||||||
|
rm -f "$target" 2>/dev/null
|
||||||
|
fi
|
||||||
|
# Request TRIM/discard on the filesystem
|
||||||
|
local mount_point
|
||||||
|
mount_point=$(df -P "${target%/*}" 2>/dev/null | tail -1 | awk '{print $6}')
|
||||||
|
if [ -n "$mount_point" ]; then
|
||||||
|
fstrim "$mount_point" 2>/dev/null || true
|
||||||
|
fi
|
||||||
|
;;
|
||||||
|
luks)
|
||||||
|
# For LUKS: zero file + rely on LUKS key destroy for full wipe
|
||||||
|
if [ -d "$target" ]; then
|
||||||
|
find "$target" -type f -exec dd if=/dev/zero of={} bs=4k count=1 conv=notrunc 2>/dev/null \;
|
||||||
|
find "$target" -type f -delete 2>/dev/null
|
||||||
|
rm -rf "$target" 2>/dev/null
|
||||||
|
elif [ -f "$target" ]; then
|
||||||
|
dd if=/dev/zero of="$target" bs=4k count=1 conv=notrunc 2>/dev/null
|
||||||
|
rm -f "$target" 2>/dev/null
|
||||||
|
fi
|
||||||
|
# Note: full LUKS key destroy handled by emergency-wipe
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
# Fallback: basic shred
|
||||||
|
if [ -d "$target" ]; then
|
||||||
|
find "$target" -type f -exec shred -fuz -n 1 {} \; 2>/dev/null
|
||||||
|
rm -rf "$target" 2>/dev/null
|
||||||
|
elif [ -f "$target" ]; then
|
||||||
|
shred -fuz -n 1 "$target" 2>/dev/null
|
||||||
|
fi
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
}
|
||||||
|
|
||||||
|
# ─── WIDGET THEME MANAGEMENT ─────────────────────────────────────────────────
|
||||||
|
|
||||||
|
OPSEC_THEMES_DIR="/etc/opsec/themes"
|
||||||
|
|
||||||
|
opsec_theme_list() {
|
||||||
|
if [ -d "$OPSEC_THEMES_DIR" ]; then
|
||||||
|
find "$OPSEC_THEMES_DIR" -name '*.theme' -printf '%f\n' | sed 's/\.theme$//' | sort
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
opsec_generate_conky() {
|
||||||
|
opsec_load_config || return 1
|
||||||
|
|
||||||
|
local theme="${WIDGET_THEME:-default}"
|
||||||
|
local theme_file="${OPSEC_THEMES_DIR}/${theme}.theme"
|
||||||
|
|
||||||
|
if [ ! -f "$theme_file" ]; then
|
||||||
|
opsec_red "Theme '${theme}' not found at ${theme_file}"
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Source theme to get CONKY_COLOR* and CONKY_BG values
|
||||||
|
# shellcheck disable=SC1090
|
||||||
|
. "$theme_file"
|
||||||
|
|
||||||
|
local conky_conf_name="conky-opsec-widget.conf"
|
||||||
|
|
||||||
|
# Find and regenerate conky config for each user with the widget installed
|
||||||
|
for home_dir in /home/*; do
|
||||||
|
local conky_conf="${home_dir}/.config/conky/${conky_conf_name}"
|
||||||
|
[ -f "$conky_conf" ] || continue
|
||||||
|
|
||||||
|
local owner
|
||||||
|
owner=$(stat -c '%U' "$conky_conf" 2>/dev/null) || continue
|
||||||
|
|
||||||
|
cat > "$conky_conf" << CONKYEOF
|
||||||
|
-- OPSEC Status Widget
|
||||||
|
-- Colors managed by theme system via opsec-config.sh
|
||||||
|
-- Theme: ${theme} (${THEME_LABEL:-Custom})
|
||||||
|
|
||||||
|
conky.config = {
|
||||||
|
-- Window settings
|
||||||
|
alignment = 'top_right',
|
||||||
|
gap_x = 15,
|
||||||
|
gap_y = 60,
|
||||||
|
minimum_width = 400,
|
||||||
|
minimum_height = 200,
|
||||||
|
maximum_width = 420,
|
||||||
|
|
||||||
|
-- Multi-monitor: run xrandr --listmonitors to find head number
|
||||||
|
xinerama_head = 0,
|
||||||
|
|
||||||
|
-- Window type
|
||||||
|
own_window = true,
|
||||||
|
own_window_type = 'desktop',
|
||||||
|
own_window_transparent = false,
|
||||||
|
own_window_argb_visual = true,
|
||||||
|
own_window_argb_value = 210,
|
||||||
|
own_window_colour = '${CONKY_BG:-0d1117}',
|
||||||
|
own_window_hints = 'undecorated,below,sticky,skip_taskbar,skip_pager',
|
||||||
|
|
||||||
|
-- Drawing
|
||||||
|
double_buffer = true,
|
||||||
|
draw_shades = true,
|
||||||
|
default_shade_color = '000000',
|
||||||
|
draw_outline = false,
|
||||||
|
draw_borders = true,
|
||||||
|
border_inner_margin = 12,
|
||||||
|
border_outer_margin = 4,
|
||||||
|
border_width = 1,
|
||||||
|
border_colour = '1b3a5c',
|
||||||
|
stippled_borders = 0,
|
||||||
|
|
||||||
|
-- Font
|
||||||
|
use_xft = true,
|
||||||
|
font = 'JetBrains Mono:size=10',
|
||||||
|
override_utf8_locale = true,
|
||||||
|
|
||||||
|
-- Colors — Theme: ${theme}
|
||||||
|
default_color = 'b0b0b0',
|
||||||
|
color0 = '${CONKY_COLOR0:-df2020}',
|
||||||
|
color1 = '${CONKY_COLOR1:-33ff33}',
|
||||||
|
color2 = '${CONKY_COLOR2:-3a8fd6}',
|
||||||
|
color3 = '${CONKY_COLOR3:-0d1117}',
|
||||||
|
color4 = '${CONKY_COLOR4:-1f6feb}',
|
||||||
|
color5 = '${CONKY_COLOR5:-58a6ff}',
|
||||||
|
color6 = '${CONKY_COLOR6:-79c0ff}',
|
||||||
|
color7 = '${CONKY_COLOR7:-c9d1d9}',
|
||||||
|
color8 = '${CONKY_COLOR8:-1f6feb}',
|
||||||
|
color9 = '${CONKY_COLOR9:-484f58}',
|
||||||
|
|
||||||
|
-- Update interval
|
||||||
|
update_interval = 3,
|
||||||
|
total_run_times = 0,
|
||||||
|
|
||||||
|
-- Misc
|
||||||
|
cpu_avg_samples = 2,
|
||||||
|
no_buffers = true,
|
||||||
|
text_buffer_size = 8192,
|
||||||
|
short_units = true,
|
||||||
|
};
|
||||||
|
|
||||||
|
conky.text = [[
|
||||||
|
\${execpi 5 ~/.config/conky/conky-opsec-status.sh}
|
||||||
|
]];
|
||||||
|
CONKYEOF
|
||||||
|
|
||||||
|
chown "$owner":"$owner" "$conky_conf"
|
||||||
|
chmod 644 "$conky_conf"
|
||||||
|
done
|
||||||
|
|
||||||
|
# Restart conky for all users running the opsec widget
|
||||||
|
pkill -f 'conky.*opsec' 2>/dev/null || true
|
||||||
|
sleep 1
|
||||||
|
|
||||||
|
for home_dir in /home/*; do
|
||||||
|
local conky_conf="${home_dir}/.config/conky/${conky_conf_name}"
|
||||||
|
[ -f "$conky_conf" ] || continue
|
||||||
|
|
||||||
|
local owner
|
||||||
|
owner=$(stat -c '%U' "$conky_conf" 2>/dev/null) || continue
|
||||||
|
local uid
|
||||||
|
uid=$(id -u "$owner" 2>/dev/null) || continue
|
||||||
|
|
||||||
|
# Relaunch conky as the file owner
|
||||||
|
su - "$owner" -c "DISPLAY=:0 DBUS_SESSION_BUS_ADDRESS=unix:path=/run/user/${uid}/bus nohup conky -c '${conky_conf}' >/dev/null 2>&1 &" 2>/dev/null || true
|
||||||
|
done
|
||||||
|
}
|
||||||
|
|
||||||
|
# ─── INIT CHECK ────────────────────────────────────────────────────────────────
|
||||||
|
# Ensure /etc/opsec exists
|
||||||
|
_opsec_init_dirs() {
|
||||||
|
[ -d /etc/opsec ] || mkdir -p /etc/opsec
|
||||||
|
[ -d "$OPSEC_PROFILES_DIR" ] || mkdir -p "$OPSEC_PROFILES_DIR"
|
||||||
|
}
|
||||||
|
|
||||||
|
# Auto-init on source if running as root
|
||||||
|
if [ "$EUID" = "0" ] 2>/dev/null || [ "$(id -u)" = "0" ] 2>/dev/null; then
|
||||||
|
_opsec_init_dirs
|
||||||
|
fi
|
||||||
Executable
+35
@@ -0,0 +1,35 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
# /usr/local/bin/opsec-banner-cache.sh — Background WAN IP Cache Updater
|
||||||
|
# Updates /var/cache/opsec/wan_ip every 5 minutes (via cron)
|
||||||
|
# Never blocks shell startup — the banner reads from cache only
|
||||||
|
|
||||||
|
CACHE_DIR="/var/cache/opsec"
|
||||||
|
CACHE_FILE="${CACHE_DIR}/wan_ip"
|
||||||
|
ENDPOINTS="https://icanhazip.com https://ifconfig.me https://api.ipify.org"
|
||||||
|
|
||||||
|
mkdir -p "$CACHE_DIR"
|
||||||
|
chmod 755 "$CACHE_DIR"
|
||||||
|
|
||||||
|
PUB_IP=""
|
||||||
|
|
||||||
|
# Try via Tor first if running
|
||||||
|
if pgrep -x tor >/dev/null 2>&1 && ss -tln 2>/dev/null | grep -q ':9050 '; then
|
||||||
|
for ep in $ENDPOINTS; do
|
||||||
|
PUB_IP=$(curl -s --max-time 10 --socks5-hostname 127.0.0.1:9050 "$ep" 2>/dev/null | tr -d '[:space:]')
|
||||||
|
[ -n "$PUB_IP" ] && break
|
||||||
|
done
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Fallback to direct if Tor failed or not running
|
||||||
|
if [ -z "$PUB_IP" ]; then
|
||||||
|
for ep in $ENDPOINTS; do
|
||||||
|
PUB_IP=$(curl -s --max-time 5 "$ep" 2>/dev/null | tr -d '[:space:]')
|
||||||
|
[ -n "$PUB_IP" ] && break
|
||||||
|
done
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Only write if we got a valid-looking IP
|
||||||
|
if echo "$PUB_IP" | grep -qE '^[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+$'; then
|
||||||
|
echo "$PUB_IP" > "$CACHE_FILE"
|
||||||
|
chmod 644 "$CACHE_FILE"
|
||||||
|
fi
|
||||||
Executable
+320
@@ -0,0 +1,320 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
# /usr/local/bin/opsec-banner.sh — Terminal OPSEC Status Banner
|
||||||
|
# Mirrors the Conky desktop widget layout and Widget theme colors
|
||||||
|
# Modes: compact | full | off (configured via OPSEC_BANNER in opsec.conf)
|
||||||
|
|
||||||
|
OPSEC_CONF="/etc/opsec/opsec.conf"
|
||||||
|
[ -f "$OPSEC_CONF" ] && . "$OPSEC_CONF"
|
||||||
|
|
||||||
|
BANNER_MODE="${OPSEC_BANNER:-compact}"
|
||||||
|
[ -n "$1" ] && BANNER_MODE="$1"
|
||||||
|
[ "$BANNER_MODE" = "off" ] && exit 0
|
||||||
|
|
||||||
|
# ─── Widget theme colors (matching Conky widget) ────────────────────────────
|
||||||
|
R=$'\e[0m'
|
||||||
|
BLD=$'\e[1m'
|
||||||
|
C0=$'\e[38;5;135m' # ALERT/BAD — #B55AFC purple
|
||||||
|
C1=$'\e[38;5;204m' # SECURE/GOOD — #FF63BE hot pink
|
||||||
|
C2=$'\e[38;5;117m' # INFO/NEUTRAL — #85E7FF cyan
|
||||||
|
C4=$'\e[38;5;32m' # STRUCTURAL — #268BD2 blue
|
||||||
|
C5=$'\e[38;5;45m' # TITLE/ACCENT — #07CAF9 bright cyan
|
||||||
|
C6=$'\e[38;5;117m' # LABELS — #85E7FF cyan
|
||||||
|
C7=$'\e[38;5;189m' # VALUES — #ECDEF7 lavender
|
||||||
|
C8=$'\e[38;5;135m' # SECTION HDR — #B55AFC purple
|
||||||
|
C9=$'\e[38;5;66m' # METADATA — #4A6A7A dark grey
|
||||||
|
|
||||||
|
# ─── DETECT STATE ────────────────────────────────────────────────────────────
|
||||||
|
ADVANCED=false
|
||||||
|
[ -f /var/run/opsec-advanced.enabled ] && ADVANCED=true
|
||||||
|
|
||||||
|
BREAKGLASS=false
|
||||||
|
if [ -f /var/run/opsec-breakglass.active ]; then
|
||||||
|
BREAKGLASS=true
|
||||||
|
_bge=$(cat /var/run/opsec-breakglass.active 2>/dev/null)
|
||||||
|
_bgn=$(date +%s)
|
||||||
|
BG_REM=""
|
||||||
|
[ -n "$_bge" ] && [ "$_bge" -gt "$_bgn" ] 2>/dev/null && BG_REM="$(( (_bge - _bgn) / 60 ))m"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# ─── HELPERS ─────────────────────────────────────────────────────────────────
|
||||||
|
HR=" ${C4}$(printf '─%.0s' {1..48})${R}"
|
||||||
|
|
||||||
|
# ─── READ CACHE ──────────────────────────────────────────────────────────────
|
||||||
|
CACHE_FILE="/tmp/.opsec-cache/netinfo"
|
||||||
|
PUB_IP="" PUB_GEO="" ROUTED_TOR=false EXIT_COUNTRY="" EXIT_CHANGE_TIME="" TOR_BOOTSTRAP="" TOR_PHASE=""
|
||||||
|
if [ -f "$CACHE_FILE" ]; then
|
||||||
|
_cr() { grep "^${1}=" "$CACHE_FILE" 2>/dev/null | head -1 | cut -d'"' -f2; }
|
||||||
|
PUB_IP=$(_cr PUB_IP)
|
||||||
|
PUB_GEO=$(_cr PUB_GEO)
|
||||||
|
ROUTED_TOR=$(_cr ROUTED_TOR)
|
||||||
|
EXIT_COUNTRY=$(_cr EXIT_COUNTRY)
|
||||||
|
EXIT_CHANGE_TIME=$(_cr EXIT_CHANGE_TIME)
|
||||||
|
TOR_BOOTSTRAP=$(_cr TOR_BOOTSTRAP)
|
||||||
|
TOR_PHASE=$(_cr TOR_PHASE)
|
||||||
|
PUB_IP=$(echo "$PUB_IP" | tr -cd '0-9.')
|
||||||
|
EXIT_COUNTRY=$(echo "$EXIT_COUNTRY" | tr -cd 'A-Za-z')
|
||||||
|
EXIT_CHANGE_TIME=$(echo "$EXIT_CHANGE_TIME" | tr -cd '0-9')
|
||||||
|
TOR_BOOTSTRAP=$(echo "$TOR_BOOTSTRAP" | tr -cd '0-9')
|
||||||
|
fi
|
||||||
|
|
||||||
|
# ─── GATHER LIVE STATUS ─────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
LOCAL_IP=$(ip route get 1.1.1.1 2>/dev/null | awk '{for(i=1;i<=NF;i++) if($i=="src") print $(i+1)}' | head -1)
|
||||||
|
[ -z "$LOCAL_IP" ] && LOCAL_IP="No route"
|
||||||
|
|
||||||
|
VPN_IF=$(ip -o link show 2>/dev/null | awk -F': ' '{print $2}' | grep -E '^(tun|wg)' | head -1)
|
||||||
|
VPN_OK=false; VPN_IP=""
|
||||||
|
if [ -n "$VPN_IF" ]; then
|
||||||
|
VPN_OK=true
|
||||||
|
VPN_IP=$(ip -4 addr show "$VPN_IF" 2>/dev/null | awk '/inet /{print $2}' | cut -d/ -f1)
|
||||||
|
fi
|
||||||
|
|
||||||
|
PRIMARY_IF=$(ip route get 1.1.1.1 2>/dev/null | awk '{for(i=1;i<=NF;i++) if($i=="dev") print $(i+1)}' | head -1)
|
||||||
|
MAC_OK=false; CUR_MAC=""
|
||||||
|
if [ -n "$PRIMARY_IF" ]; then
|
||||||
|
CUR_MAC=$(ip link show "$PRIMARY_IF" 2>/dev/null | awk '/ether/{print $2}')
|
||||||
|
_oct=$((16#$(echo "$CUR_MAC" | cut -d: -f1))) 2>/dev/null
|
||||||
|
(( _oct & 2 )) 2>/dev/null && MAC_OK=true
|
||||||
|
fi
|
||||||
|
|
||||||
|
# ═════════════════════════════════════════════════════════════════════════════
|
||||||
|
# COMPACT BANNER
|
||||||
|
# ═════════════════════════════════════════════════════════════════════════════
|
||||||
|
|
||||||
|
render_compact() {
|
||||||
|
_si() {
|
||||||
|
local ok=$1 lbl="$2"
|
||||||
|
if $ok; then
|
||||||
|
echo -n "${C6}${lbl}${C1}${BLD}+${R}"
|
||||||
|
else
|
||||||
|
echo -n "${C6}${lbl}${C0}${BLD}x${R}"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
local _tor_ok=false; ss -tln 2>/dev/null | grep -q ':9050 ' && _tor_ok=true
|
||||||
|
local _ks_ok=false
|
||||||
|
[ -f /var/run/opsec-killswitch.enabled ] && _ks_ok=true
|
||||||
|
iptables -L GP_FW >/dev/null 2>&1 && _ks_ok=true
|
||||||
|
local _dns_ok=false
|
||||||
|
local _ds=$(awk '/^nameserver/{print $2;exit}' /etc/resolv.conf 2>/dev/null)
|
||||||
|
case "$_ds" in 127.0.0.1|9.9.9.9|149.112.*|1.1.1.1|1.0.0.1) _dns_ok=true ;; esac
|
||||||
|
local _v6_ok=false
|
||||||
|
[ "$(cat /proc/sys/net/ipv6/conf/all/disable_ipv6 2>/dev/null)" = "1" ] && _v6_ok=true
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
if $BREAKGLASS; then
|
||||||
|
echo " ${C0}${BLD}⚠ BREAKGLASS${R} ${C0}KS bypassed${BG_REM:+ (${BG_REM})}${R}"
|
||||||
|
fi
|
||||||
|
|
||||||
|
if $ADVANCED; then
|
||||||
|
local sigil="${C1}${BLD}▲ ADVANCED${R}"
|
||||||
|
local ind="$(_si $_tor_ok TOR) $(_si $_ks_ok KS) $(_si $_dns_ok DNS) $(_si $MAC_OK MAC) $(_si $_v6_ok v6)"
|
||||||
|
$VPN_OK && ind="$ind $(_si $VPN_OK VPN)"
|
||||||
|
echo " ${sigil} ${ind} ${C9}${LOCAL_IP}${R}"
|
||||||
|
else
|
||||||
|
local sigil="${C2}── STANDARD${R}"
|
||||||
|
local ind="$(_si $_dns_ok DNS) $(_si $MAC_OK MAC)"
|
||||||
|
$VPN_OK && ind="$ind $(_si $VPN_OK VPN)"
|
||||||
|
echo " ${sigil} ${ind} ${C9}${LOCAL_IP}${R}"
|
||||||
|
fi
|
||||||
|
echo ""
|
||||||
|
}
|
||||||
|
|
||||||
|
# ═════════════════════════════════════════════════════════════════════════════
|
||||||
|
# FULL BANNER — mirrors Conky widget
|
||||||
|
# ═════════════════════════════════════════════════════════════════════════════
|
||||||
|
|
||||||
|
render_full() {
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
# ── HEADER ──
|
||||||
|
echo "$HR"
|
||||||
|
if $ADVANCED; then
|
||||||
|
echo " ${C5}${BLD} OPSEC STATUS${R}"
|
||||||
|
echo " ${C1}${BLD} ▲ ADVANCED ▲${R}"
|
||||||
|
else
|
||||||
|
echo " ${C5}${BLD} OPSEC STATUS${R}"
|
||||||
|
echo " ${C2} ── STANDARD ──${R}"
|
||||||
|
fi
|
||||||
|
echo "$HR"
|
||||||
|
|
||||||
|
# Breakglass warning
|
||||||
|
if $BREAKGLASS; then
|
||||||
|
echo " ${C0}${BLD} ⚠ BREAKGLASS — Kill switch bypassed ${BG_REM:+(${BG_REM})}${R}"
|
||||||
|
echo "$HR"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# ── SYSTEM ──
|
||||||
|
echo " ${C8}${BLD} ▌SYSTEM${R}"
|
||||||
|
echo " ${C6} HOST ${C7}$(hostname)${R}$(printf '%*s' 1 '')${C6}UP ${C7}$(uptime -p 2>/dev/null | sed 's/^up //' | sed 's/ hours\?/h/;s/ minutes\?/m/;s/ days\?/d/;s/, */ /g' || uptime | awk -F'up ' '{print $2}' | awk -F, '{print $1}')${R}"
|
||||||
|
local _cpu _mem _memtot _memp
|
||||||
|
_cpu=$(awk '/^cpu /{u=$2+$4; t=$2+$4+$5; if(t>0) printf "%.0f", u*100/t}' /proc/stat 2>/dev/null)
|
||||||
|
_mem=$(free -h 2>/dev/null | awk '/^Mem:/{print $3}')
|
||||||
|
_memtot=$(free -h 2>/dev/null | awk '/^Mem:/{print $2}')
|
||||||
|
_memp=$(free 2>/dev/null | awk '/^Mem:/{if($2>0) printf "%.0f", $3*100/$2}')
|
||||||
|
echo " ${C6} CPU ${C7}${_cpu:-0}%${R}$(printf '%*s' 1 '')${C6}RAM ${C7}${_memp:-0}% ${C9}(${_mem:-?}/${_memtot:-?})${R}"
|
||||||
|
|
||||||
|
# ── NETWORK ──
|
||||||
|
echo "$HR"
|
||||||
|
echo " ${C8}${BLD} ▌NETWORK${R}"
|
||||||
|
echo " ${C6} LOCAL IP ${C7}${LOCAL_IP}${R}"
|
||||||
|
|
||||||
|
# External IP
|
||||||
|
if [ -n "$PUB_IP" ]; then
|
||||||
|
if [ -n "$PUB_GEO" ]; then
|
||||||
|
echo " ${C6} EXT IP ${C7}${PUB_IP} ${C9}(${PUB_GEO})${R}"
|
||||||
|
else
|
||||||
|
echo " ${C6} EXT IP ${C7}${PUB_IP}${R}"
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
echo " ${C6} EXT IP ${C0}UNAVAILABLE${R}"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# VPN
|
||||||
|
if $VPN_OK; then
|
||||||
|
echo " ${C6} VPN ${C1}ACTIVE ${C7}${VPN_IP} ${C9}(${VPN_IF})${R}"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# MAC
|
||||||
|
if [ -n "$CUR_MAC" ]; then
|
||||||
|
if $MAC_OK; then
|
||||||
|
echo " ${C6} MAC ${C1}RANDOM ${C9}(${CUR_MAC})${R}"
|
||||||
|
else
|
||||||
|
echo " ${C6} MAC ${C0}HWADDR ${C9}(${CUR_MAC})${R}"
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
echo " ${C6} MAC ${C9}NO IFACE${R}"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# ── HARDENING (advanced only) ──
|
||||||
|
if $ADVANCED; then
|
||||||
|
echo "$HR"
|
||||||
|
echo " ${C8}${BLD} ▌HARDENING${R}"
|
||||||
|
|
||||||
|
local _v6=$(sysctl -n net.ipv6.conf.all.disable_ipv6 2>/dev/null || echo "0")
|
||||||
|
[ "$_v6" = "1" ] && echo " ${C6} IPv6 ${C1}BLOCKED${R}" || echo " ${C6} IPv6 ${C0}LEAKING${R}"
|
||||||
|
|
||||||
|
if lsattr /etc/resolv.conf 2>/dev/null | grep -q 'i'; then
|
||||||
|
echo " ${C6} DNSLK ${C1}LOCKED ${C9}(immutable)${R}"
|
||||||
|
else
|
||||||
|
echo " ${C6} DNSLK ${C0}UNLOCKED${R}"
|
||||||
|
fi
|
||||||
|
|
||||||
|
local _iso=$(grep "IsolateDestAddr" /etc/tor/torrc 2>/dev/null | head -1)
|
||||||
|
[ -n "$_iso" ] && echo " ${C6} ISOL ${C1}ACTIVE ${C9}(stream isolation)${R}"
|
||||||
|
|
||||||
|
local _pad=$(grep "ConnectionPadding" /etc/tor/torrc 2>/dev/null | awk '{print $2}')
|
||||||
|
[ "$_pad" = "1" ] && echo " ${C6} TPAD ${C1}ACTIVE ${C9}(traffic padding)${R}"
|
||||||
|
|
||||||
|
local _bl=$(grep "ExcludeExitNodes" /etc/tor/torrc 2>/dev/null | sed 's/ExcludeExitNodes //' | tr -d '{}' | tr ',' ' ')
|
||||||
|
[ -n "$_bl" ] && echo " ${C6} BLOCK ${C0}$(echo "$_bl" | tr ' ' ',' | sed 's/,$//')${R}"
|
||||||
|
|
||||||
|
local _core=$(sysctl -n kernel.core_pattern 2>/dev/null)
|
||||||
|
[[ "$_core" == *"/bin/false"* ]] && echo " ${C6} CORE ${C1}BLOCKED${R}" || echo " ${C6} CORE ${C0}ENABLED${R}"
|
||||||
|
|
||||||
|
local _swap=$(swapon --show=SIZE --noheadings 2>/dev/null | head -1)
|
||||||
|
[ -z "$_swap" ] && echo " ${C6} SWAP ${C1}OFF${R}" || echo " ${C6} SWAP ${C0}ACTIVE ${C9}(${_swap})${R}"
|
||||||
|
|
||||||
|
local _bootc=0
|
||||||
|
for _svc in opsec-boot-advanced opsec-mac-randomize opsec-hostname-randomize opsec-killswitch; do
|
||||||
|
systemctl is-enabled "$_svc" 2>/dev/null | grep -q enabled && _bootc=$((_bootc + 1))
|
||||||
|
done
|
||||||
|
if [ "$_bootc" -eq 4 ]; then
|
||||||
|
echo " ${C6} BOOT ${C1}PERSIST ${C9}(${_bootc}/4)${R}"
|
||||||
|
elif [ "$_bootc" -gt 0 ]; then
|
||||||
|
echo " ${C6} BOOT ${C2}PARTIAL ${C9}(${_bootc}/4)${R}"
|
||||||
|
else
|
||||||
|
echo " ${C6} BOOT ${C0}NONE ${C9}(${_bootc}/4)${R}"
|
||||||
|
fi
|
||||||
|
|
||||||
|
local _prof="${PROFILE_NAME:-}"
|
||||||
|
[ -n "$_prof" ] && echo " ${C6} PROF ${C2}${_prof}${R}"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# ── TOR STATUS (advanced only) ──
|
||||||
|
if $ADVANCED; then
|
||||||
|
echo "$HR"
|
||||||
|
echo " ${C8}${BLD} ▌TOR STATUS${R}"
|
||||||
|
|
||||||
|
local _tor_svc=false _tor_socks=false _ks_armed=false _tor_routed=false
|
||||||
|
systemctl is-active tor >/dev/null 2>&1 && _tor_svc=true
|
||||||
|
ss -tln 2>/dev/null | grep -q ':9050 ' && _tor_socks=true
|
||||||
|
$ADVANCED && _ks_armed=true
|
||||||
|
[ "$ROUTED_TOR" = "true" ] && _tor_routed=true
|
||||||
|
|
||||||
|
if $_tor_svc && $_tor_socks && $_ks_armed && $_tor_routed; then
|
||||||
|
echo " ${C6} STATE ${C1}PROTECTED${R}"
|
||||||
|
elif $_tor_svc && ! $_tor_socks; then
|
||||||
|
echo " ${C6} STATE ${C2}BOOTSTRAP ${C9}(${TOR_BOOTSTRAP:-0}% ${TOR_PHASE:-connecting})${R}"
|
||||||
|
elif ! $_tor_svc; then
|
||||||
|
echo " ${C6} STATE ${C0}EXPOSED ${C9}(tor down)${R}"
|
||||||
|
elif ! $_ks_armed; then
|
||||||
|
echo " ${C6} STATE ${C0}EXPOSED ${C9}(kill switch off)${R}"
|
||||||
|
else
|
||||||
|
echo " ${C6} STATE ${C0}EXPOSED ${C9}(not routed)${R}"
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ -n "$EXIT_COUNTRY" ] && [ ${#EXIT_COUNTRY} -le 3 ]; then
|
||||||
|
echo " ${C6} EXIT ${C2}${EXIT_COUNTRY}${R}"
|
||||||
|
elif $_tor_socks; then
|
||||||
|
echo " ${C6} EXIT ${C9}resolving...${R}"
|
||||||
|
else
|
||||||
|
echo " ${C6} EXIT ${C9}—${R}"
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ -n "$EXIT_CHANGE_TIME" ] && [ "$EXIT_CHANGE_TIME" -gt 0 ] 2>/dev/null; then
|
||||||
|
local _now=$(date +%s)
|
||||||
|
local _age=$(( _now - EXIT_CHANGE_TIME ))
|
||||||
|
if [ "$_age" -lt 60 ]; then
|
||||||
|
echo " ${C6} CIRCUIT ${C2}${_age}s ago${R}"
|
||||||
|
elif [ "$_age" -lt 3600 ]; then
|
||||||
|
echo " ${C6} CIRCUIT ${C2}$(( _age / 60 ))m ago${R}"
|
||||||
|
else
|
||||||
|
echo " ${C6} CIRCUIT ${C2}$(( _age / 3600 ))h $(( (_age % 3600) / 60 ))m ago${R}"
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
echo " ${C6} CIRCUIT ${C9}—${R}"
|
||||||
|
fi
|
||||||
|
|
||||||
|
local _dns_server=$(awk '/^nameserver/{print $2;exit}' /etc/resolv.conf 2>/dev/null)
|
||||||
|
local _dns_immutable=false
|
||||||
|
lsattr /etc/resolv.conf 2>/dev/null | grep -q 'i' && _dns_immutable=true
|
||||||
|
local _nm_locked=false
|
||||||
|
[ -f /etc/NetworkManager/conf.d/opsec-dns-lock.conf ] && _nm_locked=true
|
||||||
|
|
||||||
|
if [ "$_dns_server" = "127.0.0.1" ] && $_dns_immutable && $_nm_locked; then
|
||||||
|
echo " ${C6} DNS ${C1}SECURE ${C9}(tor + locked)${R}"
|
||||||
|
elif [ "$_dns_server" = "127.0.0.1" ] && $_dns_immutable; then
|
||||||
|
echo " ${C6} DNS ${C2}SECURE ${C9}(tor, NM unlocked)${R}"
|
||||||
|
elif [ "$_dns_server" = "127.0.0.1" ]; then
|
||||||
|
echo " ${C6} DNS ${C2}PARTIAL ${C9}(tor, not locked)${R}"
|
||||||
|
elif echo "$_dns_server" | grep -qE '^(9\.9\.9\.9|149\.112|1\.1\.1\.1|1\.0\.0\.1)'; then
|
||||||
|
echo " ${C6} DNS ${C2}PRIVACY ${C9}(${_dns_server})${R}"
|
||||||
|
else
|
||||||
|
echo " ${C6} DNS ${C0}LEAKED ${C9}(${_dns_server})${R}"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
# ── MODE / LEVEL ──
|
||||||
|
echo "$HR"
|
||||||
|
if $ADVANCED; then
|
||||||
|
echo " ${C6} MODE ${C1}ADVANCED${R}"
|
||||||
|
else
|
||||||
|
echo " ${C6} MODE ${C0}STANDARD${R}"
|
||||||
|
fi
|
||||||
|
local _lvl="${DEPLOYMENT_LEVEL:-}"
|
||||||
|
[ -n "$_lvl" ] && echo " ${C6} LEVEL ${C7}${_lvl}${R}"
|
||||||
|
|
||||||
|
echo "$HR"
|
||||||
|
echo " ${C9} // $(date +%H:%M:%S) //${R}"
|
||||||
|
echo ""
|
||||||
|
}
|
||||||
|
|
||||||
|
# ═════════════════════════════════════════════════════════════════════════════
|
||||||
|
# DISPATCH
|
||||||
|
# ═════════════════════════════════════════════════════════════════════════════
|
||||||
|
|
||||||
|
case "$BANNER_MODE" in
|
||||||
|
compact) render_compact ;;
|
||||||
|
full) render_full ;;
|
||||||
|
esac
|
||||||
Executable
+82
@@ -0,0 +1,82 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
# /usr/local/bin/opsec-boot-init.sh — Level-aware boot initializer
|
||||||
|
# Called by opsec-boot-advanced.service at boot
|
||||||
|
# Standard levels: apply base privacy hardening only
|
||||||
|
# Paranoid levels: activate full ghost mode
|
||||||
|
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
BOOT_MARKER="/etc/opsec/boot-advanced.enabled"
|
||||||
|
STATE_FILE="/var/run/opsec-advanced.enabled"
|
||||||
|
OPSEC_LIB="/usr/local/lib/opsec/opsec-lib.sh"
|
||||||
|
OPSEC_CONF="/etc/opsec/opsec.conf"
|
||||||
|
|
||||||
|
# Source shared library if available
|
||||||
|
if [ -f "$OPSEC_LIB" ]; then
|
||||||
|
. "$OPSEC_LIB"
|
||||||
|
opsec_load_config 2>/dev/null || true
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Load config directly if lib not available
|
||||||
|
if [ -f "$OPSEC_CONF" ]; then
|
||||||
|
. "$OPSEC_CONF"
|
||||||
|
fi
|
||||||
|
|
||||||
|
LEVEL_TYPE="${LEVEL_TYPE:-standard}"
|
||||||
|
DEPLOYMENT_LEVEL="${DEPLOYMENT_LEVEL:-bare-metal-standard}"
|
||||||
|
|
||||||
|
echo "[opsec-boot] Level: ${DEPLOYMENT_LEVEL} (type: ${LEVEL_TYPE})"
|
||||||
|
|
||||||
|
# Paranoid levels: always activate full ghost mode
|
||||||
|
if [ "$LEVEL_TYPE" = "paranoid" ]; then
|
||||||
|
echo "[opsec-boot] Paranoid level detected — activating full ghost mode"
|
||||||
|
touch "$STATE_FILE"
|
||||||
|
|
||||||
|
if [ -x /usr/local/bin/opsec-mode.sh ]; then
|
||||||
|
/usr/local/bin/opsec-mode.sh on
|
||||||
|
else
|
||||||
|
echo "[opsec-boot] ERROR: opsec-mode.sh not found"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "[opsec-boot] Ghost mode activated at boot (paranoid level)"
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Standard levels: check boot marker, apply base or full accordingly
|
||||||
|
if [ -f "$BOOT_MARKER" ]; then
|
||||||
|
echo "[opsec-boot] Boot marker detected — activating ghost mode"
|
||||||
|
touch "$STATE_FILE"
|
||||||
|
|
||||||
|
if [ -x /usr/local/bin/opsec-mode.sh ]; then
|
||||||
|
/usr/local/bin/opsec-mode.sh on
|
||||||
|
else
|
||||||
|
echo "[opsec-boot] ERROR: opsec-mode.sh not found"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "[opsec-boot] Ghost mode activated at boot (boot marker)"
|
||||||
|
else
|
||||||
|
echo "[opsec-boot] Standard level, no boot marker — applying base privacy hardening"
|
||||||
|
|
||||||
|
# Apply base hardening inline (cannot source opsec-mode.sh — its exit kills the caller)
|
||||||
|
echo "[opsec-boot] Applying inline base hardening..."
|
||||||
|
sysctl -w net.ipv6.conf.all.disable_ipv6=1 >/dev/null 2>&1 || true
|
||||||
|
sysctl -w net.ipv6.conf.default.disable_ipv6=1 >/dev/null 2>&1 || true
|
||||||
|
swapoff -a 2>/dev/null || true
|
||||||
|
sysctl -w kernel.core_pattern='|/bin/false' >/dev/null 2>&1 || true
|
||||||
|
|
||||||
|
# Set privacy DNS
|
||||||
|
base_dns="${BASE_DNS:-quad9}"
|
||||||
|
chattr -i /etc/resolv.conf 2>/dev/null || true
|
||||||
|
case "$base_dns" in
|
||||||
|
quad9)
|
||||||
|
printf "nameserver 9.9.9.9\nnameserver 149.112.112.112\n" > /etc/resolv.conf
|
||||||
|
;;
|
||||||
|
cloudflare)
|
||||||
|
printf "nameserver 1.1.1.1\nnameserver 1.0.0.1\n" > /etc/resolv.conf
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
echo "[opsec-boot] Base privacy hardening applied"
|
||||||
|
fi
|
||||||
Executable
+76
@@ -0,0 +1,76 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
# OPSEC Status Check
|
||||||
|
|
||||||
|
echo "===== OPSEC Status Check ====="
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
# Check for running VPN
|
||||||
|
echo "[*] VPN Status:"
|
||||||
|
if pgrep -x openvpn > /dev/null; then
|
||||||
|
echo " [+] OpenVPN is running"
|
||||||
|
else
|
||||||
|
echo " [-] OpenVPN is NOT running"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Check for Tor
|
||||||
|
echo ""
|
||||||
|
echo "[*] Tor Status:"
|
||||||
|
if systemctl is-active tor >/dev/null 2>&1; then
|
||||||
|
echo " [+] Tor is running"
|
||||||
|
else
|
||||||
|
echo " [-] Tor is NOT running"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Check current connections
|
||||||
|
echo ""
|
||||||
|
echo "[*] Active Connections:"
|
||||||
|
CONNECTIONS=$(ss -tupn | grep ESTAB | wc -l)
|
||||||
|
echo " [*] $CONNECTIONS established connections"
|
||||||
|
|
||||||
|
# Check listening services
|
||||||
|
echo ""
|
||||||
|
echo "[*] Listening Services:"
|
||||||
|
LISTENERS=$(ss -tupln | grep LISTEN | wc -l)
|
||||||
|
echo " [*] $LISTENERS listening services"
|
||||||
|
|
||||||
|
# Check DNS
|
||||||
|
echo ""
|
||||||
|
echo "[*] DNS Configuration:"
|
||||||
|
grep "nameserver" /etc/resolv.conf | head -3
|
||||||
|
|
||||||
|
# Check for history files
|
||||||
|
echo ""
|
||||||
|
echo "[*] History Files:"
|
||||||
|
for hist in ~/.bash_history ~/.zsh_history ~/.python_history ~/.mysql_history; do
|
||||||
|
if [ -f "$hist" ]; then
|
||||||
|
SIZE=$(stat -c%s "$hist" 2>/dev/null)
|
||||||
|
echo " [!] $hist exists (size: $SIZE bytes)"
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
# Check MAC address
|
||||||
|
echo ""
|
||||||
|
echo "[*] Network Interfaces:"
|
||||||
|
for iface in $(ip -o link show | awk -F': ' '{print $2}' | grep -v lo); do
|
||||||
|
MAC=$(ip link show $iface | awk '/ether/ {print $2}')
|
||||||
|
echo " [*] $iface: $MAC"
|
||||||
|
done
|
||||||
|
|
||||||
|
# Check timezone
|
||||||
|
echo ""
|
||||||
|
echo "[*] System Timezone:"
|
||||||
|
echo " [*] $(timedatectl | grep "Time zone" | awk '{print $3}')"
|
||||||
|
|
||||||
|
# Check for running security tools
|
||||||
|
echo ""
|
||||||
|
echo "[*] Running Security Tools:"
|
||||||
|
# Tool inventory is configurable via OPSEC_TOOL_CHECK in opsec.conf
|
||||||
|
OPSEC_TOOL_CHECK="${OPSEC_TOOL_CHECK:-curl wget openssl gpg tor}"
|
||||||
|
for tool in $OPSEC_TOOL_CHECK; do
|
||||||
|
if pgrep -f $tool > /dev/null; then
|
||||||
|
echo " [!] $tool is running"
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
echo "===== Check Complete ====="
|
||||||
Executable
+1044
File diff suppressed because it is too large
Load Diff
Executable
+377
@@ -0,0 +1,377 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
# /usr/local/bin/opsec-harden.sh — System hardening apply/revert
|
||||||
|
# Usage: sudo opsec-harden.sh apply|revert
|
||||||
|
#
|
||||||
|
# Reads settings from /etc/opsec/opsec.conf
|
||||||
|
# Handles: core dumps, swap, timezone, locale, screen lock, WebRTC,
|
||||||
|
# USB blocking, clipboard auto-clear, traffic jitter
|
||||||
|
|
||||||
|
## NOTE: Do NOT use "set -euo pipefail" — it causes silent crashes
|
||||||
|
## when commands like sysctl, dconf, tc, etc. return non-zero.
|
||||||
|
## Use explicit error handling instead (|| true).
|
||||||
|
|
||||||
|
if [ "$EUID" -ne 0 ]; then
|
||||||
|
echo "[-] Please run as root"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
OPSEC_LIB="/usr/local/lib/opsec/opsec-lib.sh"
|
||||||
|
BACKUP_DIR="/etc/opsec/.harden-backup"
|
||||||
|
|
||||||
|
# Source shared library
|
||||||
|
if [ -f "$OPSEC_LIB" ]; then
|
||||||
|
. "$OPSEC_LIB"
|
||||||
|
else
|
||||||
|
# Minimal fallback
|
||||||
|
opsec_green() { echo -e "\033[38;5;49m[+] $*\033[0m"; }
|
||||||
|
opsec_red() { echo -e "\033[38;5;196m[-] $*\033[0m"; }
|
||||||
|
opsec_yellow() { echo -e "\033[38;5;214m[*] $*\033[0m"; }
|
||||||
|
opsec_info() { echo -e "\033[38;5;39m[~] $*\033[0m"; }
|
||||||
|
opsec_load_config() {
|
||||||
|
[ -f /etc/opsec/opsec.conf ] && . /etc/opsec/opsec.conf
|
||||||
|
}
|
||||||
|
fi
|
||||||
|
|
||||||
|
mkdir -p "$BACKUP_DIR"
|
||||||
|
opsec_load_config 2>/dev/null || true
|
||||||
|
|
||||||
|
# ─── CORE DUMPS ────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
apply_core_dumps() {
|
||||||
|
if [ "${HARDEN_CORE_DUMPS:-1}" = "1" ]; then
|
||||||
|
opsec_info "Disabling core dumps..."
|
||||||
|
|
||||||
|
# sysctl
|
||||||
|
sysctl -w kernel.core_pattern='|/bin/false' >/dev/null 2>&1
|
||||||
|
sysctl -w fs.suid_dumpable=0 >/dev/null 2>&1
|
||||||
|
|
||||||
|
# limits.d
|
||||||
|
cat > /etc/security/limits.d/opsec-coredump.conf << 'EOF'
|
||||||
|
# OPSEC: Disable core dumps
|
||||||
|
* hard core 0
|
||||||
|
* soft core 0
|
||||||
|
EOF
|
||||||
|
# systemd coredump
|
||||||
|
mkdir -p /etc/systemd/coredump.conf.d
|
||||||
|
cat > /etc/systemd/coredump.conf.d/opsec.conf << 'EOF'
|
||||||
|
[Coredump]
|
||||||
|
Storage=none
|
||||||
|
ProcessSizeMax=0
|
||||||
|
EOF
|
||||||
|
opsec_green "Core dumps disabled"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
revert_core_dumps() {
|
||||||
|
sysctl -w kernel.core_pattern='core' >/dev/null 2>&1
|
||||||
|
sysctl -w fs.suid_dumpable=1 >/dev/null 2>&1
|
||||||
|
rm -f /etc/security/limits.d/opsec-coredump.conf
|
||||||
|
rm -f /etc/systemd/coredump.conf.d/opsec.conf
|
||||||
|
opsec_green "Core dumps restored"
|
||||||
|
}
|
||||||
|
|
||||||
|
# ─── SWAP ──────────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
apply_swap() {
|
||||||
|
if [ "${HARDEN_SWAP:-1}" = "1" ]; then
|
||||||
|
opsec_info "Disabling swap..."
|
||||||
|
swapoff -a 2>/dev/null || true
|
||||||
|
|
||||||
|
# Comment out swap entries in fstab (backup first)
|
||||||
|
if [ ! -f "$BACKUP_DIR/fstab.swap" ]; then
|
||||||
|
grep -E '^\s*[^#].*\sswap\s' /etc/fstab > "$BACKUP_DIR/fstab.swap" 2>/dev/null || true
|
||||||
|
fi
|
||||||
|
sed -i '/\sswap\s/s/^/#OPSEC# /' /etc/fstab 2>/dev/null || true
|
||||||
|
opsec_green "Swap disabled"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
revert_swap() {
|
||||||
|
sed -i 's/^#OPSEC# //' /etc/fstab 2>/dev/null || true
|
||||||
|
swapon -a 2>/dev/null || true
|
||||||
|
opsec_green "Swap restored"
|
||||||
|
}
|
||||||
|
|
||||||
|
# ─── IPv6 ──────────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
apply_ipv6() {
|
||||||
|
if [ "${HARDEN_IPV6:-1}" = "1" ]; then
|
||||||
|
opsec_info "Disabling IPv6..."
|
||||||
|
sysctl -w net.ipv6.conf.all.disable_ipv6=1 >/dev/null
|
||||||
|
sysctl -w net.ipv6.conf.default.disable_ipv6=1 >/dev/null
|
||||||
|
opsec_green "IPv6 disabled"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
revert_ipv6() {
|
||||||
|
sysctl -w net.ipv6.conf.all.disable_ipv6=0 >/dev/null
|
||||||
|
sysctl -w net.ipv6.conf.default.disable_ipv6=0 >/dev/null
|
||||||
|
opsec_green "IPv6 restored"
|
||||||
|
}
|
||||||
|
|
||||||
|
# ─── TIMEZONE SPOOF ────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
apply_timezone() {
|
||||||
|
if [ "${HARDEN_TIMEZONE_SPOOF:-0}" = "1" ]; then
|
||||||
|
local tz="${HARDEN_TIMEZONE_VALUE:-UTC}"
|
||||||
|
opsec_info "Spoofing timezone to ${tz}..."
|
||||||
|
|
||||||
|
# Backup current timezone
|
||||||
|
timedatectl show -p Timezone --value > "$BACKUP_DIR/timezone.orig" 2>/dev/null || true
|
||||||
|
timedatectl set-timezone "$tz" 2>/dev/null || {
|
||||||
|
ln -sf "/usr/share/zoneinfo/${tz}" /etc/localtime
|
||||||
|
}
|
||||||
|
opsec_green "Timezone set to ${tz}"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
revert_timezone() {
|
||||||
|
if [ -f "$BACKUP_DIR/timezone.orig" ]; then
|
||||||
|
local orig_tz
|
||||||
|
orig_tz=$(cat "$BACKUP_DIR/timezone.orig")
|
||||||
|
timedatectl set-timezone "$orig_tz" 2>/dev/null || true
|
||||||
|
rm -f "$BACKUP_DIR/timezone.orig"
|
||||||
|
opsec_green "Timezone restored to ${orig_tz}"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
# ─── LOCALE SPOOF ─────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
apply_locale() {
|
||||||
|
if [ "${HARDEN_LOCALE_SPOOF:-0}" = "1" ]; then
|
||||||
|
local loc="${HARDEN_LOCALE_VALUE:-en_US.UTF-8}"
|
||||||
|
opsec_info "Spoofing locale to ${loc}..."
|
||||||
|
|
||||||
|
# Backup
|
||||||
|
locale > "$BACKUP_DIR/locale.orig" 2>/dev/null || true
|
||||||
|
export LANG="$loc"
|
||||||
|
export LC_ALL="$loc"
|
||||||
|
echo "LANG=${loc}" > /etc/default/locale.opsec
|
||||||
|
opsec_green "Locale set to ${loc}"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
revert_locale() {
|
||||||
|
rm -f /etc/default/locale.opsec
|
||||||
|
if [ -f "$BACKUP_DIR/locale.orig" ]; then
|
||||||
|
rm -f "$BACKUP_DIR/locale.orig"
|
||||||
|
fi
|
||||||
|
opsec_green "Locale restored"
|
||||||
|
}
|
||||||
|
|
||||||
|
# ─── SCREEN LOCK ───────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
apply_screen_lock() {
|
||||||
|
if [ "${HARDEN_SCREEN_LOCK:-1}" = "1" ]; then
|
||||||
|
local timeout="${HARDEN_SCREEN_LOCK_TIMEOUT:-300}"
|
||||||
|
opsec_info "Setting screen lock timeout to ${timeout}s..."
|
||||||
|
|
||||||
|
# Try GNOME dconf (runs as user via sudo)
|
||||||
|
local real_user="${SUDO_USER:-$USER}"
|
||||||
|
if command -v dconf >/dev/null 2>&1; then
|
||||||
|
su - "$real_user" -c "
|
||||||
|
dconf write /org/gnome/desktop/session/idle-delay 'uint32 ${timeout}' 2>/dev/null
|
||||||
|
dconf write /org/gnome/desktop/screensaver/lock-enabled 'true' 2>/dev/null
|
||||||
|
dconf write /org/gnome/desktop/screensaver/lock-delay 'uint32 0' 2>/dev/null
|
||||||
|
" 2>/dev/null || true
|
||||||
|
fi
|
||||||
|
opsec_green "Screen lock set to ${timeout}s"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
revert_screen_lock() {
|
||||||
|
local real_user="${SUDO_USER:-$USER}"
|
||||||
|
if command -v dconf >/dev/null 2>&1; then
|
||||||
|
su - "$real_user" -c "
|
||||||
|
dconf write /org/gnome/desktop/session/idle-delay 'uint32 900' 2>/dev/null
|
||||||
|
" 2>/dev/null || true
|
||||||
|
fi
|
||||||
|
opsec_green "Screen lock restored to default"
|
||||||
|
}
|
||||||
|
|
||||||
|
# ─── WEBRTC BLOCKING ──────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
apply_webrtc() {
|
||||||
|
if [ "${LEAK_WEBRTC_BLOCK:-1}" = "1" ]; then
|
||||||
|
opsec_info "Blocking WebRTC..."
|
||||||
|
local real_user="${SUDO_USER:-$USER}"
|
||||||
|
local user_home
|
||||||
|
user_home=$(eval echo "~${real_user}")
|
||||||
|
|
||||||
|
# Firefox profiles
|
||||||
|
for profile_dir in "${user_home}"/.mozilla/firefox/*.default* "${user_home}"/.mozilla/firefox/*.opsec*; do
|
||||||
|
[ -d "$profile_dir" ] || continue
|
||||||
|
cat >> "${profile_dir}/user.js" << 'EOF'
|
||||||
|
// OPSEC: Disable WebRTC IP leak
|
||||||
|
user_pref("media.peerconnection.enabled", false);
|
||||||
|
user_pref("media.peerconnection.turn.disable", true);
|
||||||
|
user_pref("media.peerconnection.use_document_iceservers", false);
|
||||||
|
user_pref("media.peerconnection.video.enabled", false);
|
||||||
|
user_pref("media.peerconnection.identity.timeout", 1);
|
||||||
|
EOF
|
||||||
|
done
|
||||||
|
|
||||||
|
# Brave/Chromium policies
|
||||||
|
mkdir -p /etc/brave/policies/managed /etc/chromium/policies/managed
|
||||||
|
cat > /etc/brave/policies/managed/opsec-webrtc.json << 'EOF'
|
||||||
|
{ "WebRtcIPHandling": "disable_non_proxied_udp" }
|
||||||
|
EOF
|
||||||
|
cp /etc/brave/policies/managed/opsec-webrtc.json /etc/chromium/policies/managed/ 2>/dev/null || true
|
||||||
|
opsec_green "WebRTC blocked (Firefox + Brave/Chromium)"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
revert_webrtc() {
|
||||||
|
local real_user="${SUDO_USER:-$USER}"
|
||||||
|
local user_home
|
||||||
|
user_home=$(eval echo "~${real_user}")
|
||||||
|
|
||||||
|
for profile_dir in "${user_home}"/.mozilla/firefox/*.default* "${user_home}"/.mozilla/firefox/*.opsec*; do
|
||||||
|
[ -d "$profile_dir" ] || continue
|
||||||
|
sed -i '/OPSEC: Disable WebRTC/,/peerconnection\.identity/d' "${profile_dir}/user.js" 2>/dev/null || true
|
||||||
|
done
|
||||||
|
rm -f /etc/brave/policies/managed/opsec-webrtc.json
|
||||||
|
rm -f /etc/chromium/policies/managed/opsec-webrtc.json
|
||||||
|
opsec_green "WebRTC restrictions removed"
|
||||||
|
}
|
||||||
|
|
||||||
|
# ─── USB BLOCKING ─────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
apply_usb_block() {
|
||||||
|
if [ "${LEAK_USB_BLOCK:-0}" = "1" ]; then
|
||||||
|
opsec_info "Blocking new USB devices..."
|
||||||
|
echo 0 > /sys/bus/usb/drivers_autoprobe 2>/dev/null || true
|
||||||
|
opsec_green "USB auto-probe disabled (new devices blocked)"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
revert_usb_block() {
|
||||||
|
echo 1 > /sys/bus/usb/drivers_autoprobe 2>/dev/null || true
|
||||||
|
opsec_green "USB auto-probe restored"
|
||||||
|
}
|
||||||
|
|
||||||
|
# ─── CLIPBOARD AUTO-CLEAR ─────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
apply_clipboard_clear() {
|
||||||
|
if [ "${HARDEN_CLIPBOARD_CLEAR:-0}" = "1" ]; then
|
||||||
|
opsec_info "Starting clipboard auto-clear..."
|
||||||
|
# Launch background clearer (clears clipboard every 30 seconds)
|
||||||
|
local pidfile="/var/run/opsec-clipboard.pid"
|
||||||
|
if [ -f "$pidfile" ] && kill -0 "$(cat "$pidfile")" 2>/dev/null; then
|
||||||
|
opsec_yellow "Clipboard cleaner already running"
|
||||||
|
return
|
||||||
|
fi
|
||||||
|
|
||||||
|
(
|
||||||
|
while true; do
|
||||||
|
sleep 30
|
||||||
|
# Clear X clipboard
|
||||||
|
if command -v xclip >/dev/null 2>&1; then
|
||||||
|
echo -n "" | xclip -selection clipboard 2>/dev/null || true
|
||||||
|
echo -n "" | xclip -selection primary 2>/dev/null || true
|
||||||
|
elif command -v xsel >/dev/null 2>&1; then
|
||||||
|
xsel --clipboard --clear 2>/dev/null || true
|
||||||
|
xsel --primary --clear 2>/dev/null || true
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
) &
|
||||||
|
echo $! > "$pidfile"
|
||||||
|
opsec_green "Clipboard auto-clear active (every 30s)"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
revert_clipboard_clear() {
|
||||||
|
local pidfile="/var/run/opsec-clipboard.pid"
|
||||||
|
if [ -f "$pidfile" ]; then
|
||||||
|
kill "$(cat "$pidfile")" 2>/dev/null || true
|
||||||
|
rm -f "$pidfile"
|
||||||
|
fi
|
||||||
|
opsec_green "Clipboard auto-clear stopped"
|
||||||
|
}
|
||||||
|
|
||||||
|
# ─── TRAFFIC JITTER ───────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
apply_traffic_jitter() {
|
||||||
|
if [ "${TRAFFIC_JITTER_ENABLED:-0}" = "1" ]; then
|
||||||
|
local ms="${TRAFFIC_JITTER_MS:-50}"
|
||||||
|
opsec_info "Adding ${ms}ms traffic jitter..."
|
||||||
|
|
||||||
|
# Find primary outbound interface
|
||||||
|
local iface
|
||||||
|
iface=$(ip route get 1.1.1.1 2>/dev/null | awk '{for(i=1;i<=NF;i++) if($i=="dev") print $(i+1)}' | head -1)
|
||||||
|
if [ -n "$iface" ]; then
|
||||||
|
# Remove existing qdisc first
|
||||||
|
tc qdisc del dev "$iface" root 2>/dev/null || true
|
||||||
|
tc qdisc add dev "$iface" root netem delay "${ms}ms" "${ms}ms" distribution normal 2>/dev/null || {
|
||||||
|
opsec_yellow "tc not available — jitter skipped"
|
||||||
|
return
|
||||||
|
}
|
||||||
|
echo "$iface" > "$BACKUP_DIR/jitter-iface"
|
||||||
|
opsec_green "Traffic jitter active on ${iface} (${ms}ms)"
|
||||||
|
else
|
||||||
|
opsec_yellow "No outbound interface found for jitter"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
revert_traffic_jitter() {
|
||||||
|
if [ -f "$BACKUP_DIR/jitter-iface" ]; then
|
||||||
|
local iface
|
||||||
|
iface=$(cat "$BACKUP_DIR/jitter-iface")
|
||||||
|
tc qdisc del dev "$iface" root 2>/dev/null || true
|
||||||
|
rm -f "$BACKUP_DIR/jitter-iface"
|
||||||
|
opsec_green "Traffic jitter removed from ${iface}"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
# ─── MAIN ──────────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
do_apply() {
|
||||||
|
echo ""
|
||||||
|
opsec_hdr "APPLYING SYSTEM HARDENING"
|
||||||
|
echo ""
|
||||||
|
apply_core_dumps
|
||||||
|
apply_swap
|
||||||
|
apply_ipv6
|
||||||
|
apply_timezone
|
||||||
|
apply_locale
|
||||||
|
apply_screen_lock
|
||||||
|
apply_webrtc
|
||||||
|
# Ghost-mode-only features — skip when called from mode_base (OPSEC_BASE_ONLY=1)
|
||||||
|
if [ "${OPSEC_BASE_ONLY:-0}" != "1" ]; then
|
||||||
|
apply_usb_block
|
||||||
|
apply_clipboard_clear
|
||||||
|
apply_traffic_jitter
|
||||||
|
else
|
||||||
|
opsec_info "Base-only mode — skipping USB block, clipboard clear, traffic jitter"
|
||||||
|
fi
|
||||||
|
echo ""
|
||||||
|
opsec_green "All hardening measures applied"
|
||||||
|
}
|
||||||
|
|
||||||
|
do_revert() {
|
||||||
|
echo ""
|
||||||
|
opsec_hdr "REVERTING SYSTEM HARDENING"
|
||||||
|
echo ""
|
||||||
|
revert_core_dumps
|
||||||
|
revert_swap
|
||||||
|
revert_ipv6
|
||||||
|
revert_timezone
|
||||||
|
revert_locale
|
||||||
|
revert_screen_lock
|
||||||
|
revert_webrtc
|
||||||
|
revert_usb_block
|
||||||
|
revert_clipboard_clear
|
||||||
|
revert_traffic_jitter
|
||||||
|
echo ""
|
||||||
|
opsec_green "All hardening measures reverted"
|
||||||
|
}
|
||||||
|
|
||||||
|
case "${1:-}" in
|
||||||
|
apply) do_apply ;;
|
||||||
|
revert) do_revert ;;
|
||||||
|
*)
|
||||||
|
echo "Usage: $(basename "$0") apply|revert"
|
||||||
|
exit 1
|
||||||
|
;;
|
||||||
|
esac
|
||||||
Executable
+64
@@ -0,0 +1,64 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
# OPSEC Hostname Randomization
|
||||||
|
# Config-aware: reads HOSTNAME_PATTERN and HOSTNAME_CUSTOM_PREFIX from /etc/opsec/opsec.conf
|
||||||
|
# Patterns: desktop (desktop-XXXX), random (8 random chars), custom (PREFIX-XXXX)
|
||||||
|
|
||||||
|
if [ "$EUID" -ne 0 ]; then
|
||||||
|
echo "Please run as root"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Source config if available
|
||||||
|
OPSEC_LIB="/usr/local/lib/opsec/opsec-lib.sh"
|
||||||
|
if [ -f "$OPSEC_LIB" ]; then
|
||||||
|
. "$OPSEC_LIB"
|
||||||
|
opsec_load_config 2>/dev/null || true
|
||||||
|
else
|
||||||
|
opsec_green() { echo -e "\033[38;5;49m[+] $*\033[0m"; }
|
||||||
|
opsec_info() { echo -e "\033[38;5;39m[~] $*\033[0m"; }
|
||||||
|
opsec_dim() { echo -e "\033[38;5;244m $*\033[0m"; }
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Config values with defaults
|
||||||
|
PATTERN="${HOSTNAME_PATTERN:-desktop}"
|
||||||
|
PREFIX="${HOSTNAME_CUSTOM_PREFIX:-}"
|
||||||
|
|
||||||
|
RAND_HEX=$(head -c 2 /dev/urandom | od -An -tx1 | tr -d ' ')
|
||||||
|
OLD_HOSTNAME=$(hostname)
|
||||||
|
|
||||||
|
case "$PATTERN" in
|
||||||
|
desktop)
|
||||||
|
NEW_HOSTNAME="desktop-${RAND_HEX}"
|
||||||
|
;;
|
||||||
|
random)
|
||||||
|
NEW_HOSTNAME=$(head -c 4 /dev/urandom | od -An -tx1 | tr -d ' ')
|
||||||
|
;;
|
||||||
|
custom)
|
||||||
|
NEW_HOSTNAME="${PREFIX}-${RAND_HEX}"
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
NEW_HOSTNAME="desktop-${RAND_HEX}"
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
opsec_info "Randomizing hostname..."
|
||||||
|
opsec_dim "Old: ${OLD_HOSTNAME}"
|
||||||
|
opsec_dim "New: ${NEW_HOSTNAME} (pattern: ${PATTERN})"
|
||||||
|
|
||||||
|
# Set hostname via hostnamectl (persistent)
|
||||||
|
hostnamectl set-hostname "$NEW_HOSTNAME" 2>/dev/null || {
|
||||||
|
echo "$NEW_HOSTNAME" > /etc/hostname
|
||||||
|
hostname "$NEW_HOSTNAME"
|
||||||
|
}
|
||||||
|
|
||||||
|
# Update /etc/hosts to match
|
||||||
|
if grep -q "$OLD_HOSTNAME" /etc/hosts 2>/dev/null; then
|
||||||
|
sed -i "s/$OLD_HOSTNAME/$NEW_HOSTNAME/g" /etc/hosts
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Ensure localhost entries exist
|
||||||
|
if ! grep -q "127.0.0.1.*$NEW_HOSTNAME" /etc/hosts; then
|
||||||
|
sed -i "/127\.0\.0\.1/s/$/ $NEW_HOSTNAME/" /etc/hosts
|
||||||
|
fi
|
||||||
|
|
||||||
|
opsec_green "Hostname randomized to: ${NEW_HOSTNAME}"
|
||||||
Executable
+261
@@ -0,0 +1,261 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
# OPSEC Kill Switch — iptables rules to block non-Tor/VPN traffic
|
||||||
|
# Config-aware: reads KILLSWITCH_* settings from /etc/opsec/opsec.conf
|
||||||
|
# Usage: opsec-killswitch.sh on|off|status
|
||||||
|
|
||||||
|
if [ "$EUID" -ne 0 ]; then
|
||||||
|
echo "Please run as root"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Source config if available
|
||||||
|
OPSEC_LIB="/usr/local/lib/opsec/opsec-lib.sh"
|
||||||
|
if [ -f "$OPSEC_LIB" ]; then
|
||||||
|
. "$OPSEC_LIB"
|
||||||
|
opsec_load_config 2>/dev/null || true
|
||||||
|
else
|
||||||
|
opsec_green() { echo -e "\033[38;5;49m[+] $*\033[0m"; }
|
||||||
|
opsec_info() { echo -e "\033[38;5;39m[~] $*\033[0m"; }
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Config values with defaults
|
||||||
|
ALLOW_DHCP="${KILLSWITCH_ALLOW_DHCP:-1}"
|
||||||
|
ALLOW_OPENVPN="${KILLSWITCH_ALLOW_OPENVPN:-1}"
|
||||||
|
ALLOW_WIREGUARD="${KILLSWITCH_ALLOW_WIREGUARD:-1}"
|
||||||
|
EXTRA_PORTS="${KILLSWITCH_EXTRA_PORTS:-}"
|
||||||
|
TRANS_PORT="${TOR_TRANS_PORT:-9040}"
|
||||||
|
DNS_PORT="${TOR_DNS_PORT:-5353}"
|
||||||
|
|
||||||
|
CHAIN="GP_FW"
|
||||||
|
TOR_UID=$(id -u debian-tor 2>/dev/null || id -u tor 2>/dev/null || echo "")
|
||||||
|
KS_LOG="/run/opsec/debug.log"
|
||||||
|
mkdir -p /run/opsec 2>/dev/null || true
|
||||||
|
KS_ERR=$(mktemp /run/opsec/.ks-err.XXXXXX 2>/dev/null || mktemp /tmp/.ks-err.XXXXXX)
|
||||||
|
KS_FAIL=0
|
||||||
|
trap 'rm -f "$KS_ERR"' EXIT
|
||||||
|
|
||||||
|
ks_log() { echo "[$(date -Is)] [killswitch] $*" >> "$KS_LOG"; }
|
||||||
|
|
||||||
|
# Wrapper that logs failures and tracks failure count
|
||||||
|
ipt() {
|
||||||
|
if ! iptables "$@" 2>>"$KS_ERR"; then
|
||||||
|
ks_log "FAILED: iptables $* — $(cat "$KS_ERR" 2>/dev/null)"
|
||||||
|
cat "$KS_ERR" >&2 2>/dev/null
|
||||||
|
KS_FAIL=$((KS_FAIL + 1))
|
||||||
|
: > "$KS_ERR" 2>/dev/null
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
: > "$KS_ERR" 2>/dev/null
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
ipt6() {
|
||||||
|
if ! ip6tables "$@" 2>>"$KS_ERR"; then
|
||||||
|
ks_log "FAILED: ip6tables $* — $(cat "$KS_ERR" 2>/dev/null)"
|
||||||
|
KS_FAIL=$((KS_FAIL + 1))
|
||||||
|
: > "$KS_ERR" 2>/dev/null
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
: > "$KS_ERR" 2>/dev/null
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
ks_filter_on() {
|
||||||
|
opsec_info "Arming OPSEC kill switch (filter-only)..."
|
||||||
|
ks_log "=== KILL SWITCH FILTER-ONLY ==="
|
||||||
|
ks_log "TOR_UID=${TOR_UID:-NONE}"
|
||||||
|
ks_log "ALLOW_DHCP=${ALLOW_DHCP} ALLOW_OPENVPN=${ALLOW_OPENVPN} ALLOW_WIREGUARD=${ALLOW_WIREGUARD}"
|
||||||
|
ks_log "EXTRA_PORTS=${EXTRA_PORTS:-none}"
|
||||||
|
|
||||||
|
iptables -N "$CHAIN" 2>/dev/null || iptables -F "$CHAIN"
|
||||||
|
|
||||||
|
ipt -A "$CHAIN" -o lo -j ACCEPT
|
||||||
|
ipt -A "$CHAIN" -i lo -j ACCEPT
|
||||||
|
ipt -A "$CHAIN" -d 127.0.0.0/8 -j ACCEPT
|
||||||
|
ipt -A "$CHAIN" -m state --state ESTABLISHED,RELATED -j ACCEPT
|
||||||
|
|
||||||
|
if [ -n "$TOR_UID" ]; then
|
||||||
|
ipt -A "$CHAIN" -m owner --uid-owner "$TOR_UID" -j ACCEPT
|
||||||
|
else
|
||||||
|
ks_log "WARNING: No TOR_UID — Tor will be blocked by filter!"
|
||||||
|
fi
|
||||||
|
|
||||||
|
ipt -A "$CHAIN" -o tun+ -j ACCEPT
|
||||||
|
ipt -A "$CHAIN" -i tun+ -j ACCEPT
|
||||||
|
ipt -A "$CHAIN" -o wg+ -j ACCEPT
|
||||||
|
ipt -A "$CHAIN" -i wg+ -j ACCEPT
|
||||||
|
|
||||||
|
[ "$ALLOW_DHCP" = "1" ] && ipt -A "$CHAIN" -p udp --dport 67:68 --sport 67:68 -j ACCEPT
|
||||||
|
[ "$ALLOW_OPENVPN" = "1" ] && { ipt -A "$CHAIN" -p udp --dport 1194 -j ACCEPT; ipt -A "$CHAIN" -p tcp --dport 1194 -j ACCEPT; }
|
||||||
|
[ "$ALLOW_WIREGUARD" = "1" ] && ipt -A "$CHAIN" -p udp --dport 51820 -j ACCEPT
|
||||||
|
|
||||||
|
if [ -n "$EXTRA_PORTS" ]; then
|
||||||
|
local IFS=','
|
||||||
|
for rule in $EXTRA_PORTS; do
|
||||||
|
local proto=$(echo "$rule" | cut -d: -f1)
|
||||||
|
local port=$(echo "$rule" | cut -d: -f2)
|
||||||
|
[ -n "$proto" ] && [ -n "$port" ] && ipt -A "$CHAIN" -p "$proto" --dport "$port" -j ACCEPT
|
||||||
|
done
|
||||||
|
fi
|
||||||
|
|
||||||
|
ipt -A "$CHAIN" -j DROP
|
||||||
|
iptables -D OUTPUT -j "$CHAIN" 2>/dev/null
|
||||||
|
ipt -I OUTPUT 1 -j "$CHAIN"
|
||||||
|
|
||||||
|
# IPv6
|
||||||
|
ip6tables -N "$CHAIN" 2>/dev/null || ip6tables -F "$CHAIN"
|
||||||
|
ipt6 -A "$CHAIN" -o lo -j ACCEPT
|
||||||
|
ipt6 -A "$CHAIN" -j DROP
|
||||||
|
ip6tables -D OUTPUT -j "$CHAIN" 2>/dev/null
|
||||||
|
ipt6 -I OUTPUT 1 -j "$CHAIN"
|
||||||
|
|
||||||
|
ks_log "Filter-only lockdown active (no NAT)"
|
||||||
|
opsec_green "Kill switch filter ARMED (pre-bootstrap)"
|
||||||
|
}
|
||||||
|
|
||||||
|
ks_nat_on() {
|
||||||
|
opsec_info "Arming NAT transparent proxy..."
|
||||||
|
ks_log "=== NAT PROXY ON ==="
|
||||||
|
|
||||||
|
local NAT_CHAIN="GP_NAT"
|
||||||
|
|
||||||
|
# Remove jump before flush to prevent brief bypass window
|
||||||
|
iptables -t nat -D OUTPUT -j "$NAT_CHAIN" 2>/dev/null
|
||||||
|
iptables -t nat -N "$NAT_CHAIN" 2>/dev/null || iptables -t nat -F "$NAT_CHAIN"
|
||||||
|
|
||||||
|
# Tor UID RETURN
|
||||||
|
[ -n "$TOR_UID" ] && ipt -t nat -A "$NAT_CHAIN" -p tcp -m owner --uid-owner "$TOR_UID" -j RETURN
|
||||||
|
|
||||||
|
# VPN RETURN — BEFORE DNS redirect
|
||||||
|
ipt -t nat -A "$NAT_CHAIN" -o tun+ -j RETURN
|
||||||
|
ipt -t nat -A "$NAT_CHAIN" -o wg+ -j RETURN
|
||||||
|
|
||||||
|
# DNS redirect
|
||||||
|
ipt -t nat -A "$NAT_CHAIN" -p udp --dport 53 -j REDIRECT --to-ports "$DNS_PORT"
|
||||||
|
ipt -t nat -A "$NAT_CHAIN" -p tcp --dport 53 -j REDIRECT --to-ports "$DNS_PORT"
|
||||||
|
|
||||||
|
# Loopback RETURN
|
||||||
|
ipt -t nat -A "$NAT_CHAIN" -d 127.0.0.0/8 -j RETURN
|
||||||
|
|
||||||
|
# TCP catch-all TransPort
|
||||||
|
ipt -t nat -A "$NAT_CHAIN" -p tcp -j REDIRECT --to-ports "$TRANS_PORT"
|
||||||
|
|
||||||
|
# Install jump
|
||||||
|
ipt -t nat -I OUTPUT 1 -j "$NAT_CHAIN"
|
||||||
|
|
||||||
|
# Verify chain was populated (at least 3 rules: DNS redirect + TCP catch-all + others)
|
||||||
|
local nat_count
|
||||||
|
nat_count=$(iptables -t nat -L "$NAT_CHAIN" --line-numbers 2>/dev/null | tail -n +3 | wc -l)
|
||||||
|
ks_log "NAT chain rule count: ${nat_count}"
|
||||||
|
|
||||||
|
if [ "$nat_count" -lt 3 ]; then
|
||||||
|
ks_log "WARNING: NAT chain has only ${nat_count} rules — falling back to direct OUTPUT rules"
|
||||||
|
opsec_info "NAT chain underpopulated (${nat_count} rules) — using direct OUTPUT fallback"
|
||||||
|
|
||||||
|
# Remove the jump to the empty/broken chain
|
||||||
|
iptables -t nat -D OUTPUT -j "$NAT_CHAIN" 2>/dev/null
|
||||||
|
iptables -t nat -F "$NAT_CHAIN" 2>/dev/null
|
||||||
|
iptables -t nat -X "$NAT_CHAIN" 2>/dev/null
|
||||||
|
|
||||||
|
# Direct OUTPUT rules as fallback
|
||||||
|
[ -n "$TOR_UID" ] && iptables -t nat -A OUTPUT -p tcp -m owner --uid-owner "$TOR_UID" -j RETURN 2>/dev/null
|
||||||
|
iptables -t nat -A OUTPUT -o tun+ -j RETURN 2>/dev/null
|
||||||
|
iptables -t nat -A OUTPUT -o wg+ -j RETURN 2>/dev/null
|
||||||
|
iptables -t nat -A OUTPUT -p udp --dport 53 -j REDIRECT --to-ports "$DNS_PORT" 2>/dev/null
|
||||||
|
iptables -t nat -A OUTPUT -p tcp --dport 53 -j REDIRECT --to-ports "$DNS_PORT" 2>/dev/null
|
||||||
|
iptables -t nat -A OUTPUT -d 127.0.0.0/8 -j RETURN 2>/dev/null
|
||||||
|
iptables -t nat -A OUTPUT -p tcp -j REDIRECT --to-ports "$TRANS_PORT" 2>/dev/null
|
||||||
|
|
||||||
|
local fb_count
|
||||||
|
fb_count=$(iptables -t nat -L OUTPUT --line-numbers 2>/dev/null | tail -n +3 | wc -l)
|
||||||
|
ks_log "Fallback OUTPUT NAT rule count: ${fb_count}"
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ "$KS_FAIL" -gt 0 ]; then
|
||||||
|
ks_log "WARNING: ${KS_FAIL} iptables commands failed during NAT setup"
|
||||||
|
opsec_info "WARNING: ${KS_FAIL} iptables rule(s) failed — check /run/opsec/debug.log"
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
ks_log "NAT transparent proxy armed"
|
||||||
|
opsec_green "Kill switch fully ARMED — Tor + NAT active"
|
||||||
|
}
|
||||||
|
|
||||||
|
ks_on() {
|
||||||
|
ks_filter_on
|
||||||
|
ks_nat_on
|
||||||
|
}
|
||||||
|
|
||||||
|
ks_off() {
|
||||||
|
opsec_info "Disarming OPSEC kill switch..."
|
||||||
|
ks_log "=== KILL SWITCH OFF ==="
|
||||||
|
|
||||||
|
# Filter chain
|
||||||
|
iptables -D OUTPUT -j "$CHAIN" 2>/dev/null
|
||||||
|
iptables -F "$CHAIN" 2>/dev/null
|
||||||
|
iptables -X "$CHAIN" 2>/dev/null
|
||||||
|
|
||||||
|
# NAT chain (config-independent teardown)
|
||||||
|
local NAT_CHAIN="GP_NAT"
|
||||||
|
iptables -t nat -D OUTPUT -j "$NAT_CHAIN" 2>/dev/null
|
||||||
|
iptables -t nat -F "$NAT_CHAIN" 2>/dev/null
|
||||||
|
iptables -t nat -X "$NAT_CHAIN" 2>/dev/null
|
||||||
|
|
||||||
|
# Clean up fallback direct OUTPUT NAT rules (if chain approach failed during arm)
|
||||||
|
# Loop-delete to catch duplicates — keeps removing until no match left
|
||||||
|
local _fb_cleaned=0
|
||||||
|
while iptables -t nat -D OUTPUT -p tcp -j REDIRECT --to-ports "$TRANS_PORT" 2>/dev/null; do _fb_cleaned=$((_fb_cleaned+1)); done
|
||||||
|
while iptables -t nat -D OUTPUT -p udp --dport 53 -j REDIRECT --to-ports "$DNS_PORT" 2>/dev/null; do _fb_cleaned=$((_fb_cleaned+1)); done
|
||||||
|
while iptables -t nat -D OUTPUT -p tcp --dport 53 -j REDIRECT --to-ports "$DNS_PORT" 2>/dev/null; do _fb_cleaned=$((_fb_cleaned+1)); done
|
||||||
|
while iptables -t nat -D OUTPUT -d 127.0.0.0/8 -j RETURN 2>/dev/null; do _fb_cleaned=$((_fb_cleaned+1)); done
|
||||||
|
while iptables -t nat -D OUTPUT -o tun+ -j RETURN 2>/dev/null; do _fb_cleaned=$((_fb_cleaned+1)); done
|
||||||
|
while iptables -t nat -D OUTPUT -o wg+ -j RETURN 2>/dev/null; do _fb_cleaned=$((_fb_cleaned+1)); done
|
||||||
|
if [ -n "$TOR_UID" ]; then
|
||||||
|
while iptables -t nat -D OUTPUT -p tcp -m owner --uid-owner "$TOR_UID" -j RETURN 2>/dev/null; do _fb_cleaned=$((_fb_cleaned+1)); done
|
||||||
|
fi
|
||||||
|
[ "$_fb_cleaned" -gt 0 ] && ks_log "Cleaned ${_fb_cleaned} fallback NAT OUTPUT rules"
|
||||||
|
|
||||||
|
# IPv6
|
||||||
|
ip6tables -D OUTPUT -j "$CHAIN" 2>/dev/null
|
||||||
|
ip6tables -F "$CHAIN" 2>/dev/null
|
||||||
|
ip6tables -X "$CHAIN" 2>/dev/null
|
||||||
|
|
||||||
|
# Flush conntrack to clear stale NAT entries
|
||||||
|
conntrack -F 2>/dev/null && ks_log "conntrack flushed" || true
|
||||||
|
|
||||||
|
ks_log "Kill switch disarmed"
|
||||||
|
opsec_green "Kill switch DISARMED — normal traffic allowed"
|
||||||
|
}
|
||||||
|
|
||||||
|
ks_status() {
|
||||||
|
if iptables -L "$CHAIN" >/dev/null 2>&1; then
|
||||||
|
opsec_green "Kill switch is ARMED"
|
||||||
|
echo ""
|
||||||
|
echo "IPv4 rules:"
|
||||||
|
iptables -L "$CHAIN" -v -n --line-numbers
|
||||||
|
echo ""
|
||||||
|
echo "NAT chain:"
|
||||||
|
if iptables -t nat -L GP_NAT >/dev/null 2>&1; then
|
||||||
|
iptables -t nat -L GP_NAT -v -n --line-numbers
|
||||||
|
else
|
||||||
|
echo " (not armed)"
|
||||||
|
fi
|
||||||
|
echo ""
|
||||||
|
echo "IPv6 rules:"
|
||||||
|
ip6tables -L "$CHAIN" -v -n --line-numbers 2>/dev/null
|
||||||
|
else
|
||||||
|
opsec_info "Kill switch is OFF"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
case "${1}" in
|
||||||
|
on) ks_on ;;
|
||||||
|
filter-on) ks_filter_on ;;
|
||||||
|
nat-on) ks_nat_on ;;
|
||||||
|
off) ks_off ;;
|
||||||
|
status) ks_status ;;
|
||||||
|
*)
|
||||||
|
echo "Usage: $(basename "$0") on|off|filter-on|nat-on|status"
|
||||||
|
exit 1
|
||||||
|
;;
|
||||||
|
esac
|
||||||
Executable
+97
@@ -0,0 +1,97 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
# /usr/local/bin/opsec-log-rotate.sh — Secure log rotation and cleanup
|
||||||
|
# Truncates sensitive logs, shreds rotated files, clears journald + shell histories
|
||||||
|
# Runs via cron (configurable LOG_ROTATION_HOURS in /etc/opsec/opsec.conf)
|
||||||
|
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
if [ "$EUID" -ne 0 ]; then
|
||||||
|
echo "[-] Please run as root"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
OPSEC_LIB="/usr/local/lib/opsec/opsec-lib.sh"
|
||||||
|
_HAS_LIB=false
|
||||||
|
if [ -f "$OPSEC_LIB" ]; then
|
||||||
|
. "$OPSEC_LIB"
|
||||||
|
opsec_load_config 2>/dev/null || true
|
||||||
|
_HAS_LIB=true
|
||||||
|
else
|
||||||
|
opsec_green() { echo "[+] $*"; }
|
||||||
|
opsec_info() { echo "[~] $*"; }
|
||||||
|
fi
|
||||||
|
|
||||||
|
LOG_FILE="/var/log/opsec-log-rotate.log"
|
||||||
|
TIMESTAMP=$(date '+%Y-%m-%d %H:%M:%S')
|
||||||
|
|
||||||
|
log() { echo "[$TIMESTAMP] $*" >> "$LOG_FILE"; }
|
||||||
|
|
||||||
|
log "Starting secure log rotation"
|
||||||
|
|
||||||
|
# ─── TRUNCATE SENSITIVE SYSTEM LOGS ────────────────────────────────────────────
|
||||||
|
SENSITIVE_LOGS=(
|
||||||
|
/var/log/auth.log
|
||||||
|
/var/log/syslog
|
||||||
|
/var/log/kern.log
|
||||||
|
/var/log/daemon.log
|
||||||
|
/var/log/messages
|
||||||
|
/var/log/user.log
|
||||||
|
/var/log/mail.log
|
||||||
|
/var/log/debug
|
||||||
|
/var/log/wtmp
|
||||||
|
/var/log/btmp
|
||||||
|
/var/log/lastlog
|
||||||
|
/var/log/faillog
|
||||||
|
)
|
||||||
|
|
||||||
|
for logfile in "${SENSITIVE_LOGS[@]}"; do
|
||||||
|
if [ -f "$logfile" ]; then
|
||||||
|
truncate -s 0 "$logfile" 2>/dev/null || true
|
||||||
|
log "Truncated: $logfile"
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
# ─── SHRED ROTATED LOG FILES (SSD-aware) ─────────────────────────────────────
|
||||||
|
for rotated in /var/log/*.gz /var/log/*.1 /var/log/*.old; do
|
||||||
|
if [ -f "$rotated" ]; then
|
||||||
|
if [ "$_HAS_LIB" = "true" ]; then
|
||||||
|
opsec_secure_delete "$rotated"
|
||||||
|
else
|
||||||
|
shred -fuz "$rotated" 2>/dev/null || rm -f "$rotated"
|
||||||
|
fi
|
||||||
|
log "Wiped: $rotated"
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
# ─── CLEAR JOURNALD ───────────────────────────────────────────────────────────
|
||||||
|
if command -v journalctl >/dev/null 2>&1; then
|
||||||
|
journalctl --vacuum-time=1h 2>/dev/null || true
|
||||||
|
log "Journald vacuumed (1h retention)"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# ─── CLEAR SHELL HISTORIES ────────────────────────────────────────────────────
|
||||||
|
for user_home in /home/* /root; do
|
||||||
|
[ -d "$user_home" ] || continue
|
||||||
|
for hist_file in .bash_history .zsh_history .python_history .psql_history .mysql_history .lesshst .viminfo; do
|
||||||
|
if [ -f "${user_home}/${hist_file}" ]; then
|
||||||
|
if [ "$_HAS_LIB" = "true" ]; then
|
||||||
|
opsec_secure_delete "${user_home}/${hist_file}"
|
||||||
|
else
|
||||||
|
shred -fuz "${user_home}/${hist_file}" 2>/dev/null || truncate -s 0 "${user_home}/${hist_file}" 2>/dev/null || true
|
||||||
|
fi
|
||||||
|
log "Cleared: ${user_home}/${hist_file}"
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
done
|
||||||
|
|
||||||
|
# ─── CLEAR RECENTLY USED ──────────────────────────────────────────────────────
|
||||||
|
for user_home in /home/* /root; do
|
||||||
|
[ -d "$user_home" ] || continue
|
||||||
|
rm -f "${user_home}/.local/share/recently-used.xbel" 2>/dev/null || true
|
||||||
|
done
|
||||||
|
|
||||||
|
# ─── CLEAR TEMP FILES ─────────────────────────────────────────────────────────
|
||||||
|
find /tmp -type f -mmin +60 -delete 2>/dev/null || true
|
||||||
|
find /var/tmp -type f -mmin +60 -delete 2>/dev/null || true
|
||||||
|
|
||||||
|
log "Secure log rotation complete"
|
||||||
Executable
+25
@@ -0,0 +1,25 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
# opsec-mode-toggle — Keyboard shortcut wrapper for opsec-mode on/off
|
||||||
|
# Toggles between standard and advanced OPSEC mode
|
||||||
|
# Bind to: Ctrl+Alt+\
|
||||||
|
|
||||||
|
STATE_FILE="/var/run/opsec-advanced.enabled"
|
||||||
|
BOOTSTRAP_STOP="/var/run/opsec-bootstrap-stop"
|
||||||
|
DEBUG_LOG="/var/log/opsec-debug.log"
|
||||||
|
|
||||||
|
toggle_log() {
|
||||||
|
echo "[$(date -Is)] [toggle] $*" >> "$DEBUG_LOG" 2>/dev/null || true
|
||||||
|
}
|
||||||
|
|
||||||
|
if [ -f "$STATE_FILE" ]; then
|
||||||
|
# Turning OFF — signal bootstrap loop to stop, then run mode_off
|
||||||
|
toggle_log "Toggling OFF (state file exists)"
|
||||||
|
# Create stop signal so mode_on's bootstrap loop exits immediately
|
||||||
|
# (needs root — use pkexec for a one-liner)
|
||||||
|
pkexec bash -c "touch ${BOOTSTRAP_STOP} && /usr/local/bin/opsec-mode.sh off"
|
||||||
|
toggle_log "Toggle OFF complete (exit: $?)"
|
||||||
|
else
|
||||||
|
toggle_log "Toggling ON (no state file)"
|
||||||
|
pkexec /usr/local/bin/opsec-mode.sh on
|
||||||
|
toggle_log "Toggle ON complete (exit: $?)"
|
||||||
|
fi
|
||||||
Executable
+1210
File diff suppressed because it is too large
Load Diff
Executable
+119
@@ -0,0 +1,119 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
# /usr/local/bin/opsec-monitor.sh — Background connection monitor
|
||||||
|
# Watches for non-Tor/non-VPN outbound connections, new listening ports,
|
||||||
|
# unexpected DNS queries. Sends desktop notifications + logs.
|
||||||
|
# Usage: sudo opsec-monitor.sh start|stop|status
|
||||||
|
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
PIDFILE="/var/run/opsec-monitor.pid"
|
||||||
|
LOGFILE="/var/log/opsec-monitor.log"
|
||||||
|
INTERVAL=10
|
||||||
|
|
||||||
|
OPSEC_LIB="/usr/local/lib/opsec/opsec-lib.sh"
|
||||||
|
if [ -f "$OPSEC_LIB" ]; then
|
||||||
|
. "$OPSEC_LIB"
|
||||||
|
else
|
||||||
|
opsec_green() { echo "[+] $*"; }
|
||||||
|
opsec_red() { echo "[-] $*"; }
|
||||||
|
opsec_info() { echo "[~] $*"; }
|
||||||
|
fi
|
||||||
|
|
||||||
|
notify() {
|
||||||
|
local msg="$1" urgency="${2:-normal}"
|
||||||
|
local real_user="${SUDO_USER:-$USER}"
|
||||||
|
# Desktop notification
|
||||||
|
su - "$real_user" -c "DISPLAY=:0 DBUS_SESSION_BUS_ADDRESS=unix:path=/run/user/$(id -u "$real_user")/bus notify-send -u '$urgency' 'OPSEC Monitor' '$msg'" 2>/dev/null || true
|
||||||
|
# Log
|
||||||
|
echo "[$(date '+%H:%M:%S')] $msg" >> "$LOGFILE"
|
||||||
|
}
|
||||||
|
|
||||||
|
monitor_loop() {
|
||||||
|
local known_listeners=""
|
||||||
|
local tor_uid
|
||||||
|
tor_uid=$(id -u debian-tor 2>/dev/null || id -u tor 2>/dev/null || echo "")
|
||||||
|
|
||||||
|
while true; do
|
||||||
|
# ─── Check for non-Tor/VPN outbound connections ────────────────────
|
||||||
|
local suspicious
|
||||||
|
suspicious=$(ss -tunp 2>/dev/null | grep ESTAB | grep -v '127.0.0.1' | grep -v '::1' | \
|
||||||
|
grep -v "tun\|wg\|tor\|${tor_uid:-NOOP}" | \
|
||||||
|
grep -v ':9050\|:5353\|:1194\|:51820' || true)
|
||||||
|
|
||||||
|
if [ -n "$suspicious" ]; then
|
||||||
|
local count
|
||||||
|
count=$(echo "$suspicious" | wc -l)
|
||||||
|
notify "ALERT: ${count} non-Tor/VPN outbound connection(s) detected" "critical"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# ─── Check for new listening ports ─────────────────────────────────
|
||||||
|
local current_listeners
|
||||||
|
current_listeners=$(ss -tlnp 2>/dev/null | tail -n +2 | awk '{print $4}' | sort)
|
||||||
|
|
||||||
|
if [ -n "$known_listeners" ] && [ "$current_listeners" != "$known_listeners" ]; then
|
||||||
|
local new_ports
|
||||||
|
new_ports=$(comm -13 <(echo "$known_listeners") <(echo "$current_listeners") 2>/dev/null || true)
|
||||||
|
if [ -n "$new_ports" ]; then
|
||||||
|
notify "New listening port(s): ${new_ports}" "critical"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
known_listeners="$current_listeners"
|
||||||
|
|
||||||
|
# ─── Check for unexpected DNS queries ──────────────────────────────
|
||||||
|
local dns_leaks
|
||||||
|
dns_leaks=$(ss -tunp 2>/dev/null | grep ':53 ' | grep -v '127.0.0.1' | grep -v '::1' || true)
|
||||||
|
if [ -n "$dns_leaks" ]; then
|
||||||
|
notify "DNS LEAK: Non-local DNS query detected" "critical"
|
||||||
|
fi
|
||||||
|
|
||||||
|
sleep "$INTERVAL"
|
||||||
|
done
|
||||||
|
}
|
||||||
|
|
||||||
|
do_start() {
|
||||||
|
if [ -f "$PIDFILE" ] && kill -0 "$(cat "$PIDFILE")" 2>/dev/null; then
|
||||||
|
opsec_info "Monitor already running (PID: $(cat "$PIDFILE"))"
|
||||||
|
return
|
||||||
|
fi
|
||||||
|
|
||||||
|
monitor_loop &
|
||||||
|
echo $! > "$PIDFILE"
|
||||||
|
opsec_green "Monitor started (PID: $!, logging to ${LOGFILE})"
|
||||||
|
}
|
||||||
|
|
||||||
|
do_stop() {
|
||||||
|
if [ -f "$PIDFILE" ]; then
|
||||||
|
local pid
|
||||||
|
pid=$(cat "$PIDFILE")
|
||||||
|
kill "$pid" 2>/dev/null || true
|
||||||
|
# Kill child processes
|
||||||
|
pkill -P "$pid" 2>/dev/null || true
|
||||||
|
rm -f "$PIDFILE"
|
||||||
|
opsec_green "Monitor stopped"
|
||||||
|
else
|
||||||
|
opsec_info "Monitor not running"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
do_status() {
|
||||||
|
if [ -f "$PIDFILE" ] && kill -0 "$(cat "$PIDFILE")" 2>/dev/null; then
|
||||||
|
opsec_green "Monitor running (PID: $(cat "$PIDFILE"))"
|
||||||
|
if [ -f "$LOGFILE" ]; then
|
||||||
|
echo ""
|
||||||
|
echo "Last 10 log entries:"
|
||||||
|
tail -10 "$LOGFILE" 2>/dev/null || true
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
opsec_info "Monitor not running"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
case "${1:-}" in
|
||||||
|
start) do_start ;;
|
||||||
|
stop) do_stop ;;
|
||||||
|
status) do_status ;;
|
||||||
|
*)
|
||||||
|
echo "Usage: $(basename "$0") start|stop|status"
|
||||||
|
exit 1
|
||||||
|
;;
|
||||||
|
esac
|
||||||
Executable
+291
@@ -0,0 +1,291 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
# /usr/local/bin/opsec-preflight.sh — OPSEC Preflight Verification Gate
|
||||||
|
# Verifies all required security controls are active for the current mode.
|
||||||
|
#
|
||||||
|
# Usage:
|
||||||
|
# opsec-preflight.sh Human-readable HUD
|
||||||
|
# opsec-preflight.sh --score Pass/fail count
|
||||||
|
# opsec-preflight.sh --enforce Exit 1 if any applicable check fails
|
||||||
|
# opsec-preflight.sh --base Only run base checks (standard mode)
|
||||||
|
# opsec-preflight.sh --full Run base + ghost checks
|
||||||
|
# opsec-preflight.sh --base --enforce Base checks, exit 1 on fail
|
||||||
|
# opsec-preflight.sh --full --enforce Full checks, exit 1 on fail
|
||||||
|
|
||||||
|
OPSEC_LIB="/usr/local/lib/opsec/opsec-lib.sh"
|
||||||
|
OPSEC_CONF="/etc/opsec/opsec.conf"
|
||||||
|
PREFLIGHT_LOG="/var/log/opsec-preflight.log"
|
||||||
|
|
||||||
|
# Source library
|
||||||
|
if [ -f "$OPSEC_LIB" ]; then
|
||||||
|
. "$OPSEC_LIB"
|
||||||
|
opsec_load_config 2>/dev/null || true
|
||||||
|
elif [ -f "$OPSEC_CONF" ]; then
|
||||||
|
. "$OPSEC_CONF"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Fallback colors if lib not loaded
|
||||||
|
type opsec_green >/dev/null 2>&1 || {
|
||||||
|
opsec_green() { echo -e "\033[38;5;49m[+] $*\033[0m"; }
|
||||||
|
opsec_red() { echo -e "\033[38;5;196m[-] $*\033[0m"; }
|
||||||
|
opsec_yellow() { echo -e "\033[38;5;214m[*] $*\033[0m"; }
|
||||||
|
opsec_info() { echo -e "\033[38;5;39m[~] $*\033[0m"; }
|
||||||
|
opsec_cyan() { echo -e "\033[38;5;51m[>] $*\033[0m"; }
|
||||||
|
opsec_dim() { echo -e "\033[38;5;244m $*\033[0m"; }
|
||||||
|
opsec_hdr() { echo -e "\033[38;5;51m━━━ \033[38;5;201m$*\033[38;5;51m ━━━\033[0m"; }
|
||||||
|
}
|
||||||
|
|
||||||
|
# ─── PARSE ARGS ───────────────────────────────────────────────────────────────
|
||||||
|
MODE_SCORE=false
|
||||||
|
MODE_ENFORCE=false
|
||||||
|
CHECK_LEVEL=""
|
||||||
|
|
||||||
|
while [ $# -gt 0 ]; do
|
||||||
|
case "$1" in
|
||||||
|
--score) MODE_SCORE=true ;;
|
||||||
|
--enforce) MODE_ENFORCE=true ;;
|
||||||
|
--base) CHECK_LEVEL="base" ;;
|
||||||
|
--full) CHECK_LEVEL="full" ;;
|
||||||
|
*) echo "Usage: opsec-preflight.sh [--score|--enforce] [--base|--full]"; exit 1 ;;
|
||||||
|
esac
|
||||||
|
shift
|
||||||
|
done
|
||||||
|
|
||||||
|
# Auto-detect check level if not specified
|
||||||
|
if [ -z "$CHECK_LEVEL" ]; then
|
||||||
|
if [ -f /var/run/opsec-advanced.enabled ]; then
|
||||||
|
CHECK_LEVEL="full"
|
||||||
|
else
|
||||||
|
CHECK_LEVEL="base"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
# ─── ENVIRONMENT ──────────────────────────────────────────────────────────────
|
||||||
|
LEVEL="${DEPLOYMENT_LEVEL:-bare-metal-standard}"
|
||||||
|
LTYPE="${LEVEL_TYPE:-standard}"
|
||||||
|
|
||||||
|
is_cloud() {
|
||||||
|
case "$LEVEL" in
|
||||||
|
cloud-*) return 0 ;;
|
||||||
|
*) return 1 ;;
|
||||||
|
esac
|
||||||
|
}
|
||||||
|
|
||||||
|
is_bare_metal() {
|
||||||
|
case "$LEVEL" in
|
||||||
|
bare-metal-*) return 0 ;;
|
||||||
|
*) return 1 ;;
|
||||||
|
esac
|
||||||
|
}
|
||||||
|
|
||||||
|
# ─── CHECK ENGINE ─────────────────────────────────────────────────────────────
|
||||||
|
PASS=0
|
||||||
|
FAIL=0
|
||||||
|
SKIP=0
|
||||||
|
FAILURES=""
|
||||||
|
|
||||||
|
check() {
|
||||||
|
local name="$1" result="$2" detail="${3:-}"
|
||||||
|
if [ "$result" = "pass" ]; then
|
||||||
|
PASS=$((PASS + 1))
|
||||||
|
$MODE_SCORE || opsec_green "PASS ${name}${detail:+ ${detail}}"
|
||||||
|
elif [ "$result" = "fail" ]; then
|
||||||
|
FAIL=$((FAIL + 1))
|
||||||
|
FAILURES="${FAILURES}\n - ${name}${detail:+: ${detail}}"
|
||||||
|
$MODE_SCORE || opsec_red "FAIL ${name}${detail:+ ${detail}}"
|
||||||
|
elif [ "$result" = "skip" ]; then
|
||||||
|
SKIP=$((SKIP + 1))
|
||||||
|
$MODE_SCORE || opsec_dim "SKIP ${name}${detail:+ (${detail})}"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
# ─── BASE CHECKS (always run) ────────────────────────────────────────────────
|
||||||
|
run_base_checks() {
|
||||||
|
$MODE_SCORE || opsec_cyan "Base Privacy Checks"
|
||||||
|
|
||||||
|
# 1. MAC randomized (bare-metal only)
|
||||||
|
if is_bare_metal; then
|
||||||
|
local pif cur_mac first_octet first_dec
|
||||||
|
pif=$(ip route get 1.1.1.1 2>/dev/null | awk '{for(i=1;i<=NF;i++) if($i=="dev") print $(i+1)}' | head -1)
|
||||||
|
if [ -n "$pif" ]; then
|
||||||
|
cur_mac=$(ip link show "$pif" 2>/dev/null | awk '/ether/ {print $2}')
|
||||||
|
first_octet=$(echo "$cur_mac" | cut -d: -f1)
|
||||||
|
first_dec=$((16#${first_octet})) 2>/dev/null || first_dec=0
|
||||||
|
if (( first_dec & 2 )); then
|
||||||
|
check "MAC randomized" "pass" "${pif}: ${cur_mac}"
|
||||||
|
else
|
||||||
|
check "MAC randomized" "fail" "${pif}: ${cur_mac} (hardware address)"
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
check "MAC randomized" "skip" "no primary interface"
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
check "MAC randomized" "skip" "cloud deployment"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# 2. IPv6 disabled
|
||||||
|
local ipv6_all
|
||||||
|
ipv6_all=$(sysctl -n net.ipv6.conf.all.disable_ipv6 2>/dev/null || echo "0")
|
||||||
|
if [ "$ipv6_all" = "1" ]; then
|
||||||
|
check "IPv6 disabled" "pass"
|
||||||
|
else
|
||||||
|
check "IPv6 disabled" "fail" "net.ipv6.conf.all.disable_ipv6=${ipv6_all}"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# 3. Core dumps disabled
|
||||||
|
local core_pat
|
||||||
|
core_pat=$(sysctl -n kernel.core_pattern 2>/dev/null)
|
||||||
|
if [[ "$core_pat" == *"/bin/false"* ]] || [[ "$core_pat" == *"devnull"* ]]; then
|
||||||
|
check "Core dumps disabled" "pass"
|
||||||
|
else
|
||||||
|
check "Core dumps disabled" "fail" "core_pattern=${core_pat}"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# 4. Swap disabled
|
||||||
|
local swap_count
|
||||||
|
swap_count=$(swapon --show --noheadings 2>/dev/null | wc -l)
|
||||||
|
if [ "$swap_count" -eq 0 ]; then
|
||||||
|
check "Swap disabled" "pass"
|
||||||
|
else
|
||||||
|
check "Swap disabled" "fail" "${swap_count} swap device(s) active"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# 5. DNS is privacy resolver (not ISP)
|
||||||
|
local dns_server
|
||||||
|
dns_server=$(awk '/^nameserver/ {print $2; exit}' /etc/resolv.conf 2>/dev/null)
|
||||||
|
case "$dns_server" in
|
||||||
|
127.0.0.1|127.0.0.53|9.9.9.9|149.112.112.112|1.1.1.1|1.0.0.1)
|
||||||
|
check "DNS privacy resolver" "pass" "${dns_server}"
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
check "DNS privacy resolver" "fail" "${dns_server} (possibly ISP)"
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
# 6. WebRTC blocked (if Firefox present)
|
||||||
|
if [ -d "$HOME/.mozilla/firefox" ] || [ -d "/root/.mozilla/firefox" ]; then
|
||||||
|
local userjs_found=false
|
||||||
|
local profile_dirs
|
||||||
|
profile_dirs=$(find /home/*/.mozilla/firefox /root/.mozilla/firefox -maxdepth 1 -name '*.default*' 2>/dev/null | head -3)
|
||||||
|
for pd in $profile_dirs; do
|
||||||
|
if [ -f "$pd/user.js" ] && grep -q "media.peerconnection.enabled.*false" "$pd/user.js" 2>/dev/null; then
|
||||||
|
userjs_found=true
|
||||||
|
break
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
if $userjs_found; then
|
||||||
|
check "WebRTC blocked" "pass" "user.js configured"
|
||||||
|
else
|
||||||
|
check "WebRTC blocked" "fail" "no user.js with WebRTC disable found"
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
check "WebRTC blocked" "skip" "Firefox not detected"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
# ─── GHOST CHECKS (only when ghost mode active) ──────────────────────────────
|
||||||
|
run_ghost_checks() {
|
||||||
|
$MODE_SCORE || opsec_cyan "Ghost Mode Checks"
|
||||||
|
|
||||||
|
# 1. VPN or Tor active
|
||||||
|
local vpn_if tor_ok=false
|
||||||
|
vpn_if=$(ip -o link show 2>/dev/null | awk -F': ' '{print $2}' | grep -E '^(tun|wg)' | head -1)
|
||||||
|
ss -tln 2>/dev/null | grep -q ':9050 ' && tor_ok=true
|
||||||
|
|
||||||
|
if [ -n "$vpn_if" ] || $tor_ok; then
|
||||||
|
local detail=""
|
||||||
|
[ -n "$vpn_if" ] && detail="VPN:${vpn_if}"
|
||||||
|
$tor_ok && detail="${detail:+${detail} + }Tor:9050"
|
||||||
|
check "VPN/Tor active" "pass" "$detail"
|
||||||
|
else
|
||||||
|
check "VPN/Tor active" "fail" "no tun/wg interface, no Tor on :9050"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# 2. Kill switch armed
|
||||||
|
if [ -f /var/run/opsec-breakglass.active ]; then
|
||||||
|
check "Kill switch armed" "fail" "BREAK-GLASS active — kill switch intentionally dropped"
|
||||||
|
elif iptables -L GP_FW >/dev/null 2>&1; then
|
||||||
|
check "Kill switch armed" "pass"
|
||||||
|
else
|
||||||
|
check "Kill switch armed" "fail" "GP_FW chain not found"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# 3. DNS locked to Tor
|
||||||
|
local dns_server
|
||||||
|
dns_server=$(awk '/^nameserver/ {print $2; exit}' /etc/resolv.conf 2>/dev/null)
|
||||||
|
if [ "$dns_server" = "127.0.0.1" ]; then
|
||||||
|
check "DNS locked to Tor" "pass"
|
||||||
|
else
|
||||||
|
check "DNS locked to Tor" "fail" "DNS=${dns_server} (expected 127.0.0.1)"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# 4. Hostname randomized (bare-metal only)
|
||||||
|
if is_bare_metal; then
|
||||||
|
local cur_hostname
|
||||||
|
cur_hostname=$(hostname)
|
||||||
|
# Check if hostname looks randomized (not default patterns like kali, debian, localhost)
|
||||||
|
case "$cur_hostname" in
|
||||||
|
kali|debian|localhost|ubuntu|parrot)
|
||||||
|
check "Hostname randomized" "fail" "hostname=${cur_hostname} (appears default)"
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
check "Hostname randomized" "pass" "${cur_hostname}"
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
else
|
||||||
|
check "Hostname randomized" "skip" "cloud deployment"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# 5. resolv.conf immutable
|
||||||
|
if lsattr /etc/resolv.conf 2>/dev/null | grep -q 'i'; then
|
||||||
|
check "resolv.conf immutable" "pass"
|
||||||
|
else
|
||||||
|
check "resolv.conf immutable" "fail" "chattr +i not set"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
# ─── EXECUTE CHECKS ──────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
if ! $MODE_SCORE; then
|
||||||
|
echo ""
|
||||||
|
opsec_hdr "OPSEC PREFLIGHT — ${CHECK_LEVEL^^}"
|
||||||
|
opsec_dim "Level: ${LEVEL} (${LTYPE})"
|
||||||
|
echo ""
|
||||||
|
fi
|
||||||
|
|
||||||
|
run_base_checks
|
||||||
|
|
||||||
|
if [ "$CHECK_LEVEL" = "full" ]; then
|
||||||
|
$MODE_SCORE || echo ""
|
||||||
|
run_ghost_checks
|
||||||
|
fi
|
||||||
|
|
||||||
|
# ─── RESULTS ──────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
TOTAL=$((PASS + FAIL))
|
||||||
|
|
||||||
|
if $MODE_SCORE; then
|
||||||
|
echo "${PASS}/${TOTAL} passed"
|
||||||
|
[ "$FAIL" -gt 0 ] && exit 1
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
opsec_hdr "PREFLIGHT RESULT"
|
||||||
|
|
||||||
|
if [ "$FAIL" -eq 0 ]; then
|
||||||
|
opsec_green "ALL CHECKS PASSED (${PASS}/${TOTAL})"
|
||||||
|
[ "$SKIP" -gt 0 ] && opsec_dim "${SKIP} checks skipped (not applicable)"
|
||||||
|
else
|
||||||
|
opsec_red "FAILED: ${FAIL}/${TOTAL} checks"
|
||||||
|
opsec_dim "Passed: ${PASS} | Failed: ${FAIL} | Skipped: ${SKIP}"
|
||||||
|
echo -e "\033[38;5;196mFailures:${FAILURES}\033[0m"
|
||||||
|
# Log failures
|
||||||
|
echo "[$(date -Is)] PREFLIGHT ${CHECK_LEVEL^^}: ${PASS}/${TOTAL} passed, ${FAIL} failed${FAILURES}" >> "$PREFLIGHT_LOG" 2>/dev/null || true
|
||||||
|
fi
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
if $MODE_ENFORCE && [ "$FAIL" -gt 0 ]; then
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
exit 0
|
||||||
Executable
+115
@@ -0,0 +1,115 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
# /usr/local/bin/opsec-sentry.sh — Continuous Threat Sentry Manager
|
||||||
|
# Manages the sentry daemon for background threat detection
|
||||||
|
# Usage: opsec-sentry.sh start|stop|status|restart
|
||||||
|
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
SENTRY_BIN="${SENTRY_BIN:-/usr/local/bin/sentry-daemon}"
|
||||||
|
PID_FILE="/var/run/opsec-sentry.pid"
|
||||||
|
LOG_FILE="/var/log/opsec-sentry.log"
|
||||||
|
ALERT_HOOK=""
|
||||||
|
|
||||||
|
# Source config for alert hook if available
|
||||||
|
OPSEC_CONF="/etc/opsec/opsec.conf"
|
||||||
|
[ -f "$OPSEC_CONF" ] && . "$OPSEC_CONF"
|
||||||
|
|
||||||
|
# Color output helpers
|
||||||
|
_green() { echo -e "\033[38;5;49m[+] $*\033[0m"; }
|
||||||
|
_red() { echo -e "\033[38;5;196m[-] $*\033[0m"; }
|
||||||
|
_yellow() { echo -e "\033[38;5;214m[*] $*\033[0m"; }
|
||||||
|
_info() { echo -e "\033[38;5;39m[~] $*\033[0m"; }
|
||||||
|
|
||||||
|
is_running() {
|
||||||
|
[ -f "$PID_FILE" ] && kill -0 "$(cat "$PID_FILE" 2>/dev/null)" 2>/dev/null
|
||||||
|
}
|
||||||
|
|
||||||
|
sentry_start() {
|
||||||
|
if is_running; then
|
||||||
|
_yellow "Sentry already running (PID $(cat "$PID_FILE"))"
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ ! -x "$SENTRY_BIN" ]; then
|
||||||
|
_red "Sentry binary not found at ${SENTRY_BIN}"
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
local cmd="$SENTRY_BIN --daemon --sentry-pid $PID_FILE --sentry-log $LOG_FILE"
|
||||||
|
[ -n "$ALERT_HOOK" ] && cmd="$cmd --alert-hook $ALERT_HOOK"
|
||||||
|
|
||||||
|
# Run sentry in daemon mode
|
||||||
|
$cmd &
|
||||||
|
local pid=$!
|
||||||
|
echo "$pid" > "$PID_FILE"
|
||||||
|
|
||||||
|
# Verify it started
|
||||||
|
sleep 1
|
||||||
|
if kill -0 "$pid" 2>/dev/null; then
|
||||||
|
_green "Sentry daemon started (PID ${pid})"
|
||||||
|
_info "Log: ${LOG_FILE}"
|
||||||
|
else
|
||||||
|
rm -f "$PID_FILE"
|
||||||
|
_red "Sentry failed to start — check ${LOG_FILE}"
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
sentry_stop() {
|
||||||
|
if ! is_running; then
|
||||||
|
_yellow "Sentry not running"
|
||||||
|
rm -f "$PID_FILE"
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
local pid
|
||||||
|
pid=$(cat "$PID_FILE" 2>/dev/null)
|
||||||
|
kill "$pid" 2>/dev/null
|
||||||
|
# Wait for graceful shutdown
|
||||||
|
local i=0
|
||||||
|
while kill -0 "$pid" 2>/dev/null && [ $i -lt 10 ]; do
|
||||||
|
sleep 1
|
||||||
|
i=$((i + 1))
|
||||||
|
done
|
||||||
|
|
||||||
|
if kill -0 "$pid" 2>/dev/null; then
|
||||||
|
kill -9 "$pid" 2>/dev/null
|
||||||
|
fi
|
||||||
|
|
||||||
|
rm -f "$PID_FILE"
|
||||||
|
_green "Sentry daemon stopped"
|
||||||
|
}
|
||||||
|
|
||||||
|
sentry_status() {
|
||||||
|
if is_running; then
|
||||||
|
local pid
|
||||||
|
pid=$(cat "$PID_FILE" 2>/dev/null)
|
||||||
|
_green "Sentry ACTIVE (PID ${pid})"
|
||||||
|
if [ -f "$LOG_FILE" ]; then
|
||||||
|
local lines
|
||||||
|
lines=$(wc -l < "$LOG_FILE" 2>/dev/null || echo "0")
|
||||||
|
_info "Log entries: ${lines}"
|
||||||
|
local last
|
||||||
|
last=$(tail -1 "$LOG_FILE" 2>/dev/null)
|
||||||
|
[ -n "$last" ] && _info "Last: ${last}"
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
_yellow "Sentry NOT running"
|
||||||
|
rm -f "$PID_FILE"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
case "${1:-}" in
|
||||||
|
start) sentry_start ;;
|
||||||
|
stop) sentry_stop ;;
|
||||||
|
restart)
|
||||||
|
sentry_stop
|
||||||
|
sleep 1
|
||||||
|
sentry_start
|
||||||
|
;;
|
||||||
|
status) sentry_status ;;
|
||||||
|
*)
|
||||||
|
echo "Usage: opsec-sentry.sh start|stop|status|restart"
|
||||||
|
exit 1
|
||||||
|
;;
|
||||||
|
esac
|
||||||
Executable
+123
@@ -0,0 +1,123 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
# /usr/local/bin/opsec-ssh-check.sh — SSH Honeypot Detection
|
||||||
|
# Checks target SSH banners for known honeypot signatures
|
||||||
|
# Alias: ssh-safe
|
||||||
|
#
|
||||||
|
# Usage: opsec-ssh-check.sh <host> [port]
|
||||||
|
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
OPSEC_LIB="/usr/local/lib/opsec/opsec-lib.sh"
|
||||||
|
if [ -f "$OPSEC_LIB" ]; then
|
||||||
|
. "$OPSEC_LIB"
|
||||||
|
else
|
||||||
|
opsec_green() { echo -e "\033[38;5;49m[+] $*\033[0m"; }
|
||||||
|
opsec_red() { echo -e "\033[38;5;196m[-] $*\033[0m"; }
|
||||||
|
opsec_yellow() { echo -e "\033[38;5;214m[*] $*\033[0m"; }
|
||||||
|
opsec_info() { echo -e "\033[38;5;39m[~] $*\033[0m"; }
|
||||||
|
opsec_cyan() { echo -e "\033[38;5;51m[>] $*\033[0m"; }
|
||||||
|
opsec_hdr() { echo -e "\033[38;5;51m━━━ \033[38;5;201m$*\033[38;5;51m ━━━\033[0m"; }
|
||||||
|
fi
|
||||||
|
|
||||||
|
HOST="${1:-}"
|
||||||
|
PORT="${2:-22}"
|
||||||
|
|
||||||
|
if [ -z "$HOST" ]; then
|
||||||
|
echo "Usage: $(basename "$0") <host> [port]"
|
||||||
|
echo " ssh-safe <host> [port]"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
opsec_hdr "SSH HONEYPOT CHECK: ${HOST}:${PORT}"
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
SUSPICIOUS=0
|
||||||
|
|
||||||
|
# ─── GRAB BANNER ───────────────────────────────────────────────────────────────
|
||||||
|
opsec_info "Grabbing SSH banner..."
|
||||||
|
BANNER=$(timeout 5 bash -c "echo '' | nc -w 3 $HOST $PORT 2>/dev/null" || true)
|
||||||
|
|
||||||
|
if [ -z "$BANNER" ]; then
|
||||||
|
opsec_yellow "No banner received — port may be filtered or service is not SSH"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
opsec_cyan "Banner: ${BANNER}"
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
# ─── KNOWN HONEYPOT SIGNATURES ─────────────────────────────────────────────────
|
||||||
|
|
||||||
|
# Cowrie
|
||||||
|
if echo "$BANNER" | grep -qiE 'SSH-2\.0-OpenSSH_6\.(0|1|2|6)p1.*Debian'; then
|
||||||
|
opsec_red "COWRIE SIGNATURE: Old OpenSSH version commonly used by Cowrie honeypot"
|
||||||
|
SUSPICIOUS=$((SUSPICIOUS + 3))
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Kippo
|
||||||
|
if echo "$BANNER" | grep -qi 'SSH-1\.99-OpenSSH_5\.1p1'; then
|
||||||
|
opsec_red "KIPPO SIGNATURE: SSH-1.99 with OpenSSH_5.1p1 is a known Kippo default"
|
||||||
|
SUSPICIOUS=$((SUSPICIOUS + 3))
|
||||||
|
fi
|
||||||
|
|
||||||
|
# HonSSH
|
||||||
|
if echo "$BANNER" | grep -qi 'HonSSH'; then
|
||||||
|
opsec_red "HONSH SIGNATURE: Banner explicitly mentions HonSSH"
|
||||||
|
SUSPICIOUS=$((SUSPICIOUS + 5))
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Generic old version check
|
||||||
|
if echo "$BANNER" | grep -qE 'OpenSSH_[345]\.' ; then
|
||||||
|
opsec_yellow "SUSPICIOUS: Very old OpenSSH version (common in honeypots)"
|
||||||
|
SUSPICIOUS=$((SUSPICIOUS + 2))
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Unusual SSH protocol version
|
||||||
|
if echo "$BANNER" | grep -q 'SSH-1\.'; then
|
||||||
|
opsec_yellow "SUSPICIOUS: SSHv1 protocol (deprecated, often honeypot)"
|
||||||
|
SUSPICIOUS=$((SUSPICIOUS + 2))
|
||||||
|
fi
|
||||||
|
|
||||||
|
# ─── KEY EXCHANGE CHECK ────────────────────────────────────────────────────────
|
||||||
|
opsec_info "Checking key exchange algorithms..."
|
||||||
|
|
||||||
|
KEX_OUTPUT=$(timeout 5 ssh -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null \
|
||||||
|
-o BatchMode=yes -o ConnectTimeout=3 -v -p "$PORT" "check@${HOST}" 2>&1 || true)
|
||||||
|
|
||||||
|
# Check for weak/unusual key types
|
||||||
|
if echo "$KEX_OUTPUT" | grep -qi 'ssh-dss'; then
|
||||||
|
opsec_yellow "SUSPICIOUS: DSA host key (often seen in honeypots)"
|
||||||
|
SUSPICIOUS=$((SUSPICIOUS + 1))
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Check for unusually fast key exchange (honeypots often respond instantly)
|
||||||
|
if echo "$KEX_OUTPUT" | grep -qi 'Connection reset\|Connection refused'; then
|
||||||
|
opsec_info "Connection dropped (may be rate-limited or filtered)"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# ─── TIMING CHECK ─────────────────────────────────────────────────────────────
|
||||||
|
opsec_info "Checking authentication timing..."
|
||||||
|
AUTH_START=$(date +%s%N)
|
||||||
|
timeout 3 ssh -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null \
|
||||||
|
-o BatchMode=yes -o ConnectTimeout=3 -p "$PORT" "probe@${HOST}" 2>/dev/null || true
|
||||||
|
AUTH_END=$(date +%s%N)
|
||||||
|
AUTH_MS=$(( (AUTH_END - AUTH_START) / 1000000 ))
|
||||||
|
|
||||||
|
if [ "$AUTH_MS" -lt 50 ] && [ "$AUTH_MS" -gt 0 ]; then
|
||||||
|
opsec_yellow "SUSPICIOUS: Unusually fast auth response (${AUTH_MS}ms) — possible honeypot"
|
||||||
|
SUSPICIOUS=$((SUSPICIOUS + 1))
|
||||||
|
else
|
||||||
|
opsec_info "Auth response time: ${AUTH_MS}ms"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# ─── VERDICT ───────────────────────────────────────────────────────────────────
|
||||||
|
echo ""
|
||||||
|
if [ "$SUSPICIOUS" -ge 3 ]; then
|
||||||
|
opsec_red "VERDICT: HIGH RISK — Likely honeypot (score: ${SUSPICIOUS})"
|
||||||
|
opsec_red "DO NOT connect to this host for operations"
|
||||||
|
elif [ "$SUSPICIOUS" -ge 1 ]; then
|
||||||
|
opsec_yellow "VERDICT: MODERATE RISK — Some anomalies detected (score: ${SUSPICIOUS})"
|
||||||
|
opsec_yellow "Proceed with caution"
|
||||||
|
else
|
||||||
|
opsec_green "VERDICT: LOW RISK — No honeypot signatures detected (score: ${SUSPICIOUS})"
|
||||||
|
fi
|
||||||
|
echo ""
|
||||||
Executable
+23
@@ -0,0 +1,23 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
# OPSEC Toggle — single entry point for keyboard shortcut + desktop file
|
||||||
|
# Uses pkexec directly on opsec-mode.sh for proper polkit policy matching
|
||||||
|
OPSEC_MODE="/usr/local/bin/opsec-mode.sh"
|
||||||
|
STATE_FILE="/var/run/opsec-advanced.enabled"
|
||||||
|
|
||||||
|
[ ! -x "$OPSEC_MODE" ] && exit 1
|
||||||
|
|
||||||
|
if [ -f "$STATE_FILE" ]; then
|
||||||
|
# Turning OFF — opsec-mode.sh off handles bootstrap stop signal internally
|
||||||
|
if [ "$EUID" -eq 0 ] 2>/dev/null; then
|
||||||
|
"$OPSEC_MODE" off
|
||||||
|
else
|
||||||
|
pkexec "$OPSEC_MODE" off
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
# Turning ON — pkexec prompts for auth via polkit policy
|
||||||
|
if [ "$EUID" -eq 0 ] 2>/dev/null; then
|
||||||
|
"$OPSEC_MODE" on
|
||||||
|
else
|
||||||
|
pkexec "$OPSEC_MODE" on
|
||||||
|
fi
|
||||||
|
fi
|
||||||
Executable
+180
@@ -0,0 +1,180 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
# /usr/local/bin/opsec-traffic-blend.sh — Decoy Traffic Blending Daemon
|
||||||
|
# Generates background browsing noise through Tor/VPN to blend tool traffic
|
||||||
|
# with normal-looking web activity. Ghost mode only.
|
||||||
|
# Usage: opsec-traffic-blend.sh start|stop|status
|
||||||
|
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
PID_FILE="/var/run/opsec-traffic-blend.pid"
|
||||||
|
LOG_FILE="/var/log/opsec-traffic-blend.log"
|
||||||
|
|
||||||
|
# Source config
|
||||||
|
OPSEC_CONF="/etc/opsec/opsec.conf"
|
||||||
|
[ -f "$OPSEC_CONF" ] && . "$OPSEC_CONF"
|
||||||
|
|
||||||
|
SOCKS_PORT="${TOR_SOCKS_PORT:-9050}"
|
||||||
|
# Mean interval in seconds (Poisson distribution approximation)
|
||||||
|
BLEND_INTERVAL="${TRAFFIC_BLEND_INTERVAL:-30}"
|
||||||
|
|
||||||
|
# Color helpers
|
||||||
|
_green() { echo -e "\033[38;5;49m[+] $*\033[0m"; }
|
||||||
|
_red() { echo -e "\033[38;5;196m[-] $*\033[0m"; }
|
||||||
|
_yellow() { echo -e "\033[38;5;214m[*] $*\033[0m"; }
|
||||||
|
|
||||||
|
# Benign URLs to simulate normal browsing (mix of news, tech, social)
|
||||||
|
DECOY_URLS=(
|
||||||
|
"https://en.wikipedia.org/wiki/Special:Random"
|
||||||
|
"https://www.bbc.com/news"
|
||||||
|
"https://news.ycombinator.com"
|
||||||
|
"https://www.reuters.com"
|
||||||
|
"https://stackoverflow.com/questions"
|
||||||
|
"https://github.com/trending"
|
||||||
|
"https://www.reddit.com/r/technology/.json"
|
||||||
|
"https://www.weather.gov"
|
||||||
|
"https://httpbin.org/get"
|
||||||
|
"https://www.kernel.org"
|
||||||
|
"https://www.python.org"
|
||||||
|
"https://www.debian.org"
|
||||||
|
"https://archive.org"
|
||||||
|
"https://www.mozilla.org"
|
||||||
|
"https://duckduckgo.com/?q=weather"
|
||||||
|
"https://lite.cnn.com"
|
||||||
|
"https://text.npr.org"
|
||||||
|
)
|
||||||
|
|
||||||
|
# Random delay using Poisson-like distribution (exponential inter-arrival)
|
||||||
|
poisson_delay() {
|
||||||
|
# Approximate exponential distribution using bash
|
||||||
|
# -ln(U) * mean where U is uniform(0,1)
|
||||||
|
local mean="$1"
|
||||||
|
local rand
|
||||||
|
rand=$((RANDOM % 1000 + 1))
|
||||||
|
# Approximate: -ln(rand/1000) * mean
|
||||||
|
# Using awk for floating point
|
||||||
|
awk -v r="$rand" -v m="$mean" 'BEGIN {
|
||||||
|
u = r / 1000.0;
|
||||||
|
if (u < 0.001) u = 0.001;
|
||||||
|
delay = -log(u) * m;
|
||||||
|
if (delay < 5) delay = 5;
|
||||||
|
if (delay > 120) delay = 120;
|
||||||
|
printf "%d\n", delay;
|
||||||
|
}'
|
||||||
|
}
|
||||||
|
|
||||||
|
is_running() {
|
||||||
|
[ -f "$PID_FILE" ] && kill -0 "$(cat "$PID_FILE" 2>/dev/null)" 2>/dev/null
|
||||||
|
}
|
||||||
|
|
||||||
|
blend_loop() {
|
||||||
|
local url_count=${#DECOY_URLS[@]}
|
||||||
|
|
||||||
|
echo "[$(date -Is)] Traffic blend daemon started (mean interval: ${BLEND_INTERVAL}s)" >> "$LOG_FILE"
|
||||||
|
|
||||||
|
while true; do
|
||||||
|
# Pick a random URL
|
||||||
|
local idx=$((RANDOM % url_count))
|
||||||
|
local url="${DECOY_URLS[$idx]}"
|
||||||
|
|
||||||
|
# Determine proxy method
|
||||||
|
local curl_opts=("--silent" "--output" "/dev/null" "--max-time" "15")
|
||||||
|
|
||||||
|
# Use Tor SOCKS if available
|
||||||
|
if ss -tln 2>/dev/null | grep -q ":${SOCKS_PORT} "; then
|
||||||
|
curl_opts+=("--socks5-hostname" "127.0.0.1:${SOCKS_PORT}")
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Add realistic headers
|
||||||
|
curl_opts+=("-H" "User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:128.0) Gecko/20100101 Firefox/128.0")
|
||||||
|
curl_opts+=("-H" "Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8")
|
||||||
|
curl_opts+=("-H" "Accept-Language: en-US,en;q=0.5")
|
||||||
|
|
||||||
|
# Make the request
|
||||||
|
curl "${curl_opts[@]}" "$url" 2>/dev/null || true
|
||||||
|
|
||||||
|
# Calculate next delay (Poisson-distributed)
|
||||||
|
local delay
|
||||||
|
delay=$(poisson_delay "$BLEND_INTERVAL")
|
||||||
|
|
||||||
|
sleep "$delay"
|
||||||
|
done
|
||||||
|
}
|
||||||
|
|
||||||
|
blend_start() {
|
||||||
|
if ! [ -f /var/run/opsec-advanced.enabled ]; then
|
||||||
|
_yellow "Traffic blending requires ghost mode to be active"
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
if is_running; then
|
||||||
|
_yellow "Traffic blend already running (PID $(cat "$PID_FILE"))"
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Start the blend loop as a background process
|
||||||
|
blend_loop &
|
||||||
|
local pid=$!
|
||||||
|
echo "$pid" > "$PID_FILE"
|
||||||
|
disown "$pid"
|
||||||
|
|
||||||
|
sleep 1
|
||||||
|
if kill -0 "$pid" 2>/dev/null; then
|
||||||
|
_green "Traffic blend started (PID ${pid}, mean interval ${BLEND_INTERVAL}s)"
|
||||||
|
else
|
||||||
|
rm -f "$PID_FILE"
|
||||||
|
_red "Traffic blend failed to start"
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
blend_stop() {
|
||||||
|
if ! is_running; then
|
||||||
|
_yellow "Traffic blend not running"
|
||||||
|
rm -f "$PID_FILE"
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
local pid
|
||||||
|
pid=$(cat "$PID_FILE" 2>/dev/null)
|
||||||
|
kill "$pid" 2>/dev/null || true
|
||||||
|
|
||||||
|
# Wait for process to stop
|
||||||
|
local i=0
|
||||||
|
while kill -0 "$pid" 2>/dev/null && [ $i -lt 5 ]; do
|
||||||
|
sleep 1
|
||||||
|
i=$((i + 1))
|
||||||
|
done
|
||||||
|
|
||||||
|
kill -9 "$pid" 2>/dev/null || true
|
||||||
|
rm -f "$PID_FILE"
|
||||||
|
|
||||||
|
echo "[$(date -Is)] Traffic blend daemon stopped" >> "$LOG_FILE"
|
||||||
|
_green "Traffic blend stopped"
|
||||||
|
}
|
||||||
|
|
||||||
|
blend_status() {
|
||||||
|
if is_running; then
|
||||||
|
local pid
|
||||||
|
pid=$(cat "$PID_FILE" 2>/dev/null)
|
||||||
|
_green "Traffic blend ACTIVE (PID ${pid})"
|
||||||
|
_yellow "Mean interval: ${BLEND_INTERVAL}s"
|
||||||
|
else
|
||||||
|
_yellow "Traffic blend NOT running"
|
||||||
|
rm -f "$PID_FILE"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
case "${1:-}" in
|
||||||
|
start) blend_start ;;
|
||||||
|
stop) blend_stop ;;
|
||||||
|
restart)
|
||||||
|
blend_stop
|
||||||
|
sleep 1
|
||||||
|
blend_start
|
||||||
|
;;
|
||||||
|
status) blend_status ;;
|
||||||
|
*)
|
||||||
|
echo "Usage: opsec-traffic-blend.sh start|stop|status|restart"
|
||||||
|
exit 1
|
||||||
|
;;
|
||||||
|
esac
|
||||||
Executable
+127
@@ -0,0 +1,127 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
# OPSEC Widget Launcher — positions and launches the Conky widget
|
||||||
|
# Usage: opsec-widget-launch.sh [position]
|
||||||
|
#
|
||||||
|
# Positions:
|
||||||
|
# tl = top-left tc = top-center tr = top-right
|
||||||
|
# bl = bottom-left bc = bottom-center br = bottom-right
|
||||||
|
#
|
||||||
|
# Default: tr (top-right)
|
||||||
|
# Uses the theme system — regenerates config from /etc/opsec/themes/
|
||||||
|
|
||||||
|
CONKY_DIR="$HOME/.config/conky"
|
||||||
|
CONF="$CONKY_DIR/conky-opsec-widget.conf"
|
||||||
|
POS="${1:-tr}"
|
||||||
|
|
||||||
|
# Map shorthand to conky alignment + gaps
|
||||||
|
case "$POS" in
|
||||||
|
tl) ALIGN="top_left"; GAP_X=15; GAP_Y=15 ;;
|
||||||
|
tc) ALIGN="top_middle"; GAP_X=0; GAP_Y=15 ;;
|
||||||
|
tr) ALIGN="top_right"; GAP_X=15; GAP_Y=60 ;;
|
||||||
|
bl) ALIGN="bottom_left"; GAP_X=15; GAP_Y=60 ;;
|
||||||
|
bc) ALIGN="bottom_middle"; GAP_X=0; GAP_Y=60 ;;
|
||||||
|
br) ALIGN="bottom_right"; GAP_X=15; GAP_Y=60 ;;
|
||||||
|
*)
|
||||||
|
echo "Usage: $(basename "$0") [tl|tc|tr|bl|bc|br]"
|
||||||
|
echo ""
|
||||||
|
echo " tl = top-left tc = top-center tr = top-right"
|
||||||
|
echo " bl = bottom-left bc = bottom-center br = bottom-right"
|
||||||
|
exit 1
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
# Kill existing widget and stale cache daemon
|
||||||
|
killall conky 2>/dev/null
|
||||||
|
pkill -f 'conky-opsec-cache\.sh' 2>/dev/null
|
||||||
|
rm -f /tmp/.opsec-cache/netinfo 2>/dev/null
|
||||||
|
sleep 0.3
|
||||||
|
|
||||||
|
# Load theme from opsec config
|
||||||
|
OPSEC_CONF="/etc/opsec/opsec.conf"
|
||||||
|
THEME="default"
|
||||||
|
[ -f "$OPSEC_CONF" ] && THEME=$(grep "^WIDGET_THEME=" "$OPSEC_CONF" 2>/dev/null | cut -d'"' -f2)
|
||||||
|
[ -z "$THEME" ] && THEME="default"
|
||||||
|
|
||||||
|
THEME_FILE="/etc/opsec/themes/${THEME}.theme"
|
||||||
|
if [ -f "$THEME_FILE" ]; then
|
||||||
|
. "$THEME_FILE"
|
||||||
|
else
|
||||||
|
echo "Theme '${THEME}' not found, using defaults"
|
||||||
|
THEME_LABEL="Default"
|
||||||
|
CONKY_BG="0d1117"
|
||||||
|
CONKY_COLOR0="df2020"; CONKY_COLOR1="33ff33"; CONKY_COLOR2="3a8fd6"
|
||||||
|
CONKY_COLOR3="0d1117"; CONKY_COLOR4="1f6feb"; CONKY_COLOR5="58a6ff"
|
||||||
|
CONKY_COLOR6="79c0ff"; CONKY_COLOR7="c9d1d9"; CONKY_COLOR8="1f6feb"
|
||||||
|
CONKY_COLOR9="484f58"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Generate config with theme colors and chosen position
|
||||||
|
mkdir -p "$CONKY_DIR"
|
||||||
|
cat > "$CONF" << EOF
|
||||||
|
-- OPSEC Status Widget — OPSEC Status Widget
|
||||||
|
-- Theme: ${THEME} (${THEME_LABEL:-Custom})
|
||||||
|
-- Position: ${ALIGN}
|
||||||
|
|
||||||
|
conky.config = {
|
||||||
|
alignment = '${ALIGN}',
|
||||||
|
gap_x = ${GAP_X},
|
||||||
|
gap_y = ${GAP_Y},
|
||||||
|
minimum_width = 400,
|
||||||
|
minimum_height = 200,
|
||||||
|
maximum_width = 420,
|
||||||
|
|
||||||
|
own_window = true,
|
||||||
|
own_window_type = 'normal',
|
||||||
|
own_window_transparent = false,
|
||||||
|
own_window_argb_visual = true,
|
||||||
|
own_window_argb_value = 210,
|
||||||
|
own_window_colour = '${CONKY_BG:-0d1117}',
|
||||||
|
own_window_hints = 'undecorated,below,sticky,skip_taskbar,skip_pager',
|
||||||
|
|
||||||
|
xinerama_head = 0,
|
||||||
|
|
||||||
|
double_buffer = true,
|
||||||
|
draw_shades = true,
|
||||||
|
default_shade_color = '000000',
|
||||||
|
draw_outline = false,
|
||||||
|
draw_borders = true,
|
||||||
|
border_inner_margin = 12,
|
||||||
|
border_outer_margin = 4,
|
||||||
|
border_width = 1,
|
||||||
|
border_colour = '${CONKY_COLOR4:-1f6feb}',
|
||||||
|
stippled_borders = 0,
|
||||||
|
|
||||||
|
use_xft = true,
|
||||||
|
font = 'JetBrains Mono:size=10',
|
||||||
|
override_utf8_locale = true,
|
||||||
|
|
||||||
|
default_color = 'b0b0b0',
|
||||||
|
color0 = '${CONKY_COLOR0:-df2020}',
|
||||||
|
color1 = '${CONKY_COLOR1:-33ff33}',
|
||||||
|
color2 = '${CONKY_COLOR2:-3a8fd6}',
|
||||||
|
color3 = '${CONKY_COLOR3:-0d1117}',
|
||||||
|
color4 = '${CONKY_COLOR4:-1f6feb}',
|
||||||
|
color5 = '${CONKY_COLOR5:-58a6ff}',
|
||||||
|
color6 = '${CONKY_COLOR6:-79c0ff}',
|
||||||
|
color7 = '${CONKY_COLOR7:-c9d1d9}',
|
||||||
|
color8 = '${CONKY_COLOR8:-1f6feb}',
|
||||||
|
color9 = '${CONKY_COLOR9:-484f58}',
|
||||||
|
|
||||||
|
update_interval = 3,
|
||||||
|
total_run_times = 0,
|
||||||
|
|
||||||
|
cpu_avg_samples = 2,
|
||||||
|
no_buffers = true,
|
||||||
|
text_buffer_size = 8192,
|
||||||
|
short_units = true,
|
||||||
|
};
|
||||||
|
|
||||||
|
conky.text = [[
|
||||||
|
\${execpi 5 ~/.config/conky/conky-opsec-status.sh}
|
||||||
|
]];
|
||||||
|
EOF
|
||||||
|
|
||||||
|
# Launch
|
||||||
|
conky -c "$CONF" &
|
||||||
|
disown
|
||||||
|
echo "OPSEC widget launched: $ALIGN (theme: $THEME)"
|
||||||
Executable
+120
@@ -0,0 +1,120 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
# /usr/local/bin/opsec-wifi-check.sh — Wireless Evil Twin Detection
|
||||||
|
# Periodic scan for AP changes, open networks, BSSID anomalies
|
||||||
|
# Usage: opsec-wifi-check.sh [scan|watch]
|
||||||
|
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
OPSEC_LIB="/usr/local/lib/opsec/opsec-lib.sh"
|
||||||
|
if [ -f "$OPSEC_LIB" ]; then
|
||||||
|
. "$OPSEC_LIB"
|
||||||
|
else
|
||||||
|
opsec_green() { echo -e "\033[38;5;49m[+] $*\033[0m"; }
|
||||||
|
opsec_red() { echo -e "\033[38;5;196m[-] $*\033[0m"; }
|
||||||
|
opsec_yellow() { echo -e "\033[38;5;214m[*] $*\033[0m"; }
|
||||||
|
opsec_info() { echo -e "\033[38;5;39m[~] $*\033[0m"; }
|
||||||
|
opsec_cyan() { echo -e "\033[38;5;51m[>] $*\033[0m"; }
|
||||||
|
opsec_hdr() { echo -e "\033[38;5;51m━━━ \033[38;5;201m$*\033[38;5;51m ━━━\033[0m"; }
|
||||||
|
fi
|
||||||
|
|
||||||
|
STATE_DIR="/var/run/opsec-wifi"
|
||||||
|
mkdir -p "$STATE_DIR"
|
||||||
|
|
||||||
|
notify() {
|
||||||
|
local msg="$1" urgency="${2:-normal}"
|
||||||
|
local real_user="${SUDO_USER:-$USER}"
|
||||||
|
su - "$real_user" -c "DISPLAY=:0 DBUS_SESSION_BUS_ADDRESS=unix:path=/run/user/$(id -u "$real_user")/bus notify-send -u '$urgency' 'OPSEC WiFi' '$msg'" 2>/dev/null || true
|
||||||
|
}
|
||||||
|
|
||||||
|
do_scan() {
|
||||||
|
opsec_hdr "WIRELESS SECURITY SCAN"
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
# Check if connected to WiFi
|
||||||
|
local connected_ssid connected_bssid connected_security
|
||||||
|
connected_ssid=$(nmcli -t -f active,ssid dev wifi 2>/dev/null | grep '^yes:' | cut -d: -f2)
|
||||||
|
connected_bssid=$(nmcli -t -f active,bssid dev wifi 2>/dev/null | grep '^yes:' | cut -d: -f2-)
|
||||||
|
connected_security=$(nmcli -t -f active,security dev wifi 2>/dev/null | grep '^yes:' | cut -d: -f2)
|
||||||
|
|
||||||
|
if [ -z "$connected_ssid" ]; then
|
||||||
|
opsec_info "Not connected to any WiFi network"
|
||||||
|
return
|
||||||
|
fi
|
||||||
|
|
||||||
|
opsec_cyan "Connected: ${connected_ssid} (${connected_bssid})"
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
# ─── Check for open/unencrypted network ────────────────────────────────
|
||||||
|
if [ -z "$connected_security" ] || [ "$connected_security" = "--" ]; then
|
||||||
|
opsec_red "WARNING: Connected to OPEN (unencrypted) network!"
|
||||||
|
notify "Connected to OPEN WiFi: ${connected_ssid}" "critical"
|
||||||
|
elif echo "$connected_security" | grep -qi 'WEP'; then
|
||||||
|
opsec_red "WARNING: WEP encryption (trivially crackable)"
|
||||||
|
notify "WiFi using WEP: ${connected_ssid}" "critical"
|
||||||
|
else
|
||||||
|
opsec_green "Encryption: ${connected_security}"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# ─── Scan for duplicate SSIDs (evil twin indicators) ───────────────────
|
||||||
|
opsec_info "Scanning for duplicate SSIDs..."
|
||||||
|
local scan_results
|
||||||
|
scan_results=$(nmcli -t -f ssid,bssid,signal,security dev wifi list --rescan yes 2>/dev/null || true)
|
||||||
|
|
||||||
|
if [ -n "$scan_results" ]; then
|
||||||
|
# Find APs with same SSID as connected but different BSSID
|
||||||
|
local duplicates
|
||||||
|
duplicates=$(echo "$scan_results" | grep "^${connected_ssid}:" | grep -v "${connected_bssid}" || true)
|
||||||
|
|
||||||
|
if [ -n "$duplicates" ]; then
|
||||||
|
local dup_count
|
||||||
|
dup_count=$(echo "$duplicates" | wc -l)
|
||||||
|
opsec_yellow "ALERT: ${dup_count} other AP(s) broadcasting '${connected_ssid}':"
|
||||||
|
echo "$duplicates" | while IFS=: read -r ssid bssid signal security; do
|
||||||
|
echo -e " \033[38;5;214m BSSID: ${bssid} Signal: ${signal} Security: ${security}\033[0m"
|
||||||
|
done
|
||||||
|
notify "Evil twin risk: ${dup_count} duplicate AP(s) for ${connected_ssid}" "critical"
|
||||||
|
else
|
||||||
|
opsec_green "No duplicate SSIDs detected"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
# ─── BSSID change detection ────────────────────────────────────────────
|
||||||
|
local state_file="${STATE_DIR}/${connected_ssid}.bssid"
|
||||||
|
if [ -f "$state_file" ]; then
|
||||||
|
local prev_bssid
|
||||||
|
prev_bssid=$(cat "$state_file")
|
||||||
|
if [ "$prev_bssid" != "$connected_bssid" ]; then
|
||||||
|
opsec_red "BSSID CHANGED for '${connected_ssid}'!"
|
||||||
|
opsec_red " Previous: ${prev_bssid}"
|
||||||
|
opsec_red " Current: ${connected_bssid}"
|
||||||
|
notify "BSSID changed for ${connected_ssid}: ${prev_bssid} → ${connected_bssid}" "critical"
|
||||||
|
else
|
||||||
|
opsec_green "BSSID consistent with last scan"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
echo "$connected_bssid" > "$state_file"
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
}
|
||||||
|
|
||||||
|
do_watch() {
|
||||||
|
opsec_info "Starting continuous WiFi monitoring (Ctrl+C to stop)..."
|
||||||
|
while true; do
|
||||||
|
do_scan
|
||||||
|
echo ""
|
||||||
|
opsec_info "Next scan in 60 seconds..."
|
||||||
|
sleep 60
|
||||||
|
done
|
||||||
|
}
|
||||||
|
|
||||||
|
case "${1:-scan}" in
|
||||||
|
scan) do_scan ;;
|
||||||
|
watch) do_watch ;;
|
||||||
|
*)
|
||||||
|
echo "Usage: $(basename "$0") [scan|watch]"
|
||||||
|
echo ""
|
||||||
|
echo " scan — One-time wireless security scan (default)"
|
||||||
|
echo " watch — Continuous monitoring (60s interval)"
|
||||||
|
exit 1
|
||||||
|
;;
|
||||||
|
esac
|
||||||
@@ -0,0 +1,97 @@
|
|||||||
|
# phantom — Privacy Server Deployer
|
||||||
|
|
||||||
|
Deploy self-hosted privacy infrastructure with a single command. Supports cloud providers, existing servers, and local deployment.
|
||||||
|
|
||||||
|
## Server Types
|
||||||
|
|
||||||
|
| Service | Status | Description |
|
||||||
|
|---|---|---|
|
||||||
|
| Matrix + Element | Ready | Encrypted messaging homeserver with web client |
|
||||||
|
| WireGuard VPN | Ready | Private VPN server with client config generation |
|
||||||
|
| Pi-hole DNS | Ready | Ad-blocking DNS server (Docker-based) |
|
||||||
|
| All-in-One | Ready | Multiple services on one server with nginx reverse proxy |
|
||||||
|
| Nextcloud | Stub | Self-hosted file sync and collaboration |
|
||||||
|
| Vaultwarden | Stub | Self-hosted Bitwarden password manager |
|
||||||
|
| Jellyfin | Stub | Self-hosted media server |
|
||||||
|
| Mail-in-a-Box | Stub | Self-hosted email |
|
||||||
|
|
||||||
|
## Deployment Targets
|
||||||
|
|
||||||
|
- **Linode** — Automated provisioning via API
|
||||||
|
- **AWS EC2** — Automated provisioning via API
|
||||||
|
- **FlokiNET** — Register pre-provisioned server
|
||||||
|
- **Existing server** — Any server with SSH access
|
||||||
|
- **Local** — Deploy directly on the current machine
|
||||||
|
|
||||||
|
## Quick Start
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Requirements
|
||||||
|
pip install ansible pyyaml # or: apt install ansible python3-yaml
|
||||||
|
# For AWS deployments:
|
||||||
|
pip install boto3
|
||||||
|
ansible-galaxy collection install amazon.aws
|
||||||
|
|
||||||
|
# Launch
|
||||||
|
python3 phantom.py
|
||||||
|
```
|
||||||
|
|
||||||
|
Select a service, choose a deployment target, provide configuration, and phantom handles the rest:
|
||||||
|
1. Provisions infrastructure (if cloud)
|
||||||
|
2. Applies base hardening (UFW, fail2ban, SSH lockdown, kernel hardening)
|
||||||
|
3. Deploys the selected service
|
||||||
|
4. Saves deployment info to `logs/`
|
||||||
|
|
||||||
|
## All-in-One Deployment
|
||||||
|
|
||||||
|
The all-in-one option deploys multiple services on a single server with:
|
||||||
|
- Nginx reverse proxy with TLS termination
|
||||||
|
- Let's Encrypt certificates via Certbot
|
||||||
|
- Per-service vhost routing
|
||||||
|
|
||||||
|
**Warning**: Running multiple services on one server means a compromise of one service risks all services. Use for lab/testing or personal use where convenience outweighs isolation. For production, deploy one service per server.
|
||||||
|
|
||||||
|
## Local Deployment
|
||||||
|
|
||||||
|
Select "Local (this machine)" as the deployment target to install services directly on your current system. This is useful for:
|
||||||
|
- Home lab servers
|
||||||
|
- Raspberry Pi deployments
|
||||||
|
- LAN-only services
|
||||||
|
- Testing before cloud deployment
|
||||||
|
|
||||||
|
No SSH key generation or remote provisioning is needed for local deployments.
|
||||||
|
|
||||||
|
## Provider Setup
|
||||||
|
|
||||||
|
### Linode
|
||||||
|
1. Create an API token at https://cloud.linode.com/profile/tokens
|
||||||
|
2. Select "Linode" when prompted and paste your token
|
||||||
|
|
||||||
|
### AWS
|
||||||
|
1. Create an IAM user with EC2 permissions
|
||||||
|
2. Generate access keys
|
||||||
|
3. Select "AWS" when prompted and provide credentials
|
||||||
|
|
||||||
|
### FlokiNET
|
||||||
|
1. Provision a server through FlokiNET's control panel
|
||||||
|
2. Select "FlokiNET" and provide the server IP
|
||||||
|
|
||||||
|
## Project Structure
|
||||||
|
|
||||||
|
```
|
||||||
|
phantom/
|
||||||
|
├── phantom.py # Main CLI
|
||||||
|
├── modules/ # Per-service configuration
|
||||||
|
├── playbooks/ # Ansible playbooks
|
||||||
|
│ ├── common/ # Shared hardening
|
||||||
|
│ ├── matrix/ # Synapse + Element
|
||||||
|
│ ├── vpn/ # WireGuard
|
||||||
|
│ ├── dns/ # Pi-hole
|
||||||
|
│ └── all_in_one/ # Multi-service composer
|
||||||
|
├── providers/ # Cloud provisioning
|
||||||
|
└── logs/ # Deployment artifacts
|
||||||
|
```
|
||||||
|
|
||||||
|
## License
|
||||||
|
|
||||||
|
MIT
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
[defaults]
|
||||||
|
host_key_checking = False
|
||||||
|
retry_files_enabled = False
|
||||||
|
stdout_callback = yaml
|
||||||
|
timeout = 30
|
||||||
|
|
||||||
|
[ssh_connection]
|
||||||
|
pipelining = True
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
# phantom modules
|
||||||
@@ -0,0 +1,126 @@
|
|||||||
|
"""All-in-one single-server deployment module.
|
||||||
|
|
||||||
|
Composes multiple services onto a single server with nginx reverse proxy
|
||||||
|
and TLS via Certbot. Includes clear warnings about single-server risks.
|
||||||
|
"""
|
||||||
|
|
||||||
|
CYAN = "\033[38;5;51m"
|
||||||
|
WHITE = "\033[38;5;255m"
|
||||||
|
GREY = "\033[38;5;244m"
|
||||||
|
YELLOW = "\033[38;5;214m"
|
||||||
|
RED = "\033[38;5;196m"
|
||||||
|
RESET = "\033[0m"
|
||||||
|
|
||||||
|
SERVICES = [
|
||||||
|
("matrix", "Matrix + Element", "Encrypted messaging"),
|
||||||
|
("vpn", "WireGuard VPN", "Private VPN server"),
|
||||||
|
("dns", "Pi-hole DNS", "Ad-blocking DNS"),
|
||||||
|
("cloud", "Nextcloud", "File sync (stub)"),
|
||||||
|
("vault", "Vaultwarden", "Password manager (stub)"),
|
||||||
|
("media", "Jellyfin", "Media server (stub)"),
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
def gather_config(config):
|
||||||
|
"""Gather all-in-one deployment configuration."""
|
||||||
|
# Risk warning
|
||||||
|
print(f"\n{RED} ╔═══════════════════════════════════════════════════════╗{RESET}")
|
||||||
|
print(f" {RED}║{RESET} {YELLOW}WARNING: Single-Server Deployment{RESET} {RED}║{RESET}")
|
||||||
|
print(f" {RED}║{RESET} {RED}║{RESET}")
|
||||||
|
print(f" {RED}║{RESET} Running multiple services on one server means: {RED}║{RESET}")
|
||||||
|
print(f" {RED}║{RESET} - A compromise of one service risks ALL services {RED}║{RESET}")
|
||||||
|
print(f" {RED}║{RESET} - Resource contention between services {RED}║{RESET}")
|
||||||
|
print(f" {RED}║{RESET} - Single point of failure for all infrastructure {RED}║{RESET}")
|
||||||
|
print(f" {RED}║{RESET} - More complex backup and recovery {RED}║{RESET}")
|
||||||
|
print(f" {RED}║{RESET} {RED}║{RESET}")
|
||||||
|
print(f" {RED}║{RESET} For production use, prefer one service per server. {RED}║{RESET}")
|
||||||
|
print(f" {RED}║{RESET} This mode is intended for lab/testing or personal {RED}║{RESET}")
|
||||||
|
print(f" {RED}║{RESET} use where convenience outweighs isolation. {RED}║{RESET}")
|
||||||
|
print(f" {RED}╚═══════════════════════════════════════════════════════╝{RESET}")
|
||||||
|
|
||||||
|
confirm = input(f"\n {YELLOW}Acknowledge risks and continue? [y/N]:{RESET} ").strip().lower()
|
||||||
|
if confirm != "y":
|
||||||
|
return None
|
||||||
|
|
||||||
|
# Service selection
|
||||||
|
print(f"\n{CYAN} ┌─ Select Services ────────────────────────────────────┐{RESET}")
|
||||||
|
for i, (svc_id, label, desc) in enumerate(SERVICES, 1):
|
||||||
|
print(f" {CYAN}│{RESET} {WHITE}{i}{RESET}) {label:<20} {GREY}{desc}{RESET}")
|
||||||
|
print(f" {CYAN}└────────────────────────────────────────────────────────┘{RESET}")
|
||||||
|
|
||||||
|
selections = input(
|
||||||
|
f"\n {CYAN}Services to deploy (comma-separated, e.g. 1,2,3):{RESET} "
|
||||||
|
).strip()
|
||||||
|
|
||||||
|
selected_services = []
|
||||||
|
for s in selections.split(","):
|
||||||
|
s = s.strip()
|
||||||
|
try:
|
||||||
|
idx = int(s) - 1
|
||||||
|
if 0 <= idx < len(SERVICES):
|
||||||
|
selected_services.append(SERVICES[idx][0])
|
||||||
|
except ValueError:
|
||||||
|
pass
|
||||||
|
|
||||||
|
if not selected_services:
|
||||||
|
print(f" {RED}No services selected.{RESET}")
|
||||||
|
return None
|
||||||
|
|
||||||
|
# Warn about stub services
|
||||||
|
STUB_SERVICES = {"cloud", "vault", "media", "email"}
|
||||||
|
stubs_selected = [s for s in selected_services if s in STUB_SERVICES]
|
||||||
|
if stubs_selected:
|
||||||
|
stub_labels = {s[0]: s[1] for s in SERVICES}
|
||||||
|
print(f"\n {YELLOW}Warning: The following services are stubs (playbook not yet implemented):{RESET}")
|
||||||
|
for s in stubs_selected:
|
||||||
|
print(f" {YELLOW}- {stub_labels.get(s, s)}{RESET}")
|
||||||
|
proceed = input(f" {YELLOW}Continue anyway? [y/N]:{RESET} ").strip().lower()
|
||||||
|
if proceed != "y":
|
||||||
|
return None
|
||||||
|
|
||||||
|
config["services"] = selected_services
|
||||||
|
config["all_in_one"] = True
|
||||||
|
|
||||||
|
# Base domain for nginx vhosts
|
||||||
|
config["domain"] = input(
|
||||||
|
f"\n {CYAN}Base domain (e.g. example.com):{RESET} "
|
||||||
|
).strip()
|
||||||
|
if not config["domain"]:
|
||||||
|
print(f" {RED}Domain is required for reverse proxy.{RESET}")
|
||||||
|
return None
|
||||||
|
|
||||||
|
config["certbot_email"] = input(
|
||||||
|
f" {CYAN}Email for Let's Encrypt [{GREY}optional{RESET}]: "
|
||||||
|
).strip()
|
||||||
|
|
||||||
|
# Gather per-service configs
|
||||||
|
# Save base domain — each service stores config under its own prefixed keys
|
||||||
|
base_domain = config["domain"]
|
||||||
|
for svc in selected_services:
|
||||||
|
try:
|
||||||
|
mod = __import__(f"modules.{svc}", fromlist=[svc])
|
||||||
|
if hasattr(mod, "gather_config"):
|
||||||
|
# Set per-service subdomain default
|
||||||
|
svc_subdomains = {
|
||||||
|
"matrix": f"matrix.{base_domain}",
|
||||||
|
"cloud": f"cloud.{base_domain}",
|
||||||
|
"vault": f"vault.{base_domain}",
|
||||||
|
"media": f"media.{base_domain}",
|
||||||
|
"email": f"mail.{base_domain}",
|
||||||
|
"dns": f"dns.{base_domain}",
|
||||||
|
"vpn": base_domain,
|
||||||
|
}
|
||||||
|
if svc in svc_subdomains:
|
||||||
|
config["domain"] = svc_subdomains[svc]
|
||||||
|
config = mod.gather_config(config)
|
||||||
|
if config is None:
|
||||||
|
return None
|
||||||
|
except ImportError:
|
||||||
|
pass # Stub module, skip
|
||||||
|
# Restore base domain
|
||||||
|
config["domain"] = base_domain
|
||||||
|
|
||||||
|
config["nginx_reverse_proxy"] = True
|
||||||
|
config["certbot_enabled"] = bool(config.get("certbot_email"))
|
||||||
|
|
||||||
|
return config
|
||||||
@@ -0,0 +1,27 @@
|
|||||||
|
"""Nextcloud deployment module (stub — playbook TODO)."""
|
||||||
|
|
||||||
|
CYAN = "\033[38;5;51m"
|
||||||
|
WHITE = "\033[38;5;255m"
|
||||||
|
GREY = "\033[38;5;244m"
|
||||||
|
RESET = "\033[0m"
|
||||||
|
|
||||||
|
|
||||||
|
def gather_config(config):
|
||||||
|
"""Gather Nextcloud configuration."""
|
||||||
|
print(f"\n{CYAN} ┌─ Nextcloud Configuration ─────────────────────────┐{RESET}")
|
||||||
|
print(f" {CYAN}│{RESET} {GREY}Note: Playbook coming soon{RESET}")
|
||||||
|
|
||||||
|
config["domain"] = config.get("domain") or input(
|
||||||
|
f" {CYAN}│{RESET} Domain (e.g. cloud.example.com): "
|
||||||
|
).strip()
|
||||||
|
|
||||||
|
config["cloud_admin_user"] = input(
|
||||||
|
f" {CYAN}│{RESET} Admin username [{WHITE}admin{RESET}]: "
|
||||||
|
).strip() or "admin"
|
||||||
|
|
||||||
|
config["cloud_storage_gb"] = input(
|
||||||
|
f" {CYAN}│{RESET} Storage quota per user (GB) [{WHITE}10{RESET}]: "
|
||||||
|
).strip() or "10"
|
||||||
|
|
||||||
|
print(f" {CYAN}└─────────────────────────────────────────────────────┘{RESET}")
|
||||||
|
return config
|
||||||
@@ -0,0 +1,49 @@
|
|||||||
|
"""Pi-hole / AdGuard DNS server deployment module."""
|
||||||
|
|
||||||
|
CYAN = "\033[38;5;51m"
|
||||||
|
WHITE = "\033[38;5;255m"
|
||||||
|
GREY = "\033[38;5;244m"
|
||||||
|
YELLOW = "\033[38;5;214m"
|
||||||
|
RESET = "\033[0m"
|
||||||
|
|
||||||
|
|
||||||
|
def gather_config(config):
|
||||||
|
"""Gather DNS server configuration."""
|
||||||
|
print(f"\n{CYAN} ┌─ DNS Server Configuration ────────────────────────┐{RESET}")
|
||||||
|
|
||||||
|
print(f" {CYAN}│{RESET} Upstream DNS provider:")
|
||||||
|
print(f" {CYAN}│{RESET} {WHITE}1{RESET}) Quad9 (9.9.9.9) — security-focused")
|
||||||
|
print(f" {CYAN}│{RESET} {WHITE}2{RESET}) Cloudflare (1.1.1.1) — privacy-focused")
|
||||||
|
print(f" {CYAN}│{RESET} {WHITE}3{RESET}) Custom")
|
||||||
|
|
||||||
|
dns_choice = input(f" {CYAN}│{RESET} Choice [{WHITE}1{RESET}]: ").strip() or "1"
|
||||||
|
|
||||||
|
if dns_choice == "1":
|
||||||
|
config["dns_upstream"] = "9.9.9.9;149.112.112.112"
|
||||||
|
config["dns_upstream_name"] = "Quad9"
|
||||||
|
elif dns_choice == "2":
|
||||||
|
config["dns_upstream"] = "1.1.1.1;1.0.0.1"
|
||||||
|
config["dns_upstream_name"] = "Cloudflare"
|
||||||
|
else:
|
||||||
|
config["dns_upstream"] = input(
|
||||||
|
f" {CYAN}│{RESET} Custom DNS (semicolon-separated): "
|
||||||
|
).strip()
|
||||||
|
config["dns_upstream_name"] = "Custom"
|
||||||
|
|
||||||
|
config["dns_domain"] = input(
|
||||||
|
f" {CYAN}│{RESET} Admin interface domain (optional): "
|
||||||
|
).strip()
|
||||||
|
|
||||||
|
print(f" {CYAN}│{RESET}")
|
||||||
|
print(f" {CYAN}│{RESET} Blocklist presets:")
|
||||||
|
print(f" {CYAN}│{RESET} {WHITE}1{RESET}) Standard (ads + trackers)")
|
||||||
|
print(f" {CYAN}│{RESET} {WHITE}2{RESET}) Aggressive (+ social media trackers)")
|
||||||
|
print(f" {CYAN}│{RESET} {WHITE}3{RESET}) Minimal (ads only)")
|
||||||
|
|
||||||
|
bl_choice = input(f" {CYAN}│{RESET} Choice [{WHITE}1{RESET}]: ").strip() or "1"
|
||||||
|
config["dns_blocklist"] = {"1": "standard", "2": "aggressive", "3": "minimal"}.get(
|
||||||
|
bl_choice, "standard"
|
||||||
|
)
|
||||||
|
|
||||||
|
print(f" {CYAN}└─────────────────────────────────────────────────────┘{RESET}")
|
||||||
|
return config
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
"""Mail-in-a-Box deployment module (stub — playbook TODO)."""
|
||||||
|
|
||||||
|
CYAN = "\033[38;5;51m"
|
||||||
|
WHITE = "\033[38;5;255m"
|
||||||
|
GREY = "\033[38;5;244m"
|
||||||
|
RESET = "\033[0m"
|
||||||
|
|
||||||
|
|
||||||
|
def gather_config(config):
|
||||||
|
"""Gather Mail-in-a-Box configuration."""
|
||||||
|
print(f"\n{CYAN} ┌─ Mail-in-a-Box Configuration ─────────────────────┐{RESET}")
|
||||||
|
print(f" {CYAN}│{RESET} {GREY}Note: Playbook coming soon{RESET}")
|
||||||
|
|
||||||
|
config["domain"] = config.get("domain") or input(
|
||||||
|
f" {CYAN}│{RESET} Mail domain (e.g. mail.example.com): "
|
||||||
|
).strip()
|
||||||
|
|
||||||
|
config["email_first_user"] = input(
|
||||||
|
f" {CYAN}│{RESET} First email user (e.g. admin@example.com): "
|
||||||
|
).strip()
|
||||||
|
|
||||||
|
print(f" {CYAN}└─────────────────────────────────────────────────────┘{RESET}")
|
||||||
|
return config
|
||||||
@@ -0,0 +1,47 @@
|
|||||||
|
"""Matrix + Element homeserver deployment module."""
|
||||||
|
|
||||||
|
import getpass
|
||||||
|
import secrets
|
||||||
|
|
||||||
|
CYAN = "\033[38;5;51m"
|
||||||
|
WHITE = "\033[38;5;255m"
|
||||||
|
GREY = "\033[38;5;244m"
|
||||||
|
RESET = "\033[0m"
|
||||||
|
|
||||||
|
|
||||||
|
def gather_config(config):
|
||||||
|
"""Gather Matrix/Synapse + Element configuration."""
|
||||||
|
print(f"\n{CYAN} ┌─ Matrix Homeserver Configuration ─────────────────┐{RESET}")
|
||||||
|
|
||||||
|
config["domain"] = config.get("domain") or input(
|
||||||
|
f" {CYAN}│{RESET} Domain (e.g. matrix.example.com): "
|
||||||
|
).strip()
|
||||||
|
if not config["domain"]:
|
||||||
|
print(f" {CYAN}│{RESET} Domain is required.")
|
||||||
|
return None
|
||||||
|
|
||||||
|
config["matrix_admin_user"] = input(
|
||||||
|
f" {CYAN}│{RESET} Admin username [{WHITE}admin{RESET}]: "
|
||||||
|
).strip() or "admin"
|
||||||
|
|
||||||
|
config["matrix_admin_password"] = getpass.getpass(
|
||||||
|
f" {CYAN}│{RESET} Admin password (blank=generate): "
|
||||||
|
) or secrets.token_urlsafe(20)
|
||||||
|
|
||||||
|
config["matrix_registration"] = input(
|
||||||
|
f" {CYAN}│{RESET} Open registration? [{WHITE}no{RESET}]: "
|
||||||
|
).strip().lower()
|
||||||
|
config["matrix_registration"] = config["matrix_registration"] in ("yes", "y", "true")
|
||||||
|
|
||||||
|
config["matrix_element_web"] = input(
|
||||||
|
f" {CYAN}│{RESET} Deploy Element Web? [{WHITE}yes{RESET}]: "
|
||||||
|
).strip().lower()
|
||||||
|
config["matrix_element_web"] = config["matrix_element_web"] not in ("no", "n", "false")
|
||||||
|
|
||||||
|
config["matrix_server_name"] = config["domain"].replace("matrix.", "", 1) \
|
||||||
|
if config["domain"].startswith("matrix.") else config["domain"]
|
||||||
|
|
||||||
|
config["matrix_signing_key"] = secrets.token_hex(32)
|
||||||
|
|
||||||
|
print(f" {CYAN}└─────────────────────────────────────────────────────┘{RESET}")
|
||||||
|
return config
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
"""Jellyfin media server deployment module (stub — playbook TODO)."""
|
||||||
|
|
||||||
|
CYAN = "\033[38;5;51m"
|
||||||
|
WHITE = "\033[38;5;255m"
|
||||||
|
GREY = "\033[38;5;244m"
|
||||||
|
RESET = "\033[0m"
|
||||||
|
|
||||||
|
|
||||||
|
def gather_config(config):
|
||||||
|
"""Gather Jellyfin configuration."""
|
||||||
|
print(f"\n{CYAN} ┌─ Jellyfin Configuration ──────────────────────────┐{RESET}")
|
||||||
|
print(f" {CYAN}│{RESET} {GREY}Note: Playbook coming soon{RESET}")
|
||||||
|
|
||||||
|
config["domain"] = config.get("domain") or input(
|
||||||
|
f" {CYAN}│{RESET} Domain (e.g. media.example.com): "
|
||||||
|
).strip()
|
||||||
|
|
||||||
|
config["media_library_path"] = input(
|
||||||
|
f" {CYAN}│{RESET} Media library path [{WHITE}/srv/media{RESET}]: "
|
||||||
|
).strip() or "/srv/media"
|
||||||
|
|
||||||
|
print(f" {CYAN}└─────────────────────────────────────────────────────┘{RESET}")
|
||||||
|
return config
|
||||||
@@ -0,0 +1,29 @@
|
|||||||
|
"""Vaultwarden (Bitwarden) deployment module (stub — playbook TODO)."""
|
||||||
|
|
||||||
|
import secrets
|
||||||
|
|
||||||
|
CYAN = "\033[38;5;51m"
|
||||||
|
WHITE = "\033[38;5;255m"
|
||||||
|
GREY = "\033[38;5;244m"
|
||||||
|
RESET = "\033[0m"
|
||||||
|
|
||||||
|
|
||||||
|
def gather_config(config):
|
||||||
|
"""Gather Vaultwarden configuration."""
|
||||||
|
print(f"\n{CYAN} ┌─ Vaultwarden Configuration ───────────────────────┐{RESET}")
|
||||||
|
print(f" {CYAN}│{RESET} {GREY}Note: Playbook coming soon{RESET}")
|
||||||
|
|
||||||
|
config["domain"] = config.get("domain") or input(
|
||||||
|
f" {CYAN}│{RESET} Domain (e.g. vault.example.com): "
|
||||||
|
).strip()
|
||||||
|
|
||||||
|
config["vault_admin_token"] = input(
|
||||||
|
f" {CYAN}│{RESET} Admin token (blank=generate): "
|
||||||
|
).strip() or secrets.token_urlsafe(32)
|
||||||
|
|
||||||
|
config["vault_signups_allowed"] = input(
|
||||||
|
f" {CYAN}│{RESET} Allow signups? [{WHITE}no{RESET}]: "
|
||||||
|
).strip().lower() in ("yes", "y", "true")
|
||||||
|
|
||||||
|
print(f" {CYAN}└─────────────────────────────────────────────────────┘{RESET}")
|
||||||
|
return config
|
||||||
@@ -0,0 +1,39 @@
|
|||||||
|
"""WireGuard VPN server deployment module."""
|
||||||
|
|
||||||
|
CYAN = "\033[38;5;51m"
|
||||||
|
WHITE = "\033[38;5;255m"
|
||||||
|
GREY = "\033[38;5;244m"
|
||||||
|
RESET = "\033[0m"
|
||||||
|
|
||||||
|
|
||||||
|
def gather_config(config):
|
||||||
|
"""Gather WireGuard VPN configuration."""
|
||||||
|
print(f"\n{CYAN} ┌─ WireGuard VPN Configuration ──────────────────────┐{RESET}")
|
||||||
|
|
||||||
|
config["vpn_port"] = input(
|
||||||
|
f" {CYAN}│{RESET} Listen port [{WHITE}51820{RESET}]: "
|
||||||
|
).strip() or "51820"
|
||||||
|
|
||||||
|
config["vpn_client_count"] = input(
|
||||||
|
f" {CYAN}│{RESET} Number of client configs [{WHITE}3{RESET}]: "
|
||||||
|
).strip() or "3"
|
||||||
|
|
||||||
|
try:
|
||||||
|
config["vpn_client_count"] = int(config["vpn_client_count"])
|
||||||
|
except ValueError:
|
||||||
|
config["vpn_client_count"] = 3
|
||||||
|
|
||||||
|
config["vpn_dns"] = input(
|
||||||
|
f" {CYAN}│{RESET} Client DNS server [{WHITE}1.1.1.1{RESET}]: "
|
||||||
|
).strip() or "1.1.1.1"
|
||||||
|
|
||||||
|
config["vpn_allowed_ips"] = input(
|
||||||
|
f" {CYAN}│{RESET} Allowed IPs [{WHITE}0.0.0.0/0, ::/0{RESET}]: "
|
||||||
|
).strip() or "0.0.0.0/0, ::/0"
|
||||||
|
|
||||||
|
config["vpn_subnet"] = input(
|
||||||
|
f" {CYAN}│{RESET} VPN subnet [{WHITE}10.66.66.0/24{RESET}]: "
|
||||||
|
).strip() or "10.66.66.0/24"
|
||||||
|
|
||||||
|
print(f" {CYAN}└─────────────────────────────────────────────────────┘{RESET}")
|
||||||
|
return config
|
||||||
Executable
+463
@@ -0,0 +1,463 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""phantom — Privacy Server Deployer
|
||||||
|
|
||||||
|
Deploy self-hosted privacy infrastructure with a single command.
|
||||||
|
Supports Matrix, WireGuard VPN, Pi-hole DNS, Nextcloud, and more.
|
||||||
|
|
||||||
|
Providers: Linode, AWS, FlokiNET, or local/existing server.
|
||||||
|
"""
|
||||||
|
|
||||||
|
import json
|
||||||
|
import os
|
||||||
|
import random
|
||||||
|
import subprocess
|
||||||
|
import sys
|
||||||
|
import time
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
# Allow modules to import from phantom
|
||||||
|
sys.path.insert(0, os.path.dirname(__file__))
|
||||||
|
|
||||||
|
BASE_DIR = Path(__file__).resolve().parent
|
||||||
|
PLAYBOOKS_DIR = BASE_DIR / "playbooks"
|
||||||
|
PROVIDERS_DIR = BASE_DIR / "providers"
|
||||||
|
LOGS_DIR = BASE_DIR / "logs"
|
||||||
|
LOGS_DIR.mkdir(exist_ok=True)
|
||||||
|
|
||||||
|
# ─── Color Helpers ──────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
RESET = "\033[0m"
|
||||||
|
BOLD = "\033[1m"
|
||||||
|
DIM = "\033[2m"
|
||||||
|
CYAN = "\033[38;5;51m"
|
||||||
|
GREEN = "\033[38;5;49m"
|
||||||
|
RED = "\033[38;5;196m"
|
||||||
|
YELLOW = "\033[38;5;214m"
|
||||||
|
MAGENTA = "\033[38;5;201m"
|
||||||
|
WHITE = "\033[38;5;255m"
|
||||||
|
GREY = "\033[38;5;244m"
|
||||||
|
|
||||||
|
|
||||||
|
def ok(msg):
|
||||||
|
print(f"{GREEN}[+]{RESET} {msg}")
|
||||||
|
|
||||||
|
def err(msg):
|
||||||
|
print(f"{RED}[-]{RESET} {msg}")
|
||||||
|
|
||||||
|
def warn(msg):
|
||||||
|
print(f"{YELLOW}[*]{RESET} {msg}")
|
||||||
|
|
||||||
|
def info(msg):
|
||||||
|
print(f"{CYAN}[~]{RESET} {msg}")
|
||||||
|
|
||||||
|
def dim(msg):
|
||||||
|
print(f"{GREY} {msg}{RESET}")
|
||||||
|
|
||||||
|
|
||||||
|
# ─── Banner ─────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
BANNER = f"""
|
||||||
|
{MAGENTA} ██████╗ ██╗ ██╗ █████╗ ███╗ ██╗████████╗ ██████╗ ███╗ ███╗
|
||||||
|
██╔══██╗██║ ██║██╔══██╗████╗ ██║╚══██╔══╝██╔═══██╗████╗ ████║
|
||||||
|
██████╔╝███████║███████║██╔██╗ ██║ ██║ ██║ ██║██╔████╔██║
|
||||||
|
██╔═══╝ ██╔══██║██╔══██║██║╚██╗██║ ██║ ██║ ██║██║╚██╔╝██║
|
||||||
|
██║ ██║ ██║██║ ██║██║ ╚████║ ██║ ╚██████╔╝██║ ╚═╝ ██║
|
||||||
|
╚═╝ ╚═╝ ╚═╝╚═╝ ╚═╝╚═╝ ╚═══╝ ╚═╝ ╚═════╝ ╚═╝ ╚═╝{RESET}
|
||||||
|
{GREY} Privacy Server Deployer — Your infrastructure, your rules{RESET}
|
||||||
|
"""
|
||||||
|
|
||||||
|
|
||||||
|
def banner():
|
||||||
|
print(BANNER)
|
||||||
|
|
||||||
|
|
||||||
|
# ─── Deployment ID Generation ───────────────────────────────────────────────
|
||||||
|
|
||||||
|
ADJECTIVES = [
|
||||||
|
"silent", "hidden", "shadow", "quiet", "swift", "dark", "fading", "lost",
|
||||||
|
"frozen", "drifting", "hollow", "veiled", "pale", "deep", "still", "wild",
|
||||||
|
"broken", "burning", "crimson", "golden", "silver", "iron", "copper",
|
||||||
|
"cobalt", "jade", "amber", "arctic", "lunar", "solar", "astral", "coral",
|
||||||
|
"misty", "foggy", "dusty", "rusty", "mossy", "stormy", "cloudy", "windy",
|
||||||
|
"gentle", "fierce", "steady", "rapid", "lazy", "bold", "brave", "calm",
|
||||||
|
"clever", "cryptic", "cunning", "eager", "faint", "grave", "keen", "noble",
|
||||||
|
"prime", "rare", "stark", "terse", "vivid", "wary", "zealous", "agile",
|
||||||
|
"blunt", "coarse", "dense", "eerie", "fleet", "gaunt", "harsh", "lucid",
|
||||||
|
"muted", "numb", "opaque", "plain", "rigid", "sleek", "taut", "urban",
|
||||||
|
"vacant", "woven", "binary", "cipher", "delta", "echo", "foxtrot", "gamma",
|
||||||
|
"hex", "index", "kilo", "lambda", "micro", "nano", "omega", "proxy",
|
||||||
|
"quantum", "rogue", "sigma", "theta", "ultra", "vector", "xray", "zero",
|
||||||
|
]
|
||||||
|
|
||||||
|
NOUNS = [
|
||||||
|
"phantom", "spectre", "wraith", "shade", "ghost", "echo", "void", "rift",
|
||||||
|
"nexus", "pulse", "signal", "cipher", "prism", "beacon", "aegis", "bastion",
|
||||||
|
"citadel", "forge", "haven", "vault", "harbor", "summit", "ridge", "canyon",
|
||||||
|
"glacier", "tundra", "steppe", "mesa", "delta", "fjord", "grove", "marsh",
|
||||||
|
"oasis", "reef", "shoal", "brook", "creek", "falls", "rapids", "spring",
|
||||||
|
"falcon", "raven", "hawk", "condor", "osprey", "heron", "crane", "wren",
|
||||||
|
"finch", "swift", "sparrow", "robin", "wolf", "fox", "lynx", "panther",
|
||||||
|
"tiger", "cobra", "viper", "mantis", "hornet", "spider", "scorpion",
|
||||||
|
"anchor", "arrow", "blade", "bolt", "chain", "crown", "flint", "glyph",
|
||||||
|
"helm", "ingot", "jewel", "knot", "lance", "mast", "oar", "pike",
|
||||||
|
"quill", "rune", "shard", "thorn", "urn", "wand", "atlas", "core",
|
||||||
|
"dusk", "ember", "frost", "gale", "haze", "iris", "jade", "karma",
|
||||||
|
"lumen", "myth", "nova", "orbit", "pixel", "quest", "relay", "sage",
|
||||||
|
"trace", "unity", "vertex", "zenith",
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
def generate_id():
|
||||||
|
"""Generate a deployment ID with 10k+ unique combinations."""
|
||||||
|
adj = random.choice(ADJECTIVES)
|
||||||
|
noun = random.choice(NOUNS)
|
||||||
|
num = random.randint(10, 99)
|
||||||
|
return f"{adj}-{noun}-{num}"
|
||||||
|
|
||||||
|
|
||||||
|
# ─── SSH Key Generation ─────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
def generate_ssh_key(deploy_id):
|
||||||
|
"""Generate an RSA 4096 SSH keypair for deployment."""
|
||||||
|
key_dir = LOGS_DIR / deploy_id
|
||||||
|
key_dir.mkdir(parents=True, exist_ok=True)
|
||||||
|
key_path = key_dir / f"deploy_{deploy_id}"
|
||||||
|
|
||||||
|
if key_path.exists():
|
||||||
|
info(f"SSH key already exists: {key_path}")
|
||||||
|
return str(key_path)
|
||||||
|
|
||||||
|
info(f"Generating SSH key: {key_path}")
|
||||||
|
subprocess.run(
|
||||||
|
["ssh-keygen", "-t", "rsa", "-b", "4096", "-f", str(key_path),
|
||||||
|
"-N", "", "-C", f"deploy-{deploy_id}"],
|
||||||
|
check=True, capture_output=True,
|
||||||
|
)
|
||||||
|
os.chmod(key_path, 0o600)
|
||||||
|
ok(f"SSH key generated: {key_path}")
|
||||||
|
return str(key_path)
|
||||||
|
|
||||||
|
|
||||||
|
# ─── Ansible Playbook Execution ─────────────────────────────────────────────
|
||||||
|
|
||||||
|
def run_playbook(playbook_path, config, extra_vars=None):
|
||||||
|
"""Execute an Ansible playbook with the given config.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
playbook_path: Path object or string to the playbook file.
|
||||||
|
config: Deployment config dict.
|
||||||
|
extra_vars: Optional dict of extra Ansible variables.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
True if playbook succeeded, False otherwise.
|
||||||
|
"""
|
||||||
|
playbook_path = Path(playbook_path)
|
||||||
|
if not playbook_path.exists():
|
||||||
|
err(f"Playbook not found: {playbook_path}")
|
||||||
|
return False
|
||||||
|
|
||||||
|
cmd = ["ansible-playbook", str(playbook_path)]
|
||||||
|
|
||||||
|
# Build vars file
|
||||||
|
vars_file = LOGS_DIR / config["deploy_id"] / "vars.yaml"
|
||||||
|
vars_file.parent.mkdir(parents=True, exist_ok=True)
|
||||||
|
|
||||||
|
try:
|
||||||
|
import yaml # noqa: optional dependency
|
||||||
|
with open(vars_file, "w") as f:
|
||||||
|
yaml.dump(config, f, default_flow_style=False)
|
||||||
|
except ImportError:
|
||||||
|
with open(vars_file, "w") as f:
|
||||||
|
for k, v in config.items():
|
||||||
|
f.write(f"{k}: {json.dumps(v)}\n")
|
||||||
|
|
||||||
|
cmd.extend(["-e", f"@{vars_file}"])
|
||||||
|
|
||||||
|
if extra_vars:
|
||||||
|
for k, v in extra_vars.items():
|
||||||
|
cmd.extend(["-e", f"{k}={v}"])
|
||||||
|
|
||||||
|
# Set inventory
|
||||||
|
target = config.get("target_host", "localhost")
|
||||||
|
if target == "localhost":
|
||||||
|
cmd.extend(["-i", "localhost,", "--connection", "local"])
|
||||||
|
else:
|
||||||
|
inv_file = LOGS_DIR / config["deploy_id"] / "inventory"
|
||||||
|
with open(inv_file, "w") as f:
|
||||||
|
ssh_key = config.get("ssh_key", "")
|
||||||
|
ssh_user = config.get("ssh_user", "root")
|
||||||
|
line = f"{target} ansible_user={ssh_user}"
|
||||||
|
if ssh_key:
|
||||||
|
line += f" ansible_ssh_private_key_file={ssh_key}"
|
||||||
|
f.write(f"[servers]\n{line}\n")
|
||||||
|
cmd.extend(["-i", str(inv_file)])
|
||||||
|
|
||||||
|
# Elevate privileges for non-root users
|
||||||
|
if ssh_user != "root":
|
||||||
|
cmd.append("--become")
|
||||||
|
|
||||||
|
info(f"Running: {' '.join(cmd)}")
|
||||||
|
result = subprocess.run(cmd)
|
||||||
|
return result.returncode == 0
|
||||||
|
|
||||||
|
|
||||||
|
# ─── Deployment Info Logging ────────────────────────────────────────────────
|
||||||
|
|
||||||
|
def save_deploy_info(config):
|
||||||
|
"""Save deployment metadata to logs."""
|
||||||
|
deploy_dir = LOGS_DIR / config["deploy_id"]
|
||||||
|
deploy_dir.mkdir(parents=True, exist_ok=True)
|
||||||
|
info_file = deploy_dir / "deploy_info.json"
|
||||||
|
|
||||||
|
config["timestamp"] = time.strftime("%Y-%m-%dT%H:%M:%S%z")
|
||||||
|
with open(info_file, "w") as f:
|
||||||
|
json.dump(config, f, indent=2, default=str)
|
||||||
|
ok(f"Deployment info saved: {info_file}")
|
||||||
|
|
||||||
|
|
||||||
|
# ─── SSH Connect ────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
def ssh_connect(config):
|
||||||
|
"""Offer to SSH into the deployed server."""
|
||||||
|
target = config.get("target_host", "")
|
||||||
|
if not target or target == "localhost":
|
||||||
|
return
|
||||||
|
|
||||||
|
ssh_key = config.get("ssh_key", "")
|
||||||
|
ssh_user = config.get("ssh_user", "root")
|
||||||
|
|
||||||
|
cmd = ["ssh"]
|
||||||
|
if ssh_key:
|
||||||
|
cmd.extend(["-i", ssh_key])
|
||||||
|
cmd.append(f"{ssh_user}@{target}")
|
||||||
|
|
||||||
|
print()
|
||||||
|
resp = input(f"{CYAN}[?]{RESET} SSH into {target}? [y/N] ").strip().lower()
|
||||||
|
if resp == "y":
|
||||||
|
os.execvp("ssh", cmd)
|
||||||
|
|
||||||
|
|
||||||
|
# ─── Provider Selection ─────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
def select_provider():
|
||||||
|
"""Select deployment target: cloud provider or local/existing server."""
|
||||||
|
print(f"\n{CYAN} Select deployment target:{RESET}")
|
||||||
|
print(f" {WHITE}1{RESET}) Linode")
|
||||||
|
print(f" {WHITE}2{RESET}) AWS (EC2)")
|
||||||
|
print(f" {WHITE}3{RESET}) FlokiNET (pre-provisioned)")
|
||||||
|
print(f" {WHITE}4{RESET}) Existing server (SSH)")
|
||||||
|
print(f" {WHITE}5{RESET}) Local (this machine)")
|
||||||
|
print()
|
||||||
|
|
||||||
|
choice = input(f" {MAGENTA}>{RESET} ").strip()
|
||||||
|
providers = {"1": "linode", "2": "aws", "3": "flokinet", "4": "existing", "5": "local"}
|
||||||
|
return providers.get(choice)
|
||||||
|
|
||||||
|
|
||||||
|
def gather_credentials(provider, config):
|
||||||
|
"""Gather provider-specific credentials."""
|
||||||
|
if provider == "linode":
|
||||||
|
config["provider"] = "linode"
|
||||||
|
config["api_token"] = input(f" {CYAN}Linode API token:{RESET} ").strip()
|
||||||
|
config["region"] = input(f" {CYAN}Region (e.g. us-east):{RESET} ").strip() or "us-east"
|
||||||
|
config["plan"] = input(f" {CYAN}Plan (e.g. g6-nanode-1):{RESET} ").strip() or "g6-nanode-1"
|
||||||
|
|
||||||
|
elif provider == "aws":
|
||||||
|
config["provider"] = "aws"
|
||||||
|
config["aws_access_key"] = input(f" {CYAN}AWS Access Key ID:{RESET} ").strip()
|
||||||
|
config["aws_secret_key"] = input(f" {CYAN}AWS Secret Access Key:{RESET} ").strip()
|
||||||
|
config["region"] = input(f" {CYAN}Region (e.g. us-east-1):{RESET} ").strip() or "us-east-1"
|
||||||
|
config["instance_type"] = input(f" {CYAN}Instance type (e.g. t3.micro):{RESET} ").strip() or "t3.micro"
|
||||||
|
|
||||||
|
elif provider == "flokinet":
|
||||||
|
config["provider"] = "flokinet"
|
||||||
|
config["target_host"] = input(f" {CYAN}Server IP:{RESET} ").strip()
|
||||||
|
config["ssh_user"] = input(f" {CYAN}SSH user [root]:{RESET} ").strip() or "root"
|
||||||
|
|
||||||
|
elif provider == "existing":
|
||||||
|
config["provider"] = "existing"
|
||||||
|
config["target_host"] = input(f" {CYAN}Server IP/hostname:{RESET} ").strip()
|
||||||
|
config["ssh_user"] = input(f" {CYAN}SSH user [root]:{RESET} ").strip() or "root"
|
||||||
|
existing_key = input(f" {CYAN}SSH key path (blank to generate):{RESET} ").strip()
|
||||||
|
if existing_key:
|
||||||
|
config["ssh_key"] = existing_key
|
||||||
|
|
||||||
|
elif provider == "local":
|
||||||
|
config["provider"] = "local"
|
||||||
|
config["target_host"] = "localhost"
|
||||||
|
config["ssh_user"] = os.getenv("USER", "root")
|
||||||
|
warn("Local deployment will install services directly on this machine.")
|
||||||
|
confirm = input(f" {YELLOW}Continue? [y/N]:{RESET} ").strip().lower()
|
||||||
|
if confirm != "y":
|
||||||
|
return False
|
||||||
|
|
||||||
|
return True
|
||||||
|
|
||||||
|
|
||||||
|
# ─── Deployment Orchestration ───────────────────────────────────────────────
|
||||||
|
|
||||||
|
def deploy(server_type, config):
|
||||||
|
"""Full deployment pipeline: summarize -> confirm -> provision -> configure."""
|
||||||
|
config["server_type"] = server_type
|
||||||
|
config.setdefault("deploy_id", generate_id())
|
||||||
|
deploy_id = config["deploy_id"]
|
||||||
|
|
||||||
|
# Summary
|
||||||
|
print(f"\n{CYAN}{'─' * 60}{RESET}")
|
||||||
|
print(f"{MAGENTA} Deployment Summary{RESET}")
|
||||||
|
print(f"{CYAN}{'─' * 60}{RESET}")
|
||||||
|
print(f" {WHITE}ID:{RESET} {deploy_id}")
|
||||||
|
print(f" {WHITE}Type:{RESET} {server_type}")
|
||||||
|
print(f" {WHITE}Provider:{RESET} {config.get('provider', 'unknown')}")
|
||||||
|
print(f" {WHITE}Target:{RESET} {config.get('target_host', 'TBD (will provision)')}")
|
||||||
|
if config.get("domain"):
|
||||||
|
print(f" {WHITE}Domain:{RESET} {config['domain']}")
|
||||||
|
for k, v in config.items():
|
||||||
|
if k not in ("deploy_id", "server_type", "provider", "target_host",
|
||||||
|
"domain", "api_token", "aws_access_key", "aws_secret_key",
|
||||||
|
"ssh_key", "ssh_user", "timestamp"):
|
||||||
|
print(f" {WHITE}{k}:{RESET} {v}")
|
||||||
|
print(f"{CYAN}{'─' * 60}{RESET}")
|
||||||
|
|
||||||
|
confirm = input(f"\n {MAGENTA}Deploy? [y/N]:{RESET} ").strip().lower()
|
||||||
|
if confirm != "y":
|
||||||
|
warn("Deployment cancelled.")
|
||||||
|
return
|
||||||
|
|
||||||
|
# Generate SSH key if needed
|
||||||
|
if config.get("provider") not in ("local",) and not config.get("ssh_key"):
|
||||||
|
config["ssh_key"] = generate_ssh_key(deploy_id)
|
||||||
|
|
||||||
|
# Provision if cloud provider
|
||||||
|
if config.get("provider") in ("linode", "aws"):
|
||||||
|
provider_playbook = PROVIDERS_DIR / f"{config['provider']}.yml"
|
||||||
|
# Tell the provider playbook where to write the provisioned IP
|
||||||
|
host_file = LOGS_DIR / deploy_id / "provisioned_host"
|
||||||
|
config["_host_output_file"] = str(host_file)
|
||||||
|
info(f"Provisioning {config['provider']} instance...")
|
||||||
|
if not run_playbook(provider_playbook, config):
|
||||||
|
err("Provisioning failed.")
|
||||||
|
return
|
||||||
|
# Read back the provisioned host IP
|
||||||
|
if host_file.exists():
|
||||||
|
config["target_host"] = host_file.read_text().strip()
|
||||||
|
ok(f"Provisioned server: {config['target_host']}")
|
||||||
|
else:
|
||||||
|
err("Provisioning completed but no host IP was returned.")
|
||||||
|
return
|
||||||
|
|
||||||
|
# Base hardening
|
||||||
|
info("Applying base hardening...")
|
||||||
|
if not run_playbook(PLAYBOOKS_DIR / "common/base_hardening.yml", config):
|
||||||
|
err("Base hardening failed — aborting deployment.")
|
||||||
|
return
|
||||||
|
|
||||||
|
# Service-specific playbook
|
||||||
|
playbook_map = {
|
||||||
|
"matrix": PLAYBOOKS_DIR / "matrix/main.yml",
|
||||||
|
"vpn": PLAYBOOKS_DIR / "vpn/main.yml",
|
||||||
|
"dns": PLAYBOOKS_DIR / "dns/main.yml",
|
||||||
|
"cloud": PLAYBOOKS_DIR / "cloud/main.yml",
|
||||||
|
"vault": PLAYBOOKS_DIR / "vault/main.yml",
|
||||||
|
"media": PLAYBOOKS_DIR / "media/main.yml",
|
||||||
|
"email": PLAYBOOKS_DIR / "email/main.yml",
|
||||||
|
"all_in_one": PLAYBOOKS_DIR / "all_in_one/main.yml",
|
||||||
|
}
|
||||||
|
|
||||||
|
playbook = playbook_map.get(server_type)
|
||||||
|
if playbook:
|
||||||
|
info(f"Configuring {server_type}...")
|
||||||
|
if run_playbook(playbook, config):
|
||||||
|
ok(f"Deployment complete: {deploy_id}")
|
||||||
|
else:
|
||||||
|
err(f"Service configuration failed for {server_type}")
|
||||||
|
return
|
||||||
|
|
||||||
|
# Save info and offer SSH
|
||||||
|
save_deploy_info(config)
|
||||||
|
ssh_connect(config)
|
||||||
|
|
||||||
|
|
||||||
|
# ─── Main Menu ──────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
MENU_ITEMS = [
|
||||||
|
("1", "Matrix + Element", "matrix", "Encrypted messaging homeserver"),
|
||||||
|
("2", "WireGuard VPN", "vpn", "Private VPN server"),
|
||||||
|
("3", "Pi-hole DNS", "dns", "Ad-blocking DNS server"),
|
||||||
|
("4", "Nextcloud", "cloud", "Self-hosted file sync (coming soon)"),
|
||||||
|
("5", "Vaultwarden", "vault", "Password manager (coming soon)"),
|
||||||
|
("6", "Jellyfin", "media", "Media server (coming soon)"),
|
||||||
|
("7", "Mail-in-a-Box", "email", "Email server (coming soon)"),
|
||||||
|
("8", "All-in-One", "all_in_one", "Multiple services on one server"),
|
||||||
|
("0", "Exit", None, None),
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
def main_menu():
|
||||||
|
banner()
|
||||||
|
|
||||||
|
while True:
|
||||||
|
print(f"\n{CYAN} ┌─ Deploy a Privacy Server ──────────────────────────┐{RESET}")
|
||||||
|
for num, label, _, desc in MENU_ITEMS:
|
||||||
|
if desc:
|
||||||
|
print(f" {CYAN}│{RESET} {WHITE}{num}{RESET}) {label:<20} {GREY}{desc}{RESET}")
|
||||||
|
else:
|
||||||
|
print(f" {CYAN}│{RESET} {WHITE}{num}{RESET}) {label}")
|
||||||
|
print(f" {CYAN}└─────────────────────────────────────────────────────┘{RESET}")
|
||||||
|
|
||||||
|
choice = input(f"\n {MAGENTA}>{RESET} ").strip()
|
||||||
|
|
||||||
|
if choice == "0":
|
||||||
|
print(f"\n{GREY} Goodbye.{RESET}\n")
|
||||||
|
break
|
||||||
|
|
||||||
|
# Find matching menu item
|
||||||
|
selected = None
|
||||||
|
for num, label, stype, _ in MENU_ITEMS:
|
||||||
|
if choice == num and stype:
|
||||||
|
selected = stype
|
||||||
|
break
|
||||||
|
|
||||||
|
if not selected:
|
||||||
|
warn("Invalid selection.")
|
||||||
|
continue
|
||||||
|
|
||||||
|
# Import the module
|
||||||
|
try:
|
||||||
|
mod = __import__(f"modules.{selected}", fromlist=[selected])
|
||||||
|
except ImportError as e:
|
||||||
|
err(f"Module not found: {selected} ({e})")
|
||||||
|
continue
|
||||||
|
|
||||||
|
# Select provider
|
||||||
|
provider = select_provider()
|
||||||
|
if not provider:
|
||||||
|
warn("Invalid provider selection.")
|
||||||
|
continue
|
||||||
|
|
||||||
|
config = {"deploy_id": generate_id()}
|
||||||
|
|
||||||
|
if not gather_credentials(provider, config):
|
||||||
|
continue
|
||||||
|
|
||||||
|
# Gather service-specific config
|
||||||
|
if hasattr(mod, "gather_config"):
|
||||||
|
config = mod.gather_config(config)
|
||||||
|
if config is None:
|
||||||
|
continue
|
||||||
|
|
||||||
|
# Deploy
|
||||||
|
deploy(selected, config)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
if len(sys.argv) > 1 and sys.argv[1] in ("--help", "-h"):
|
||||||
|
print("Usage: python3 phantom.py")
|
||||||
|
print(" Interactive privacy server deployer.")
|
||||||
|
print(" Supports Matrix, WireGuard, Pi-hole, and more.")
|
||||||
|
print(" Run without arguments to launch the interactive menu.")
|
||||||
|
sys.exit(0)
|
||||||
|
try:
|
||||||
|
main_menu()
|
||||||
|
except KeyboardInterrupt:
|
||||||
|
print(f"\n{GREY} Interrupted.{RESET}\n")
|
||||||
@@ -0,0 +1,92 @@
|
|||||||
|
---
|
||||||
|
# All-in-One deployment — multiple services on a single server
|
||||||
|
# Installs nginx as reverse proxy with TLS termination via Certbot,
|
||||||
|
# then deploys selected services behind vhost routing.
|
||||||
|
#
|
||||||
|
# NOTE: This playbook includes task files directly rather than
|
||||||
|
# importing full playbooks, to allow conditional composition.
|
||||||
|
|
||||||
|
- name: All-in-One Privacy Server
|
||||||
|
hosts: all
|
||||||
|
become: true
|
||||||
|
vars:
|
||||||
|
base_domain: "{{ domain }}"
|
||||||
|
selected_services: "{{ services | default([]) }}"
|
||||||
|
certbot_email: "{{ certbot_email | default('') }}"
|
||||||
|
target_host: "{{ target_host | default('localhost') }}"
|
||||||
|
|
||||||
|
tasks:
|
||||||
|
# ─── Base: Nginx + Certbot ────────────────────────────────────────
|
||||||
|
- name: Install nginx and certbot
|
||||||
|
apt:
|
||||||
|
name:
|
||||||
|
- nginx
|
||||||
|
- certbot
|
||||||
|
- python3-certbot-nginx
|
||||||
|
state: present
|
||||||
|
|
||||||
|
- name: Remove default nginx site
|
||||||
|
file:
|
||||||
|
path: /etc/nginx/sites-enabled/default
|
||||||
|
state: absent
|
||||||
|
notify: reload nginx
|
||||||
|
|
||||||
|
- name: Allow HTTP/HTTPS through UFW
|
||||||
|
ufw:
|
||||||
|
rule: allow
|
||||||
|
port: "{{ item }}"
|
||||||
|
proto: tcp
|
||||||
|
loop:
|
||||||
|
- "80"
|
||||||
|
- "443"
|
||||||
|
|
||||||
|
# ─── Deploy Individual Services (task files, not full playbooks) ──
|
||||||
|
- name: Deploy Matrix — Synapse
|
||||||
|
include_tasks: "{{ playbook_dir }}/../matrix/tasks/synapse.yml"
|
||||||
|
when: "'matrix' in selected_services"
|
||||||
|
|
||||||
|
- name: Deploy Matrix — Element Web
|
||||||
|
include_tasks: "{{ playbook_dir }}/../matrix/tasks/element.yml"
|
||||||
|
when: "'matrix' in selected_services and (matrix_element_web | default(true) | bool)"
|
||||||
|
|
||||||
|
- name: Deploy Matrix — Nginx vhost
|
||||||
|
include_tasks: "{{ playbook_dir }}/../matrix/tasks/nginx.yml"
|
||||||
|
when: "'matrix' in selected_services"
|
||||||
|
|
||||||
|
- name: Deploy WireGuard — Install
|
||||||
|
include_tasks: "{{ playbook_dir }}/../vpn/tasks/install.yml"
|
||||||
|
when: "'vpn' in selected_services"
|
||||||
|
|
||||||
|
- name: Deploy WireGuard — Configure
|
||||||
|
include_tasks: "{{ playbook_dir }}/../vpn/tasks/configure.yml"
|
||||||
|
when: "'vpn' in selected_services"
|
||||||
|
|
||||||
|
- name: Deploy Pi-hole — Install
|
||||||
|
include_tasks: "{{ playbook_dir }}/../dns/tasks/install.yml"
|
||||||
|
when: "'dns' in selected_services"
|
||||||
|
|
||||||
|
- name: Deploy Pi-hole — Configure
|
||||||
|
include_tasks: "{{ playbook_dir }}/../dns/tasks/configure.yml"
|
||||||
|
when: "'dns' in selected_services"
|
||||||
|
|
||||||
|
# Stub services — print notice
|
||||||
|
- name: Notice for stub services
|
||||||
|
debug:
|
||||||
|
msg: "Service '{{ item }}' playbook not yet implemented — skipping"
|
||||||
|
loop: "{{ selected_services | select('in', ['cloud', 'vault', 'media', 'email']) | list }}"
|
||||||
|
|
||||||
|
handlers:
|
||||||
|
- name: reload nginx
|
||||||
|
service:
|
||||||
|
name: nginx
|
||||||
|
state: reloaded
|
||||||
|
|
||||||
|
- name: restart synapse
|
||||||
|
service:
|
||||||
|
name: matrix-synapse
|
||||||
|
state: restarted
|
||||||
|
|
||||||
|
- name: restart sshd
|
||||||
|
service:
|
||||||
|
name: sshd
|
||||||
|
state: restarted
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
---
|
||||||
|
# Cloud deployment — Coming soon
|
||||||
|
# This is a stub playbook. Full implementation planned.
|
||||||
|
|
||||||
|
- name: Deploy Cloud Server
|
||||||
|
hosts: all
|
||||||
|
become: true
|
||||||
|
tasks:
|
||||||
|
- name: Placeholder
|
||||||
|
debug:
|
||||||
|
msg: "Cloud playbook not yet implemented. Check phantom/README.md for status."
|
||||||
@@ -0,0 +1,144 @@
|
|||||||
|
---
|
||||||
|
# Base server hardening — applied to all deployments
|
||||||
|
# Covers: updates, firewall, fail2ban, SSH hardening
|
||||||
|
|
||||||
|
- name: Base Server Hardening
|
||||||
|
hosts: all
|
||||||
|
become: true
|
||||||
|
vars:
|
||||||
|
ssh_port: "{{ ssh_port | default(22) }}"
|
||||||
|
ssh_allow_password: false
|
||||||
|
|
||||||
|
tasks:
|
||||||
|
# ─── System Updates ─────────────────────────────────────────────────
|
||||||
|
- name: Update apt cache
|
||||||
|
apt:
|
||||||
|
update_cache: true
|
||||||
|
cache_valid_time: 3600
|
||||||
|
when: ansible_os_family == "Debian"
|
||||||
|
|
||||||
|
- name: Upgrade all packages
|
||||||
|
apt:
|
||||||
|
upgrade: safe
|
||||||
|
when: ansible_os_family == "Debian"
|
||||||
|
|
||||||
|
- name: Install essential packages
|
||||||
|
apt:
|
||||||
|
name:
|
||||||
|
- ufw
|
||||||
|
- fail2ban
|
||||||
|
- unattended-upgrades
|
||||||
|
- apt-listchanges
|
||||||
|
- curl
|
||||||
|
- wget
|
||||||
|
- gnupg
|
||||||
|
- ca-certificates
|
||||||
|
- software-properties-common
|
||||||
|
state: present
|
||||||
|
when: ansible_os_family == "Debian"
|
||||||
|
|
||||||
|
# ─── UFW Firewall ───────────────────────────────────────────────────
|
||||||
|
- name: Set UFW default deny incoming
|
||||||
|
ufw:
|
||||||
|
direction: incoming
|
||||||
|
policy: deny
|
||||||
|
|
||||||
|
- name: Set UFW default allow outgoing
|
||||||
|
ufw:
|
||||||
|
direction: outgoing
|
||||||
|
policy: allow
|
||||||
|
|
||||||
|
- name: Allow SSH through UFW
|
||||||
|
ufw:
|
||||||
|
rule: allow
|
||||||
|
port: "{{ ssh_port }}"
|
||||||
|
proto: tcp
|
||||||
|
|
||||||
|
- name: Enable UFW
|
||||||
|
ufw:
|
||||||
|
state: enabled
|
||||||
|
|
||||||
|
# ─── Fail2ban ───────────────────────────────────────────────────────
|
||||||
|
- name: Configure fail2ban SSH jail
|
||||||
|
copy:
|
||||||
|
dest: /etc/fail2ban/jail.local
|
||||||
|
content: |
|
||||||
|
[sshd]
|
||||||
|
enabled = true
|
||||||
|
port = {{ ssh_port }}
|
||||||
|
filter = sshd
|
||||||
|
logpath = /var/log/auth.log
|
||||||
|
maxretry = 5
|
||||||
|
bantime = 3600
|
||||||
|
findtime = 600
|
||||||
|
mode: "0644"
|
||||||
|
notify: restart fail2ban
|
||||||
|
|
||||||
|
- name: Enable fail2ban
|
||||||
|
service:
|
||||||
|
name: fail2ban
|
||||||
|
state: started
|
||||||
|
enabled: true
|
||||||
|
|
||||||
|
# ─── SSH Hardening ──────────────────────────────────────────────────
|
||||||
|
- name: Disable SSH password authentication
|
||||||
|
lineinfile:
|
||||||
|
path: /etc/ssh/sshd_config
|
||||||
|
regexp: "^#?PasswordAuthentication"
|
||||||
|
line: "PasswordAuthentication no"
|
||||||
|
when: not ssh_allow_password
|
||||||
|
notify: restart sshd
|
||||||
|
|
||||||
|
- name: Disable SSH root login with password
|
||||||
|
lineinfile:
|
||||||
|
path: /etc/ssh/sshd_config
|
||||||
|
regexp: "^#?PermitRootLogin"
|
||||||
|
line: "PermitRootLogin prohibit-password"
|
||||||
|
notify: restart sshd
|
||||||
|
|
||||||
|
- name: Disable SSH X11 forwarding
|
||||||
|
lineinfile:
|
||||||
|
path: /etc/ssh/sshd_config
|
||||||
|
regexp: "^#?X11Forwarding"
|
||||||
|
line: "X11Forwarding no"
|
||||||
|
notify: restart sshd
|
||||||
|
|
||||||
|
# ─── Automatic Security Updates ─────────────────────────────────────
|
||||||
|
- name: Enable unattended upgrades for security
|
||||||
|
copy:
|
||||||
|
dest: /etc/apt/apt.conf.d/20auto-upgrades
|
||||||
|
content: |
|
||||||
|
APT::Periodic::Update-Package-Lists "1";
|
||||||
|
APT::Periodic::Unattended-Upgrade "1";
|
||||||
|
APT::Periodic::AutocleanInterval "7";
|
||||||
|
mode: "0644"
|
||||||
|
when: ansible_os_family == "Debian"
|
||||||
|
|
||||||
|
# ─── Kernel Hardening ───────────────────────────────────────────────
|
||||||
|
- name: Apply sysctl hardening
|
||||||
|
sysctl:
|
||||||
|
name: "{{ item.key }}"
|
||||||
|
value: "{{ item.value }}"
|
||||||
|
sysctl_set: true
|
||||||
|
reload: true
|
||||||
|
loop:
|
||||||
|
- { key: "net.ipv4.conf.all.rp_filter", value: "1" }
|
||||||
|
- { key: "net.ipv4.conf.default.rp_filter", value: "1" }
|
||||||
|
- { key: "net.ipv4.icmp_echo_ignore_broadcasts", value: "1" }
|
||||||
|
- { key: "net.ipv4.conf.all.accept_redirects", value: "0" }
|
||||||
|
- { key: "net.ipv4.conf.default.accept_redirects", value: "0" }
|
||||||
|
- { key: "net.ipv6.conf.all.accept_redirects", value: "0" }
|
||||||
|
- { key: "net.ipv6.conf.default.accept_redirects", value: "0" }
|
||||||
|
- { key: "net.ipv4.conf.all.send_redirects", value: "0" }
|
||||||
|
- { key: "net.ipv4.conf.default.send_redirects", value: "0" }
|
||||||
|
|
||||||
|
handlers:
|
||||||
|
- name: restart fail2ban
|
||||||
|
service:
|
||||||
|
name: fail2ban
|
||||||
|
state: restarted
|
||||||
|
|
||||||
|
- name: restart sshd
|
||||||
|
service:
|
||||||
|
name: sshd
|
||||||
|
state: restarted
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
---
|
||||||
|
# Pi-hole DNS server deployment (Docker-based)
|
||||||
|
|
||||||
|
- name: Deploy Pi-hole DNS Server
|
||||||
|
hosts: all
|
||||||
|
become: true
|
||||||
|
vars:
|
||||||
|
pihole_upstream: "{{ dns_upstream | default('9.9.9.9;149.112.112.112') }}"
|
||||||
|
pihole_domain: "{{ dns_domain | default('') }}"
|
||||||
|
pihole_blocklist: "{{ dns_blocklist | default('standard') }}"
|
||||||
|
target_host: "{{ target_host | default('localhost') }}"
|
||||||
|
|
||||||
|
tasks:
|
||||||
|
- name: Include Pi-hole installation
|
||||||
|
include_tasks: tasks/install.yml
|
||||||
|
|
||||||
|
- name: Include Pi-hole configuration
|
||||||
|
include_tasks: tasks/configure.yml
|
||||||
@@ -0,0 +1,66 @@
|
|||||||
|
---
|
||||||
|
# Pi-hole Docker configuration and launch
|
||||||
|
|
||||||
|
- name: Create Pi-hole directories
|
||||||
|
file:
|
||||||
|
path: "{{ item }}"
|
||||||
|
state: directory
|
||||||
|
mode: "0755"
|
||||||
|
loop:
|
||||||
|
- /opt/pihole
|
||||||
|
- /opt/pihole/etc-pihole
|
||||||
|
- /opt/pihole/etc-dnsmasq.d
|
||||||
|
|
||||||
|
- name: Generate Pi-hole admin password
|
||||||
|
shell: "openssl rand -base64 16"
|
||||||
|
register: pihole_password
|
||||||
|
args:
|
||||||
|
creates: /opt/pihole/.password
|
||||||
|
|
||||||
|
- name: Save admin password
|
||||||
|
copy:
|
||||||
|
content: "{{ pihole_password.stdout }}"
|
||||||
|
dest: /opt/pihole/.password
|
||||||
|
mode: "0600"
|
||||||
|
when: pihole_password.changed
|
||||||
|
|
||||||
|
- name: Deploy Pi-hole Docker Compose
|
||||||
|
copy:
|
||||||
|
dest: /opt/pihole/docker-compose.yml
|
||||||
|
content: |
|
||||||
|
services:
|
||||||
|
pihole:
|
||||||
|
container_name: pihole
|
||||||
|
image: pihole/pihole:latest
|
||||||
|
ports:
|
||||||
|
- "53:53/tcp"
|
||||||
|
- "53:53/udp"
|
||||||
|
- "80:80/tcp"
|
||||||
|
environment:
|
||||||
|
TZ: UTC
|
||||||
|
WEBPASSWORD_FILE: /run/secrets/webpassword
|
||||||
|
PIHOLE_DNS_: "{{ pihole_upstream }}"
|
||||||
|
DNSSEC: "true"
|
||||||
|
QUERY_LOGGING: "false"
|
||||||
|
volumes:
|
||||||
|
- /opt/pihole/etc-pihole:/etc/pihole
|
||||||
|
- /opt/pihole/etc-dnsmasq.d:/etc/dnsmasq.d
|
||||||
|
secrets:
|
||||||
|
- webpassword
|
||||||
|
restart: unless-stopped
|
||||||
|
dns:
|
||||||
|
- 127.0.0.1
|
||||||
|
- 9.9.9.9
|
||||||
|
secrets:
|
||||||
|
webpassword:
|
||||||
|
file: /opt/pihole/.password
|
||||||
|
mode: "0644"
|
||||||
|
|
||||||
|
- name: Start Pi-hole
|
||||||
|
shell: cd /opt/pihole && docker compose up -d
|
||||||
|
args:
|
||||||
|
creates: /opt/pihole/etc-pihole/pihole-FTL.db
|
||||||
|
|
||||||
|
- name: Display admin password
|
||||||
|
debug:
|
||||||
|
msg: "Pi-hole admin password: {{ pihole_password.stdout | default('(see /opt/pihole/.password)') }}"
|
||||||
@@ -0,0 +1,62 @@
|
|||||||
|
---
|
||||||
|
# Pi-hole installation via Docker
|
||||||
|
|
||||||
|
- name: Install Docker prerequisites
|
||||||
|
apt:
|
||||||
|
name:
|
||||||
|
- apt-transport-https
|
||||||
|
- ca-certificates
|
||||||
|
- curl
|
||||||
|
- gnupg
|
||||||
|
- lsb-release
|
||||||
|
state: present
|
||||||
|
|
||||||
|
- name: Add Docker GPG key
|
||||||
|
apt_key:
|
||||||
|
url: https://download.docker.com/linux/{{ ansible_distribution | lower }}/gpg
|
||||||
|
state: present
|
||||||
|
|
||||||
|
- name: Add Docker repository
|
||||||
|
apt_repository:
|
||||||
|
repo: "deb https://download.docker.com/linux/{{ ansible_distribution | lower }} {{ ansible_distribution_release }} stable"
|
||||||
|
state: present
|
||||||
|
|
||||||
|
- name: Install Docker
|
||||||
|
apt:
|
||||||
|
name:
|
||||||
|
- docker-ce
|
||||||
|
- docker-ce-cli
|
||||||
|
- containerd.io
|
||||||
|
- docker-compose-plugin
|
||||||
|
state: present
|
||||||
|
|
||||||
|
- name: Enable Docker service
|
||||||
|
service:
|
||||||
|
name: docker
|
||||||
|
state: started
|
||||||
|
enabled: true
|
||||||
|
|
||||||
|
- name: Stop systemd-resolved (conflicts with Pi-hole on port 53)
|
||||||
|
service:
|
||||||
|
name: systemd-resolved
|
||||||
|
state: stopped
|
||||||
|
enabled: false
|
||||||
|
failed_when: false
|
||||||
|
|
||||||
|
- name: Set DNS fallback
|
||||||
|
copy:
|
||||||
|
dest: /etc/resolv.conf
|
||||||
|
content: |
|
||||||
|
nameserver 9.9.9.9
|
||||||
|
nameserver 1.1.1.1
|
||||||
|
mode: "0644"
|
||||||
|
|
||||||
|
- name: Allow DNS through UFW
|
||||||
|
ufw:
|
||||||
|
rule: allow
|
||||||
|
port: "{{ item.port }}"
|
||||||
|
proto: "{{ item.proto }}"
|
||||||
|
loop:
|
||||||
|
- { port: "53", proto: "tcp" }
|
||||||
|
- { port: "53", proto: "udp" }
|
||||||
|
- { port: "80", proto: "tcp" }
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
---
|
||||||
|
# Email deployment — Coming soon
|
||||||
|
# This is a stub playbook. Full implementation planned.
|
||||||
|
|
||||||
|
- name: Deploy Email Server
|
||||||
|
hosts: all
|
||||||
|
become: true
|
||||||
|
tasks:
|
||||||
|
- name: Placeholder
|
||||||
|
debug:
|
||||||
|
msg: "Email playbook not yet implemented. Check phantom/README.md for status."
|
||||||
@@ -0,0 +1,38 @@
|
|||||||
|
---
|
||||||
|
# Matrix (Synapse) + Element Web deployment
|
||||||
|
# Installs Synapse homeserver with optional Element Web frontend
|
||||||
|
|
||||||
|
- name: Deploy Matrix Homeserver
|
||||||
|
hosts: all
|
||||||
|
become: true
|
||||||
|
vars:
|
||||||
|
matrix_domain: "{{ domain }}"
|
||||||
|
matrix_server_name: "{{ matrix_server_name | default(domain) }}"
|
||||||
|
matrix_admin: "{{ matrix_admin_user | default('admin') }}"
|
||||||
|
matrix_admin_pass: "{{ matrix_admin_password }}"
|
||||||
|
matrix_registration_enabled: "{{ matrix_registration | default(false) }}"
|
||||||
|
element_enabled: "{{ matrix_element_web | default(true) }}"
|
||||||
|
matrix_signing_key: "{{ matrix_signing_key }}"
|
||||||
|
target_host: "{{ target_host | default('localhost') }}"
|
||||||
|
|
||||||
|
tasks:
|
||||||
|
- name: Include Synapse installation tasks
|
||||||
|
include_tasks: tasks/synapse.yml
|
||||||
|
|
||||||
|
- name: Include Element Web tasks
|
||||||
|
include_tasks: tasks/element.yml
|
||||||
|
when: element_enabled | bool
|
||||||
|
|
||||||
|
- name: Include nginx reverse proxy tasks
|
||||||
|
include_tasks: tasks/nginx.yml
|
||||||
|
|
||||||
|
handlers:
|
||||||
|
- name: restart synapse
|
||||||
|
service:
|
||||||
|
name: matrix-synapse
|
||||||
|
state: restarted
|
||||||
|
|
||||||
|
- name: reload nginx
|
||||||
|
service:
|
||||||
|
name: nginx
|
||||||
|
state: reloaded
|
||||||
@@ -0,0 +1,44 @@
|
|||||||
|
---
|
||||||
|
# Element Web frontend installation
|
||||||
|
|
||||||
|
- name: Install nginx (if not already)
|
||||||
|
apt:
|
||||||
|
name: nginx
|
||||||
|
state: present
|
||||||
|
|
||||||
|
- name: Create Element Web directory
|
||||||
|
file:
|
||||||
|
path: /var/www/element
|
||||||
|
state: directory
|
||||||
|
owner: www-data
|
||||||
|
group: www-data
|
||||||
|
mode: "0755"
|
||||||
|
|
||||||
|
- name: Download latest Element Web release
|
||||||
|
shell: |
|
||||||
|
LATEST=$(curl -s https://api.github.com/repos/element-hq/element-web/releases/latest | grep -oP '"tag_name": "\K[^"]+')
|
||||||
|
curl -sL "https://github.com/element-hq/element-web/releases/download/${LATEST}/element-${LATEST}.tar.gz" | tar xz -C /var/www/element --strip-components=1
|
||||||
|
args:
|
||||||
|
creates: /var/www/element/index.html
|
||||||
|
|
||||||
|
- name: Deploy Element Web config
|
||||||
|
copy:
|
||||||
|
dest: /var/www/element/config.json
|
||||||
|
content: |
|
||||||
|
{
|
||||||
|
"default_server_config": {
|
||||||
|
"m.homeserver": {
|
||||||
|
"base_url": "https://{{ matrix_domain }}",
|
||||||
|
"server_name": "{{ matrix_server_name }}"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"brand": "Element",
|
||||||
|
"integrations_ui_url": "",
|
||||||
|
"integrations_rest_url": "",
|
||||||
|
"disable_guests": true,
|
||||||
|
"disable_3pid_login": false,
|
||||||
|
"default_theme": "dark"
|
||||||
|
}
|
||||||
|
owner: www-data
|
||||||
|
group: www-data
|
||||||
|
mode: "0644"
|
||||||
@@ -0,0 +1,115 @@
|
|||||||
|
---
|
||||||
|
# Nginx reverse proxy for Matrix + Element
|
||||||
|
# Two-phase: HTTP-only first for certbot, then full SSL config
|
||||||
|
|
||||||
|
- name: Create certbot webroot
|
||||||
|
file:
|
||||||
|
path: /var/www/certbot
|
||||||
|
state: directory
|
||||||
|
owner: www-data
|
||||||
|
group: www-data
|
||||||
|
mode: "0755"
|
||||||
|
|
||||||
|
- name: Deploy HTTP-only nginx config (for certbot)
|
||||||
|
copy:
|
||||||
|
dest: /etc/nginx/sites-available/matrix
|
||||||
|
content: |
|
||||||
|
server {
|
||||||
|
listen 80;
|
||||||
|
server_name {{ matrix_domain }};
|
||||||
|
|
||||||
|
location /.well-known/acme-challenge/ {
|
||||||
|
root /var/www/certbot;
|
||||||
|
}
|
||||||
|
|
||||||
|
location / {
|
||||||
|
return 200 'phantom setup in progress';
|
||||||
|
add_header Content-Type text/plain;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
mode: "0644"
|
||||||
|
notify: reload nginx
|
||||||
|
|
||||||
|
- name: Enable Matrix nginx site
|
||||||
|
file:
|
||||||
|
src: /etc/nginx/sites-available/matrix
|
||||||
|
dest: /etc/nginx/sites-enabled/matrix
|
||||||
|
state: link
|
||||||
|
notify: reload nginx
|
||||||
|
|
||||||
|
- name: Flush handlers to apply HTTP config
|
||||||
|
meta: flush_handlers
|
||||||
|
|
||||||
|
- name: Obtain TLS certificate
|
||||||
|
command: >
|
||||||
|
certbot certonly --webroot -w /var/www/certbot
|
||||||
|
-d {{ matrix_domain }}
|
||||||
|
--non-interactive
|
||||||
|
--agree-tos
|
||||||
|
{% if certbot_email is defined and certbot_email %}
|
||||||
|
--email {{ certbot_email }}
|
||||||
|
{% else %}
|
||||||
|
--register-unsafely-without-email
|
||||||
|
{% endif %}
|
||||||
|
args:
|
||||||
|
creates: "/etc/letsencrypt/live/{{ matrix_domain }}/fullchain.pem"
|
||||||
|
|
||||||
|
- name: Deploy full SSL nginx config
|
||||||
|
copy:
|
||||||
|
dest: /etc/nginx/sites-available/matrix
|
||||||
|
content: |
|
||||||
|
server {
|
||||||
|
listen 80;
|
||||||
|
server_name {{ matrix_domain }};
|
||||||
|
|
||||||
|
location /.well-known/acme-challenge/ {
|
||||||
|
root /var/www/certbot;
|
||||||
|
}
|
||||||
|
|
||||||
|
location / {
|
||||||
|
return 301 https://$host$request_uri;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
server {
|
||||||
|
listen 443 ssl http2;
|
||||||
|
listen 8448 ssl http2;
|
||||||
|
server_name {{ matrix_domain }};
|
||||||
|
|
||||||
|
ssl_certificate /etc/letsencrypt/live/{{ matrix_domain }}/fullchain.pem;
|
||||||
|
ssl_certificate_key /etc/letsencrypt/live/{{ matrix_domain }}/privkey.pem;
|
||||||
|
|
||||||
|
ssl_protocols TLSv1.2 TLSv1.3;
|
||||||
|
ssl_ciphers HIGH:!aNULL:!MD5;
|
||||||
|
ssl_prefer_server_ciphers on;
|
||||||
|
|
||||||
|
client_max_body_size 50m;
|
||||||
|
|
||||||
|
# Synapse
|
||||||
|
location ~* ^(\/_matrix|\/_synapse\/client) {
|
||||||
|
proxy_pass http://127.0.0.1:8008;
|
||||||
|
proxy_set_header X-Forwarded-For $remote_addr;
|
||||||
|
proxy_set_header X-Forwarded-Proto $scheme;
|
||||||
|
proxy_set_header Host $host;
|
||||||
|
}
|
||||||
|
|
||||||
|
# .well-known delegation
|
||||||
|
location /.well-known/matrix/server {
|
||||||
|
return 200 '{"m.server": "{{ matrix_domain }}:443"}';
|
||||||
|
add_header Content-Type application/json;
|
||||||
|
}
|
||||||
|
|
||||||
|
location /.well-known/matrix/client {
|
||||||
|
return 200 '{"m.homeserver": {"base_url": "https://{{ matrix_domain }}"}}';
|
||||||
|
add_header Content-Type application/json;
|
||||||
|
add_header Access-Control-Allow-Origin *;
|
||||||
|
}
|
||||||
|
|
||||||
|
# Element Web (if enabled)
|
||||||
|
location / {
|
||||||
|
root /var/www/element;
|
||||||
|
try_files $uri $uri/ /index.html;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
mode: "0644"
|
||||||
|
notify: reload nginx
|
||||||
@@ -0,0 +1,99 @@
|
|||||||
|
---
|
||||||
|
# Synapse homeserver installation and configuration
|
||||||
|
|
||||||
|
- name: Install dependencies
|
||||||
|
apt:
|
||||||
|
name:
|
||||||
|
- python3
|
||||||
|
- python3-pip
|
||||||
|
- python3-venv
|
||||||
|
- libpq-dev
|
||||||
|
- postgresql
|
||||||
|
- postgresql-contrib
|
||||||
|
- nginx
|
||||||
|
- certbot
|
||||||
|
- python3-certbot-nginx
|
||||||
|
state: present
|
||||||
|
|
||||||
|
- name: Add Matrix Synapse apt repository key
|
||||||
|
apt_key:
|
||||||
|
url: https://packages.matrix.org/debian/matrix-org-archive-keyring.gpg
|
||||||
|
state: present
|
||||||
|
|
||||||
|
- name: Add Matrix Synapse apt repository
|
||||||
|
apt_repository:
|
||||||
|
repo: "deb https://packages.matrix.org/debian/ {{ ansible_distribution_release }} main"
|
||||||
|
state: present
|
||||||
|
filename: matrix-org
|
||||||
|
|
||||||
|
- name: Install Synapse
|
||||||
|
apt:
|
||||||
|
name: matrix-synapse-py3
|
||||||
|
state: present
|
||||||
|
environment:
|
||||||
|
DEBIAN_FRONTEND: noninteractive
|
||||||
|
|
||||||
|
- name: Create PostgreSQL database
|
||||||
|
become_user: postgres
|
||||||
|
postgresql_db:
|
||||||
|
name: synapse
|
||||||
|
encoding: UTF-8
|
||||||
|
lc_collate: C
|
||||||
|
lc_ctype: C
|
||||||
|
template: template0
|
||||||
|
|
||||||
|
- name: Create PostgreSQL user
|
||||||
|
become_user: postgres
|
||||||
|
postgresql_user:
|
||||||
|
name: synapse
|
||||||
|
password: "{{ matrix_signing_key[:32] }}"
|
||||||
|
db: synapse
|
||||||
|
priv: ALL
|
||||||
|
|
||||||
|
- name: Deploy Synapse homeserver config
|
||||||
|
template:
|
||||||
|
src: ../templates/homeserver.yaml.j2
|
||||||
|
dest: /etc/matrix-synapse/homeserver.yaml
|
||||||
|
owner: matrix-synapse
|
||||||
|
group: matrix-synapse
|
||||||
|
mode: "0640"
|
||||||
|
notify: restart synapse
|
||||||
|
|
||||||
|
- name: Allow Matrix federation port through UFW
|
||||||
|
ufw:
|
||||||
|
rule: allow
|
||||||
|
port: "8448"
|
||||||
|
proto: tcp
|
||||||
|
|
||||||
|
- name: Allow HTTP/HTTPS through UFW
|
||||||
|
ufw:
|
||||||
|
rule: allow
|
||||||
|
port: "{{ item }}"
|
||||||
|
proto: tcp
|
||||||
|
loop:
|
||||||
|
- "80"
|
||||||
|
- "443"
|
||||||
|
|
||||||
|
- name: Enable and start Synapse
|
||||||
|
service:
|
||||||
|
name: matrix-synapse
|
||||||
|
state: started
|
||||||
|
enabled: true
|
||||||
|
|
||||||
|
- name: Wait for Synapse to be ready
|
||||||
|
wait_for:
|
||||||
|
port: 8008
|
||||||
|
host: 127.0.0.1
|
||||||
|
timeout: 30
|
||||||
|
|
||||||
|
- name: Create admin user
|
||||||
|
command: >
|
||||||
|
register_new_matrix_user
|
||||||
|
-u {{ matrix_admin }}
|
||||||
|
-p {{ matrix_admin_pass }}
|
||||||
|
-a
|
||||||
|
-c /etc/matrix-synapse/homeserver.yaml
|
||||||
|
http://localhost:8008
|
||||||
|
register: admin_create
|
||||||
|
failed_when: false
|
||||||
|
changed_when: admin_create.rc == 0
|
||||||
@@ -0,0 +1,53 @@
|
|||||||
|
## Synapse Homeserver Configuration
|
||||||
|
## Generated by phantom
|
||||||
|
|
||||||
|
server_name: "{{ matrix_server_name }}"
|
||||||
|
pid_file: /run/matrix-synapse.pid
|
||||||
|
|
||||||
|
listeners:
|
||||||
|
- port: 8008
|
||||||
|
tls: false
|
||||||
|
type: http
|
||||||
|
x_forwarded: true
|
||||||
|
resources:
|
||||||
|
- names: [client, federation]
|
||||||
|
compress: false
|
||||||
|
|
||||||
|
database:
|
||||||
|
name: psycopg2
|
||||||
|
args:
|
||||||
|
user: synapse
|
||||||
|
password: "{{ matrix_signing_key[:32] }}"
|
||||||
|
database: synapse
|
||||||
|
host: localhost
|
||||||
|
cp_min: 5
|
||||||
|
cp_max: 10
|
||||||
|
|
||||||
|
log_config: "/etc/matrix-synapse/log.yaml"
|
||||||
|
media_store_path: /var/lib/matrix-synapse/media
|
||||||
|
signing_key_path: "/etc/matrix-synapse/{{ matrix_server_name }}.signing.key"
|
||||||
|
|
||||||
|
enable_registration: {{ matrix_registration_enabled | lower }}
|
||||||
|
{% if not matrix_registration_enabled %}
|
||||||
|
enable_registration_without_verification: false
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
suppress_key_server_warning: true
|
||||||
|
|
||||||
|
trusted_key_servers:
|
||||||
|
- server_name: "matrix.org"
|
||||||
|
|
||||||
|
max_upload_size: 50M
|
||||||
|
|
||||||
|
url_preview_enabled: true
|
||||||
|
url_preview_ip_range_blacklist:
|
||||||
|
- '127.0.0.0/8'
|
||||||
|
- '10.0.0.0/8'
|
||||||
|
- '172.16.0.0/12'
|
||||||
|
- '192.168.0.0/16'
|
||||||
|
- '100.64.0.0/10'
|
||||||
|
- '192.0.0.0/24'
|
||||||
|
- '169.254.0.0/16'
|
||||||
|
- '::1/128'
|
||||||
|
- 'fe80::/10'
|
||||||
|
- 'fc00::/7'
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
---
|
||||||
|
# Media deployment — Coming soon
|
||||||
|
# This is a stub playbook. Full implementation planned.
|
||||||
|
|
||||||
|
- name: Deploy Media Server
|
||||||
|
hosts: all
|
||||||
|
become: true
|
||||||
|
tasks:
|
||||||
|
- name: Placeholder
|
||||||
|
debug:
|
||||||
|
msg: "Media playbook not yet implemented. Check phantom/README.md for status."
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
---
|
||||||
|
# Vault deployment — Coming soon
|
||||||
|
# This is a stub playbook. Full implementation planned.
|
||||||
|
|
||||||
|
- name: Deploy Vault Server
|
||||||
|
hosts: all
|
||||||
|
become: true
|
||||||
|
tasks:
|
||||||
|
- name: Placeholder
|
||||||
|
debug:
|
||||||
|
msg: "Vault playbook not yet implemented. Check phantom/README.md for status."
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
---
|
||||||
|
# WireGuard VPN server deployment
|
||||||
|
|
||||||
|
- name: Deploy WireGuard VPN Server
|
||||||
|
hosts: all
|
||||||
|
become: true
|
||||||
|
vars:
|
||||||
|
wg_port: "{{ vpn_port | default(51820) }}"
|
||||||
|
wg_clients: "{{ vpn_client_count | default(3) }}"
|
||||||
|
wg_dns: "{{ vpn_dns | default('1.1.1.1') }}"
|
||||||
|
wg_subnet: "{{ vpn_subnet | default('10.66.66.0/24') }}"
|
||||||
|
wg_allowed_ips: "{{ vpn_allowed_ips | default('0.0.0.0/0, ::/0') }}"
|
||||||
|
target_host: "{{ target_host | default('localhost') }}"
|
||||||
|
|
||||||
|
tasks:
|
||||||
|
- name: Include WireGuard installation tasks
|
||||||
|
include_tasks: tasks/install.yml
|
||||||
|
|
||||||
|
- name: Include WireGuard configuration tasks
|
||||||
|
include_tasks: tasks/configure.yml
|
||||||
@@ -0,0 +1,88 @@
|
|||||||
|
---
|
||||||
|
# WireGuard server and client configuration
|
||||||
|
|
||||||
|
- name: Detect primary network interface
|
||||||
|
shell: ip route get 1.1.1.1 | awk '{for(i=1;i<=NF;i++) if($i=="dev") print $(i+1)}'
|
||||||
|
register: primary_interface
|
||||||
|
changed_when: false
|
||||||
|
|
||||||
|
- name: Generate client keys
|
||||||
|
shell: |
|
||||||
|
mkdir -p /etc/wireguard/clients
|
||||||
|
for i in $(seq 1 {{ wg_clients }}); do
|
||||||
|
if [ ! -f /etc/wireguard/clients/client${i}_private.key ]; then
|
||||||
|
wg genkey | tee /etc/wireguard/clients/client${i}_private.key | wg pubkey > /etc/wireguard/clients/client${i}_public.key
|
||||||
|
wg genpsk > /etc/wireguard/clients/client${i}_psk.key
|
||||||
|
chmod 600 /etc/wireguard/clients/client${i}_*.key
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
args:
|
||||||
|
creates: /etc/wireguard/clients/client1_private.key
|
||||||
|
|
||||||
|
- name: Build WireGuard server config
|
||||||
|
shell: |
|
||||||
|
SERVER_PRIVKEY=$(cat /etc/wireguard/server_private.key)
|
||||||
|
IFACE={{ primary_interface.stdout | trim }}
|
||||||
|
|
||||||
|
cat > /etc/wireguard/wg0.conf << EOF
|
||||||
|
[Interface]
|
||||||
|
Address = {{ wg_subnet | regex_replace('/\\d+$', '') | regex_replace('\\.[0-9]+$', '.1/24') }}
|
||||||
|
ListenPort = {{ wg_port }}
|
||||||
|
PrivateKey = ${SERVER_PRIVKEY}
|
||||||
|
PostUp = iptables -A FORWARD -i wg0 -j ACCEPT; iptables -t nat -A POSTROUTING -o ${IFACE} -j MASQUERADE
|
||||||
|
PostDown = iptables -D FORWARD -i wg0 -j ACCEPT; iptables -t nat -D POSTROUTING -o ${IFACE} -j MASQUERADE
|
||||||
|
|
||||||
|
EOF
|
||||||
|
|
||||||
|
for i in $(seq 1 {{ wg_clients }}); do
|
||||||
|
CLIENT_PUBKEY=$(cat /etc/wireguard/clients/client${i}_public.key)
|
||||||
|
CLIENT_PSK=$(cat /etc/wireguard/clients/client${i}_psk.key)
|
||||||
|
cat >> /etc/wireguard/wg0.conf << EOF
|
||||||
|
[Peer]
|
||||||
|
# Client ${i}
|
||||||
|
PublicKey = ${CLIENT_PUBKEY}
|
||||||
|
PresharedKey = ${CLIENT_PSK}
|
||||||
|
AllowedIPs = {{ wg_subnet | regex_replace('/\\d+$', '') | regex_replace('\\.[0-9]+$', '') }}.$(( i + 1 ))/32
|
||||||
|
|
||||||
|
EOF
|
||||||
|
done
|
||||||
|
|
||||||
|
chmod 600 /etc/wireguard/wg0.conf
|
||||||
|
args:
|
||||||
|
creates: /etc/wireguard/wg0.conf
|
||||||
|
|
||||||
|
- name: Generate client config files
|
||||||
|
shell: |
|
||||||
|
SERVER_PUBKEY=$(echo '{{ server_privkey_content.content | b64decode | trim }}' | wg pubkey)
|
||||||
|
SERVER_ENDPOINT="{{ target_host }}:{{ wg_port }}"
|
||||||
|
|
||||||
|
for i in $(seq 1 {{ wg_clients }}); do
|
||||||
|
CLIENT_PRIVKEY=$(cat /etc/wireguard/clients/client${i}_private.key)
|
||||||
|
CLIENT_PSK=$(cat /etc/wireguard/clients/client${i}_psk.key)
|
||||||
|
CLIENT_IP="{{ wg_subnet | regex_replace('/\\d+$', '') | regex_replace('\\.[0-9]+$', '') }}.$(( i + 1 ))"
|
||||||
|
|
||||||
|
cat > /etc/wireguard/clients/client${i}.conf << EOF
|
||||||
|
[Interface]
|
||||||
|
PrivateKey = ${CLIENT_PRIVKEY}
|
||||||
|
Address = ${CLIENT_IP}/32
|
||||||
|
DNS = {{ wg_dns }}
|
||||||
|
|
||||||
|
[Peer]
|
||||||
|
PublicKey = ${SERVER_PUBKEY}
|
||||||
|
PresharedKey = ${CLIENT_PSK}
|
||||||
|
Endpoint = ${SERVER_ENDPOINT}
|
||||||
|
AllowedIPs = {{ wg_allowed_ips }}
|
||||||
|
PersistentKeepalive = 25
|
||||||
|
EOF
|
||||||
|
|
||||||
|
# Generate QR code
|
||||||
|
qrencode -t ansiutf8 < /etc/wireguard/clients/client${i}.conf > /etc/wireguard/clients/client${i}_qr.txt 2>/dev/null || true
|
||||||
|
done
|
||||||
|
args:
|
||||||
|
creates: /etc/wireguard/clients/client1.conf
|
||||||
|
|
||||||
|
- name: Enable and start WireGuard
|
||||||
|
systemd:
|
||||||
|
name: wg-quick@wg0
|
||||||
|
state: started
|
||||||
|
enabled: true
|
||||||
@@ -0,0 +1,54 @@
|
|||||||
|
---
|
||||||
|
# WireGuard installation and server key generation
|
||||||
|
|
||||||
|
- name: Install WireGuard
|
||||||
|
apt:
|
||||||
|
name:
|
||||||
|
- wireguard
|
||||||
|
- wireguard-tools
|
||||||
|
- qrencode
|
||||||
|
state: present
|
||||||
|
when: ansible_os_family == "Debian"
|
||||||
|
|
||||||
|
- name: Enable IP forwarding (IPv4)
|
||||||
|
sysctl:
|
||||||
|
name: net.ipv4.ip_forward
|
||||||
|
value: "1"
|
||||||
|
sysctl_set: true
|
||||||
|
reload: true
|
||||||
|
|
||||||
|
- name: Enable IP forwarding (IPv6)
|
||||||
|
sysctl:
|
||||||
|
name: net.ipv6.conf.all.forwarding
|
||||||
|
value: "1"
|
||||||
|
sysctl_set: true
|
||||||
|
reload: true
|
||||||
|
|
||||||
|
- name: Generate server private key
|
||||||
|
shell: wg genkey
|
||||||
|
register: wg_server_privkey
|
||||||
|
args:
|
||||||
|
creates: /etc/wireguard/server_private.key
|
||||||
|
|
||||||
|
- name: Save server private key
|
||||||
|
copy:
|
||||||
|
content: "{{ wg_server_privkey.stdout }}"
|
||||||
|
dest: /etc/wireguard/server_private.key
|
||||||
|
mode: "0600"
|
||||||
|
when: wg_server_privkey.changed
|
||||||
|
|
||||||
|
- name: Read server private key
|
||||||
|
slurp:
|
||||||
|
src: /etc/wireguard/server_private.key
|
||||||
|
register: server_privkey_content
|
||||||
|
|
||||||
|
- name: Generate server public key
|
||||||
|
shell: "echo '{{ server_privkey_content.content | b64decode | trim }}' | wg pubkey"
|
||||||
|
register: wg_server_pubkey
|
||||||
|
changed_when: false
|
||||||
|
|
||||||
|
- name: Allow WireGuard port through UFW
|
||||||
|
ufw:
|
||||||
|
rule: allow
|
||||||
|
port: "{{ wg_port }}"
|
||||||
|
proto: udp
|
||||||
@@ -0,0 +1,105 @@
|
|||||||
|
---
|
||||||
|
# Provision an AWS EC2 instance for phantom deployment
|
||||||
|
# Requires: aws_access_key, aws_secret_key variables
|
||||||
|
|
||||||
|
- name: Provision AWS EC2 Instance
|
||||||
|
hosts: localhost
|
||||||
|
connection: local
|
||||||
|
gather_facts: false
|
||||||
|
vars:
|
||||||
|
aws_region: "{{ region | default('us-east-1') }}"
|
||||||
|
ec2_instance_type: "{{ instance_type | default('t3.micro') }}"
|
||||||
|
ec2_ami: "{{ ami | default('') }}"
|
||||||
|
ec2_key_name: "phantom-{{ deploy_id }}"
|
||||||
|
ssh_key_path: "{{ ssh_key }}.pub"
|
||||||
|
|
||||||
|
environment:
|
||||||
|
AWS_ACCESS_KEY_ID: "{{ aws_access_key }}"
|
||||||
|
AWS_SECRET_ACCESS_KEY: "{{ aws_secret_key }}"
|
||||||
|
AWS_DEFAULT_REGION: "{{ aws_region }}"
|
||||||
|
|
||||||
|
tasks:
|
||||||
|
- name: Read SSH public key
|
||||||
|
slurp:
|
||||||
|
src: "{{ ssh_key_path }}"
|
||||||
|
register: ssh_pubkey
|
||||||
|
|
||||||
|
- name: Import SSH key to AWS
|
||||||
|
amazon.aws.ec2_key:
|
||||||
|
name: "{{ ec2_key_name }}"
|
||||||
|
key_material: "{{ ssh_pubkey.content | b64decode | trim }}"
|
||||||
|
region: "{{ aws_region }}"
|
||||||
|
|
||||||
|
- name: Find latest Debian AMI (if not specified)
|
||||||
|
amazon.aws.ec2_ami_info:
|
||||||
|
region: "{{ aws_region }}"
|
||||||
|
owners: ["136693071363"]
|
||||||
|
filters:
|
||||||
|
name: "debian-12-amd64-*"
|
||||||
|
architecture: x86_64
|
||||||
|
virtualization-type: hvm
|
||||||
|
register: ami_results
|
||||||
|
when: ec2_ami == ""
|
||||||
|
|
||||||
|
- name: Set AMI fact
|
||||||
|
set_fact:
|
||||||
|
ec2_ami: "{{ (ami_results.images | sort(attribute='creation_date') | last).image_id }}"
|
||||||
|
when: ec2_ami == ""
|
||||||
|
|
||||||
|
- name: Create security group
|
||||||
|
amazon.aws.ec2_security_group:
|
||||||
|
name: "phantom-{{ deploy_id }}"
|
||||||
|
description: "phantom deployment {{ deploy_id }}"
|
||||||
|
region: "{{ aws_region }}"
|
||||||
|
rules:
|
||||||
|
- proto: tcp
|
||||||
|
from_port: 22
|
||||||
|
to_port: 22
|
||||||
|
cidr_ip: 0.0.0.0/0
|
||||||
|
- proto: tcp
|
||||||
|
from_port: 80
|
||||||
|
to_port: 80
|
||||||
|
cidr_ip: 0.0.0.0/0
|
||||||
|
- proto: tcp
|
||||||
|
from_port: 443
|
||||||
|
to_port: 443
|
||||||
|
cidr_ip: 0.0.0.0/0
|
||||||
|
register: sg_result
|
||||||
|
|
||||||
|
- name: Launch EC2 instance
|
||||||
|
amazon.aws.ec2_instance:
|
||||||
|
name: "phantom-{{ deploy_id }}"
|
||||||
|
key_name: "{{ ec2_key_name }}"
|
||||||
|
instance_type: "{{ ec2_instance_type }}"
|
||||||
|
image_id: "{{ ec2_ami }}"
|
||||||
|
region: "{{ aws_region }}"
|
||||||
|
security_group: "{{ sg_result.group_id }}"
|
||||||
|
wait: true
|
||||||
|
state: running
|
||||||
|
register: ec2_result
|
||||||
|
|
||||||
|
- name: Set target host fact
|
||||||
|
set_fact:
|
||||||
|
target_host: "{{ ec2_result.instances[0].public_ip_address }}"
|
||||||
|
|
||||||
|
- name: Display instance info
|
||||||
|
debug:
|
||||||
|
msg: |
|
||||||
|
EC2 instance provisioned:
|
||||||
|
ID: {{ ec2_result.instances[0].instance_id }}
|
||||||
|
IP: {{ target_host }}
|
||||||
|
Region: {{ aws_region }}
|
||||||
|
Type: {{ ec2_instance_type }}
|
||||||
|
|
||||||
|
- name: Write provisioned host IP for phantom
|
||||||
|
copy:
|
||||||
|
content: "{{ target_host }}"
|
||||||
|
dest: "{{ _host_output_file }}"
|
||||||
|
when: _host_output_file is defined
|
||||||
|
|
||||||
|
- name: Wait for SSH
|
||||||
|
wait_for:
|
||||||
|
host: "{{ target_host }}"
|
||||||
|
port: 22
|
||||||
|
delay: 15
|
||||||
|
timeout: 300
|
||||||
@@ -0,0 +1,30 @@
|
|||||||
|
---
|
||||||
|
# Register a pre-provisioned FlokiNET server for phantom deployment
|
||||||
|
# FlokiNET servers are provisioned manually via their control panel.
|
||||||
|
# This playbook only validates SSH connectivity and prepares the server.
|
||||||
|
|
||||||
|
- name: Register FlokiNET Server
|
||||||
|
hosts: all
|
||||||
|
become: true
|
||||||
|
gather_facts: false
|
||||||
|
|
||||||
|
tasks:
|
||||||
|
- name: Ensure Python is available for Ansible
|
||||||
|
raw: test -e /usr/bin/python3 || (apt-get update && apt-get install -y python3)
|
||||||
|
changed_when: false
|
||||||
|
|
||||||
|
- name: Gather facts now that Python is available
|
||||||
|
setup:
|
||||||
|
|
||||||
|
- name: Verify SSH connectivity
|
||||||
|
ping:
|
||||||
|
|
||||||
|
- name: Display server info
|
||||||
|
debug:
|
||||||
|
msg: |
|
||||||
|
FlokiNET server registered:
|
||||||
|
Hostname: {{ ansible_hostname }}
|
||||||
|
OS: {{ ansible_distribution }} {{ ansible_distribution_version }}
|
||||||
|
IP: {{ ansible_default_ipv4.address | default(target_host) }}
|
||||||
|
RAM: {{ ansible_memtotal_mb }}MB
|
||||||
|
Cores: {{ ansible_processor_vcpus }}
|
||||||
@@ -0,0 +1,66 @@
|
|||||||
|
---
|
||||||
|
# Provision a Linode instance for phantom deployment
|
||||||
|
# Requires: LINODE_API_TOKEN or api_token variable
|
||||||
|
|
||||||
|
- name: Provision Linode Instance
|
||||||
|
hosts: localhost
|
||||||
|
connection: local
|
||||||
|
gather_facts: false
|
||||||
|
vars:
|
||||||
|
linode_token: "{{ api_token }}"
|
||||||
|
linode_region: "{{ region | default('us-east') }}"
|
||||||
|
linode_plan: "{{ plan | default('g6-nanode-1') }}"
|
||||||
|
linode_image: "{{ image | default('linode/debian12') }}"
|
||||||
|
linode_label: "phantom-{{ deploy_id }}"
|
||||||
|
ssh_key_path: "{{ ssh_key }}.pub"
|
||||||
|
|
||||||
|
tasks:
|
||||||
|
- name: Read SSH public key
|
||||||
|
slurp:
|
||||||
|
src: "{{ ssh_key_path }}"
|
||||||
|
register: ssh_pubkey
|
||||||
|
|
||||||
|
- name: Create Linode instance
|
||||||
|
uri:
|
||||||
|
url: https://api.linode.com/v4/linode/instances
|
||||||
|
method: POST
|
||||||
|
headers:
|
||||||
|
Authorization: "Bearer {{ linode_token }}"
|
||||||
|
Content-Type: application/json
|
||||||
|
body_format: json
|
||||||
|
body:
|
||||||
|
type: "{{ linode_plan }}"
|
||||||
|
region: "{{ linode_region }}"
|
||||||
|
image: "{{ linode_image }}"
|
||||||
|
label: "{{ linode_label }}"
|
||||||
|
authorized_keys:
|
||||||
|
- "{{ ssh_pubkey.content | b64decode | trim }}"
|
||||||
|
booted: true
|
||||||
|
status_code: 200
|
||||||
|
register: linode_result
|
||||||
|
|
||||||
|
- name: Set target host fact
|
||||||
|
set_fact:
|
||||||
|
target_host: "{{ linode_result.json.ipv4[0] }}"
|
||||||
|
|
||||||
|
- name: Display instance info
|
||||||
|
debug:
|
||||||
|
msg: |
|
||||||
|
Linode provisioned:
|
||||||
|
ID: {{ linode_result.json.id }}
|
||||||
|
IP: {{ linode_result.json.ipv4[0] }}
|
||||||
|
Region: {{ linode_region }}
|
||||||
|
Plan: {{ linode_plan }}
|
||||||
|
|
||||||
|
- name: Write provisioned host IP for phantom
|
||||||
|
copy:
|
||||||
|
content: "{{ target_host }}"
|
||||||
|
dest: "{{ _host_output_file }}"
|
||||||
|
when: _host_output_file is defined
|
||||||
|
|
||||||
|
- name: Wait for SSH
|
||||||
|
wait_for:
|
||||||
|
host: "{{ target_host }}"
|
||||||
|
port: 22
|
||||||
|
delay: 10
|
||||||
|
timeout: 300
|
||||||
Reference in New Issue
Block a user