Skip to content

Commit 14091f2

Browse files
Added support for JWT authentication, updated API webConfigurator page to allow admins to rotate the JWT server key and set the JWT expiration period, updated makefile to include JWT changes and prep for version 0.0.3
1 parent 2a40674 commit 14091f2

17 files changed

Lines changed: 1173 additions & 55 deletions

File tree

pfSense-pkg-API/Makefile

Lines changed: 27 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
PORTNAME= pfSense-pkg-API
44
PORTVERSION= 0.0
5-
PORTREVISION= 2
5+
PORTREVISION= 3
66
CATEGORIES= sysutils
77
MASTER_SITES= # empty
88
DISTFILES= # empty
@@ -19,6 +19,8 @@ do-extract:
1919
${MKDIR} ${WRKSRC}
2020

2121
do-install:
22+
23+
2224
# INSTALL OUR API INCLUDE FILE
2325
${MKDIR} ${STAGEDIR}/etc/inc
2426
${INSTALL_DATA} ${FILESDIR}/etc/inc/api.inc \
@@ -27,6 +29,25 @@ do-install:
2729
${STAGEDIR}/etc/inc
2830
${INSTALL_DATA} ${FILESDIR}/etc/inc/apiresp.inc \
2931
${STAGEDIR}/etc/inc
32+
# INSTALL STATIC PHP-JWT FILES (static files are required as pfSense does not allow composer installations)
33+
${MKDIR} ${STAGEDIR}/etc/inc/php-jwt
34+
${INSTALL_DATA} ${FILESDIR}/etc/inc/php-jwt/README.md \
35+
${STAGEDIR}/etc/inc/php-jwt
36+
${INSTALL_DATA} ${FILESDIR}/etc/inc/php-jwt/LICENSE \
37+
${STAGEDIR}/etc/inc/php-jwt
38+
${INSTALL_DATA} ${FILESDIR}/etc/inc/php-jwt/composer.json \
39+
${STAGEDIR}/etc/inc/php-jwt
40+
${MKDIR} ${STAGEDIR}/etc/inc/php-jwt/src
41+
${INSTALL_DATA} ${FILESDIR}/etc/inc/php-jwt/src/JWT.php \
42+
${STAGEDIR}/etc/inc/php-jwt/src
43+
${INSTALL_DATA} ${FILESDIR}/etc/inc/php-jwt/src/JWK.php \
44+
${STAGEDIR}/etc/inc/php-jwt/src
45+
${INSTALL_DATA} ${FILESDIR}/etc/inc/php-jwt/src/SignatureInvalidException.php \
46+
${STAGEDIR}/etc/inc/php-jwt/src
47+
${INSTALL_DATA} ${FILESDIR}/etc/inc/php-jwt/src/ExpiredException.php \
48+
${STAGEDIR}/etc/inc/php-jwt/src
49+
${INSTALL_DATA} ${FILESDIR}/etc/inc/php-jwt/src/BeforeValidException.php \
50+
${STAGEDIR}/etc/inc/php-jwt/src
3051

3152
# INSTALL OUR PFSENSE PKG
3253
${MKDIR} ${STAGEDIR}${PREFIX}/pkg
@@ -40,6 +61,11 @@ do-install:
4061
${STAGEDIR}${PREFIX}/www/api
4162
# API version
4263
${MKDIR} ${STAGEDIR}${PREFIX}/www/api/v1
64+
# ACCESS TOKEN API ENDPOINTS
65+
# Access Token base
66+
${MKDIR} ${STAGEDIR}${PREFIX}/www/api/v1/access_token
67+
${INSTALL_DATA} ${FILESDIR}${PREFIX}/www/api/v1/access_token/index.php \
68+
${STAGEDIR}${PREFIX}/www/api/v1/access_token
4369
# USERS API ENDPOINTS------------------------------------------
4470
# Users base
4571
${MKDIR} ${STAGEDIR}${PREFIX}/www/api/v1/users

pfSense-pkg-API/files/etc/inc/api.inc

Lines changed: 66 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,9 @@
11
<?php
22
require_once("apiresp.inc");
3+
require_once("php-jwt/src/JWT.php");
4+
require_once("php-jwt/src/ExpiredException.php");
5+
require_once("php-jwt/src/SignatureInvalidException.php");
6+
require_once("php-jwt/src/BeforeValidException.php");
37
require_once("config.inc");
48
require_once("util.inc");
59
require_once("interfaces.inc");
@@ -9,6 +13,11 @@ require_once("filter.inc");
913
require_once("shaper.inc");
1014
require_once("auth.inc");
1115
require_once("functions.inc");
16+
use Firebase\JWT\JWT;
17+
use Firebase\JWT\ExpiredException;
18+
use Firebase\JWT\SignatureInvalidException;
19+
use Firebase\JWT\BeforeValidException;
20+
1221

1322
// HEADERS--------------------------------------------------------------------------------------------------------------
1423
header("Content-Type: application/json", true);
@@ -104,6 +113,44 @@ function api_generate_token($username) {
104113
return $key_new;
105114
}
106115

116+
// Creates JWT server key if one does not exist, or optionally allows rotation of the JWT server key
117+
function api_create_jwt_server_key($rotate=false) {
118+
global $config;
119+
$pkg_index = get_api_configuration()[0]; // Save our current API configs pkg index
120+
$api_config = get_api_configuration()[1]; // Save our current API config
121+
# Create a new server key if one is not set
122+
if (empty($api_config["server_key"]) or $rotate === true) {
123+
$config["installedpackages"]["package"][$pkg_index]["conf"]["server_key"] = bin2hex(random_bytes(32));
124+
write_config();
125+
}
126+
}
127+
128+
// Creates a JWT to use for JWT authentication
129+
function api_create_jwt($data) {
130+
global $config;
131+
$api_config = get_api_configuration()[1]; // Save our current API config
132+
$token_exp = $api_config["jwt_exp"]; // Expire token in one hours
133+
api_create_jwt_server_key(); // Ensure we have a JWT server key
134+
$payload = array(
135+
"iss" => $config["system"]["hostname"],
136+
"exp" => time() + $token_exp,
137+
"nbf" => time(),
138+
"data" => $data
139+
);
140+
return JWT::encode($payload, $api_config["server_key"]);
141+
}
142+
143+
// Decodes a JWT
144+
function api_decode_jwt($token) {
145+
$key = get_api_configuration()[1]["server_key"]; // Save our current server key
146+
try {
147+
$decoded = (array) JWT::decode($token, $key, array('HS256'));
148+
} catch (Exception $e) {
149+
$decoded = false;
150+
}
151+
return $decoded;
152+
}
153+
107154
// Get our API token ID for a given username
108155
function api_get_existing_tokens($username) {
109156
// Local variables
@@ -143,6 +190,23 @@ function api_authenticate() {
143190
$authenticated = false;
144191
$api_config = get_api_configuration()[1];
145192
$users = index_users();
193+
194+
// Check for JWT in Authorization header if authmode is set to JWT
195+
if ($api_config["authmode"] === "jwt") {
196+
$auth_header = explode(" ", $_SERVER["HTTP_AUTHORIZATION"]);
197+
$token_type = $auth_header[0];
198+
$token = $auth_header[1];
199+
$decoded_jwt = api_decode_jwt($token);
200+
201+
// Check that our JWT from our Authorization header is valid
202+
if ($token_type === "Bearer" and $decoded_jwt !== false) {
203+
unset($_SESSION["Username"]);
204+
$client_id = $decoded_jwt["data"];
205+
$_SESSION["Username"] = $client_id;
206+
$authenticated = true;
207+
}
208+
}
209+
146210
// Format our client ID and token based on our configured auth mode
147211
if ($api_config["authmode"] === "base64") {
148212
$client_id = base64_decode($client_id);
@@ -312,7 +376,7 @@ function api_whitelist_check() {
312376
} elseif ($srv_ip === $if_info["ipaddrv6"]) {
313377
$allowed = true;
314378
break;
315-
} elseif ($srv_ip === "127.0.0.1" and $wif === "localhost") {
379+
} elseif (in_array($srv_ip, ["::1", "127.0.0.1", "localhost"]) and $wif === "localhost") {
316380
$allowed = true;
317381
break;
318382
}elseif ($wif === "any") {
@@ -326,7 +390,7 @@ function api_whitelist_check() {
326390
$api_resp = array("status" => "forbidden", "code" => 403, "return" => 6);
327391
$api_resp["message"] = $err_lib[$api_resp["return"]];
328392
http_response_code(403);
329-
echo json_encode($api_resp);
393+
echo json_encode($api_resp).PHP_EOL;
330394
die;
331395
}
332396
}

pfSense-pkg-API/files/etc/inc/apicalls.inc

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,21 @@ api_runtime_allowed(); // Check that our configuration allows this API call t
1919
session_start(); // Start our session. This is only used for tracking user name
2020
$pf_ver_num = get_pfsense_version()["program"]; // Pull our full pfSense version
2121

22+
function api_access_token() {
23+
global $api_resp, $client_params, $err_lib;
24+
# Check that auth mode is set to JWT before creating token
25+
if (get_api_configuration()[1]["authmode"] === "jwt") {
26+
# Return our JWT if user is authenticated
27+
if (authenticate_user($client_params["username"], $client_params["password"])) {
28+
$jwt = api_create_jwt($client_params["username"]);
29+
$api_resp = array("status" => "ok", "code" => 200, "return" => 0, "message" => "", "data" => ["token" => $jwt]);
30+
}
31+
} else {
32+
$api_resp = array("status" => "forbidden", "code" => 403, "return" => 9, "message" => $err_lib[9], "data" => []);
33+
}
34+
return $api_resp;
35+
}
36+
2237
function api_status_carp() {
2338
# VARIABLES
2439
global $err_lib, $config, $api_resp, $client_params;

pfSense-pkg-API/files/etc/inc/apiresp.inc

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,8 @@ function api_error_lib() {
1212
6 => "Forbidden",
1313
7 => "Search attribute not found",
1414
8 => "Could not locate pfSense version",
15+
9 => "Authentication mode must be set to JWT to enable access token authentication",
16+
1517
// 1000-1999 reserved for /system API calls
1618
1000 => "Invalid system hostname",
1719
1001 => "Invalid system hostname domain",
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
Copyright (c) 2011, Neuman Vong
2+
3+
All rights reserved.
4+
5+
Redistribution and use in source and binary forms, with or without
6+
modification, are permitted provided that the following conditions are met:
7+
8+
* Redistributions of source code must retain the above copyright
9+
notice, this list of conditions and the following disclaimer.
10+
11+
* Redistributions in binary form must reproduce the above
12+
copyright notice, this list of conditions and the following
13+
disclaimer in the documentation and/or other materials provided
14+
with the distribution.
15+
16+
* Neither the name of Neuman Vong nor the names of other
17+
contributors may be used to endorse or promote products derived
18+
from this software without specific prior written permission.
19+
20+
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
21+
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
22+
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
23+
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
24+
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
25+
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
26+
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
27+
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
28+
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
29+
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
30+
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

0 commit comments

Comments
 (0)