Skip to content

Commit

Permalink
Baselined from internal Repository
Browse files Browse the repository at this point in the history
last_commit:a58b944f0b92d05920d6d3b10dbb109a05e8abd3
  • Loading branch information
GVE Devnet Admin committed May 17, 2024
1 parent a7c8fd6 commit eec0d01
Show file tree
Hide file tree
Showing 5 changed files with 54 additions and 24 deletions.
2 changes: 0 additions & 2 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,6 @@ venv
.env
src/config/settings.py

src/rule.json

# IDE files
.idea
__pycache__
Expand Down
9 changes: 5 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,9 @@ This application programmatically creates/deletes a `Device Authorization Policy

The working example matches a device on `IP Address` and users on `First Name`, `Last Name` fields in a generic LDAP Database when creating a new rule. Users are under a `Read-Only` policy by default with restricted commands on all devices, but users will be granted `Write Access` via Jira Issues for a (optional) specified period of time.

**Note**: The application assumes a Device Policy, Command Set, and Shell Profile used with the rule exist within ISE. For more information on ISE TACACS+ and these components, refer to the [ISE Documentation](https://www.cisco.com/c/en/us/td/docs/security/ise/2-4/admin_guide/b_ISE_admin_guide_24/m_ise_tacacs_device_admin.html).
**Note**:
* The application assumes a Device Policy, Command Set, and Shell Profile used with the rule exist within ISE. For more information on ISE TACACS+ and these components, refer to the [ISE Documentation](https://www.cisco.com/c/en/us/td/docs/security/ise/2-4/admin_guide/b_ISE_admin_guide_24/m_ise_tacacs_device_admin.html).
* Multiple IPs can be specified to match multiple devices in a single rule by providing a comma seperated list in the Jira Issue: "X.X.X.X,Y.Y.Y.Y"

**Warning**: This application only works with ISE 3.X or later (using the OpenAPI Policy API)

Expand Down Expand Up @@ -136,9 +138,8 @@ COMMAND_SET_NAMES = ["Full RW Commands"]
SCHEDULE_START = False
SCHEDULE_END = False
```
5. Rename `src/rule_example.json` to `src/rule.json`. This JSON file represents the matching condition template the Authorization Rules will use (minus field values injected from the Jira Tickets).
6. Set up a Python virtual environment. Make sure Python 3 is installed in your environment, and if not, you may download Python [here](https://www.python.org/downloads/). Once Python 3 is installed in your environment, you can activate the virtual environment with the instructions found [here](https://docs.python.org/3/tutorial/venv.html).
7. Install the requirements with `pip install -r requirements.txt`
5. Set up a Python virtual environment. Make sure Python 3 is installed in your environment, and if not, you may download Python [here](https://www.python.org/downloads/). Once Python 3 is installed in your environment, you can activate the virtual environment with the instructions found [here](https://docs.python.org/3/tutorial/venv.html).
6. Install the requirements with `pip install -r requirements.txt`

## Usage

Expand Down
10 changes: 3 additions & 7 deletions src/ise.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@

from config.config import c
from logger.logrr import lm
from rule import json_rule

# Suppress only the single InsecureRequestWarning from urllib3 needed for unverified HTTPS requests.
urllib3.disable_warnings(InsecureRequestWarning)
Expand Down Expand Up @@ -57,13 +58,8 @@ def __init__(self):
self._matching_command_set = None
self._active_auth_rules = None

# Load in Raw Authorization Rule JSON
with open(self.RULE_JSON_PATH, 'r') as f:
json_data = json.load(f)

# Turn raw JSON into Jinja template
json_template = json.dumps(json_data)
self._rule_template = jinja2.Template(json_template)
# Load in Raw Authorization Rule JSON, convert to template
self._rule_template = jinja2.Template(json_rule)

# Setup Session (handle 429 with custom backoff)
session = requests.Session()
Expand Down
27 changes: 25 additions & 2 deletions src/rule_example.json → src/rule.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
json_rule = """
{
"condition": {
"link": null,
Expand All @@ -24,6 +25,27 @@
"dictionaryValue": null,
"attributeValue": "{{ last_name }}"
},
{% if ip_addresses|length > 1 %}
{
"link": null,
"conditionType": "ConditionOrBlock",
"isNegate": false,
"children": [
{% for ip in ip_addresses %}
{
"link": null,
"conditionType": "ConditionAttributes",
"isNegate": false,
"dictionaryName": "Network Access",
"attributeName": "Device IP Address",
"operator": "ipEquals",
"dictionaryValue": null,
"attributeValue": "{{ ip }}"
}{% if not loop.last %},{% endif %}
{% endfor %}
]
}
{% else %}
{
"link": null,
"conditionType": "ConditionAttributes",
Expand All @@ -32,13 +54,14 @@
"attributeName": "Device IP Address",
"operator": "ipEquals",
"dictionaryValue": null,
"attributeValue": "{{ ip_address }}"
"attributeValue": "{{ ip_addresses[0] }}"
}
{% endif %}
]
},
"default": false,
"name": "{{ policy_name }}",
"rank": 0,
"state": "enabled"
}

"""
30 changes: 21 additions & 9 deletions src/webhook.py
Original file line number Diff line number Diff line change
Expand Up @@ -74,13 +74,17 @@ def validate_webhook_data(data: CreationWebhookData | BaseWebhookData):
:param data: Webhook data to validate
"""
# Validate IP Address (both models have it) - check if ip valid, and ip is an actual network device in ISE
ip_address = data.ip_address
if not is_valid_ip(ip_address):
raise ValueError(f"Invalid IP address received: {ip_address}")
ip_addresses = data.ip_address.split(",")

devices = ise.find_network_devices(f"ipaddress.EQ.{ip_address}")
if not devices:
raise ValueError(f"ISE Network Device not Found for the following IP: {ip_address}. Please check IP Address.")
for ip in ip_addresses:
ip_address = ip.strip()

if not is_valid_ip(ip_address):
raise ValueError(f"Invalid IP address received: {ip_address}")

devices = ise.find_network_devices(f"ipaddress.EQ.{ip_address}")
if not devices:
raise ValueError(f"ISE Network Device not Found for the following IP: {ip_address}. Please check IP Address.")

if isinstance(data, CreationWebhookData):
# Check start_time is after current time if scheduling enabled
Expand Down Expand Up @@ -115,11 +119,15 @@ def create_authorization_rule(data: CreationWebhookData) -> dict:
# Build data structure to inject into Jinja Rule template (field names must match those referenced in rule.json)
username = data.assignee.split(' ')

# Handle Multiple IPs
ip_addresses = data.ip_address.split(",")
ip_addresses = [ip.strip() for ip in ip_addresses]

rule_data = {
'first_name': username[0],
'last_name': username[1],
"ip_address": data.ip_address,
"policy_name": f"{data.assignee}_rw_override-{data.ip_address}"
"ip_addresses": ip_addresses,
"policy_name": f"{data.assignee}_rw_override-{"-".join(ip_addresses)}"
}

# Check if active rule already exists
Expand Down Expand Up @@ -165,8 +173,12 @@ def delete_authorization_rule(data: BaseWebhookData) -> dict | None:
:param data: Incoming Webhook Data
:return: Response from ISE API Deletion
"""
# Handle Multiple IPs
ip_addresses = data.ip_address.split(",")
ip_addresses = [ip.strip() for ip in ip_addresses]

# Reconstruct policy name (based on creation)
policy_name = f"{data.assignee}_rw_override-{data.ip_address}"
policy_name = f"{data.assignee}_rw_override-{"-".join(ip_addresses)}"

# Delete Authorization Rule
return ise.delete_authorization_rule(policy_name)

0 comments on commit eec0d01

Please sign in to comment.