Subversion Repositories web_pages

Rev

Details | Last modification | View Log | RSS feed

Rev Author Line No. Line
20 rodolico 1
# opnsense.pm - Developer's Guide
2
 
3
## Table of Contents
4
 
5
- [Overview](#overview)
6
- [Installation](#installation)
7
- [Basic Usage](#basic-usage)
8
- [API Methods](#api-methods)
9
- [Advanced Topics](#advanced-topics)
10
- [Error Handling](#error-handling)
11
- [Examples](#examples)
12
- [API Endpoints Reference](#api-endpoints-reference)
13
- [Troubleshooting](#troubleshooting)
14
 
15
## Overview
16
 
17
`opnsense.pm` is a Perl module that provides an object-oriented interface to the OPNsense router API. It simplifies interaction with OPNsense routers for retrieving VPN configurations, user credentials, TOTP secrets, and managing OpenVPN export functionality.
18
 
19
### Features
20
 
21
- Object-oriented interface for OPNsense API
22
- Dual HTTP client support (curl and LWP::UserAgent)
23
- SSL verification disabled for self-signed certificates
24
- Automatic JSON encoding/decoding
25
- XML configuration parsing
26
- Complete OpenVPN export functionality
27
 
28
### Version
29
 
30
Current version: 1.0.1
31
 
32
## Installation
33
 
34
### Prerequisites
35
 
36
```bash
37
# Install required Perl modules
38
cpan install LWP::UserAgent
39
cpan install JSON
40
cpan install XML::Simple
41
cpan install Data::Dumper
42
```
43
 
44
Or using system package manager:
45
 
46
```bash
47
# Debian/Ubuntu
48
apt-get install libwww-perl libjson-perl libxml-simple-perl
49
 
50
# RedHat/CentOS
51
yum install perl-libwww-perl perl-JSON perl-XML-Simple
52
```
53
 
54
### Module Setup
55
 
56
1. Place `opnsense.pm` in your script directory or Perl library path
57
2. Ensure the module is readable by the user running your scripts
58
3. For production, consider installing in standard Perl library location
59
 
60
```bash
61
# Copy to local lib directory
62
mkdir -p ~/perl5/lib
63
cp opnsense.pm ~/perl5/lib/
64
 
65
# Or system-wide (requires root)
66
cp opnsense.pm /usr/local/lib/perl5/site_perl/
67
```
68
 
69
## Basic Usage
70
 
71
### Creating an API Client
72
 
73
```perl
74
#!/usr/bin/env perl
75
use strict;
76
use warnings;
77
use opnsense;
78
 
79
# Create OPNsense API client
80
my $opn = opnsense->new(
81
    url       => 'https://192.168.1.1',      # Router URL
82
    apiKey    => 'your-api-key-here',        # From OPNsense UI
83
    apiSecret => 'your-api-secret-here',     # From OPNsense UI
84
    ovpnIndex => '1',                        # VPN provider ID
85
    template  => 'PlainOpenVPN',             # Template type
86
    hostname  => 'vpn.example.com',          # External hostname
87
    localPort => '1194'                      # VPN port
88
);
89
```
90
 
91
### Configuration Options
92
 
93
```perl
94
# Use curl instead of LWP (default: 1)
95
$opnsense::useCurl = 1;
96
 
97
# Enable debug output (default: 0)
98
$opnsense::debug = 1;
99
```
100
 
101
### Simple Example
102
 
103
```perl
104
use opnsense;
105
 
106
my $opn = opnsense->new(
107
    url       => 'https://192.168.1.1',
108
    apiKey    => $api_key,
109
    apiSecret => $api_secret,
110
    ovpnIndex => '1',
111
    template  => 'PlainOpenVPN',
112
    hostname  => 'vpn.example.com',
113
    localPort => '1194'
114
);
115
 
116
# Get all VPN users
117
my $vpnUsers = $opn->getVpnUsers();
118
print "VPN Users:\n";
119
foreach my $cert (keys %$vpnUsers) {
120
    print "  Certificate: $cert, User: $vpnUsers->{$cert}\n";
121
}
122
```
123
 
124
## API Methods
125
 
126
### Constructor: new()
127
 
128
Creates a new OPNsense API client object.
129
 
130
```perl
131
my $opn = opnsense->new(%parameters);
132
```
133
 
134
**Parameters:**
135
 
136
| Parameter | Type   | Required | Description |
137
|-----------|--------|----------|-------------|
138
| url       | string | Yes      | Base URL of OPNsense router (https://...) |
139
| apiKey    | string | Yes      | API key from System → Access → Users |
140
| apiSecret | string | Yes      | API secret from System → Access → Users |
141
| ovpnIndex | string | Yes      | OpenVPN provider index (digit or hash) |
142
| template  | string | Yes      | OpenVPN template (usually 'PlainOpenVPN') |
143
| hostname  | string | Yes      | External VPN hostname for client configs |
144
| localPort | string | Yes      | Port number for VPN connections |
145
 
146
**Returns:** opnsense object
147
 
148
**Example:**
149
```perl
150
my $opn = opnsense->new(
151
    url       => 'https://192.168.1.1',
152
    apiKey    => 'abc123...',
153
    apiSecret => 'xyz789...',
154
    ovpnIndex => '1',
155
    template  => 'PlainOpenVPN',
156
    hostname  => 'vpn.example.com',
157
    localPort => '1194'
158
);
159
```
160
 
161
### getVpnProviders()
162
 
163
Retrieves list of OpenVPN providers (instances) configured on the router.
164
 
165
```perl
166
my $providers = $opn->getVpnProviders();
167
```
168
 
169
**Returns:** Hashref keyed by provider ID, value is provider name
170
 
171
**Example:**
172
```perl
173
my $providers = $opn->getVpnProviders();
174
foreach my $id (keys %$providers) {
175
    print "Provider ID: $id, Name: $providers->{$id}\n";
176
}
177
 
178
# Output:
179
# Provider ID: 1, Name: Main VPN Server
180
# Provider ID: 2c3f5a1b, Name: Secondary VPN
181
```
182
 
183
### getVpnUsers()
184
 
185
Retrieves VPN user certificates from the OPNsense router.
186
 
187
```perl
188
my $vpnUsers = $opn->getVpnUsers();
189
```
190
 
191
**Returns:** Hashref keyed by certificate ID, value is username
192
 
193
**Example:**
194
```perl
195
my $vpnUsers = $opn->getVpnUsers();
196
foreach my $cert (keys %$vpnUsers) {
197
    my $username = $vpnUsers->{$cert};
198
    print "Certificate: $cert, User: $username\n";
199
}
200
 
201
# Output:
202
# Certificate: 60cc1de5e6dfd, User: john
203
# Certificate: 6327c3d037f8e, User: mary
204
```
205
 
206
**Notes:**
207
- Skips users with spaces in username
208
- Uses 'users' field if available, falls back to 'description' for compatibility
209
 
210
### getAllUsers()
211
 
212
Retrieves all system users including TOTP secrets and authentication data.
213
 
214
```perl
215
my $allUsers = $opn->getAllUsers();
216
```
217
 
218
**Returns:** Hashref keyed by username, value is user object
219
 
220
**User Object Structure:**
221
```perl
222
{
223
    'john' => {
224
        'name'        => 'john',
225
        'password'    => '<bcrypt hash>',
226
        'otp_seed'    => 'BASE32SECRET',
227
        'disabled'    => 0,
228
        'description' => 'John Doe',
229
        'email'       => 'john@example.com',
230
        # ... additional fields
231
    }
232
}
233
```
234
 
235
**Example:**
236
```perl
237
my $allUsers = $opn->getAllUsers();
238
foreach my $username (keys %$allUsers) {
239
    my $user = $allUsers->{$username};
240
    my $otp = $user->{otp_seed} || 'Not set';
241
    my $disabled = $user->{disabled} ? 'Yes' : 'No';
242
    print "User: $username, OTP: $otp, Disabled: $disabled\n";
243
}
244
```
245
 
246
**Important Notes:**
247
- Downloads entire router configuration via backup API
248
- Parses XML to extract user data
249
- Required because /api/system/user endpoint is broken in some versions
250
- Use sparingly as it's resource-intensive
251
 
252
### getVpnConfig()
253
 
254
Downloads OpenVPN configuration file for a specific certificate.
255
 
256
```perl
257
my $vpnConfig = $opn->getVpnConfig($certificateId);
258
```
259
 
260
**Parameters:**
261
 
262
| Parameter     | Type   | Required | Description |
263
|---------------|--------|----------|-------------|
264
| certificateId | string | Yes      | Certificate ID from getVpnUsers() |
265
 
266
**Returns:** Hashref containing base64-encoded .ovpn file
267
 
268
**Response Structure:**
269
```perl
270
{
271
    'content' => '<base64 encoded .ovpn configuration>',
272
    # ... other metadata
273
}
274
```
275
 
276
**Example:**
277
```perl
278
use MIME::Base64 qw(decode_base64);
279
 
280
my $cert = '60cc1de5e6dfd';
281
my $vpnConfig = $opn->getVpnConfig($cert);
282
 
283
if ($vpnConfig && $vpnConfig->{content}) {
284
    # Decode base64 content
285
    my $ovpnContent = decode_base64($vpnConfig->{content});
286
 
287
    # Write to file
288
    open my $fh, '>', "user_$cert.ovpn" or die $!;
289
    print $fh $ovpnContent;
290
    close $fh;
291
 
292
    print "VPN config written to user_$cert.ovpn\n";
293
}
294
```
295
 
296
### apiRequest() (Internal)
297
 
298
Low-level method for making API requests. Generally not called directly.
299
 
300
```perl
301
my $response = $opn->apiRequest($endpoint, $method, $data);
302
```
303
 
304
**Parameters:**
305
 
306
| Parameter | Type    | Required | Description |
307
|-----------|---------|----------|-------------|
308
| endpoint  | string  | Yes      | API endpoint path |
309
| method    | string  | No       | HTTP method (GET/POST/PUT/DELETE), default: GET |
310
| data      | hashref | No       | Data to send with POST/PUT |
311
 
312
**Returns:** Decoded JSON response or XML string
313
 
314
## Advanced Topics
315
 
316
### Choosing HTTP Client
317
 
318
The module supports two HTTP clients:
319
 
320
**curl (Default):**
321
```perl
322
$opnsense::useCurl = 1;
323
```
324
- Uses system curl command
325
- More reliable with self-signed certificates
326
- Requires curl installed on system
327
- Recommended for production
328
 
329
**LWP::UserAgent:**
330
```perl
331
$opnsense::useCurl = 0;
332
```
333
- Pure Perl solution
334
- No external dependencies
335
- May have issues with some SSL configurations
336
- Good for development/testing
337
 
338
### Debug Mode
339
 
340
Enable detailed debugging output:
341
 
342
```perl
343
$opnsense::debug = 1;
344
 
345
my $providers = $opn->getVpnProviders();
346
# Outputs API request details, responses, etc.
347
```
348
 
349
### Custom API Requests
350
 
351
For endpoints not covered by built-in methods:
352
 
353
```perl
354
# GET request
355
my $result = $opn->apiRequest('/api/some/endpoint');
356
 
357
# POST request with data
358
my $payload = {
359
    field1 => 'value1',
360
    field2 => 'value2'
361
};
362
my $result = $opn->apiRequest('/api/some/endpoint', 'POST', $payload);
363
 
364
# PUT request
365
my $result = $opn->apiRequest('/api/some/endpoint', 'PUT', $payload);
366
```
367
 
368
### Working with Multiple Routers
369
 
370
```perl
371
my @routers = (
372
    {
373
        name      => 'router1',
374
        url       => 'https://192.168.1.1',
375
        apiKey    => 'key1',
376
        apiSecret => 'secret1',
377
        # ... other params
378
    },
379
    {
380
        name      => 'router2',
381
        url       => 'https://192.168.2.1',
382
        apiKey    => 'key2',
383
        apiSecret => 'secret2',
384
        # ... other params
385
    }
386
);
387
 
388
foreach my $router (@routers) {
389
    my $opn = opnsense->new(%$router);
390
    my $users = $opn->getVpnUsers();
391
    print "Router $router->{name} has " . scalar(keys %$users) . " VPN users\n";
392
}
393
```
394
 
395
## Error Handling
396
 
397
### Basic Error Handling
398
 
399
```perl
400
use Try::Tiny;
401
 
402
try {
403
    my $opn = opnsense->new(%params);
404
    my $users = $opn->getAllUsers();
405
    # Process users...
406
} catch {
407
    warn "Error connecting to OPNsense: $_";
408
};
409
```
410
 
411
### Checking Results
412
 
413
```perl
414
my $vpnUsers = $opn->getVpnUsers();
415
 
416
if (!$vpnUsers || ref($vpnUsers) ne 'HASH') {
417
    die "Failed to retrieve VPN users\n";
418
}
419
 
420
if (keys %$vpnUsers == 0) {
421
    warn "No VPN users found\n";
422
}
423
```
424
 
425
### API Request Failures
426
 
427
```perl
428
# LWP mode will die on failure
429
eval {
430
    my $result = $opn->apiRequest('/api/endpoint');
431
};
432
if ($@) {
433
    print "API request failed: $@\n";
434
}
435
 
436
# curl mode returns empty/undef on failure
437
my $result = $opn->apiRequest('/api/endpoint');
438
if (!defined $result) {
439
    die "API request returned no data\n";
440
}
441
```
442
 
443
## Examples
444
 
445
### Complete VPN User Export
446
 
447
```perl
448
#!/usr/bin/env perl
449
use strict;
450
use warnings;
451
use opnsense;
452
use MIME::Base64 qw(decode_base64);
453
use Data::Dumper;
454
 
455
# Configuration
456
my $config = {
457
    url       => 'https://192.168.1.1',
458
    apiKey    => 'your-api-key',
459
    apiSecret => 'your-api-secret',
460
    ovpnIndex => '1',
461
    template  => 'PlainOpenVPN',
462
    hostname  => 'vpn.example.com',
463
    localPort => '1194'
464
};
465
 
466
# Create API client
467
my $opn = opnsense->new(%$config);
468
 
469
# Get VPN users (certificate ID => username mapping)
470
my $vpnUsers = $opn->getVpnUsers();
471
print "Found " . scalar(keys %$vpnUsers) . " VPN users\n";
472
 
473
# Get all user details including TOTP
474
my $allUsers = $opn->getAllUsers();
475
 
476
# Process each VPN user
477
foreach my $cert (keys %$vpnUsers) {
478
    my $username = $vpnUsers->{$cert};
479
    my $userDetails = $allUsers->{$username};
480
 
481
    next unless $userDetails;
482
    next if $userDetails->{disabled};
483
 
484
    print "\nProcessing user: $username (cert: $cert)\n";
485
 
486
    # Get TOTP seed
487
    my $otpSeed = $userDetails->{otp_seed} || '';
488
    print "  OTP Seed: " . ($otpSeed ? $otpSeed : "Not configured") . "\n";
489
 
490
    # Download VPN config
491
    my $vpnConfig = $opn->getVpnConfig($cert);
492
    if ($vpnConfig && $vpnConfig->{content}) {
493
        my $ovpnContent = decode_base64($vpnConfig->{content});
494
 
495
        # Write to file
496
        my $filename = "${username}_${cert}.ovpn";
497
        open my $fh, '>', $filename or die "Cannot write $filename: $!";
498
        print $fh $ovpnContent;
499
        close $fh;
500
 
501
        print "  VPN config: $filename\n";
502
    }
503
}
504
 
505
print "\nExport complete!\n";
506
```
507
 
508
### List All VPN Providers
509
 
510
```perl
511
#!/usr/bin/env perl
512
use strict;
513
use warnings;
514
use opnsense;
515
 
516
my $opn = opnsense->new(
517
    url       => 'https://192.168.1.1',
518
    apiKey    => $ENV{OPNSENSE_API_KEY},
519
    apiSecret => $ENV{OPNSENSE_API_SECRET},
520
    ovpnIndex => '1',
521
    template  => 'PlainOpenVPN',
522
    hostname  => 'vpn.example.com',
523
    localPort => '1194'
524
);
525
 
526
my $providers = $opn->getVpnProviders();
527
 
528
print "Available OpenVPN Providers:\n";
529
print "=" x 50 . "\n";
530
 
531
foreach my $id (sort keys %$providers) {
532
    printf "%-15s : %s\n", $id, $providers->{$id};
533
}
534
```
535
 
536
### User Audit Report
537
 
538
```perl
539
#!/usr/bin/env perl
540
use strict;
541
use warnings;
542
use opnsense;
543
 
544
my $opn = opnsense->new(%config);
545
 
546
my $vpnUsers = $opn->getVpnUsers();
547
my $allUsers = $opn->getAllUsers();
548
 
549
print "VPN User Audit Report\n";
550
print "=" x 70 . "\n";
551
printf "%-20s %-15s %-10s %-20s\n", 
552
    "Username", "Status", "Has TOTP", "Email";
553
print "-" x 70 . "\n";
554
 
555
foreach my $username (sort keys %$allUsers) {
556
    my $user = $allUsers->{$username};
557
    my $hasVpn = 0;
558
 
559
    # Check if user has VPN access
560
    foreach my $cert (keys %$vpnUsers) {
561
        $hasVpn = 1 if $vpnUsers->{$cert} eq $username;
562
    }
563
 
564
    next unless $hasVpn;
565
 
566
    my $status = $user->{disabled} ? "Disabled" : "Active";
567
    my $hasTotp = $user->{otp_seed} ? "Yes" : "No";
568
    my $email = $user->{email} || "N/A";
569
 
570
    printf "%-20s %-15s %-10s %-20s\n",
571
        $username, $status, $hasTotp, $email;
572
}
573
```
574
 
575
## API Endpoints Reference
576
 
577
### Endpoints Used by Module
578
 
579
| Endpoint | Method | Purpose | Module Method |
580
|----------|--------|---------|---------------|
581
| `/api/openvpn/export/providers` | GET | List VPN providers | getVpnProviders() |
582
| `/api/openvpn/export/accounts/{id}` | GET | List VPN users/certs | getVpnUsers() |
583
| `/api/core/backup/download/this` | GET | Download config (XML) | getAllUsers() |
584
| `/api/openvpn/export/download/{id}/{cert}` | POST | Download VPN config | getVpnConfig() |
585
 
586
### Authentication
587
 
588
All requests use HTTP Basic Authentication:
589
```
590
Authorization: key <apiKey>:<apiSecret>
591
```
592
 
593
### SSL/TLS
594
 
595
SSL verification is disabled to support self-signed certificates:
596
- curl: `--insecure` flag
597
- LWP: `SSL_verify_mode => 0`
598
 
599
## Troubleshooting
600
 
601
### Common Issues
602
 
603
**1. SSL Certificate Errors**
604
 
605
If using LWP and encountering SSL errors:
606
```perl
607
# Switch to curl
608
$opnsense::useCurl = 1;
609
```
610
 
611
**2. Empty Response from API**
612
 
613
Enable debug mode to see raw API responses:
614
```perl
615
$opnsense::debug = 1;
616
my $result = $opn->getVpnUsers();
617
```
618
 
619
**3. "API request failed" Error**
620
 
621
Check:
622
- Router is accessible from your network
623
- API credentials are correct
624
- API access is enabled in OPNsense (System → Settings → Administration)
625
- User has necessary permissions
626
 
627
**4. No VPN Users Returned**
628
 
629
Verify:
630
- `ovpnIndex` matches an active VPN provider
631
- Users have VPN certificates assigned
632
- Users are not disabled
633
 
634
**5. getAllUsers() Returns Empty**
635
 
636
This endpoint requires admin privileges. Ensure API user has full admin access.
637
 
638
### Debug Output
639
 
640
Example debug session:
641
```perl
642
$opnsense::debug = 1;
643
$opnsense::useCurl = 1;
644
 
645
my $opn = opnsense->new(%config);
646
my $users = $opn->getVpnUsers();
647
 
648
# Outputs:
649
# In apiRequestCurl, command is:
650
#  curl -s --insecure --request GET --user 'key:secret' https://...
651
```
652
 
653
### Testing Connection
654
 
655
```perl
656
#!/usr/bin/env perl
657
use strict;
658
use warnings;
659
use opnsense;
660
 
661
my $opn = eval {
662
    opnsense->new(
663
        url       => 'https://192.168.1.1',
664
        apiKey    => 'test-key',
665
        apiSecret => 'test-secret',
666
        ovpnIndex => '1',
667
        template  => 'PlainOpenVPN',
668
        hostname  => 'vpn.example.com',
669
        localPort => '1194'
670
    );
671
};
672
 
673
if ($@) {
674
    die "Failed to create API client: $@\n";
675
}
676
 
677
print "Testing API connection...\n";
678
 
679
my $providers = $opn->getVpnProviders();
680
if ($providers && keys %$providers) {
681
    print "✓ Connection successful!\n";
682
    print "Found " . scalar(keys %$providers) . " VPN provider(s)\n";
683
} else {
684
    print "✗ Connection failed or no providers found\n";
685
}
686
```
687
 
688
## Performance Considerations
689
 
690
### Caching Results
691
 
692
For frequent access, cache results to minimize API calls:
693
 
694
```perl
695
my %cache;
696
 
697
sub getCachedUsers {
698
    my ($opn, $cache_time) = @_;
699
    $cache_time ||= 300; # 5 minutes
700
 
701
    my $now = time;
702
    if (!$cache{users} || ($now - $cache{timestamp}) > $cache_time) {
703
        $cache{users} = $opn->getAllUsers();
704
        $cache{timestamp} = $now;
705
    }
706
 
707
    return $cache{users};
708
}
709
```
710
 
711
### Parallel Processing
712
 
713
Use forking or threading for multiple routers:
714
 
715
```perl
716
use Parallel::ForkManager;
717
 
718
my $pm = Parallel::ForkManager->new(5);
719
 
720
foreach my $router (@routers) {
721
    $pm->start and next;
722
 
723
    my $opn = opnsense->new(%$router);
724
    my $users = $opn->getVpnUsers();
725
    # Process users...
726
 
727
    $pm->finish;
728
}
729
 
730
$pm->wait_all_children;
731
```
732
 
733
## Security Best Practices
734
 
735
1. **Store API credentials securely:**
736
   ```perl
737
   # Use environment variables
738
   my $apiKey = $ENV{OPNSENSE_API_KEY};
739
   my $apiSecret = $ENV{OPNSENSE_API_SECRET};
740
 
741
   # Or encrypted configuration files
742
   # Never hardcode credentials in scripts
743
   ```
744
 
745
2. **Use dedicated API keys:**
746
   - Create separate API users for each application
747
   - Grant minimum necessary permissions
748
   - Rotate keys regularly
749
 
750
3. **Protect configuration files:**
751
   ```bash
752
   chmod 600 routers.json
753
   chown root:root routers.json
754
   ```
755
 
756
4. **Use HTTPS:**
757
   - Always connect via HTTPS
758
   - Consider proper SSL certificates for production
759
 
760
5. **Audit access:**
761
   - Log all API access
762
   - Monitor for unusual activity
763
   - Review OPNsense logs regularly
764
 
765
## Support and Contributing
766
 
767
### Getting Help
768
 
769
- Check OPNsense API documentation: https://docs.opnsense.org/
770
- Review module source code for implementation details
771
- Enable debug mode for troubleshooting
772
 
773
### Reporting Issues
774
 
775
When reporting issues, include:
776
- OPNsense version
777
- Perl version (`perl -v`)
778
- Module version
779
- Debug output (sanitize credentials!)
780
- Complete error messages
781
 
782
### Contributing
783
 
784
Improvements welcome! Consider:
785
- Additional API endpoint methods
786
- Better error handling
787
- Performance optimizations
788
- Documentation improvements
789
 
790
## License
791
 
792
Copyright (c) 2025, Daily Data, Inc.
793
All rights reserved.
794
 
795
This software is provided under the BSD 3-Clause License.
796
 
797
---
798
 
799
**Last Updated:** January 4, 2026  
800
**Version:** 1.0.1