Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[PAN-OS] add the pan-os-list-templates command #21507

Merged
merged 13 commits into from
Oct 3, 2022
Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
130 changes: 130 additions & 0 deletions Packs/PAN-OS/Integrations/Panorama/Panorama.py
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@

# pan-os-python device timeout value, in seconds
DEVICE_TIMEOUT = 120
DEFAULT_LIMIT_PAGE_SIZE = 50

# Security rule arguments for output handling
SECURITY_RULE_ARGS = {
Expand Down Expand Up @@ -333,6 +334,37 @@ def parse_pan_os_un_committed_data(dictionary, keys_to_remove):
parse_pan_os_un_committed_data(item, keys_to_remove)


def do_pagination(
entries: list,
page: Optional[int] = None,
page_size: int = DEFAULT_LIMIT_PAGE_SIZE,
limit: int = DEFAULT_LIMIT_PAGE_SIZE
):
if page is not None:
if page <= 0:
raise DemistoException(f'page {page} must be a positive number')
entries = entries[(page - 1) * page_size:page_size * page] # do pagination
else:
entries = entries[:limit]

return entries


def extract_objects_info_by_key(_entry, _key):
if isinstance(_entry, dict):
key_info = _entry.get(_key)
if not key_info: # api could not return the key
return None

if isinstance(key_info, dict) and (_member := key_info.get('member')):
return _member
elif isinstance(key_info, str):
return key_info
elif isinstance(_entry, list):
return [item.get(_key) for item in _entry]
return None


def add_argument_list(arg: Any, field_name: str, member: Optional[bool], any_: Optional[bool] = False) -> str:
member_stringify_list = ''
if arg:
Expand Down Expand Up @@ -11170,6 +11202,102 @@ def pan_os_get_merged_config(args: dict):
return fileResult("merged_config", result)


def build_template_xpath(name: Optional[str]):
_xpath = "/config/devices/entry[@name='localhost.localdomain']/template"
if name:
_xpath = f"{_xpath}/entry[@name='{name}']"
return _xpath


def parse_list_templates_response(entries):

def parse_template_variables(_variables):

# when there is only one variable it is not returned as a list
if isinstance(_variables, dict):
_variables = [_variables]

return [
{
'Name': variable.get('@name'),
'Type': list(variable.get('type').keys())[0] if variable.get('type') else None,
'Value':list(variable.get('type').values())[0] if variable.get('type') else None,
'Description': variable.get('description')
}
for variable in _variables
]

table, context = [], []

for entry in entries:
parse_pan_os_un_committed_data(entry, ['@admin', '@dirtyId', '@time'])
name = entry.get('@name')
description = entry.get('description')
variables = entry.get('variable', {}).get('entry', [])
context.append(
{
'Name': name,
'Description': description,
'Variable': parse_template_variables(variables)
}
)
table.append(
{
'Name': name,
'Description': description,
'Variable': extract_objects_info_by_key(variables, '@name')
}
)

return table, context


def pan_os_list_templates(template_name: Optional[str]):
params = {
'type': 'config',
'action': 'get',
'key': API_KEY,
'xpath': build_template_xpath(template_name)
}

return http_request(URL, 'GET', params=params)


def pan_os_list_templates_command(args):
template_name = args.get('template_name')
if not DEVICE_GROUP and VSYS:
raise DemistoException('The pan-os-list-templates should only be used for Panorama instances')
GuyAfik marked this conversation as resolved.
Show resolved Hide resolved

raw_response = pan_os_list_templates(template_name)
result = raw_response.get('response', {}).get('result', {})

# the 'entry' key could be a single dict as well.
entries = dict_safe_get(result, ['template', 'entry'], default_return_value=result.get('entry'))
if not isinstance(entries, list): # when only one template is returned it could be returned as a dict.
entries = [entries]

if not template_name:
# if name was provided, api returns one entry so no need to do limit/pagination
page = arg_to_number(args.get('page'))
page_size = arg_to_number(args.get('page_size')) or DEFAULT_LIMIT_PAGE_SIZE
limit = arg_to_number(args.get('limit')) or DEFAULT_LIMIT_PAGE_SIZE
entries = do_pagination(entries, page=page, page_size=page_size, limit=limit)

table, templates = parse_list_templates_response(entries)

return CommandResults(
raw_response=raw_response,
outputs=templates,
readable_output=tableToMarkdown(
'Templates:',
table,
removeNull=True
),
outputs_prefix='Panorama.Template',
outputs_key_field='Name'
)


def main():
try:
args = demisto.args()
Expand Down Expand Up @@ -11820,6 +11948,8 @@ def main():
return_results(pan_os_get_merged_config(args))
elif command == 'pan-os-get-running-config':
return_results(pan_os_get_running_config(args))
elif command == 'pan-os-list-templates':
return_results(pan_os_list_templates_command(args))
else:
raise NotImplementedError(f'Command {command} is not implemented.')
except Exception as err:
Expand Down
40 changes: 40 additions & 0 deletions Packs/PAN-OS/Integrations/Panorama/Panorama.yml
Original file line number Diff line number Diff line change
Expand Up @@ -8100,6 +8100,46 @@ script:
- contextPath: InfoFile.Info
description: Basic information of the file.
type: String
- arguments:
- description: The name of the template to retrieve, if not mentioned will bring all the available templates.
GuyAfik marked this conversation as resolved.
Show resolved Hide resolved
isArray: false
name: template_name
required: false
- defaultValue: '50'
description: The maximum number of templates to retrieve, will be used by default if page argument was not provided.
isArray: false
name: limit
required: false
- defaultValue: '50'
description: The page size of the templates to return.
isArray: false
name: page_size
required: false
- description: The page at which to start listing templates, must be a positive number.
isArray: false
name: page
required: false
description: Returns a list of available templates (Use only in Panorama instances).
name: pan-os-list-templates
outputs:
- contextPath: Panorama.Template.Name
description: The name of the template.
type: String
- contextPath: Panorama.Template.Description
description: The description of the template.
type: String
- contextPath: Panorama.Template.Variable.Name
description: The variable name of the template.
type: String
- contextPath: Panorama.Template.Variable.Type
description: The type of the template.
type: String
- contextPath: Panorama.Template.Variable.Value
description: The value of the variable of the template.
type: String
- contextPath: Panorama.Template.Variable.Description
description: The description of the variable of the template.
type: String
dockerimage: demisto/pan-os-python:1.0.0.34572
feed: false
isfetch: false
Expand Down
70 changes: 70 additions & 0 deletions Packs/PAN-OS/Integrations/Panorama/Panorama_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -3470,3 +3470,73 @@ def test_pan_os_get_merged_config(mocker):
mocker.patch("Panorama.http_request", return_value=return_mock)
created_file = pan_os_get_merged_config({"target": "SOME_SERIAL_NUMBER"})
assert created_file['File'] == 'merged_config'


class TestPanOSListTemplatesCommand:

def test_pan_os_list_templates_main_flow(self, mocker):
"""
Given:
- Panorama instance configuration.

When:
- running the pan-os-list-templates through the main flow.

Then:
- make sure the context output is parsed correctly.
- make sure the xpath and the request is correct.
"""
from Panorama import main

mock_request = mocker.patch(
"Panorama.http_request", return_value=load_json('test_data/list_templates_including_uncommitted.json')
)
mocker.patch.object(demisto, 'params', return_value=integration_panorama_params)
mocker.patch.object(demisto, 'args', return_value={})
mocker.patch.object(demisto, 'command', return_value='pan-os-list-templates')
result = mocker.patch('demistomock.results')

main()

assert list(result.call_args.args[0]['EntryContext'].values())[0] == [
{
'Name': 'test-1', 'Description': None,
'Variable': [
{'Name': None, 'Type': None, 'Value': None, 'Description': None},
{'Name': None, 'Type': None, 'Value': None, 'Description': None}
]
},
{
'Name': 'test-2', 'Description': 'just a test description',
'Variable': [
{
'Name': '$variable-1', 'Type': 'ip-netmask',
'Value': '1.1.1.1', 'Description': 'description for $variable-1'
}
]
}
]

assert mock_request.call_args.kwargs['params'] == {
'type': 'config', 'action': 'get', 'key': 'thisisabogusAPIKEY!',
'xpath': "/config/devices/entry[@name='localhost.localdomain']/template"
}

def test_pan_os_list_templates_main_flow_firewall_instance(self):
"""
Given:
- Firewall instance configuration.

When:
- running the pan_os_list_templates_command function.

Then:
- make sure an exception is raised because hte pan-os-list-templates can run only on Panorama instances.
"""
from Panorama import pan_os_list_templates_command
import Panorama

Panorama.VSYS = 'vsys' # VSYS are only firewall instances
Panorama.DEVICE_GROUP = '' # device-groups are only panorama instances.
with pytest.raises(DemistoException):
pan_os_list_templates_command({})
72 changes: 72 additions & 0 deletions Packs/PAN-OS/Integrations/Panorama/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -7436,3 +7436,75 @@ There is no context output for this command.
}
```

### pan-os-list-templates
***
Returns a list of available templates (Use only in Panorama instances).


#### Base Command

`pan-os-list-templates`
#### Input

| **Argument Name** | **Description** | **Required** |
| --- | --- | --- |
| template_name | The name of the template to retrieve, if not mentioned will bring all the available templates. | Optional |
| limit | The maximum number of templates to retrieve, will be used by default if page argument was not provided. Default is 50. | Optional |
| page_size | The page size of the templates to return. Default is 50. | Optional |
| page | The page at which to start listing templates, must be a positive number. | Optional |


#### Context Output

| **Path** | **Type** | **Description** |
| --- | --- | --- |
| Panorama.Template.Name | String | The name of the template. |
| Panorama.Template.Description | String | The description of the template. |
| Panorama.Template.Variable.Name | String | The variable name of the template. |
| Panorama.Template.Variable.Type | String | The type of the template. |
| Panorama.Template.Variable.Value | String | The value of the variable of the template. |
| Panorama.Template.Variable.Description | String | The description of the variable of the template. |

#### Command example
```!pan-os-list-templates limit=20```
#### Context Example
```json
{
"Panorama": {
"Template": [
{
"Description": null,
"Name": "test-1",
"Variable": []
},
{
"Description": "test description",
"Name": "test-2",
"Variable": [
{
"Description": "variable-1-test",
"Name": "$variable-1",
"Type": "ip-netmask",
"Value": "1.1.1.1"
},
{
"Description": null,
"Name": "$variable-2",
"Type": "fqdn",
"Value": "google.com"
}
]
}
]
}
}
```

#### Human Readable Output

>### Templates:
>|Description|Name|Variable|
>|---|---|---|
>| | test-1 | |
>| test description | test-2 | $variable-1,<br/>$variable-2 |

3 changes: 2 additions & 1 deletion Packs/PAN-OS/Integrations/Panorama/command_examples.txt
Original file line number Diff line number Diff line change
Expand Up @@ -119,4 +119,5 @@ not_running_this!panorama-get-ssl-decryption-rules
!pan-os xpath=“/config/devices/entry[@name=‘localhost.localdomain’]/template/entry[@name=‘test’]/config/devices/entry[@name=‘localhost.localdomain’]/network/profiles/zone-protection-profile/entry[@name=‘test’]/scan-white-list/entry[@name=‘bla’]/ipv4" type=config action=edit element=“<ipv4>1.1.1.1</ipv4>”
!pan-os-commit description=test polling=true interval_in_seconds=5 timeout=60
!pan-os-push-to-device-group description=test polling=true interval_in_seconds=5 timeout=60
!pan-os-push-status job_id=31377
!pan-os-push-status job_id=31377
!pan-os-list-templates limit=20