| Line 1... |
Line 1... |
| 1 |
# Program Overview: totp_opnsense Suite (Updated)
|
1 |
# Program Overview: totp_opnsense Suite
|
| 2 |
|
2 |
|
| 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 parsing to user authentication and QR code delivery.
|
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.
|
| 4 |
|
4 |
|
| 5 |
---
|
5 |
---
|
| 6 |
|
6 |
|
| 7 |
## 1. configure
|
7 |
## 1. configure
|
| 8 |
|
8 |
|
| 9 |
**Purpose:**
|
9 |
**Purpose:**
|
| 10 |
- Reads and parses OPNSense configuration files.
|
10 |
- Interactive tool for managing router configurations stored in `routers.json`.
|
| 11 |
- Loads settings and credentials for API access and OpenVPN configuration.
|
11 |
- Allows adding, editing, and removing router definitions.
|
| 12 |
- Provides utility subs for configuration management.
|
12 |
- Manages API credentials, VPN provider settings, and OpenVPN configuration formats.
|
| 13 |
|
13 |
|
| 14 |
**Key Features:**
|
14 |
**Key Features:**
|
| 15 |
- 3-space indentation and camelCase variable naming.
|
15 |
- 3-space indentation and camelCase variable naming.
|
| 16 |
- Modular subroutines with headers for clarity.
|
16 |
- Menu-driven interface for easy configuration management.
|
| 17 |
- Loads the custom `Opnsense` Perl module for API interaction.
|
17 |
- Supports multiple OpenVPN configuration formats per router.
|
| - |
|
18 |
- Direct integration with OPNsense API to select VPN providers.
|
| 18 |
|
19 |
|
| 19 |
**Main Subroutines:**
|
20 |
**Main Subroutines:**
|
| 20 |
- `usage`: Prints usage instructions.
|
21 |
- `usage`: Prints usage instructions.
|
| 21 |
- `readConfig`: Reads and parses the configuration file.
|
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.
|
| - |
|
29 |
|
| - |
|
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)
|
| 22 |
|
41 |
|
| 23 |
---
|
42 |
---
|
| 24 |
|
43 |
|
| 25 |
## 2. index.php
|
44 |
## 2. index.php
|
| 26 |
|
45 |
|
| 27 |
**Purpose:**
|
46 |
**Purpose:**
|
| 28 |
- Provides a web interface for users to log in and retrieve their TOTP QR code and OpenVPN configuration.
|
47 |
- Web interface for users to authenticate and retrieve their TOTP QR code and OpenVPN configurations.
|
| 29 |
- Validates user credentials and displays relevant information.
|
48 |
- Validates user credentials against password hashes stored in `users.json`.
|
| - |
|
49 |
- Displays QR codes, TOTP seeds, and provides download links for VPN configurations.
|
| 30 |
|
50 |
|
| 31 |
**Key Features:**
|
51 |
**Key Features:**
|
| 32 |
- 3-space indentation and camelCase variable naming for PHP code.
|
52 |
- 3-space indentation and camelCase variable naming for PHP code.
|
| 33 |
- Function headers for all major operations.
|
53 |
- Password verification using bcrypt hashes.
|
| - |
|
54 |
- Support for multiple OpenVPN configuration files per user.
|
| 34 |
- Reads configuration generated by `processOPNSense.pl`.
|
55 |
- Router selection dropdown populated from available routers.
|
| - |
|
56 |
- Request refresh functionality for router data.
|
| - |
|
57 |
- Automatic logging of successful logins to `logs/` directory.
|
| 35 |
|
58 |
|
| 36 |
**Main Functions:**
|
59 |
**Main Functionality:**
|
| 37 |
- `validateUser`: Validates user credentials against stored data.
|
60 |
- Reads configuration from `users.json` generated by `opnsense-totp-ovpn-export`.
|
| 38 |
- `displayQRCode`: Shows the QR code for the user's OTP.
|
61 |
- Displays QR code image for TOTP setup.
|
| 39 |
- `displayOTPSeed`: Displays the OTP seed for manual entry.
|
62 |
- Shows TOTP seed for manual entry into authenticator apps.
|
| 40 |
- `downloadOvpnFile`: Provides a download link for the user's OpenVPN configuration.
|
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.
|
| 41 |
- Displays the last time users.json was updated and allows a request for that from the webui. If user requests an update, a file is placed in /tmp which can be managed by a cron job
|
68 |
- Allows users to request router data refresh via web interface.
|
| - |
|
69 |
|
| - |
|
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:**
|
| 42 |
- see `updaterouters.cron` for an example of allowing users to request a rescan of a router.
|
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.
|
| 43 |
|
78 |
|
| 44 |
---
|
79 |
---
|
| 45 |
|
80 |
|
| 46 |
## 3. opnsense-totp-ovpn-export
|
81 |
## 3. opnsense-totp-ovpn-export
|
| 47 |
|
82 |
|
| 48 |
**Purpose:**
|
83 |
**Purpose:**
|
| 49 |
- Creates a lock file and checks there is only one instance running to decrease chance of corruption
|
84 |
- Connects to OPNsense router via API to retrieve user credentials and VPN configurations.
|
| 50 |
- Loads router and user configuration from JSON files.
|
85 |
- Generates QR codes for TOTP authentication.
|
| 51 |
- Uses the `opnsense` module to interact with the OPNSense API.
|
86 |
- Creates OpenVPN configuration files based on defined formats.
|
| 52 |
- Generates QR codes and OpenVPN configuration files for users.
|
87 |
- Updates `users.json` with current user data.
|
| 53 |
|
88 |
|
| 54 |
**Key Features:**
|
89 |
**Key Features:**
|
| 55 |
- 3-space indentation and camelCase variable naming.
|
90 |
- 3-space indentation and camelCase variable naming.
|
| - |
|
91 |
- Lock file mechanism prevents concurrent execution and data corruption.
|
| 56 |
- Clearly marked subroutine headers for maintainability.
|
92 |
- Clearly marked subroutine headers for maintainability.
|
| 57 |
- Uses `GD::Barcode::QRcode` for QR code generation and `MIME::Base64` for decoding OpenVPN files.
|
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.
|
| 58 |
|
96 |
|
| 59 |
**Main Subroutines:**
|
97 |
**Main Subroutines:**
|
| - |
|
98 |
- `slurpFile`: Reads entire file contents into a string.
|
| - |
|
99 |
- `dumpFile`: Writes string contents to a file.
|
| 60 |
- `loadConfig`: Loads router configuration from a JSON file.
|
100 |
- `loadConfig`: Loads router configuration from JSON file (`routers.json`).
|
| 61 |
- `loadUsers`: Loads user data from a JSON file.
|
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.
|
| - |
|
110 |
|
| - |
|
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`)
|
| 62 |
- Additional subs for QR code and OpenVPN file management.
|
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.
|
| 63 |
|
131 |
|
| 64 |
---
|
132 |
---
|
| 65 |
|
133 |
|
| 66 |
## 4. opnsense.pm (Perl Module)
|
134 |
## 4. opnsense.pm (Perl Module)
|
| 67 |
|
135 |
|
| Line 77... |
Line 145... |
| 77 |
**Main Subroutines:**
|
145 |
**Main Subroutines:**
|
| 78 |
- `new`: Constructor for the Opnsense object.
|
146 |
- `new`: Constructor for the Opnsense object.
|
| 79 |
- `apiRequest`: Dispatches API requests to curl or LWP handler.
|
147 |
- `apiRequest`: Dispatches API requests to curl or LWP handler.
|
| 80 |
- `apiRequestCurl`: Handles API requests using curl.
|
148 |
- `apiRequestCurl`: Handles API requests using curl.
|
| 81 |
- `apiRequestLwp`: Handles API requests using LWP::UserAgent.
|
149 |
- `apiRequestLwp`: Handles API requests using LWP::UserAgent.
|
| - |
|
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.
|
| 82 |
|
154 |
|
| 83 |
---
|
155 |
---
|
| 84 |
|
156 |
|
| 85 |
## Datafile Structures
|
157 |
## Datafile Structures
|
| 86 |
|
158 |
|
| 87 |
**user configuration file**
|
159 |
**user configuration file (users.json)**
|
| 88 |
- The users configuration file (users.json) contains sensitive information (totp codes and password hash)
|
160 |
- Contains sensitive information (TOTP codes and password hashes).
|
| 89 |
- The users configuration file is used by both index.php and loadOpnSense.pl, so should be owned by root, but with read access to others
|
161 |
- Used by both `index.php` and `opnsense-totp-ovpn-export`.
|
| 90 |
- The users configuration file is a json file with the following structure
|
162 |
- Should be owned by root with read access for web server.
|
| - |
|
163 |
- JSON structure:
|
| 91 |
```json
|
164 |
```json
|
| 92 |
{
|
165 |
{
|
| 93 |
"router1" : {
|
166 |
"router1": {
|
| 94 |
"qrLocationFileystem": "/file/system/path/to/qrcode/directory",
|
167 |
"qrLocationFileystem": "/file/system/path/to/qrcode/directory",
|
| 95 |
"qrLocation": "./qrcodes", # relative path to qrcode directory
|
168 |
"qrLocation": "./qrcodes",
|
| 96 |
"ovpnLocationFileSystem": "/file/system/path/to/ovpn/directory",
|
169 |
"ovpnLocationFileSystem": "/file/system/path/to/ovpn/directory",
|
| 97 |
"ovpnLocation": "./openvpn_configs", # relative path to ovpn file directory
|
170 |
"ovpnLocation": "./openvpn_configs",
|
| 98 |
"lastUpdate": 1759817343, # unix timestamp of last update
|
171 |
"lastUpdate": 1759817343,
|
| 99 |
"users" : {
|
172 |
"users": {
|
| 100 |
"user1" : {
|
173 |
"user1": {
|
| 101 |
"password" : "<hashed password>",
|
174 |
"password": "<bcrypt hashed password>",
|
| 102 |
"otp_seed" : "<otp seed>",
|
175 |
"otp_seed": "<TOTP seed in base32>",
|
| - |
|
176 |
"certs": ["cert-id-1"],
|
| 103 |
"qrFile" : "<path to qr code image file (relative)>",
|
177 |
"qrFile": "./qrcodes/router1_user1.png",
|
| 104 |
"ovpnFile" : "<path to openvpn config file (relative)>"
|
178 |
"ovpnFile": "./openvpn_configs/router1_user1.ovpn"
|
| 105 |
},
|
- |
|
| 106 |
"user2" : {
|
- |
|
| 107 |
...
|
- |
|
| 108 |
}
|
- |
|
| 109 |
}
|
- |
|
| 110 |
},
|
179 |
},
|
| 111 |
"router2" : {
|
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"
|
| 112 |
...
|
188 |
]
|
| 113 |
}
|
189 |
}
|
| 114 |
}
|
190 |
}
|
| - |
|
191 |
},
|
| - |
|
192 |
"router2": {
|
| - |
|
193 |
...
|
| - |
|
194 |
}
|
| - |
|
195 |
}
|
| 115 |
```
|
196 |
```
|
| - |
|
197 |
|
| 116 |
**router configuration file**
|
198 |
**Note on ovpnFile:**
|
| 117 |
- The router configuration file contains the apiSecret and apiKey that could allow black hats to access and modify your router
|
199 |
- Can be a string (single configuration file) or array (multiple configuration files).
|
| 118 |
- The router configuration file is owned by root and set with permissions 0600, allowing access only to the root user
|
200 |
- Multiple files are automatically displayed as separate download links in web interface.
|
| - |
|
201 |
|
| 119 |
- The router configuration file is a json file with the following structure
|
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:
|
| 120 |
```json
|
206 |
```json
|
| 121 |
{
|
207 |
{
|
| 122 |
"routername": {
|
208 |
"routername": {
|
| 123 |
"hostname": "router.example.org", # hostname written to ovpn files
|
209 |
"url": "https://192.168.1.1",
|
| 124 |
"localPort": "1194", # port number written to ovpn files
|
210 |
"apiKey": "public API key from OPNsense Router",
|
| 125 |
"apiSecret": "secret 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",
|
| 126 |
"apiKey": "public API key from openSense Router",
|
216 |
"downloadToken": "random-token-string",
|
| - |
|
217 |
"formats": {
|
| - |
|
218 |
"tcp": {
|
| - |
|
219 |
"filename": "ROUTER_USER_tcp.ovpn",
|
| 127 |
"url": "https://192.168.1.1", # how the scripts connect to router
|
220 |
"additionalStrings": "proto tcp\\nport 443\\nauth-nocache"
|
| - |
|
221 |
},
|
| - |
|
222 |
"udp": {
|
| - |
|
223 |
"filename": "ROUTER_USER_udp.ovpn",
|
| 128 |
"template": "PlainOpenVPN", # valid template from opnsense, PlainOpenVPN is used exclusively so far
|
224 |
"additionalStrings": "proto udp\\nport 1194\\nauth-nocache"
|
| - |
|
225 |
},
|
| - |
|
226 |
"tcp-alt": {
|
| - |
|
227 |
"filename": "ROUTER_USER_tcp_alt.ovpn",
|
| 129 |
"ovpnIndex": "1" # the index into the OVPN instance. In older systems, a single digit, in newer ones a long hash
|
228 |
"additionalStrings": "proto tcp\\nport 8443\\nauth-nocache"
|
| - |
|
229 |
}
|
| - |
|
230 |
}
|
| 130 |
}
|
231 |
}
|
| 131 |
}
|
232 |
}
|
| 132 |
```
|
233 |
```
|
| 133 |
|
234 |
|
| - |
|
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
|
| - |
|
246 |
|
| - |
|
247 |
---
|
| 134 |
|
248 |
|
| 135 |
# Workflow Summary
|
249 |
# Workflow Summary
|
| - |
|
250 |
|
| - |
|
251 |
1. **Initial Setup:**
|
| 136 |
1. Get API Key from router and save
|
252 |
- Obtain API key and secret from OPNsense router (System → Access → Users).
|
| 137 |
2. Run `configure` to set up router
|
253 |
- Run `./configure` to set up router configuration.
|
| - |
|
254 |
- Define OpenVPN formats if multiple configurations needed.
|
| - |
|
255 |
|
| - |
|
256 |
2. **Data Export:**
|
| 138 |
3. run `opnsense-totp-ovpn-export` to export the .ovpn files and generate the totp QR code image
|
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.
|
| 139 |
4. Users access `index.php` to log in, view their QR code, OTP seed, and download OpenVPN config.
|
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 |
---
|
| 140 |
|
277 |
|
| 141 |
# Security Note
|
278 |
# Security Note
|
| - |
|
279 |
|
| 142 |
All scripts treat OTP secrets and user data as sensitive. It is recommended to keep generated files and QR codes on a secure internal network.
|
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 |
|