Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 23 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,28 @@
# Changelog

## 2023/04/18 - Version 0.26
### Features Added
* Added Security Services
* Security Rules - Delete, Edit, Create, List
* Anti-Spyware Profiles - Delete, Edit, Create, List
* Anti-Spyware Signatures - Delete, Edit, Create, List
* Dns Security Profiles - Delete, Edit, Create, List
* Decryption Exclusions - Delete, Edit, Create, List
* Decryption Profiles - Delete, Edit, Create, List
* Decryption Rules - Delete, Edit, Create, List
* File-Blocking Profiles - Delete, Edit, Create, List
* Http-Header Profiles - Delete, Edit, Create, List
* Profile Groups - Delete, Edit, Create, List
* Url Access Profiles - Delete, Edit, Create, List
* Vulnerability Protect Profiles - Delete, Edit, Create, List
* Vulnerability Protect Signatures - Delete, Edit, Create, List
* Wildfire AntiVirus Profiles - Delete, Edit, Create, List
### Changes
* Add position arg to saseApi paList, paCreate, paEdit, paDelete to support Security Rules and Decryption Rules
* Update paList - a check for response type to return a list without further processing (For paLocationsListLocations which returns a list)
* Added name_key arg to paCreate, paEdit, paDelete to support Vuln & AntiSpam Signatures which have a different object name (threatname)


## 2023/01/26 - Version 0.25
* Added URL Filtering Categories - List. Please note that this is currently not working.

Expand Down
22 changes: 19 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# Prisma SASE Cloud Managed API
Python framework to make changes to Prisma Access Cloud Managed
Current working version - _0.24_
Current working version - _0.26_

* Authors - [TheScriptGuy](https://github.com/TheScriptGuy)

Expand Down Expand Up @@ -43,7 +43,23 @@ See [CHANGELOG.md](https://github.com/PaloAltoNetworks/PrismaSASECloudManaged-Py
| Prisma Access Locations | :white_check_mark: | n/a | n/a | n/a |
| Infrastructure Settings | :white_check_mark: | n/a | :white_check_mark: | n/a |


### Security Services
| Feature | List | Create | Edit | Delete |
| ------- | ---- | ------ | ---- | ------ |
| Security Rules | :white_check_mark: | :white_check_mark: | :white_check_mark: | :white_check_mark: |
| Anti-Spyware Profiles | :white_check_mark: | :white_check_mark: | :white_check_mark: | :white_check_mark: |
| Anti-Spyware Signatures | :white_check_mark: | :white_check_mark: | :white_check_mark: | :white_check_mark: |
| Dns Security Profiles | :white_check_mark: | :white_check_mark: | :white_check_mark: | :white_check_mark: |
| Decryption Exclusions | :white_check_mark: | :white_check_mark: | :white_check_mark: | :white_check_mark: |
| Decryption Profiles | :white_check_mark: | :white_check_mark: | :white_check_mark: | :white_check_mark: |
| Decryption Rules | :white_check_mark: | :white_check_mark: | :white_check_mark: | :white_check_mark: |
| File-Blocking Profiles | :white_check_mark: | :white_check_mark: | :white_check_mark: | :white_check_mark: |
| Http-Header Profiles | :white_check_mark: | :white_check_mark: | :white_check_mark: | :white_check_mark: |
| Profile Groups | :white_check_mark: | :white_check_mark: | :white_check_mark: | :white_check_mark: |
| Url Access Profiles | :white_check_mark: | :white_check_mark: | :white_check_mark: | :white_check_mark: |
| Vulnerability Protect Profiles | :white_check_mark: | :white_check_mark: | :white_check_mark: | :white_check_mark: |
| Vulnerability Protect Signatures | :white_check_mark: | :white_check_mark: | :white_check_mark: | :white_check_mark: |
| Wildfire AntiVirus Profiles | :white_check_mark: | :white_check_mark: | :white_check_mark: | :white_check_mark: |

# SDWAN
Not supported (yet)
Expand All @@ -53,4 +69,4 @@ How to use the API

[Setup API Access in TSG](https://github.com/PaloAltoNetworks/PrismaSASECloudManaged-Python/blob/main/usage-identity-access.md)

[Python Script Usage](https://github.com/PaloAltoNetworks/PrismaSASECloudManaged-Python/blob/main/usage-python.md)
[Python Script Usage](https://github.com/PaloAltoNetworks/PrismaSASECloudManaged-Python/blob/main/usage-python.md)
2 changes: 1 addition & 1 deletion access/policyObjects.py
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,7 @@ def paLicenseTypesListTypes(self):
print("Please request new token and create new prismaAccess object.")

def paLocationsListLocations(self):
"""List all the Prisma Access Locations"""
"""List all the Prisma Access Locations. This data is returned as a List"""
if self.checkTokenStillValid():
paLocations = saseApi.saseApi(self.prismaAccessObject.locationsUri, self.prismaAccessObject.saseToken, self.prismaAccessObject.contentType, self.prismaAccessObject.saseAuthHeaders)
paLocations.paList()
Expand Down
32 changes: 16 additions & 16 deletions access/prismaAccess.py
Original file line number Diff line number Diff line change
Expand Up @@ -70,24 +70,24 @@ def initApiUri(self):
#self.trafficSteeringUri = self.saseApi + __configV1 + "traffic-steering"

# Security Services
#self.antiSpywareProfilesUri = self.saseApi + __configV1 + "anti-spyware-profiles"
#self.antiSpywareSignaturesUri = self.saseApi + __configV1 + "anti-spyware-signatures"
#self.dnsSecurityProfilesUri = self.saseApi + __configV1 + "dns-security-profiles"
#self.decryptionExclusionsUri = self.saseApi + __configV1 + "decryption-exclusions"
#self.decryptionProfilesUri = self.saseApi + __configV1 + "decryption-profiles"
#self.decryptionRulesUri = self.saseApi + __configV1 + "decryption-rules"
#self.fileBlockingProfilesUri = self.saseApi + __configV1 + "file-blocking-profiles"
#self.httpHeaderProfilesUri = self.saseApi + __configV1 + "http-header-profiles"
#self.profileGroupsUri = self.saseApi + __configV1 + "profile-groups"
#self.securityRulesUri = self.saseApi + __configV1 + "security-rules"
#self.urlAccessProfilesUri = self.saseApi + __configV1 + "url-access-profiles"
#self.vulnerabilityProtectProfilesUri = self.saseApi + __configV1 + "vulnerability-protection-profiles"
#self.vulnerabilityProtectSignaturesUri = self.saseApi + __configV1 + "vulnerability-protection-signatures"
#self.wildfireAntiVirusProfilesUri = self.saseApi + __configV1 + "wildfire-anti-virus-profiles"
self.antiSpywareProfilesUri = self.saseApi + __configV1 + "anti-spyware-profiles"
self.antiSpywareSignaturesUri = self.saseApi + __configV1 + "anti-spyware-signatures"
self.dnsSecurityProfilesUri = self.saseApi + __configV1 + "dns-security-profiles"
self.decryptionExclusionsUri = self.saseApi + __configV1 + "decryption-exclusions"
self.decryptionProfilesUri = self.saseApi + __configV1 + "decryption-profiles"
self.decryptionRulesUri = self.saseApi + __configV1 + "decryption-rules"
self.fileBlockingProfilesUri = self.saseApi + __configV1 + "file-blocking-profiles"
self.httpHeaderProfilesUri = self.saseApi + __configV1 + "http-header-profiles"
self.profileGroupsUri = self.saseApi + __configV1 + "profile-groups"
self.securityRulesUri = self.saseApi + __configV1 + "security-rules"
self.urlAccessProfilesUri = self.saseApi + __configV1 + "url-access-profiles"
self.vulnerabilityProtectProfilesUri = self.saseApi + __configV1 + "vulnerability-protection-profiles"
self.vulnerabilityProtectSignaturesUri = self.saseApi + __configV1 + "vulnerability-protection-signatures"
self.wildfireAntiVirusProfilesUri = self.saseApi + __configV1 + "wildfire-anti-virus-profiles"

def __init__(self, __saseToken):
"""Initialize Class"""
self.prismaAccessPythonAPIVersion = "0.25"
self.prismaAccessPythonAPIVersion = "0.26"
self.saseApi = "https://api.sase.paloaltonetworks.com"
self.saseToken = __saseToken
self.contentType = "application/json"
Expand All @@ -97,4 +97,4 @@ def __init__(self, __saseToken):
}

# Set all API URI variables
self.initApiUri()
self.initApiUri()
85 changes: 53 additions & 32 deletions access/saseApi.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,45 +3,62 @@
class saseApi:
"""saseApi class"""

def paList(self, __folder="Shared", __displayOutput=True):
def paList(self, __folder="Shared", __position="pre", includePosition=False, __displayOutput=True):
"""
This will list the addresses from the folder.
Folder defaults to shared.
Position is included in params if includePosition=True. Position defaults to pre.
"""
__params = { "folder": __folder, "limit": self.saseLimit }
__response = requests.get(url=self.saseUri, headers=self.saseAuthHeaders, data=__dataPayload)
if includePosition:
__params["position"] = __position
__response = requests.get(url=self.saseUri, headers=self.saseAuthHeaders, params=__params)
__response = __response.json()

if __response["total"] > self.saseLimit:
# There are more than self.saseLimit (default: 200) objects retrieved. We need to get through the entire list.
numRecords = 0
if isinstance(__response, dict) and "total" in __response:
if __response["total"] > self.saseLimit:
# There are more than self.saseLimit (default: 200) objects retrieved. We need to get through the entire list.
numRecords = 0

while numRecords < __response["total"]:
"""
Perform all API queries until we've retrieved all objects.
"""
numRecords = len(__response["data"])
while numRecords < __response["total"]:
"""
Perform all API queries until we've retrieved all objects.
"""
numRecords = len(__response["data"])

# Update the offset to reflect the number of records already retrieved.
__params2 = { "folder": __folder, "offset": numRecords, "limit": self.saseLimit }
# Update the offset to reflect the number of records already retrieved.
__params2 = { "folder": __folder, "offset": numRecords, "limit": self.saseLimit }
if includePosition:
__params2["position"] = __position

__response2 = requests.get(url=self.saseUri, headers=self.saseAuthHeaders, params=__params2)
__response2 = __response2.json()
__response2 = requests.get(url=self.saseUri, headers=self.saseAuthHeaders, params=__params2)
__response2 = __response2.json()

if "data" in __response2:
# Append the next batch of application objects to the original data object that we retrieved from __response.
__response["data"] = [*__response["data"], *(__response2["data"])]
if "data" in __response2:
# Append the next batch of application objects to the original data object that we retrieved from __response.
__response["data"] = [*__response["data"], *(__response2["data"])]

elif isinstance(__response, list):
# The endpoint returned a list. We do not need to perform any additional API queries.
pass

else:
# Unexpected response type, raise an error
raise ValueError("Unexpected response type")

if __displayOutput:
# We need to display the output to stdout.
print(__response)
return __response

def paCreate(self, __jsonObject, __folder="Shared"):
def paCreate(self, __jsonObject, __folder="Shared", __position="pre", includePosition=False, name_key='name'):
"""
This will create an object (by default in Shared)
name_key is included in params to add the ability to define a new name key for cases where 'name' isn't passed in the payload (AntiSpyware/VulnerabilityProtect Signatures)
"""
__params = { "folder": __folder }
if includePosition:
__params["position"] = __position
__response = requests.post(url=self.saseUri, headers=self.saseAuthHeaders, json=__jsonObject, params=__params)
__responseStatusCode = __response.status_code
__response = __response.json()
Expand All @@ -50,61 +67,63 @@ def paCreate(self, __jsonObject, __folder="Shared"):
case 404:
print(f"jsonobject = {__jsonObject}")
print(f"response = {__response}")
print(f"404 - An error occured while creating object {__jsonObject['name']} - {__response['_errors'][0]['details']['message']} in folder {__folder}")
print(f"404 - An error occured while creating object {__jsonObject[name_key]} - {__response['_errors'][0]['details']['message']} in folder {__folder}")
case 400:
print("400 - Bad request. Malformed payload.")
case 201:
print(f"201 - Object {__jsonObject['name']} created in folder {__folder}.")
print(f"201 - Object {__jsonObject[name_key]} created in folder {__folder}.")
case _:
print("Not sure how to interpret response.")
print(f"Response Status Code - {__responseStatusCode}")
print(f"json response = {__response}")

def paEdit(self, __jsonObject, __folder="Shared"):
def paEdit(self, __jsonObject, __folder="Shared", __position="pre", includePosition=False, name_key='name'):
"""
This will edit an existing object (by default in Shared)
If your object references something external from it e.g. an address-group referencing an address object, make sure the address is created first.
This will be completed in 2 parts:
1. First is to get the unique ID for the address.
2. Once we have the unique ID, we can change the information about it (except the unique ID)
"""
myList = self.paList(__folder,False)
myList = self.paList(__folder, __position, True, False) if includePosition else self.paList(__folder, False, False)
myObjectId = ""
if 'data' in myList:
# Let's go and find the object ID
for item in myList['data']:
if __folder != "Service Connections":
if item['name'] == __jsonObject['name'] and item['folder'] == __folder:
if item[name_key] == __jsonObject[name_key] and item['folder'] == __folder:
myObjectId = item['id']
break
else:
if item['name'] == __jsonObject['name']:
if item[name_key] == __jsonObject[name_key]:
myObjectId = item['id']
break

if myObjectId != "":
# We should now have the ID.
__editUri = self.saseUri + f"/{myObjectId}"
__params = { "folder": __folder }
if includePosition:
__params["position"] = __position
__response = requests.put(url=__editUri, headers=self.saseAuthHeaders, json=__jsonObject, params=__params)
__responseStatusCode = __response.status_code
__response = __response.json()

match __responseStatusCode:
case 404:
print(f"404 - An error occured while editing object {__jsonObject['name']} - {__response['_errors'][0]['details']['message']} in folder {__folder}.")
print(f"404 - An error occured while editing object {__jsonObject[name_key]} - {__response['_errors'][0]['details']['message']} in folder {__folder}.")
case 400:
print("400 - Bad request. Malformed payload.")
case 200:
print(f"200 - Object {__jsonObject['name']} edited in folder {__folder}.")
print(f"200 - Object {__jsonObject[name_key]} edited in folder {__folder}.")
case _:
print("Not sure how to interpret response.")
print(f"Response Status Code - {__responseStatusCode}")
print(f"json response = {__response}")
else:
print(f"Unable to find object ID in {__folder}.")

def paDelete(self, __jsonObject, __folder="Shared"):
def paDelete(self, __jsonObject, __folder="Shared", __position="pre", includePosition=False, name_key='name'):
"""
This will delete an existing object (by default in Shared)
The comments field are optional.
Expand All @@ -116,17 +135,17 @@ def paDelete(self, __jsonObject, __folder="Shared"):
1. First is to get the unique ID for the address.
2. Once we have the unique ID, we can delete the address
"""
myList = self.paList(__folder,False)
myList = self.paList(__folder, __position, True, False) if includePosition else self.paList(__folder, False, False)
myObjectId = ""
if 'data' in myList:
# Let's go and find the address ID
for item in myList['data']:
if (__folder != "Service Connections") or (__folder != "Remote Networks"):
if item['name'] == __jsonObject['name'] and item['folder'] == __folder:
if item[name_key] == __jsonObject[name_key] and item['folder'] == __folder:
myObjectId = item['id']
break
else:
if item['name'] == __jsonObject['name']:
if item[name_key] == __jsonObject[name_key]:
myObjectId = item['id']
break

Expand All @@ -138,6 +157,8 @@ def paDelete(self, __jsonObject, __folder="Shared"):
# We should now have the ID.
__deleteUri = self.saseUri + f"/{myObjectId}"
__params = { "folder": __folder }
if includePosition:
__params["position"] = __position
__response = requests.delete(url=__deleteUri, headers=self.saseAuthHeaders, json=__jsonObject, params=__params)
__responseStatusCode = __response.status_code
__response = __response.json()
Expand All @@ -146,11 +167,11 @@ def paDelete(self, __jsonObject, __folder="Shared"):
case 409:
print(f"409 - Cannot delete object being referenced {__response['_errors'][0]['details']['message']}")
case 404:
print(f"404 - An error occured while creating object {__addresssObject['name']} - {__response['_errors'][0]['details']['message']} in folder {__folder}.")
print(f"404 - An error occured while creating object {__jsonObject[name_key]} - {__response['_errors'][0]['details']['message']} in folder {__folder}.")
case 400:
print("400 - Bad request. Malformed payload.")
case 200:
print(f"200 - Object {__jsonObject['name']} deleted in folder {__folder}.")
print(f"200 - Object {__jsonObject[name_key]} deleted in folder {__folder}.")
case _:
print("Not sure how to interpret response.")
print(f"Response Status Code - {__responseStatusCode}")
Expand Down
Loading