Coder Social home page Coder Social logo

jaredhendrickson13 / pfsense-api Goto Github PK

View Code? Open in Web Editor NEW
615.0 31.0 90.0 12.73 MB

The missing REST API package for pfSense

License: Apache License 2.0

PHP 73.18% Shell 0.11% Python 26.66% CSS 0.01% JavaScript 0.02% Jinja 0.03%
pfsense api networking devops security firewall freebsd pfsense-api pfsense-webconfigurator firewall-rule

pfsense-api's Introduction

pfSense-API

Build OpenAPI PHPlint Pylint

Introduction

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!

Requirements

Supported pfSense Versions
  • pfSense CE 2.7.1 (amd64)
  • pfSense CE 2.7.2 (amd64)
  • pfSense Plus 23.09 (community supported)

Don't see your version listed? Check the releases page. Older versions of this package may support older versions of pfSense.

This package is not supported on other architectures such as arm64 and aarch64. However, the package should still install and operate on these systems. Compatibility on unsupported systems is not guaranteed and is at your own risk.


  • pfSense API requires a local user account in pfSense. The same permissions required to make configurations in the webConfigurator are required to make calls to the API endpoints.
  • While not an enforced requirement, it is strongly recommended that you configure pfSense to use HTTPS instead of HTTP. This ensures that login credentials and/or API tokens remain secure in-transit.

Installation

To install pfSense API, simply run the following command from the pfSense shell:

pkg -C /dev/null add https://github.com/jaredhendrickson13/pfsense-api/releases/latest/download/pfSense-2.7-pkg-API.pkg && /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

Notes:

  • While not always necessary, it's recommended to change the installation command to reference the package built for your version of pfSense. You can check the releases page for available versions.
  • In order for pfSense to apply some required web server changes, it is required to restart the webConfigurator after installing the package.
  • If you do not have shell access to pfSense, you can still install via the webConfigurator by navigating to ' Diagnostics > Command Prompt' and enter the commands there.
  • When updating pfSense, you must reinstall pfSense API afterwards. Unfortunately, pfSense removes all existing packages and only re-installs packages found within pfSense's package repositories. Since pfSense API is not an official package in pfSense's repositories, it does not get reinstalled automatically.
  • The pfsense-api command line tool was introduced in v1.1.0. Refer to the corresponding documentation for earlier releases.

webConfigurator Settings & Documentation

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.

Notes:

  • Users must hold the page-all or page-system-api privileges to access the API page within the webConfigurator.

Authentication & Authorization

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:

Local Database (default)

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

JWT

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
API Token

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

Authorization

pfSense API uses the same privileges as the pfSense webConfigurator. The required privileges for each endpoint are stated within the API documentation.

Login Protection

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.

Content Types

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:

application/json

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
application/x-www-form-urlencoded

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.

Queries

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.

Targeting Objects

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

Query filters allow you to apply logic to the objects you target. This makes it easy to target data that meets specific criteria:

Starts With

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
      }
    }
  ]
}

Ends With

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
      }
    }
  ]
}

Contains

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
      }
    }
  ]
}

Less Than

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
      }
    }
  ]
}

Less Than or Equal To

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
      }
    }
  ]
}

Greater Than

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
      }
    }
  ]
}

Greater Than or Equal To

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
      }
    }
  ]
}
Obtaining Object IDs

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:

  • pfSense starts arrays with an index of 0. If you use a loop counter to determine the ID of a specific object, you must start that counter at 0.
  • These IDs are dynamic. For example, if you have 3 static route objects stored (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.
  • API queries will retain the object's ID in the response. In the event that the 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.

Requirements for queries:

  • API call must be a successful GET request and return 0 in the return field.
  • Endpoints must return an array of objects in the data field ( e.g. [{"id": 0, "name": "Test"}, {"id": 1, "name": "Other Test"}]).
  • At least two objects must be present within the data field to support queries.

Limitations

There are a few key limitations to keep in mind while using this API:

  • pfSense's XML configuration was not designed for quick simultaneous writes like a traditional database. It may be necessary to delay API calls in sequence to prevent unexpected behavior such as configuration overwrites.
  • By design, values stored in pfSense's XML configuration can only be parsed as strings, arrays or objects. This means that even though request data requires data to be of a certain type, it will not necessarily be stored as that type. Data read from the API may be represented differently than the format it was requested as.

pfsense-api's People

Contributors

beavis69 avatar dependabot[bot] avatar dihedral avatar elacy avatar finic8 avatar grillp avatar jaredhendrickson13 avatar kevin-bannier avatar mj84 avatar pincher95 avatar punk-t avatar voice1 avatar wuarmin avatar zerodeux avatar zsxsoft avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

pfsense-api's Issues

All API calls return Unauthorized.

Been looking for a way to do API calls on pfsense. Specifically regarding Aliases, when I came across your package.
Unfortunately it seems that no matter what I do everything returns

 'code': 401,
 'return': 3,
 'message': 'Authentication failed'}```

I have tried creating a new user called API, tried changing to token auth, with no change.

Unfortunately, there is no documentation on how to really do an API call, but I was able to use your other issue to extract the URL format and found that basically the exposed API is found here https://github.com/jaredhendrickson13/pfsense-api/tree/master/pfSense-pkg-API/files/usr/local/www/api.

_I could help update some documentation if I could get it to connect_

Problem setting blockbogons interface param

It would appear that regardless of which Boolean param passed for "blockbogons" (true/false) the end result is that blockbogons is set to <null/blank> on API interface create calls. Not setting a value causes PfSense to label the interface as block bogons enabled (within the UI). This creates a bogon firewall rule for the interface as well.

Request (api/v1/interface) - POST

{
    "client-id": "admin",
    "client-token": "pfsense",
    "if": "vmx2.10",
	"descr": "VLAN10",
	"enable": "true",
	"type": "staticv4",
	"ipaddr": "10.250.0.1",
	"subnet": "24",
	"blockbogons": false
}

Request Response

{
    "status": "ok",
    "code": 200,
    "return": 0,
    "message": "Success",
    "data": {
        "opt2": {
            "if": "vmx2.10",
            "enable": "",
            "descr": "VLAN10",
            "blockbogons": "",
            "ipaddr": "10.250.0.1",
            "subnet": "24"
        }
    }
}

As a side note I also noticed that the documented list of params for "/api/v1/interface" does not include a param for blockbogons; however, the sample code on github does. Thanks for any insights you might have.

Dynamic Json in API Responses

Hi, i have been testing this Plugin and found out that it returns JSON where a values type can change.
For Example: if you Read an Alias from the API you'll notice that when there is only one address in the Alias it will return a string and if there are multiple addresses it will return an array. This behaviour makes using the API hard as you will always have to expect that it will return string or an array. Some Parsers parse JSON rigidly, Meaning you have to define all values and types before parsing. These parsers will then complain and refuse to parse if there is a type mismatch between the predefined Struct and the actual JSON. Because of this Issue id suggest changing the API so that it always returns an array even if there is only one element. This change should not break backwards compatibility as older clients had to support arrays anyway.

Return code 4031 when trying to toggle a firewall rule

I'm using Postman for my client trying to toggle an firewall rule on/off in the config.

A packet capture shows:

PUT /api/v1/firewall/rule?client-id=61646d696e&client-token=6c45ada8e2e4d44acd2b61e8028e94f9 HTTP/1.1
Content-Type: application/json
User-Agent: PostmanRuntime/7.26.5
Accept: */*
Postman-Token: 3a179673-05f7-425d-b09b-6c7963cd1220
Host: 192.168.90.25
Accept-Encoding: gzip, deflate, br
Connection: keep-alive
Content-Length: 46

{
"tracker": 1602257611,
"disabled": true
}HTTP/1.1 400 Bad Request
Server: nginx
Date: Fri, 09 Oct 2020 17:29:27 GMT
Content-Type: application/json
Transfer-Encoding: chunked
Connection: keep-alive
Referer: no-referrer

{"status":"bad request","code":400,"return":4031,"message":"Firewall rule tracker ID required","data":[]}

Create multiple VLANs fails

PFSense: 2.4.5-RELEASE-p1 (amd64)
pfsense-API: latest

On pfsense machine with 2 interfaces (vmx0 - wan, vmx1 - lan), when creating multiple VLANs using the API the result is that only a single VLAN shows in the VLAN list within the administrative GUI. What's interesting is that even though only 1 vlan shows in the VLAN list (the last VLAN created via the API), I am able to see and select all VLANs created through the API using the "Interface Assignments" page.

Sample Code:

# Create Interfaces and VLANs
vlans = {
    "10": {
        "vlan_interface": "vmx1",
        "vlan_description": "VLAN10-DMZ"
    },
    "18": {
        "vlan_interface": "vmx1",
        "vlan_description": "VLAN18-ALPHAIOT"
    },
    "25": {
        "vlan_interface": "vmx1",
        "vlan_description": "VLAN25-ALPHAGUEST"
    },
    "50": {
        "vlan_interface": "vmx1",
        "vlan_description": "VLAN50-ALPHATRUSTED"
    },
    "51": {
        "vlan_interface": "vmx1",
        "vlan_description": "VLAN51-ALPHAPOLICY"
    },
    "100": {
        "vlan_interface": "vmx1",
        "vlan_description": "VLAN100-CLIENTNET1"
    },
    "200": {
        "vlan_interface": "vmx1",
        "vlan_description": "VLAN200-SRVFARM1"
    },
    "201": {
        "vlan_interface": "vmx1",
        "vlan_description": "VLAN201-SRVFARM2"
    },
}

# Build VLAN Assignments
for key, value in vlans.items():
    print("Building VLAN assignment: " + key + "-" + value['vlan_description'])
    
    parameters = {
        "client-id": PFUsername,
        "client-token": PFPassword,
        "if": value['vlan_interface'],
        "tag": key,
        "descr": value['vlan_description']
    }

    response = requests.post("https://" + PFIPAddress + "/api/v1/interface/vlan", params=parameters, verify=False)
    jprint(response.json())

    # API Wait Time
    time.sleep(5)

Issues when creating outbound mapping

So I was trying to add outbound rules by:
https://xxx/api/v1/firewall/nat/outbound/mapping
with params:
{
"interface": "wan",
"protocol": "any",
"src": "172.16.1.100",
"dst": "any",
"target": "X.X.X.X",
"descr": "OUT",
"top": false,
"apply": true
}

It return :
{
"status": "bad request",
"code": 400,
"return": 4089,
"message": "Unknown outbound NAT mapping protocol",
"data": []
}

But I want the outbound NAT match all network protocols, is that a BUG?

Update firewall alias

When we try to update a firewall alias we get {"status":"bad request","code":400,"return":4056,"message":"Firewall alias already exists","data":[]}

Setting static CARP VHID on /api/v1/firewall/virtual_ip throws error

curl -s -k -d '{"client-id": "admin", "client-token": "pfsense", "mode": "carp", "interface": "em0", "subnet": "172.16.77.25", "vhid": 100, "password": "testest"}' -X POST https://172.16.77.2/api/v1/firewall/virtual_ip

Fatal error: Uncaught Error: Call to undefined function vhid_exists() in /etc/inc/api/models/APIFirewallVirtualIPCreate.inc:108
Stack trace:
#0 /etc/inc/api/framework/APIModel.inc(86): APIFirewallVirtualIPCreate->validate_payload()
#1 /etc/inc/api/framework/APIModel.inc(96): APIModel->validate()
#2 /etc/inc/api/endpoints/APIFirewallVirtualIP.inc(28): APIModel->call()
#3 /etc/inc/api/framework/APIEndpoint.inc(59): APIFirewallVirtualIP->post()
#4 /usr/local/www/api/v1/firewall/virtual_ip/index.php(3): APIEndpoint->listen()
#5 {main}
  thrown in /etc/inc/api/models/APIFirewallVirtualIPCreate.inc on line 108
PHP ERROR: Type: 1, File: /etc/inc/api/models/APIFirewallVirtualIPCreate.inc, Line: 108, Message: Uncaught Error: Call to undefined function vhid_exists() in /etc/inc/api/models/APIFirewallVirtualIPCreate.inc:108
Stack trace:
#0 /etc/inc/api/framework/APIModel.inc(86): APIFirewallVirtualIPCreate->validate_payload()
#1 /etc/inc/api/framework/APIModel.inc(96): APIModel->validate()
#2 /etc/inc/api/endpoints/APIFirewallVirtualIP.inc(28): APIModel->call()
#3 /etc/inc/api/framework/APIEndpoint.inc(59): APIFirewallVirtualIP->post()
#4 /usr/local/www/api/v1/firewall/virtual_ip/index.php(3): APIEndpoint->listen()
#5 {main}

Looks like we're missing the APITools namespace before the vhid_exists() function

Endpoint return data does not match accepted request data

Currently, return data from API calls returns the internal XML data values which do not always match the request data parameters.

For example: say a client wanted to GET an existing firewall rule object to create a new object with the same values or slightly modified values. They could not simply use the return data as a payload for their POST request because the internal XML data returned from the GET request do not match the request parameters for a POST request.

This is not REST like. It would be preferred to have the API return data in the same representation it accepts as request data, and then have the backend translate the representation data into the internal values pfSense understands.

Create Port Forward Rule ID incorrectly set to 'pass'

When creating a port forwarding rule, the ID is hard coded to 'pass'

This is located here:

https://github.com/jaredhendrickson13/pfsense-api/blob/master/pfSense-pkg-API/files/etc/inc/api/models/APIFirewallNATPortForwardCreate.inc

At line 151

$this->validated_data["associated-rule-id"] = "pass";

In order to support deleting a port forward rule with the API, it needs to be set to

$this->validated_data["associated-rule-id"] = (int)microtime(true);

Recursive API query not working as designed

When querying recursively without a filter, the query will always return empty. This should return all objects matching the query exactly. To replicate:

  1. If I read all firewall rules I see there are rules that will match my query source__network=lan
curl -k -s -d '{"client-id": "admin", "client-token": "pfsense"}' -X GET "https://172.16.209.9/api/v1/firewall/rule" | jq .
{
  "status": "ok",
  "code": 200,
  "return": 0,
  "message": "Success",
  "data": [
    {
      "type": "pass",
      "ipprotocol": "inet",
      "descr": "Default allow LAN to any rule",
      "interface": "lan",
      "tracker": "0100000101",
      "source": {
        "network": "lan"
      },
      "destination": {
        "any": ""
      }
    },
    {
      "type": "pass",
      "ipprotocol": "inet6",
      "descr": "Default allow LAN IPv6 to any rule",
      "interface": "lan",
      "tracker": "0100000102",
      "source": {
        "network": "lan"
      },
      "destination": {
        "any": ""
      }
    },
    {
      "tracker": "1606766007",
      "type": "pass",
      "interface": "wan",
      "ipprotocol": "inet46",
      "statetype": "keep state",
      "os": "",
      "source": {
        "any": ""
      },
      "destination": {
        "any": ""
      },
      "descr": "Allow all ipv4+ipv6 via pfSsh.php",
      "created": {
        "time": "1606766007",
        "username": "pfSsh.php added allow all wan rule"
      }
    }
  ]
}
  1. when I rerun the call including my query parameter, I get no matched objects in the return.
curl -k -s -d '{"source__network": "lan", "client-id": "admin", "client-token": "pfsense"}' -X GET "https://172.16.209.9/api/v1/firewall/rule" | jq .
{
  "status": "ok",
  "code": 200,
  "return": 0,
  "message": "Success",
  "data": []
}
  1. If I rerun the same call with the contains filter on my query, I get the expected result
curl -k -s -d '{"source__network__contains": "lan", "client-id": "admin", "client-token": "pfsense"}' -X GET "https://172.16.209.9/api/v1/firewall/rule" | jq .
{
  "status": "ok",
  "code": 200,
  "return": 0,
  "message": "Success",
  "data": [
    {
      "type": "pass",
      "ipprotocol": "inet",
      "descr": "Default allow LAN to any rule",
      "interface": "lan",
      "tracker": "0100000101",
      "source": {
        "network": "lan"
      },
      "destination": {
        "any": ""
      }
    },
    {
      "type": "pass",
      "ipprotocol": "inet6",
      "descr": "Default allow LAN IPv6 to any rule",
      "interface": "lan",
      "tracker": "0100000102",
      "source": {
        "network": "lan"
      },
      "destination": {
        "any": ""
      }
    }
  ]
}

This is problematic as the only way to match an exact query is to specify the query with no filter. Since this is a framework component this is likely to effect all GET supported endpoints that return multiple objects.

Firewall/rule - update rule - discrepancy between API and documentation?

Thanks for the solid work on this project! Just what I've been looking for to automate firewall rule enabling/disabling from my Home Assistant system.

However, when I try to implement rule dis- / enabling using the API, I run into problems.

The documentation (again, solid work!) states that all fields except tracker ID are optional. However, when I try to disable an existing rule using the bare minimum of information, it doesn't work:

ragnar@ragnars-bigmac ~ % curl -i http://pfsense.address/api/v1/firewall/rule -X PUT -d '{"client-id": "secretapiuser", "client-token": "secretapipassword", "tracker": 1577540218, "disabled": true}'

HTTP/1.1 400 Bad Request
Server: nginx
Date: Thu, 01 Oct 2020 17:21:34 GMT
Content-Type: application/json
Transfer-Encoding: chunked
Connection: keep-alive
Referer: no-referrer

{"status":"bad request","code":400,"return":4036,"message":"Firewall rule protocol required","data":[]}

If I try to include protocol, it asks further:

ragnar@ragnars-bigmac ~ % curl -i http://pfsense.address/api/v1/firewall/rule -X PUT -d '{"client-id": "secretapiuser", "client-token": "secretapipassword", "tracker": 1577540218, "disabled": true, "protocol": "tcp"}'

HTTP/1.1 400 Bad Request
Server: nginx
Date: Thu, 01 Oct 2020 17:31:42 GMT
Content-Type: application/json
Transfer-Encoding: chunked
Connection: keep-alive
Referer: no-referrer

{"status":"bad request","code":400,"return":4047,"message":"Firewall rule source and destination port required","data":[]}

I haven't dug any further, but I suspect it will go on asking until I've expanded the information to define a complete firewall rule.

Is this by design, and have I just misread or misinterpreted the documentation?

BR,
Ragnar

Adding an entry to empty alias creates an extra blank entry

I'm working with an empty Alias called HS_Staff_Desktops. If I invoke the API to add an entry or entries, a blank entry is also created above/before the entry I was intending to create. If the Alias already has an entry or entries, this effect is not observed. I am using PFSense 2.4.5-RELEASE-p1 (amd64). I installed the latest version of the API as of today.

I invoked the API to create an entry in the HS_Staff_Desktops alias as follows:

curl -X POST -H "Content-Type: text/plain" --data '{"client-id": "admin", "client-token":"password","name": "HS_Staff_Desktops","address": ["10.1.1.8"]}' 'http://10.1.48.1/api/v1/firewall/alias/entry'

{"status":"ok","code":200,"return":0,"message":"Success","data":{"name":"HS_Staff_Desktops","type":"host","address":" 10.1.1.8","descr":"High School Staff Desktops","detail":""}}

firewall_aliases php
firewall_aliases_edit php

Unbound host deletion returns null when criteria is met but host does not exist

Attempting to delete a Unbound host override that does not exist results in a null response:

$ curl -k -X POST "https://172.16.209.129/api/v1/services/unbound/delete/hosts/?client-id=admin&client-token=pfsense&host=test&domain=jaredhendrickson.com"
null

The expected result is to return a response confirming the override does not exist

Issues Updating The WAN Interface IP via the API call from PowerShell

Jared,

Thank you for creating the ability to update the WAN IP Addresses. The problem I seem to be stuck on is how to properly format the request to to update the IP address from PowerShell. I am able to update the Virtual IP address using the following request:

$VirtualIP = "2.2.2.2"
Invoke-RestMethod - Uri "https://1.1.1.1/api/v1/firewall/virtual_ip/?client-id=Username&client-token=Password&id=0&subnet=$($VirtualIP)&interface=wan" -Method PUT

It appears that no matter what I do to update the WAN Interface IP, I get a 400 error.

$WANIPAddress = "3.3.3.3"
Invoke-RestMethod - Uri "https://1.1.1.1/api/v1/interface/?client-id=Username&client-token=Password&if=em0&enable=true&type=staticv4&ipaddr=$($WANIPAddress)$subnet=24$blockgons=true" -Method PUT

Also when I try to get the interface values from powershell , the commands are successful but they basically do not appear to return any data. So I can't tell if the problem is just my requests or if something is not working as expected.

I am presuming the former. Any insights would be appreciated.

Thanks again.

PHP Warning: Invalid argument supplied for foreach() in /etc/inc/api/framework/APITools.inc on line 148

PHP Errors:
[18-Nov-2020 21:32:03 US/Central] PHP Warning:  Invalid argument supplied for foreach() in /etc/inc/api/framework/APITools.inc on line 148

[18-Nov-2020 21:32:10 US/Central] PHP Warning:  Invalid argument supplied for foreach() in /etc/inc/api/framework/APITools.inc on line 148

I’m getting seeing this error when I login to webConfig after reading all firewall rules as explained here

http://192.168.1.1/api/documentation/#jump-FIREWALL_2FRULE-ReadFirewallRules

——

pfSense version - 2.5.0-DEVELOPMENT (amd64) built on Tue Nov 10 07:03:15 EST 2020 FreeBSD 12.2-STABLE

API for updating System/API/Settings

Hi Jared,
It is great if you can add one api to update your plugin settings like for updating Network Interfaces, Authentication Mode,
JWT Expiration
.
image

Add FireWall Shaper support please

Hi, thank you for your hard work to make this amazing api,

and I am wondering if you can add the support for Firewall-Shaper functions, I would appreciate it, thank you very much!

Incorrect disabled user check in APIAuth::authenticate_token()

I've noticed that the disabled user check in APIAuth::authenticate_token() incorrectly passes the client-id to the check:

if (APITools\is_user_disabled($this->request["client-id"]) === false) {

It's implemented correctly for the other authentication mechanisms:

if (APITools\is_user_disabled($this->username) === false) {

Please let me know if you prefer that I open a bug fix PR - I figured it's such a minor change that it's probably not worth the overhead.

Anyway, thank you for this awesome project!

disable/enable firewall rule

it seems the "disabled": "" is empty on output, should it really be empty as it seems enabled rules simply don't have this key set. this is not good as you cannot delete the key to remove it. Setting the key value to "false" does nothing.

curl -k -s -H "Content-Type: application/json" -d '{"client-id": "admin", "client-token": "......", "tracker": "1608451554", "disabled":"false"}' -X PUT https://172.a.b.c.d/api/v1/firewall/rule|jq

{
"status": "ok",
"code": 200,
"return": 0,
"message": "Success",
"data": {
"id": "",
"tracker": "1608451554",
"type": "block",
"interface": "lan",
"ipprotocol": "inet",
"tag": "",
"tagged": "",
"max": "",
"max-src-nodes": "",
"max-src-conn": "",
"max-src-states": "",
"statetimeout": "",
"statetype": "keep state",
"os": "",
"source": {
"address": "172.x.y.z"
},
"destination": {
"any": ""
},
"descr": "tablet",
"created": {
"time": "1608451554",
"username": "[email protected] (Local Database)"
},
"updated": {
"time": 1608793109,
"username": "[email protected] (API)"
},
"disabled": ""
}
}

still returns as disable and no boolean flag as a value.

can you explain how to enable an existing rule eg....from the command line ?

Multiple Entries of same Host Overrides not possible

Currently it is not possible to create multiple host overides for a specific host/domain combination. However, this would be very useful for a dual-stack configuration or simple dns loadbalancing. The pfsense UI allow such a configuration.

{
    "status": "bad request",
    "code": 400,
    "return": 2010,
    "message": "Unbound host override already exists",
    "data": []
}

Maybe it makes sense to rebuild the check to check the combination of host override and IP address. This would prevent exact duplicates, but also allow different IP addresses on a single host override.

Allow Modification of WAN IP Address

  1. I used pfsense-API to make modification to images deployed through automation.
  2. The current API allows me to make one of two changes I need to complete the solution:
    (a) Get and update the VIP
    (b) does not allow me to Update the WAN IP address.
  3. Ideally, it would be great to get the IP address of the WAN and then change it.
  4. It looks like we can delete interfaces (not the WAN) and create new interfaces but we cannot associate a gateway with the WAN IP address.
  5. If we could do number 3 that would be great.

missing endpoints

this shows as an endpoint:
/api/v1/firewall/rule/
this doesn't:
/api/v1/firewall/rules/
(404 error)
The doc's show 'rules' though...

also /api/v1/firewall/rule/add is missing

any idea why?

Allow API Token authentication to be passed via header

Derived from #17

Specifying the API Token currently clutters the request body and is inconsistent across different content-types. Allowing clients to specify their client-id and client-token via the Authorization header may be a better option.

This is a potential breaking change. If implemented, users will need to change their scripts to authenticate using the Authorization header instead of specifying credentials in the request body. Alternatively, we could support both and assume the client wants to authenticate using the Authorization header if one was specified.

firewall states read not returning valid data

States output not correct or being parsed properly. Also API doesn't appear to support killing any existing states/connections which I need too.
(a.b.c.d is my public ip.)

{
"interface": "pppoe0",
"protocol": "tcp",
"source": "->",
"destination": "a.b.c.d:48517",
"status": "74.125.28.188:5228"
}

HAProxy Service support

Hi, thanks for this attempt to automate pfsense.

Do you plan on adding more services and is HAProxy one of them?

Is it possible to write a small guide on how we can contribute new services to this project?

Cheers,
Nik.

Feature Request : Add / Edit / Delete NAT Entries

Currently, the API does not have capabilities to add/edit/delete NAT entries of the firewall. Only Read is supported.
It would be nice to have this capability in the API has NAT is a highly used function of a firewall in our IPv4 (shortage) world.

Delete nat on_to_one mapping

Hi I am wandering how to perform the delete of nat one_to_one mapping where an id is expected in the request, considering that when we performe list (read) nat one_to_one the id is not available in the response.

Wish: status OpenVPN

The most interesting would be from the OpenVPN server, so I can see client status (we have up to 80 clients online)

unbound host override update fails when fields are not changing

First thanks for this ! pfsense need this for ages.

I'm using this inside ansible to keep internal dns overrides up to date for a bunch of hosts. I added logic to determine if it's an update or a new override (personally I would have loved to simply set the override and let the backend decide if it's new or update) but I still get failures when there are no actual updates.

I would expect it to work even if nothing changes. I run it like this:

      - name: update unbound override for {{ inventory_hostname }}
        uri:
          url: "https://1.2.3.4/api/v1/services/unbound/host_override"
          method: PUT
          body:
            client-id: "{{ client_id }}"
            client-token: "{{ client_token }}"
            id: "{{ item_index }}"
            domain: example.com
            ip: "{{ base_vlan_net_ifaces[0].ip }}"
          body_format: json

Unless there's a change in the domain name it fails. Funily enough PUTing only the ip works even if there are no changes...

fatal: [localhost]: FAILED! => changed=false 
  connection: close
  content_type: application/json
  date: Fri, 30 Oct 2020 15:35:36 GMT
  elapsed: 0
  json:
    code: 400
    data: []
    message: Unbound host override already exists
    return: 2010
    status: bad request
  msg: 'Status code was 400 and not [200]: HTTP Error 400: Bad Request'
  redirected: false
  referer: no-referrer
  server: nginx
  status: 400
  transfer_encoding: chunked
  url: https://1.2.3.4/api/v1/services/unbound/host_override

Edit: please ignore the now removed comment, I was calling the api with a missing parameter.

How to use this with a node application

Am not good with node.js, got the code snippets from the internet and wrote a script to get firewall aliases rules.
But ,each time when I run node app.js I get an error message saying
FetchError: request to https://192.168.10.1/api/v1/firewall/alias failed, reason: unable to verify the first certificate

Here's my code :

var express = require('express');
const fetch = require("node-fetch");
var app = express();
fetch("https://192.168.10.1/api/v1/firewall/alias", {
    method: "get",
    headers: {
        "Authorization": `Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJwZlNlbnNlIiwiZXhwIjoxNjExNDAyMDMwLCJuYmYiOjE2MTEzMTU2MzAsImRhdGEiOiJhZG1pbiJ9.bXkn7wZJsxMJ1J78teXHIdylWTpuyIdVOMuLborrVqQ`     
    }
}).then((res) => console.log(res.json())).catch(res => {console.log(res)})
app.get('/', function (req, res) {
  res.send('Hello World!');
});

app.listen(3000, function () {
  console.log('Example app listening on port 3000!');
});

What am I doing wrong ?

Support for NTP Server Configurations

Hi, Want to appriciate your hard work for making this amazing api.

It is great if we can add one API for adding/updating time servers, thank you very much!

image

Create firewall rule using aliases

When we try to create a firewall rule using aliase name for dstport attribute we get this error {"status": "bad request,"code": 400,"return": 4049", "message": "Invalid firewall rule destination port", "data":[]}

The body
{
'type': 'pass',
'interface': 'WAN',
'ipprotocol': 'inet',
'protocol': 'tcp',
'srcport': 'any',
'src': 'any',
'dst': '20_0019_ip_itgg0ebarwmsingk',
'dstport': 'Ports_20_0019_itgg0ebarwmsingk',
'descr': 'fw with alias',
'apply': True
}

Proper format for &search=

The api_extended_search checks to see if the search_data is an array, how are you expecting to pass in an array in the query parameter?

I have tried a few iterations, seach=name[value], both escaped and not, and everything returns
{"status":"not found","code":404,"return":7,"message":"Search attribute not found"}

This sort of makes sense as there is no supported way of passing a PHP array from the CLI, and it does not look like the api_extended_search makes any attempt to convert query strings to a PHP Array.

Is this intended to work?

How to add User Certificate

Hello

Help me please.
I try to add User Certificate with pfsense-api but all my certificates like Server.
How I can change it?

In API Docs I didn't find how to mark type of cert.

Thanks

The creation firewall NAT1TO1 induces changes of the outbound NAT mode

When we create a firewall NAT 1TO1 mapping with this API, the firewall outbound NAT mode is switching to automatic.
I am wondering why this behavior because if we perform the same action through the user interface, there is no change on the firewall outbound NAT which is a logic behavior.

Monitoring Support

First of all, thank you for all of your work on this. I was about ready to give up on my pFSense installation just because of the lack of ability to program it remotely from other systems.

Do you have any plans to add any kind of status monitoring to the API? I am thinking primarily of status's for the individual gateway interfaces. There are others that could be useful, but that is the primary one. I find the notification abilities of pFSense to be pretty lacking on connection issues and would much prefer to monitor it from other systems.

Strict Content-Type matching

Derived from #17

Strict Content-Type matching will prevent issues when a client specifies data from two different content types.

For example if a user submits a portion of their payload using application/x-www-form-urlencoded parameters, and the rest using application/json, application/x-www-form-urlencoded is assumed and the data passed in as JSON is ignored. Fixing this will also allow new content types to be implemented easily in future releases.

Add documentation on how to use the API

Please add some one liners on how to use the api with curl
Im kind of stuck at:

curl -X GET 'https://pfsense/api/v1/system/api/' -d '{"client-id": "rufus", "client-token": "baghera"}' | jq '.'    

Add VirtualIP will not apply automatically when interface is not wan

Hi, thank you for your work.
Today I was trying to add virtualip via api,
What's different from the past is that the interface is opt3, not the wan, and then I found it can't be active:
QQ20210130-155627@2x

I have to enter the setting of that vip record, click 'save' button, and it will active immediately, is that a bug related to the interface or something wrong? I have never met this problem when I was trying to add vip on wan.

QQ20210130-155636@2x

QQ20210130-155648@2x

Thanks again !

Report an Issue button not functioning properly

Ironically, the report an issue button with the UI page is not functioning. Looks like a search and replace may have caught the // in the URL unintentionally:

<a style="float: right;" class="fa fa-question-circle" href='https:#github.com/jaredhendrickson13/pfsense-api/issues/new'> <span style="font-family: 'Helvetica'; font-size: 14px;">Report an Issue</span></a>

May just back port a patched package to the v1.1.0 release since it's such a small issue.

complete uninstall?

on pfsense 2.5 when i run

pkg delete pfSense-pkg-API

i still see API as sub menu in systems

API Token only works for Local Users

I was able to generate an API Token with my LDAP user, but attempts to authenticate against the API failed. I finally created a Local User and tested and it works. So I guess this doesn't support LDAP users?

If that's the case, I suggest updating the documentation to reflect that.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.