Stars: 411
Forks: 60
Pull Requests: 97
Issues: 199
Watchers: 27
Last Updated: 2022-12-14 15:57:14
The missing REST API package for pfSense
License: Apache License 2.0
Languages: PHP, Shell, Python, CSS, JavaScript, Jinja
pfSense API is a fast, safe, REST API package for pfSense firewalls. This works by leveraging the same PHP functions and processes used by pfSense's webConfigurator into API endpoints to create, read, update and delete pfSense configurations. All API endpoints enforce input validation to prevent invalid configurations from being made. Configurations made via API are properly written to the master XML configuration and the correct backend configurations are made preventing the need for a reboot. All this results in the fastest, safest, and easiest way to automate pfSense!
To install pfSense API, simply run the following command from the pfSense shell:
pkg add https://github.com/jaredhendrickson13/pfsense-api/releases/latest/download/pfSense-2.6-pkg-API.txz && /etc/rc.restart_webgui
To uninstall pfSense API, run the following command:
pfsense-api delete
To update pfSense API to the latest stable version, run the following command:
pfsense-api update
To revert to a previous version of pfSense API (e.g. v1.1.7), run the following command:
pfsense-api revert v1.1.7
pfsense-api
command line tool was introduced in v1.1.0. Refer to the corresponding documentation for earlier
releases.After installation, you will be able to access the API user interface pages within the pfSense webConfigurator. These will be found under System > API. The settings tab will allow you change various API settings such as enabled API interfaces, authentication modes, and more. Additionally, the documentation tab will give you access to an embedded documentation tool that makes it easy to view the full API documentation in context to your pfSense instance.
page-all
or page-system-api
privileges to access the API page within the webConfigurator.By default, pfSense API uses the same credentials as the webConfigurator. This behavior allows you to configure pfSense from the API out of the box, and user passwords may be changed from the API to immediately add additional security if needed. After installation, you can navigate to System > API in the pfSense webConfigurator to configure API authentication. Please note that external authentication servers like LDAP or RADIUS are not supported with any API authentication method at this time. To authenticate your API call, follow the instructions for your configured authentication mode:
Uses the same credentials as the pfSense webConfigurator. To authenticate API calls, pass in your username and password
using basic authentication. For example:
curl -u admin:pfsense https://pfsense.example.com/api/v1/firewall/rule
Note: in previous releases, local database authentication used the client-id
and client-token
fields in your
request body to authenticate. This functionality still exists but is not recommended. It will be removed in a future
release.
Requires a bearer token to be included in the Authorization
header of your request. These are time-based tokens that
will expire after the configured amount of time. To configure the JWT expiration, navigate to System > API within the
webConfigurator and ensure the the Authentication Mode is set to JWT. Then you should have the option to configure the
JWT Expiration value. Alternatively, you can use the /api/v1/system/api endpoint to update the jwt_exp
value. To
receive a JWT, you must make a POST request to the /api/v1/access_token endpoint. This endpoint will always require the
use of the Local Database authentication type to receive the JWT.
For example:
curl -u admin:pfsense -X POST https://pfsense.example.com/api/v1/access_token
Once you have your JWT, you can authenticate your API calls by adding it to the request's authorization
header. For example:
curl -H "Authorization: Bearer xxxxx.xxxxxx.xxxxxx" -X GET https://pfsense.example.com/api/v1/system/arp
Uses standalone tokens generated via API or webConfigurator. These are better suited to distribute to systems as they are
revocable and will only allow API authentication; not webConfigurator or SSH authentication (like the local database
credentials). To generate or revoke credentials, navigate to System > API within the webConfigurator and ensure the
Authentication Mode is set to API token. Then you should have the options to configure API Token generation, generate
new tokens, and revoke existing tokens. After generating a new API token, the actual token will display at the top of
the page on the success banner. This token will only be displayed once so ensure it is stored somewhere safe.
Alternatively, you can generate new API tokens using the /api/v1/access_token endpoint. This endpoint will always
require the use of the Local Database authentication type to receive the API token.
Once you have your API token, you may authenticate your API call by specifying your client-id and client-token within
an Authorization
header, these values must be separated by a space. For example:
curl -H "Authorization: CLIENT_ID_HERE CLIENT_TOKEN_HERE" -X GET https://pfsense.example.com/api/v1/system/arp
Note: In previous versions of pfSense API, the client-id and client-token were provided via the request payload. This
functionality is still supported but is not recommended. It will be removed in a future release.
pfSense API uses the same privileges as the pfSense webConfigurator. The required privileges for each endpoint are stated within the API documentation.
By default, all API requests will be monitored by pfSense's Login Protection feature. This will allow API authentication attempts to be logged and temporarily blocked if too many failed authentication attempts are made by any one client. It is strongly recommended that this feature be used at all times to prevent brute force attacks on API endpoints. This feature can be disabled by within the webConfigurator system-wide under System > Advanced or only for API requests under System > API.
pfSense API can handle a few different content types. Please note, if a Content-Type
header is not specified in your
request, pfSense API will attempt to determine the content type which may have undesired results. It is recommended you
specify your preferred Content-Type
on each request.
While several content types may be enabled, application/json
is the recommended content type. Supported content types
are:
Parses the request body as a JSON formatted string. This is the recommended content type.
Example:
curl -u admin:pfsense -H "Content-Type: application/json" -d '{"service": "sshd"}' -X POST https://pfsense.example.com/api/v1/services/restart
Parses the request body as URL encoded parameters.
Example:
curl -u admin:pfsense -H "Content-Type: application/x-www-form-urlencoded" -X DELETE "https://pfsense.example.com/api/v1/firewall/alias?id=EXAMPLE_ALIAS"
Note: this content type only has the ability to pass values of string, integer, or boolean data types. Complex data
types like arrays and objects cannot be parsed by the API when using this content type. It is recommended to only
use this content type when making GET or DELETE requests.
pfSense API contains an advanced query engine to make it easy to query specific data from API calls. For endpoints
supporting GET
requests, you may query the return data to only return data you are looking for. To query data, you may
add the data you are looking for to your payload. You may specify as many query parameters as you need. In order to
match the query, each parameter must match exactly, or utilize a query filter to set criteria. If no matches were found,
the endpoint will return an empty array in the data field.
You may find yourself only needing to read objects with specific values set. For example, say an API endpoint normally
returns this response without a query:
{
"status": "ok",
"code": 200,
"return": 0,
"message": "Success",
"data": [
{
"id": 0,
"name": "Test",
"type": "type1",
"extra": {
"tag": 0
}
},
{
"id": 1,
"name": "Other Test",
"type": "type2",
"extra": {
"tag": 100
}
},
{
"id": 2,
"name": "Another Test",
"type": "type1",
"extra": {
"tag": 200
}
}
]
}
If you want the endpoint to only return the objects that have their type
value set to type1
you could
add {"type": "type1"}
to your payload. This returns:
{
"status": "ok",
"code": 200,
"return": 0,
"message": "Success",
"data": [
{
"id": 0,
"name": "Test",
"type": "type1",
"extra": {
"tag": 0
}
},
{
"id": 2,
"name": "Another Test",
"type": "type1",
"extra": {
"tag": 200
}
}
]
}
Additionally, if you need to target values that are nested within an array, you can add {"extra__tag": 100}
to
recursively target the tag
value within the extra
array. Note the double underscore separating the parent and child
keys. This returns:
{
"status": "ok",
"code": 200,
"return": 0,
"message": "Success",
"data": [
{
"id": 1,
"name": "Other Test",
"type": "type2",
"extra": {
"tag": 100
}
}
]
}
Query filters allow you to apply logic to the objects you target. This makes it easy to target data that meets specific
criteria:
The startswith
filter allows you to target objects whose values start with a specific substring. This will work on
both string and integer data types. Below is an example response without any queries:
{
"status": "ok",
"code": 200,
"return": 0,
"message": "Success",
"data": [
{
"id": 0,
"name": "Test",
"type": "type1",
"extra": {
"tag": 0
}
},
{
"id": 1,
"name": "Other Test",
"type": "type2",
"extra": {
"tag": 100
}
},
{
"id": 2,
"name": "Another Test",
"type": "type1",
"extra": {
"tag": 200
}
}
]
}
If you wanted to target objects whose names started with Other
, you could use the
payload {"name__startswith": "Other"}
. This returns:
{
"status": "ok",
"code": 200,
"return": 0,
"message": "Success",
"data": [
{
"id": 1,
"name": "Other Test",
"type": "type2",
"extra": {
"tag": 100
}
}
]
}
The endswith
filter allows you to target objects whose values end with a specific substring. This will work on both
string and integer data types. Below is an example response without any queries:
{
"status": "ok",
"code": 200,
"return": 0,
"message": "Success",
"data": [
{
"id": 0,
"name": "Test",
"type": "type1",
"extra": {
"tag": 0
}
},
{
"id": 1,
"name": "Other Test",
"type": "type2",
"extra": {
"tag": 100
}
},
{
"id": 2,
"name": "Another Test",
"type": "type1",
"extra": {
"tag": 200
}
}
]
}
If you wanted to target objects whose names ended with er Test
, you could use the
payload {"name__endswith" "er Test"}
. This returns:
{
"status": "ok",
"code": 200,
"return": 0,
"message": "Success",
"data": [
{
"id": 1,
"name": "Other Test",
"type": "type2",
"extra": {
"tag": 100
}
},
{
"id": 2,
"name": "Another Test",
"type": "type1",
"extra": {
"tag": 200
}
}
]
}
The contains
filter allows you to target objects whose values contain a specific substring. This will work on both
string and integer data types. Below is an example response without any queries:
{
"status": "ok",
"code": 200,
"return": 0,
"message": "Success",
"data": [
{
"id": 0,
"name": "Test",
"type": "type1",
"extra": {
"tag": 0
}
},
{
"id": 1,
"name": "Other Test",
"type": "type2",
"extra": {
"tag": 100
}
},
{
"id": 2,
"name": "Another Test",
"type": "type1",
"extra": {
"tag": 200
}
}
]
}
If you wanted to target objects whose names contain ther
, you could use the payload {"name__contains": "ther"}
. This
returns:
{
"status": "ok",
"code": 200,
"return": 0,
"message": "Success",
"data": [
{
"id": 1,
"name": "Other Test",
"type": "type2",
"extra": {
"tag": 100
}
},
{
"id": 2,
"name": "Another Test",
"type": "type1",
"extra": {
"tag": 200
}
}
]
}
The lt
filter allows you to target objects whose values are less than a specific number. This will work on both
numeric strings and integer data types. Below is an example response without any queries:
{
"status": "ok",
"code": 200,
"return": 0,
"message": "Success",
"data": [
{
"id": 0,
"name": "Test",
"type": "type1",
"extra": {
"tag": 0
}
},
{
"id": 1,
"name": "Other Test",
"type": "type2",
"extra": {
"tag": 100
}
},
{
"id": 2,
"name": "Another Test",
"type": "type1",
"extra": {
"tag": 200
}
}
]
}
If you wanted to target objects whose tag is less than 100
, you could use the payload {"extra__tag__lt": 100}
. This
returns:
{
"status": "ok",
"code": 200,
"return": 0,
"message": "Success",
"data": [
{
"id": 0,
"name": "Test",
"type": "type1",
"extra": {
"tag": 0
}
}
]
}
The lte
filter allows you to target objects whose values are less than or equal to a specific number. This will work
on both numeric strings and integer data types. Below is an example response without any queries:
{
"status": "ok",
"code": 200,
"return": 0,
"message": "Success",
"data": [
{
"id": 0,
"name": "Test",
"type": "type1",
"extra": {
"tag": 0
}
},
{
"id": 1,
"name": "Other Test",
"type": "type2",
"extra": {
"tag": 100
}
},
{
"id": 2,
"name": "Another Test",
"type": "type1",
"extra": {
"tag": 200
}
}
]
}
If you wanted to target objects whose tag is less than or equal to 100
, you could use the
payload {"extra__tag__lte": 100}
. This returns:
{
"status": "ok",
"code": 200,
"return": 0,
"message": "Success",
"data": [
{
"id": 0,
"name": "Test",
"type": "type1",
"extra": {
"tag": 0
}
},
{
"id": 1,
"name": "Other Test",
"type": "type2",
"extra": {
"tag": 100
}
}
]
}
The gt
filter allows you to target objects whose values are greater than a specific number. This will work on both numeric strings and integer data types. Below is an example response without any queries:
{
"status": "ok",
"code": 200,
"return": 0,
"message": "Success",
"data": [
{
"id": 0,
"name": "Test",
"type": "type1",
"extra": {
"tag": 0
}
},
{
"id": 1,
"name": "Other Test",
"type": "type2",
"extra": {
"tag": 100
}
},
{
"id": 2,
"name": "Another Test",
"type": "type1",
"extra": {
"tag": 200
}
}
]
}
If you wanted to target objects whose tag is greater than 100
, you could use the payload {"extra__tag__gt": 100}
.
This returns:
{
"status": "ok",
"code": 200,
"return": 0,
"message": "Success",
"data": [
{
"id": 2,
"name": "Another Test",
"type": "type1",
"extra": {
"tag": 200
}
}
]
}
The lte
filter allows you to target objects whose values are greater than or equal to a specific number. This will
work on both numeric strings and integer data types. Below is an example response without any queries:
{
"status": "ok",
"code": 200,
"return": 0,
"message": "Success",
"data": [
{
"id": 0,
"name": "Test",
"type": "type1",
"extra": {
"tag": 0
}
},
{
"id": 1,
"name": "Other Test",
"type": "type2",
"extra": {
"tag": 100
}
},
{
"id": 2,
"name": "Another Test",
"type": "type1",
"extra": {
"tag": 200
}
}
]
}
If you wanted to target objects whose tag is greater than or equal to 100
, you could use the
payload {"extra__tag__gte": 100}
. This returns:
{
"status": "ok",
"code": 200,
"return": 0,
"message": "Success",
"data": [
{
"id": 1,
"name": "Other Test",
"type": "type2",
"extra": {
"tag": 100
}
},
{
"id": 2,
"name": "Another Test",
"type": "type1",
"extra": {
"tag": 200
}
}
]
}
You may notice some API endpoints require an object id
to update or delete objects. These IDs are not stored values,
rather pfSense uses the object's array index value to locate and identify objects. Unless specified otherwise, API
endpoints will use the same array index value (as returned in the data
response field) to locate objects when updating
or deleting. Some important items to note about these IDs:
0
, 1
, and 2
) and you delete
the object with ID 1
, pfSense will resort the array so the object with ID 2
will now have an ID of 1
.data
response field is no longer a
sequential array due to the query, the data
field will be represented as an associative array with the array items`
key being the objects ID.0
in the return
field.[{"id": 0, "name": "Test"}, {"id": 1, "name": "Other Test"}]
).There are a few key limitations to keep in mind while using this API: