Add SMTP_HOST/PORT/USER/PASSWORD/TLS settings. When SMTP_HOST is set, send_email() uses smtplib (supports STARTTLS, SSL, or plain); auth is skipped when SMTP_USER is empty. Falls back to MAIL_BINARY when SMTP_HOST is unset, preserving existing behaviour. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
88 lines
3.7 KiB
Markdown
88 lines
3.7 KiB
Markdown
# CLAUDE.md
|
|
|
|
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
|
|
|
|
## What this is
|
|
|
|
`openvpncertupdate` is a single-file Python + curses TUI tool for managing OpenVPN user certificates via EasyRSA 3.2.x. It lists expiring/expired certs, re-issues them with fresh keys, creates new certs, and delivers configs via Cryptgeon (one-time password URL) and email. It also supports a non-interactive CLI mode for scripting and cron use.
|
|
|
|
## Running
|
|
|
|
```bash
|
|
pip install -r requirements.txt # just: cryptography>=41
|
|
python3 openvpncertupdate.py # interactive TUI
|
|
python3 openvpncertupdate.py --create CN --email user@example.com
|
|
python3 openvpncertupdate.py --reissue CN [--email user@example.com]
|
|
python3 openvpncertupdate.py --revoke CN
|
|
python3 openvpncertupdate.py --gen-crl
|
|
```
|
|
|
|
Edit the `SETTINGS` block at the top of `openvpncertupdate.py` before first run.
|
|
|
|
### CLI flags
|
|
|
|
| Flag | Effect |
|
|
|---|---|
|
|
| `--create CN` | Issue new cert (requires `--email`) |
|
|
| `--reissue CN` | Revoke + reissue cert (`--email` optional, falls back to stored) |
|
|
| `--revoke CN` | Revoke cert and regenerate CRL |
|
|
| `--gen-crl` | Regenerate and copy CRL only |
|
|
| `--email EMAIL` | Recipient address |
|
|
| `--send-email` | Force email delivery |
|
|
| `--no-send-email` | Skip email; print URL to stdout |
|
|
| `--show-eml` | Print base64-encoded `.eml` to stdout (implies `--no-send-email` unless `--send-email` also given) |
|
|
|
|
## Tests
|
|
|
|
```bash
|
|
python3 -m pytest tests/ -v
|
|
python3 -m pytest tests/test_password.py -v # single file
|
|
python3 -m pytest tests/test_pki.py::test_sorted_ascending -v # single test
|
|
```
|
|
|
|
## File layout — sections inside `openvpncertupdate.py`
|
|
|
|
| Section | Key symbols |
|
|
|---|---|
|
|
| SETTINGS | all-caps constants |
|
|
| PKI | `CertInfo`, `load_expiring_certs()`, `load_all_certs()`, `_parse_index_line()` |
|
|
| PASSWORD | `generate_password()` |
|
|
| EASYRSA | `EasyRSAError`, `revoke_issued()`, `build_client_full()`, `gen_crl()`, `copy_crl()` |
|
|
| METADATA | `load_metadata()`, `save_email()`, `get_email()` |
|
|
| CONFIG | `build_ovpn()` → `vpn-configs/<CN>_<YYYY-MM-DD>_<NN>/CONFIG_NAME` |
|
|
| CRYPTGEON | `CryptgeonError`, `create_note()` |
|
|
| MAILER | `build_mime_message()`, `send_email()` |
|
|
| TUI WIDGETS | `InputField`, `clamp()`, `draw_box()`, `init_colors()`, `COLOR_*` |
|
|
| TUI DIALOGS | `show_confirm()`, `show_cert_form()`, `CertFormResult` |
|
|
| TUI SCREEN | `show_main_screen()`, `Action`, `ScreenResult` |
|
|
| APP | `CursesApp` |
|
|
| CLI | `CliRunner`, `_build_parser()` |
|
|
| ENTRY POINT | `main()` |
|
|
|
|
## Re-issue workflow
|
|
|
|
1. `revoke-issued <CN>` — archives old key + CSR to `pki/revoked/`
|
|
2. `build-client-full <CN> --passout=pass:<pw>` — generates new key + cert
|
|
3. CRL **not** auto-updated during renewal; use "Regenerate CRL" menu item or `r` hotkey
|
|
|
|
## TUI key bindings
|
|
|
|
| Key | Action |
|
|
|---|---|
|
|
| `↑`/`↓` | Navigate list |
|
|
| `Space` | Toggle checkbox selection |
|
|
| `Enter` | Confirm / open selected item |
|
|
| `r` | Revoke selected cert |
|
|
| `A` | Toggle between expiring-only and all-certs view |
|
|
| `q`/`Esc` | Quit |
|
|
|
|
## Key constraints
|
|
|
|
- Email: set `SMTP_HOST` to use smtplib (SMTP_TLS: `"starttls"`/`"ssl"`/`""`); leave empty to use `MAIL_BINARY`. Auth skipped when `SMTP_USER=""`
|
|
- EasyRSA called with `--batch`; `--passin=pass:<CA_PASSPHRASE>` omitted when `CA_PASSPHRASE=""`
|
|
- Cryptgeon: `raw_key=os.urandom(32)`, `aes_key=SHA-256(raw_key)`, AES-256-GCM, URL fragment=`base64url(raw_key)`
|
|
- `copy_crl()` does `chmod 644` after copy
|
|
- Password: pos 1=uppercase, pos 2=lowercase (no j), pos 3-27=alphanumeric, pos 28=lowercase (no j); `oO01lIQ5S2Z8B` banned everywhere
|
|
- Inline file path: `<PKI_DIR>/inline/private/<CN>.inline`
|
|
- User emails stored in `<PKI_DIR>/openvpncertupdate-metadata.json`
|