Subversion Repositories web_pages

Rev

Rev 17 | Show entire file | Ignore whitespace | Details | Blame | Last modification | View Log | RSS feed

Rev 17 Rev 20
Line 21... Line 21...
21
# TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
21
# TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
22
# BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
22
# BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
23
# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
23
# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
24
# ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
24
# ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
25
# DAMAGE.
25
# DAMAGE.
-
 
26
 
-
 
27
=head1 NAME
26
#
28
 
27
# Library to interface with the OPNsense API
29
opnsense - Perl interface for OPNsense Router API
-
 
30
 
-
 
31
=head1 SYNOPSIS
-
 
32
 
-
 
33
   use opnsense;
-
 
34
   
-
 
35
   # Create an OPNsense API client
-
 
36
   my $opn = opnsense->new(
-
 
37
      url       => 'https://192.168.1.1',
-
 
38
      apiKey    => 'your-api-key',
-
 
39
      apiSecret => 'your-api-secret',
-
 
40
      ovpnIndex => '1',
-
 
41
      template  => 'PlainOpenVPN',
-
 
42
      hostname  => 'vpn.example.com',
-
 
43
      localPort => '1194'
-
 
44
   );
-
 
45
   
-
 
46
   # Get VPN providers
-
 
47
   my $providers = $opn->getVpnProviders();
-
 
48
   
-
 
49
   # Get VPN user certificates
-
 
50
   my $vpnUsers = $opn->getVpnUsers();
-
 
51
   
-
 
52
   # Get all system users (including TOTP secrets)
-
 
53
   my $allUsers = $opn->getAllUsers();
-
 
54
   
-
 
55
   # Download VPN configuration for a certificate
-
 
56
   my $vpnConfig = $opn->getVpnConfig($certId);
-
 
57
 
-
 
58
=head1 DESCRIPTION
-
 
59
 
28
# Provides methods to interact with OPNsense routers via their API.
60
This module provides an object-oriented interface to the OPNsense router API.
-
 
61
It supports both curl-based and LWP::UserAgent-based HTTP requests with SSL
-
 
62
verification disabled for compatibility with self-signed certificates.
-
 
63
 
-
 
64
The module is designed to retrieve VPN configurations, user credentials,
-
 
65
TOTP secrets, and manage OpenVPN export functionality.
-
 
66
 
-
 
67
=head1 CONFIGURATION
-
 
68
 
-
 
69
   $opnsense::useCurl = 1;  # Use curl (default) or 0 for LWP::UserAgent
-
 
70
   $opnsense::debug = 0;    # Enable debug output (1) or disable (0)
29
#
71
 
30
# Change History:
72
=head1 CHANGE HISTORY
-
 
73
 
-
 
74
=over 4
-
 
75
 
31
#  v1.0.0 2025-10-01 - RWR
76
=item v1.0.0 2025-10-01 - RWR
-
 
77
 
32
#    Initial version
78
Initial version
-
 
79
 
33
#  v1.0.1 2025-10-07 - RWR
80
=item v1.0.1 2025-10-07 - RWR
-
 
81
 
34
#    Fixed bug where script was looking for localport, but was localPort
82
Fixed bug where script was looking for localport, but was localPort.
35
#    Fixed bug where not all users were returned because opnSense (v24.7)
83
Fixed bug where not all users were returned because opnSense (v24.7)
36
#       does not send the username, it sends the description. Possible
84
does not send the username, it sends the description. Quick fix is to
37
#       programming error, but quick fix is to use username, then description
85
use username, then description.
-
 
86
 
-
 
87
=back
-
 
88
 
-
 
89
=cut
38
 
90
 
39
 
91
 
40
package opnsense;
92
package opnsense;
41
use strict;
93
use strict;
42
use warnings;
94
use warnings;
Line 49... Line 101...
49
our $VERSION = "1.0.0";
101
our $VERSION = "1.0.0";
50
 
102
 
51
our $useCurl = 1; # set to 1 to use curl for API requests, 0 to use LWP
103
our $useCurl = 1; # set to 1 to use curl for API requests, 0 to use LWP
52
our $debug = 0;    # set to 1 for debug output
104
our $debug = 0;    # set to 1 for debug output
53
 
105
 
-
 
106
=head1 METHODS
-
 
107
 
-
 
108
=head2 new
-
 
109
 
-
 
110
   my $opn = opnsense->new(%args);
-
 
111
 
-
 
112
Constructor. Creates a new OPNsense API client object.
-
 
113
 
-
 
114
B<Parameters:>
-
 
115
 
-
 
116
=over 4
-
 
117
 
-
 
118
=item * url - Base URL of the OPNsense router (e.g., 'https://192.168.1.1')
-
 
119
 
-
 
120
=item * apiKey - API key from OPNsense (System | Access | Users)
-
 
121
 
-
 
122
=item * apiSecret - API secret from OPNsense
-
 
123
 
-
 
124
=item * ovpnIndex - OpenVPN provider index (single digit or hash)
-
 
125
 
-
 
126
=item * template - OpenVPN template type (typically 'PlainOpenVPN')
-
 
127
 
-
 
128
=item * hostname - External hostname for VPN endpoint
-
 
129
 
-
 
130
=item * localPort - Port number for VPN connections
-
 
131
 
-
 
132
=back
-
 
133
 
-
 
134
B<Returns:> opnsense object
-
 
135
 
-
 
136
=cut
-
 
137
 
54
#-----------------------------
138
#-----------------------------
55
# new: Constructor
139
# new: Constructor
56
#-----------------------------
140
#-----------------------------
57
sub new {
141
sub new {
58
   my ($class, %args) = @_;
142
   my ($class, %args) = @_;
Line 72... Line 156...
72
   };
156
   };
73
   bless $self, $class;
157
   bless $self, $class;
74
   return $self;
158
   return $self;
75
}
159
}
76
 
160
 
-
 
161
=head2 apiRequest
-
 
162
 
-
 
163
   my $response = $opn->apiRequest($endpoint, $method, $data);
-
 
164
 
-
 
165
Internal method that dispatches API requests to either curl or LWP handler
-
 
166
based on the $opnsense::useCurl setting.
-
 
167
 
-
 
168
B<Parameters:>
-
 
169
 
-
 
170
=over 4
-
 
171
 
-
 
172
=item * endpoint - API endpoint path (e.g., '/api/openvpn/export/providers')
-
 
173
 
-
 
174
=item * method - HTTP method (GET, POST, PUT, DELETE). Default: GET
-
 
175
 
-
 
176
=item * data - Optional hashref of data to send with POST/PUT requests
-
 
177
 
-
 
178
=back
-
 
179
 
-
 
180
B<Returns:> Decoded JSON response or XML string
-
 
181
 
-
 
182
=cut
-
 
183
 
77
#-----------------------------
184
#-----------------------------
78
# apiRequest: API request dispatcher
185
# apiRequest: API request dispatcher
79
#-----------------------------
186
#-----------------------------
80
sub apiRequest {
187
sub apiRequest {
81
 
188
 
Line 84... Line 191...
84
   } else {
191
   } else {
85
       return apiRequestLwp(@_);
192
       return apiRequestLwp(@_);
86
   }
193
   }
87
}
194
}
88
 
195
 
-
 
196
=head2 apiRequestCurl
-
 
197
 
-
 
198
   my $response = $self->apiRequestCurl($endpoint, $method, $data);
-
 
199
 
-
 
200
Internal method that performs API requests using system curl command.
-
 
201
SSL verification is disabled (--insecure flag).
-
 
202
 
-
 
203
B<Parameters:>
-
 
204
 
-
 
205
=over 4
-
 
206
 
-
 
207
=item * endpoint - API endpoint path
-
 
208
 
-
 
209
=item * method - HTTP method (GET, POST, PUT, DELETE). Default: GET
-
 
210
 
-
 
211
=item * data - Optional hashref of data to send
-
 
212
 
-
 
213
=back
-
 
214
 
-
 
215
B<Returns:> Decoded JSON response or raw XML string
-
 
216
 
-
 
217
=cut
-
 
218
 
89
#-----------------------------
219
#-----------------------------
90
# apiRequestCurl: API request using curl
220
# apiRequestCurl: API request using curl
91
#-----------------------------
221
#-----------------------------
92
sub apiRequestCurl {
222
sub apiRequestCurl {
93
   my ($self, $endpoint, $method, $data) = @_;
223
   my ($self, $endpoint, $method, $data) = @_;
Line 107... Line 237...
107
      return $json_text;
237
      return $json_text;
108
   }
238
   }
109
   return decode_json($json_text);
239
   return decode_json($json_text);
110
}
240
}
111
 
241
 
-
 
242
=head2 apiRequestLwp
-
 
243
 
-
 
244
   my $response = $self->apiRequestLwp($endpoint, $method, $data);
-
 
245
 
-
 
246
Internal method that performs API requests using LWP::UserAgent.
-
 
247
SSL verification is disabled for self-signed certificates.
-
 
248
 
-
 
249
B<Parameters:>
-
 
250
 
-
 
251
=over 4
-
 
252
 
-
 
253
=item * endpoint - API endpoint path
-
 
254
 
-
 
255
=item * method - HTTP method (GET, POST, PUT, DELETE). Default: GET
-
 
256
 
-
 
257
=item * data - Optional hashref of data to send
-
 
258
 
-
 
259
=back
-
 
260
 
-
 
261
B<Returns:> Decoded JSON response
-
 
262
 
-
 
263
B<Dies:> If the API request fails
-
 
264
 
-
 
265
=cut
112
 
266
 
113
#-----------------------------
267
#-----------------------------
114
# apiRequestLwp: API request using LWP
268
# apiRequestLwp: API request using LWP
115
#-----------------------------
269
#-----------------------------
116
sub apiRequestLwp {
270
sub apiRequestLwp {
Line 126... Line 280...
126
    return decode_json($res->decoded_content) if $res->is_success;
280
    return decode_json($res->decoded_content) if $res->is_success;
127
    die "API request failed: " . $res->status_line;
281
    die "API request failed: " . $res->status_line;
128
 
282
 
129
}
283
}
130
 
284
 
-
 
285
=head2 getVpnUsers
-
 
286
 
-
 
287
   my $vpnUsers = $opn->getVpnUsers();
-
 
288
 
-
 
289
Retrieves VPN user certificates from the OPNsense router.
-
 
290
 
-
 
291
B<Returns:> Hashref keyed by certificate ID, value is username
-
 
292
 
-
 
293
Example:
-
 
294
   {
-
 
295
      '60cc1de5e6dfd' => 'john',
-
 
296
      '6327c3d037f8e' => 'mary'
-
 
297
   }
-
 
298
 
-
 
299
B<Note:> Skips users with spaces in username. Uses 'users' field if available,
-
 
300
falls back to 'description' field for compatibility with OPNsense v24.7.
-
 
301
 
-
 
302
=cut
-
 
303
 
131
#-----------------------------
304
#-----------------------------
132
# getVpnUsers: get VPN users
305
# getVpnUsers: get VPN users
133
#-----------------------------
306
#-----------------------------
134
sub getVpnUsers {
307
sub getVpnUsers {
135
    my ($self) = @_;
308
    my ($self) = @_;
Line 152... Line 325...
152
    }
325
    }
153
#    print "In get_vpn_users, return object:\n" . Dumper($return); die;
326
#    print "In get_vpn_users, return object:\n" . Dumper($return); die;
154
    return $return;
327
    return $return;
155
}
328
}
156
 
329
 
-
 
330
=head2 getAllUsers
-
 
331
 
-
 
332
   my $allUsers = $opn->getAllUsers();
-
 
333
 
-
 
334
Retrieves all system users from the OPNsense router, including TOTP secrets
-
 
335
and authentication data.
-
 
336
 
-
 
337
B<Returns:> Hashref keyed by username, value is user object
-
 
338
 
-
 
339
User object structure:
-
 
340
   {
-
 
341
      'john' => {
-
 
342
         'name' => 'john',
-
 
343
         'password' => '<hashed password>',
-
 
344
         'otp_seed' => '<TOTP secret in base32>',
-
 
345
         'disabled' => 0,
-
 
346
         'description' => 'John Doe',
-
 
347
         ...
-
 
348
      }
-
 
349
   }
-
 
350
 
-
 
351
B<Note:> This method downloads the entire router configuration via
-
 
352
/api/core/backup/download/this and extracts user data, as the /api/system/user
-
 
353
endpoint is not functional in some OPNsense versions.
-
 
354
 
-
 
355
=cut
-
 
356
 
157
#-----------------------------
357
#-----------------------------
158
# getAllUsers: get all users on the system
358
# getAllUsers: get all users on the system
159
#-----------------------------
359
#-----------------------------
160
# returns a hashref keyed by username, value is the user object
360
# returns a hashref keyed by username, value is the user object
161
# The api is seriously broken. It is supposed to be /api/system/user, but that returns an error
361
# The api is seriously broken. It is supposed to be /api/system/user, but that returns an error
Line 175... Line 375...
175
        $return = $config->{system}->{user};
375
        $return = $config->{system}->{user};
176
    }
376
    }
177
    return $return;
377
    return $return;
178
}
378
}
179
 
379
 
-
 
380
=head2 getVpnProviders
-
 
381
 
-
 
382
   my $providers = $opn->getVpnProviders();
-
 
383
 
-
 
384
Retrieves list of OpenVPN providers (instances) configured on the router.
-
 
385
 
-
 
386
B<Returns:> Hashref keyed by provider ID (ovpnIndex), value is provider name
-
 
387
 
-
 
388
Example:
-
 
389
   {
-
 
390
      '1' => 'Main VPN Server',
-
 
391
      '2c3f5a1b' => 'Secondary VPN'
-
 
392
   }
-
 
393
 
-
 
394
B<Used by:> configure script to allow selection of VPN provider
-
 
395
 
-
 
396
=cut
-
 
397
 
180
#-----------------------------
398
#-----------------------------
181
# getVpnProviders: get VPN providers
399
# getVpnProviders: get VPN providers
182
#-----------------------------
400
#-----------------------------
183
sub getVpnProviders {
401
sub getVpnProviders {
184
   my ($self) = @_;
402
   my ($self) = @_;
Line 193... Line 411...
193
       $return->{$providers->{$provider}->{'vpnid'}} = $providers->{$provider}->{'name'};
411
       $return->{$providers->{$provider}->{'vpnid'}} = $providers->{$provider}->{'name'};
194
    }
412
    }
195
   return $return; 
413
   return $return; 
196
}
414
}
197
 
415
 
-
 
416
=head2 getVpnConfig
-
 
417
 
-
 
418
   my $vpnConfig = $opn->getVpnConfig($certId);
-
 
419
 
-
 
420
Downloads OpenVPN configuration file for a specific certificate.
-
 
421
 
-
 
422
B<Parameters:>
-
 
423
 
-
 
424
=over 4
-
 
425
 
-
 
426
=item * certId - Certificate ID from getVpnUsers()
-
 
427
 
-
 
428
=back
-
 
429
 
-
 
430
B<Returns:> Hashref containing:
-
 
431
   {
-
 
432
      'content' => '<base64 encoded .ovpn file>',
-
 
433
      ...
-
 
434
   }
-
 
435
 
-
 
436
B<Note:> The returned content is base64 encoded and must be decoded before use.
-
 
437
See makeVPNConfigFile() in opnsense-totp-ovpn-export for decoding example.
-
 
438
 
-
 
439
=cut
-
 
440
 
198
#-----------------------------
441
#-----------------------------
199
# getVpnConfig: get VPN configuration file
442
# getVpnConfig: get VPN configuration file
200
#-----------------------------
443
#-----------------------------
201
sub getVpnConfig {
444
sub getVpnConfig {
202
   my ( $self, $cert ) = @_;
445
   my ( $self, $cert ) = @_;
Line 218... Line 461...
218
   $debug = 0;
461
   $debug = 0;
219
   my $return = $self->apiRequest($endpoint, 'POST', $payload);
462
   my $return = $self->apiRequest($endpoint, 'POST', $payload);
220
   return $return;
463
   return $return;
221
}
464
}
222
 
465
 
-
 
466
=head1 DEPENDENCIES
-
 
467
 
-
 
468
=over 4
-
 
469
 
-
 
470
=item * LWP::UserAgent - For LWP-based HTTP requests
-
 
471
 
-
 
472
=item * JSON - For encoding/decoding JSON data
-
 
473
 
-
 
474
=item * Data::Dumper - For debugging output
-
 
475
 
-
 
476
=item * XML::Simple - For parsing router configuration XML
-
 
477
 
-
 
478
=back
-
 
479
 
-
 
480
=head1 SECURITY CONSIDERATIONS
-
 
481
 
-
 
482
=over 4
-
 
483
 
-
 
484
=item * SSL verification is disabled to support self-signed certificates
-
 
485
 
-
 
486
=item * API credentials are passed in plain text (use HTTPS)
-
 
487
 
-
 
488
=item * Store API keys securely with restrictive file permissions (0600)
-
 
489
 
-
 
490
=back
-
 
491
 
-
 
492
=head1 SEE ALSO
-
 
493
 
-
 
494
=over 4
-
 
495
 
-
 
496
=item * configure - Router configuration management tool
-
 
497
 
-
 
498
=item * opnsense-totp-ovpn-export - Main export script
-
 
499
 
-
 
500
=item * opnsense.pm.md - Developer's guide
-
 
501
 
-
 
502
=back
-
 
503
 
-
 
504
=head1 AUTHOR
-
 
505
 
-
 
506
Daily Data, Inc.
-
 
507
 
-
 
508
=head1 COPYRIGHT AND LICENSE
-
 
509
 
-
 
510
Copyright (c) 2025, Daily Data, Inc.
-
 
511
All rights reserved.
-
 
512
 
-
 
513
This software is provided under the BSD 3-Clause License.
-
 
514
See the LICENSE file for details.
-
 
515
 
-
 
516
=cut
223
 
517
 
224
1;
518
1;