| 20 |
rodolico |
1 |
# Program Overview: totp_opnsense Suite
|
| 16 |
rodolico |
2 |
|
| 20 |
rodolico |
3 |
This suite consists of four main programs/modules for managing TOTP and OpenVPN user access with OPNSense routers. Each has a distinct role in the workflow, from configuration management to user authentication and credential delivery.
|
| 16 |
rodolico |
4 |
|
|
|
5 |
---
|
|
|
6 |
|
|
|
7 |
## 1. configure
|
|
|
8 |
|
|
|
9 |
**Purpose:**
|
| 20 |
rodolico |
10 |
- Interactive tool for managing router configurations stored in `routers.json`.
|
|
|
11 |
- Allows adding, editing, and removing router definitions.
|
|
|
12 |
- Manages API credentials, VPN provider settings, and OpenVPN configuration formats.
|
| 16 |
rodolico |
13 |
|
|
|
14 |
**Key Features:**
|
|
|
15 |
- 3-space indentation and camelCase variable naming.
|
| 20 |
rodolico |
16 |
- Menu-driven interface for easy configuration management.
|
|
|
17 |
- Supports multiple OpenVPN configuration formats per router.
|
|
|
18 |
- Direct integration with OPNsense API to select VPN providers.
|
| 16 |
rodolico |
19 |
|
|
|
20 |
**Main Subroutines:**
|
|
|
21 |
- `usage`: Prints usage instructions.
|
| 20 |
rodolico |
22 |
- `readConfig`: Reads router configuration from JSON file.
|
|
|
23 |
- `writeConfig`: Writes router configuration to JSON file with proper permissions (0600, root-only).
|
|
|
24 |
- `trim`: Removes leading/trailing whitespace from strings.
|
|
|
25 |
- `menuSelect`: Displays interactive menu and captures user selection.
|
|
|
26 |
- `selectOvpnIndex`: Retrieves VPN provider list from router and allows selection.
|
|
|
27 |
- `editFormats`: Manages the formats hash for creating multiple OpenVPN configurations.
|
|
|
28 |
- `editRouter`: Main editor for router configuration fields.
|
| 16 |
rodolico |
29 |
|
| 20 |
rodolico |
30 |
**Formats Configuration:**
|
|
|
31 |
- Each format defines a unique OpenVPN configuration variant.
|
|
|
32 |
- Format structure: `{ "filename": "ROUTER_USER_format.ovpn", "additionalStrings": "proto tcp\\nport 443" }`
|
|
|
33 |
- ROUTER and USER placeholders in filename are replaced with actual values.
|
|
|
34 |
- `additionalStrings` supports `\n` for line breaks in configuration directives.
|
|
|
35 |
|
|
|
36 |
**Usage:**
|
|
|
37 |
```bash
|
|
|
38 |
./configure [configfile]
|
|
|
39 |
```
|
|
|
40 |
(defaults to `routers.json` in script directory if not specified)
|
|
|
41 |
|
| 16 |
rodolico |
42 |
---
|
|
|
43 |
|
|
|
44 |
## 2. index.php
|
|
|
45 |
|
|
|
46 |
**Purpose:**
|
| 20 |
rodolico |
47 |
- Web interface for users to authenticate and retrieve their TOTP QR code and OpenVPN configurations.
|
|
|
48 |
- Validates user credentials against password hashes stored in `users.json`.
|
|
|
49 |
- Displays QR codes, TOTP seeds, and provides download links for VPN configurations.
|
| 16 |
rodolico |
50 |
|
|
|
51 |
**Key Features:**
|
|
|
52 |
- 3-space indentation and camelCase variable naming for PHP code.
|
| 20 |
rodolico |
53 |
- Password verification using bcrypt hashes.
|
|
|
54 |
- Support for multiple OpenVPN configuration files per user.
|
|
|
55 |
- Router selection dropdown populated from available routers.
|
|
|
56 |
- Request refresh functionality for router data.
|
|
|
57 |
- Automatic logging of successful logins to `logs/` directory.
|
| 16 |
rodolico |
58 |
|
| 20 |
rodolico |
59 |
**Main Functionality:**
|
|
|
60 |
- Reads configuration from `users.json` generated by `opnsense-totp-ovpn-export`.
|
|
|
61 |
- Displays QR code image for TOTP setup.
|
|
|
62 |
- Shows TOTP seed for manual entry into authenticator apps.
|
|
|
63 |
- Provides download links for OpenVPN configuration files:
|
|
|
64 |
- Single file: displays one download link
|
|
|
65 |
- Multiple files: displays separate links for each configuration
|
|
|
66 |
- Logs successful authentication with timestamp, IP address, and username.
|
|
|
67 |
- Displays last update timestamp for selected router.
|
|
|
68 |
- Allows users to request router data refresh via web interface.
|
| 16 |
rodolico |
69 |
|
| 20 |
rodolico |
70 |
**Log Files:**
|
|
|
71 |
- Location: `./logs/log_YYYY-MM-DD.log`
|
|
|
72 |
- Format: `YYYY-MM-DD HH:MM:SS<tab>IP_ADDRESS<tab>Success<tab>USERNAME<tab>`
|
|
|
73 |
- Log directory created automatically if it doesn't exist.
|
|
|
74 |
|
|
|
75 |
**Refresh Mechanism:**
|
|
|
76 |
- See `updaterouters.cron` for an example of allowing users to request a rescan of a router.
|
|
|
77 |
- Refresh request creates a marker file in `/tmp` that cron job processes.
|
|
|
78 |
|
| 16 |
rodolico |
79 |
---
|
|
|
80 |
|
|
|
81 |
## 3. opnsense-totp-ovpn-export
|
|
|
82 |
|
|
|
83 |
**Purpose:**
|
| 20 |
rodolico |
84 |
- Connects to OPNsense router via API to retrieve user credentials and VPN configurations.
|
|
|
85 |
- Generates QR codes for TOTP authentication.
|
|
|
86 |
- Creates OpenVPN configuration files based on defined formats.
|
|
|
87 |
- Updates `users.json` with current user data.
|
| 16 |
rodolico |
88 |
|
|
|
89 |
**Key Features:**
|
|
|
90 |
- 3-space indentation and camelCase variable naming.
|
| 20 |
rodolico |
91 |
- Lock file mechanism prevents concurrent execution and data corruption.
|
| 16 |
rodolico |
92 |
- Clearly marked subroutine headers for maintainability.
|
| 20 |
rodolico |
93 |
- Uses `GD::Barcode::QRcode` for QR code generation.
|
|
|
94 |
- Uses `MIME::Base64` for decoding OpenVPN files from API.
|
|
|
95 |
- Supports multiple OpenVPN configuration formats per user.
|
| 16 |
rodolico |
96 |
|
|
|
97 |
**Main Subroutines:**
|
| 20 |
rodolico |
98 |
- `slurpFile`: Reads entire file contents into a string.
|
|
|
99 |
- `dumpFile`: Writes string contents to a file.
|
|
|
100 |
- `loadConfig`: Loads router configuration from JSON file (`routers.json`).
|
|
|
101 |
- `loadUsers`: Loads user data from JSON file (`users.json`).
|
|
|
102 |
- `saveUsers`: Saves user data to JSON file with proper structure.
|
|
|
103 |
- `makeQR`: Generates QR code image from TOTP seed and account information.
|
|
|
104 |
- `makeVPNConfigFile`: Creates OpenVPN configuration file with custom settings:
|
|
|
105 |
- Accepts filename template with ROUTER and USER placeholders
|
|
|
106 |
- Expands `\n` in additionalStrings to actual newlines
|
|
|
107 |
- Appends configuration directives to base VPN config
|
|
|
108 |
- Returns relative path to created file
|
|
|
109 |
- `cleanOldDataFiles`: Removes obsolete data files from previous runs.
|
| 16 |
rodolico |
110 |
|
| 20 |
rodolico |
111 |
**Format Processing:**
|
|
|
112 |
- Reads formats from `routers.json` for each router.
|
|
|
113 |
- Iterates through all defined formats to create multiple .ovpn files.
|
|
|
114 |
- Each format specifies:
|
|
|
115 |
- Filename template (e.g., `ROUTER_USER_tcp.ovpn`)
|
|
|
116 |
- Additional configuration strings (e.g., `proto tcp\nport 443`)
|
|
|
117 |
- Falls back to default single file if no formats defined.
|
|
|
118 |
- Stores result as array if multiple files, or string if single file.
|
|
|
119 |
|
|
|
120 |
**Lock File:**
|
|
|
121 |
- Location: `/tmp/opnsense.lock`
|
|
|
122 |
- Prevents concurrent execution.
|
|
|
123 |
- Retries: 6 attempts with 10-second intervals.
|
|
|
124 |
- Automatically removed on successful completion.
|
|
|
125 |
|
|
|
126 |
**Usage:**
|
|
|
127 |
```bash
|
|
|
128 |
./opnsense-totp-ovpn-export <routername>
|
|
|
129 |
```
|
|
|
130 |
Must be run as root.
|
|
|
131 |
|
| 16 |
rodolico |
132 |
---
|
|
|
133 |
|
|
|
134 |
## 4. opnsense.pm (Perl Module)
|
|
|
135 |
|
|
|
136 |
**Purpose:**
|
|
|
137 |
- Provides an object-oriented interface for interacting with the OPNSense API.
|
|
|
138 |
- Handles API requests using either curl or LWP::UserAgent, with SSL verification disabled for compatibility.
|
|
|
139 |
|
|
|
140 |
**Key Features:**
|
|
|
141 |
- 3-space indentation and camelCase variable naming.
|
|
|
142 |
- Clearly marked subroutine headers for maintainability.
|
|
|
143 |
- Flexible API request handling (curl or LWP).
|
|
|
144 |
|
|
|
145 |
**Main Subroutines:**
|
|
|
146 |
- `new`: Constructor for the Opnsense object.
|
|
|
147 |
- `apiRequest`: Dispatches API requests to curl or LWP handler.
|
|
|
148 |
- `apiRequestCurl`: Handles API requests using curl.
|
|
|
149 |
- `apiRequestLwp`: Handles API requests using LWP::UserAgent.
|
| 20 |
rodolico |
150 |
- `getVpnProviders`: Retrieves list of VPN providers from router.
|
|
|
151 |
- `getVpnUsers`: Gets VPN user certificates from router.
|
|
|
152 |
- `getAllUsers`: Retrieves all users with authentication data (including TOTP secrets).
|
|
|
153 |
- `getVpnConfig`: Exports VPN configuration file for a specific certificate.
|
| 16 |
rodolico |
154 |
|
|
|
155 |
---
|
|
|
156 |
|
|
|
157 |
## Datafile Structures
|
|
|
158 |
|
| 20 |
rodolico |
159 |
**user configuration file (users.json)**
|
|
|
160 |
- Contains sensitive information (TOTP codes and password hashes).
|
|
|
161 |
- Used by both `index.php` and `opnsense-totp-ovpn-export`.
|
|
|
162 |
- Should be owned by root with read access for web server.
|
|
|
163 |
- JSON structure:
|
| 16 |
rodolico |
164 |
```json
|
| 20 |
rodolico |
165 |
{
|
|
|
166 |
"router1": {
|
|
|
167 |
"qrLocationFileystem": "/file/system/path/to/qrcode/directory",
|
|
|
168 |
"qrLocation": "./qrcodes",
|
|
|
169 |
"ovpnLocationFileSystem": "/file/system/path/to/ovpn/directory",
|
|
|
170 |
"ovpnLocation": "./openvpn_configs",
|
|
|
171 |
"lastUpdate": 1759817343,
|
|
|
172 |
"users": {
|
|
|
173 |
"user1": {
|
|
|
174 |
"password": "<bcrypt hashed password>",
|
|
|
175 |
"otp_seed": "<TOTP seed in base32>",
|
|
|
176 |
"certs": ["cert-id-1"],
|
|
|
177 |
"qrFile": "./qrcodes/router1_user1.png",
|
|
|
178 |
"ovpnFile": "./openvpn_configs/router1_user1.ovpn"
|
| 16 |
rodolico |
179 |
},
|
| 20 |
rodolico |
180 |
"user2": {
|
|
|
181 |
"password": "<bcrypt hashed password>",
|
|
|
182 |
"otp_seed": "<TOTP seed in base32>",
|
|
|
183 |
"certs": ["cert-id-2"],
|
|
|
184 |
"qrFile": "./qrcodes/router1_user2.png",
|
|
|
185 |
"ovpnFile": [
|
|
|
186 |
"./openvpn_configs/router1_user2_tcp.ovpn",
|
|
|
187 |
"./openvpn_configs/router1_user2_udp.ovpn"
|
|
|
188 |
]
|
| 16 |
rodolico |
189 |
}
|
| 20 |
rodolico |
190 |
}
|
|
|
191 |
},
|
|
|
192 |
"router2": {
|
|
|
193 |
...
|
|
|
194 |
}
|
|
|
195 |
}
|
| 16 |
rodolico |
196 |
```
|
| 20 |
rodolico |
197 |
|
|
|
198 |
**Note on ovpnFile:**
|
|
|
199 |
- Can be a string (single configuration file) or array (multiple configuration files).
|
|
|
200 |
- Multiple files are automatically displayed as separate download links in web interface.
|
|
|
201 |
|
|
|
202 |
**router configuration file (routers.json)**
|
|
|
203 |
- Contains apiSecret and apiKey that provide access to router management.
|
|
|
204 |
- Owned by root with permissions 0600 (root-only access).
|
|
|
205 |
- JSON structure:
|
| 16 |
rodolico |
206 |
```json
|
|
|
207 |
{
|
|
|
208 |
"routername": {
|
| 20 |
rodolico |
209 |
"url": "https://192.168.1.1",
|
|
|
210 |
"apiKey": "public API key from OPNsense Router",
|
|
|
211 |
"apiSecret": "secret API key from OPNsense Router",
|
|
|
212 |
"ovpnIndex": "1",
|
|
|
213 |
"template": "PlainOpenVPN",
|
|
|
214 |
"hostname": "router.example.org",
|
|
|
215 |
"localPort": "1194",
|
|
|
216 |
"downloadToken": "random-token-string",
|
|
|
217 |
"formats": {
|
|
|
218 |
"tcp": {
|
|
|
219 |
"filename": "ROUTER_USER_tcp.ovpn",
|
|
|
220 |
"additionalStrings": "proto tcp\\nport 443\\nauth-nocache"
|
|
|
221 |
},
|
|
|
222 |
"udp": {
|
|
|
223 |
"filename": "ROUTER_USER_udp.ovpn",
|
|
|
224 |
"additionalStrings": "proto udp\\nport 1194\\nauth-nocache"
|
|
|
225 |
},
|
|
|
226 |
"tcp-alt": {
|
|
|
227 |
"filename": "ROUTER_USER_tcp_alt.ovpn",
|
|
|
228 |
"additionalStrings": "proto tcp\\nport 8443\\nauth-nocache"
|
|
|
229 |
}
|
|
|
230 |
}
|
| 16 |
rodolico |
231 |
}
|
|
|
232 |
}
|
|
|
233 |
```
|
|
|
234 |
|
| 20 |
rodolico |
235 |
**Configuration Fields:**
|
|
|
236 |
- `url`: Router management URL (how scripts connect).
|
|
|
237 |
- `apiKey` / `apiSecret`: API credentials from OPNsense.
|
|
|
238 |
- `ovpnIndex`: VPN provider index (single digit in older systems, long hash in newer versions).
|
|
|
239 |
- `template`: OpenVPN template type (typically "PlainOpenVPN").
|
|
|
240 |
- `hostname`: Hostname written to .ovpn files (external VPN endpoint).
|
|
|
241 |
- `localPort`: Port number written to .ovpn files (can be overridden by format strings).
|
|
|
242 |
- `downloadToken`: Authentication token for download requests.
|
|
|
243 |
- `formats`: Hash of configuration format definitions:
|
|
|
244 |
- Key: Format identifier (e.g., "tcp", "udp", "tcp-alt")
|
|
|
245 |
- Value: Hash with `filename` template and `additionalStrings` for configuration directives
|
| 16 |
rodolico |
246 |
|
| 20 |
rodolico |
247 |
---
|
|
|
248 |
|
| 16 |
rodolico |
249 |
# Workflow Summary
|
|
|
250 |
|
| 20 |
rodolico |
251 |
1. **Initial Setup:**
|
|
|
252 |
- Obtain API key and secret from OPNsense router (System → Access → Users).
|
|
|
253 |
- Run `./configure` to set up router configuration.
|
|
|
254 |
- Define OpenVPN formats if multiple configurations needed.
|
|
|
255 |
|
|
|
256 |
2. **Data Export:**
|
|
|
257 |
- Run `./opnsense-totp-ovpn-export routername` to:
|
|
|
258 |
- Connect to router via API
|
|
|
259 |
- Retrieve user credentials and TOTP secrets
|
|
|
260 |
- Generate QR code images
|
|
|
261 |
- Create OpenVPN configuration files for all formats
|
|
|
262 |
- Update `users.json`
|
|
|
263 |
|
|
|
264 |
3. **User Access:**
|
|
|
265 |
- Users navigate to `index.php` web interface.
|
|
|
266 |
- Log in with username, password, and router selection.
|
|
|
267 |
- View QR code for TOTP setup (first time).
|
|
|
268 |
- Download OpenVPN configuration file(s).
|
|
|
269 |
- Import .ovpn file into OpenVPN client.
|
|
|
270 |
|
|
|
271 |
4. **Automated Updates:**
|
|
|
272 |
- Set up cron job to run `opnsense-totp-ovpn-export` periodically.
|
|
|
273 |
- Users can request on-demand refresh via web interface.
|
|
|
274 |
- See `updaterouters.cron` for implementation example.
|
|
|
275 |
|
|
|
276 |
---
|
|
|
277 |
|
| 16 |
rodolico |
278 |
# Security Note
|
| 20 |
rodolico |
279 |
|
|
|
280 |
All scripts treat OTP secrets and user data as sensitive information:
|
|
|
281 |
|
|
|
282 |
- **Router configuration:** Root-only access (0600 permissions).
|
|
|
283 |
- **User configuration:** Root-owned, world-readable for web server.
|
|
|
284 |
- **QR codes:** Pre-generated and stored on filesystem (consider access restrictions).
|
|
|
285 |
- **Passwords:** Hashed using bcrypt with cost factor 10-11.
|
|
|
286 |
- **API credentials:** Stored in protected configuration file.
|
|
|
287 |
- **Logs:** Record successful authentication attempts with timestamps and IP addresses.
|
|
|
288 |
|
|
|
289 |
**Recommendations:**
|
|
|
290 |
- Host system on secure internal network or behind firewall.
|
|
|
291 |
- Use HTTPS for web interface.
|
|
|
292 |
- Implement web server access controls (.htaccess, IP restrictions).
|
|
|
293 |
- Regularly review access logs.
|
|
|
294 |
- Use dedicated API keys with minimal required permissions.
|
|
|
295 |
- Consider implementing rate limiting for login attempts.
|
|
|
296 |
|
|
|
297 |
---
|
|
|
298 |
|
|
|
299 |
# Version Information
|
|
|
300 |
|
|
|
301 |
- **configure:** v1.1.0
|
|
|
302 |
- **opnsense-totp-ovpn-export:** v1.1.0
|
|
|
303 |
- **index.php:** v1.3.0
|
|
|
304 |
- **opnsense.pm:** (see module for version)
|
|
|
305 |
|
|
|
306 |
See individual script headers for detailed version history and changes.
|
|
|
307 |
|