├── .gitignore ├── 00_deployment_info.py ├── 01_add_groups.py ├── 02_add_users.py ├── 03_create_tacacs_profiles.py ├── 04_change_tacacs_authc_source.py ├── 05_create_tacacs_authz_policies.py ├── 06_create_sgts.py ├── 07_change_authentication_source.py ├── 08_create_authorization_profiles.py ├── 09_create_authorization_policies.py ├── 10_create_guest_authz_profiles.py ├── 11_create_guest_authz_policies.py ├── 12_access_point_profiling.py ├── README.md ├── credentials.yaml ├── groupsandusers.yaml ├── images └── ise_api.png └── policy.yaml /.gitignore: -------------------------------------------------------------------------------- 1 | # OS generated files # 2 | ###################### 3 | .DS_Store 4 | .DS_Store? 5 | ._* 6 | .Spotlight-V100 7 | .Trashes 8 | ehthumbs.db 9 | Thumbs.db 10 | 11 | # Byte-compiled / optimized / DLL files 12 | __pycache__/ 13 | *.py[cod] 14 | *$py.class 15 | 16 | # C extensions 17 | *.so 18 | 19 | # Distribution / packaging 20 | .Python 21 | build/ 22 | develop-eggs/ 23 | dist/ 24 | downloads/ 25 | eggs/ 26 | .eggs/ 27 | lib/ 28 | lib64/ 29 | parts/ 30 | sdist/ 31 | var/ 32 | wheels/ 33 | share/python-wheels/ 34 | *.egg-info/ 35 | .installed.cfg 36 | *.egg 37 | MANIFEST 38 | 39 | # PyInstaller 40 | # Usually these files are written by a python script from a template 41 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 42 | *.manifest 43 | *.spec 44 | 45 | # Installer logs 46 | pip-log.txt 47 | pip-delete-this-directory.txt 48 | 49 | # Unit test / coverage reports 50 | htmlcov/ 51 | .tox/ 52 | .nox/ 53 | .coverage 54 | .coverage.* 55 | .cache 56 | nosetests.xml 57 | coverage.xml 58 | *.cover 59 | *.py,cover 60 | .hypothesis/ 61 | .pytest_cache/ 62 | cover/ 63 | 64 | # Translations 65 | *.mo 66 | *.pot 67 | 68 | # Django stuff: 69 | *.log 70 | local_settings.py 71 | db.sqlite3 72 | db.sqlite3-journal 73 | 74 | # Flask stuff: 75 | instance/ 76 | .webassets-cache 77 | 78 | # Scrapy stuff: 79 | .scrapy 80 | 81 | # Sphinx documentation 82 | docs/_build/ 83 | 84 | # PyBuilder 85 | .pybuilder/ 86 | target/ 87 | 88 | # Jupyter Notebook 89 | .ipynb_checkpoints 90 | 91 | # IPython 92 | profile_default/ 93 | ipython_config.py 94 | 95 | # pyenv 96 | # For a library or package, you might want to ignore these files since the code is 97 | # intended to run in multiple environments; otherwise, check them in: 98 | # .python-version 99 | 100 | # pipenv 101 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. 102 | # However, in case of collaboration, if having platform-specific dependencies or dependencies 103 | # having no cross-platform support, pipenv may install dependencies that don't work, or not 104 | # install all needed dependencies. 105 | #Pipfile.lock 106 | 107 | # PEP 582; used by e.g. github.com/David-OConnor/pyflow 108 | __pypackages__/ 109 | 110 | # Celery stuff 111 | celerybeat-schedule 112 | celerybeat.pid 113 | 114 | # SageMath parsed files 115 | *.sage.py 116 | 117 | # Environments 118 | .env 119 | .venv 120 | env/ 121 | venv/ 122 | ENV/ 123 | env.bak/ 124 | venv.bak/ 125 | 126 | # Spyder project settings 127 | .spyderproject 128 | .spyproject 129 | 130 | # Rope project settings 131 | .ropeproject 132 | 133 | # mkdocs documentation 134 | /site 135 | 136 | # mypy 137 | .mypy_cache/ 138 | .dmypy.json 139 | dmypy.json 140 | 141 | # Pyre type checker 142 | .pyre/ 143 | 144 | # pytype static type analyzer 145 | .pytype/ 146 | 147 | # Cython debug symbols 148 | cython_debug/ -------------------------------------------------------------------------------- /00_deployment_info.py: -------------------------------------------------------------------------------- 1 | # 00_deployment_info.py 2 | # 3 | # This script will gather deployment info from your ISE instance. Some of this may be interesting to you, 4 | # but mostly it's just informational. I may modify this playbook in the future to gather 5 | # some useful info that can be used by subsequent playbooks (policy and rule IDs), 6 | # or maybe gather all of the data that we configure for comparison 7 | 8 | import yaml # import pyyaml package 9 | 10 | # open the credentials yaml file and load it into data 11 | with open('credentials.yaml') as f: 12 | data = yaml.safe_load(f) 13 | 14 | # Pull in the Cisco ISE SDK 15 | from ciscoisesdk import IdentityServicesEngineAPI 16 | 17 | # define our API with credentials from credentials.yaml 18 | api = IdentityServicesEngineAPI(username=data['ise_username'], 19 | password=data['ise_password'], 20 | uses_api_gateway=True, 21 | base_url='https://' + data['ise_hostname'], 22 | version=data['ise_version'], 23 | verify=data['ise_verify']) 24 | 25 | # Get our deployment version and put it in a variable 26 | ise_deployment_version = api.pull_deployment_info.get_version() 27 | 28 | # Print the variable plus some deep thoughts 29 | print("Suck it, Python " + ise_deployment_version.text) -------------------------------------------------------------------------------- /01_add_groups.py: -------------------------------------------------------------------------------- 1 | # 01_add_groups.py 2 | # 3 | # This file will create user identity groups in the internal ISE identity store. 4 | # You should not need to modify this file as it will dynamically read all of the groups 5 | # from groupsandusers.yaml. 6 | 7 | import yaml # import pyyaml package 8 | 9 | # open the credentials.yaml file and load it into data 10 | with open('credentials.yaml') as f: 11 | data = yaml.safe_load(f) 12 | 13 | # open the groupsandusers.yaml file and load it into groups 14 | with open('groupsandusers.yaml') as g: 15 | groups = yaml.safe_load(g) 16 | 17 | # Pull in the Cisco ISE SDK 18 | from ciscoisesdk import IdentityServicesEngineAPI 19 | 20 | # define our API with credentials from credentials.yaml 21 | api = IdentityServicesEngineAPI(username=data['ise_username'], 22 | password=data['ise_password'], 23 | uses_api_gateway=True, 24 | base_url='https://' + data['ise_hostname'], 25 | version=data['ise_version'], 26 | verify=data['ise_verify']) 27 | 28 | # iterate through the list of groups and create them using the information in credentials.yaml 29 | for groupname in groups['usergroups']: 30 | api.identity_groups.create_identity_group(name=groupname['name'], 31 | description=groupname['desc'], 32 | parent="NAC Group:NAC:IdentityGroups:User Identity Groups") 33 | print("Creating group:", groupname['name']) -------------------------------------------------------------------------------- /02_add_users.py: -------------------------------------------------------------------------------- 1 | # 02_add_users.py 2 | # 3 | # This file will create users and assign them to the appropriate groups. It should be run after 4 | # 01_add_groups.py as it will query ISE for the created groups and their corresponding ids. 5 | # You should not need to modify this file as it will dynamically read all of the users and groups 6 | # from groupsandusers.yaml. 7 | # 8 | # The group name to group id matching and variable assignment is kind of irritating, but..... 9 | # 10 | # Actually it's more that ISE requires a group id instead of a group name when creating a user. 11 | 12 | import yaml # import pyyaml package 13 | 14 | # open the yaml file and load it into data 15 | with open('credentials.yaml') as f: 16 | data = yaml.safe_load(f) 17 | 18 | # open the groupsandusers.yaml file and load it into groups 19 | with open('groupsandusers.yaml') as g: 20 | groups = yaml.safe_load(g) 21 | 22 | # Pull in the Cisco ISE SDK 23 | from ciscoisesdk import IdentityServicesEngineAPI 24 | 25 | # define our API 26 | api = IdentityServicesEngineAPI(username=data['ise_username'], 27 | password=data['ise_password'], 28 | uses_api_gateway=True, 29 | base_url='https://' + data['ise_hostname'], 30 | version=data['ise_version'], 31 | verify=data['ise_verify']) 32 | 33 | # We're going to iterate through the list of users and create them using the information in credentials.yaml, 34 | # but first we need to get the group id for each group 35 | for groupname in groups['userlist']: 36 | groupinfo = api.identity_groups.get_identity_group_by_name(name=groupname['groups']).response 37 | groupid = groupinfo.IdentityGroup.id 38 | api.internal_user.create_internal_user(name=groupname['name'], 39 | first_name=groupname['firstname'], 40 | last_name=groupname['lastname'], 41 | description=groupname['description'], 42 | password=groups['default_password'], 43 | change_password=False, 44 | identity_groups=groupid, 45 | password_idstore="Internal Users") 46 | print("Creating user:", groupname['name']) -------------------------------------------------------------------------------- /03_create_tacacs_profiles.py: -------------------------------------------------------------------------------- 1 | # 03_create_tacacs_profiles.py 2 | # 3 | # This file will create a TACACS profile and command set that will be assigned to authenticated users. 4 | # For the purposes of this repo, the profile and command set are basic. It will simply assign privilege 5 | # 15 and allow all commands for an authenticated user. 6 | 7 | import yaml # import pyyaml package 8 | 9 | # open the yaml file and load it into data 10 | with open('credentials.yaml') as f: 11 | data = yaml.safe_load(f) 12 | 13 | # Pull in the Cisco ISE SDK 14 | from ciscoisesdk import IdentityServicesEngineAPI 15 | 16 | # define our API 17 | api = IdentityServicesEngineAPI(username=data['ise_username'], 18 | password=data['ise_password'], 19 | uses_api_gateway=True, 20 | base_url='https://' + data['ise_hostname'], 21 | version=data['ise_version'], 22 | verify=data['ise_verify']) 23 | 24 | 25 | # Define our session attributes to send TACACS privilege 15 26 | # I broke this out to separate lines to silence John 27 | # 28 | # Also, since we're name-dropping in comments, shoutout to Sue 29 | # 30 | # https://twitter.com/sueinphilly/status/1457748734218055686?s=20 31 | # 32 | # 10 REM For Sue 33 | # 20 PRINT Hi Sue 34 | # 30 GOTO 20 35 | 36 | attributes = {"sessionAttributeList": [{"type": "MANDATORY", 37 | "name": "priv-lvl", 38 | "value": "15"}, 39 | {"type": "MANDATORY", 40 | "name": "max_priv_lvl", 41 | "value": "15"}]} 42 | 43 | # Create a new TACACS profile called "PermitAllShell" which will give TACACS privilege 15 44 | api.tacacs_profile.create_tacacs_profile(name="PermitAllShell", 45 | description="Permit all", 46 | session_attributes=attributes 47 | ) 48 | 49 | # Create a new TACACS command set called "PermitAllCommands" which will allow all commands 50 | api.tacacs_command_sets.create_tacacs_command_sets(name="PermitAllCommands", 51 | description="PermitAllCommands Command Set", 52 | permit_unmatched=True 53 | ) -------------------------------------------------------------------------------- /04_change_tacacs_authc_source.py: -------------------------------------------------------------------------------- 1 | # 04_change_tacacs_authc_source.py 2 | # 3 | # This file will simply change the identity source of the default device administration policy to use 4 | # internal users instead of all user stores 5 | # 6 | # It's not strictly necessary to do this, but it can sometimes speed up authc for a lab environment 7 | # that isn't talking to an AD or LDAP for device administrator user information. 8 | # 9 | # This was also the second most annoying script to develop because there are two settings that we need 10 | # from ISE (policy id and rule id) in order to change the setting. ISE's API also required a bunch of 11 | # seemingly random fields to be sent all so we can set this: identitySourceName: "Internal Users" 12 | 13 | import yaml # import pyyaml package 14 | 15 | # open the yaml file and load it into data 16 | with open('credentials.yaml') as f: 17 | data = yaml.safe_load(f) 18 | 19 | # Pull in the Cisco ISE SDK 20 | from ciscoisesdk import IdentityServicesEngineAPI 21 | 22 | # define our API 23 | api = IdentityServicesEngineAPI(username=data['ise_username'], 24 | password=data['ise_password'], 25 | uses_api_gateway=True, 26 | base_url='https://' + data['ise_hostname'], 27 | version=data['ise_version'], 28 | verify=data['ise_verify']) 29 | 30 | # First we have to get the information for all device admin policy sets 31 | policysets = api.device_administration_policy_set.get_device_admin_policy_sets() 32 | 33 | # Then we have to pull the policy ID for the Default policy set 34 | policyId = policysets.response.response[0].id 35 | 36 | # Then we use the policy ID to grab all of the device administration authc rules 37 | rulesets = api.device_administration_authentication_rules.get_device_admin_authentication_rules(policy_id=policyId) 38 | 39 | # And finally we pull the rule ID from the "Default" policy set rule 40 | ruleId = rulesets.response.response[0].rule.id 41 | 42 | # But wait, the ISE API needs some mandatory fields for some reason 43 | mandatorydata = { 44 | "default": True, 45 | "name": "Default" 46 | } 47 | 48 | # And now, after all of that work, we can change the identity source in the default authc policy to "Internal Users" 49 | # I had to add the if_* rules because the ISE API changed between 3.1 and 3.1p1 and now they're mandatory 50 | # for some ridiculous reason 51 | api.device_administration_authentication_rules.update_device_admin_authentication_rule_by_id(id=ruleId, 52 | policy_id=policyId, 53 | if_auth_fail="REJECT", 54 | if_user_not_found="REJECT", 55 | if_process_fail="DROP", 56 | rule=mandatorydata, 57 | identity_source_name="Internal Users") -------------------------------------------------------------------------------- /05_create_tacacs_authz_policies.py: -------------------------------------------------------------------------------- 1 | # 05_create_tacacs_authz_policies.py 2 | # 3 | # This file will create an authorization policy rule in the default device administation policy set 4 | # that will authorize anyone in the "netadmin" user group and assign them privilege 15 in TACACS 5 | # for device administration 6 | 7 | import yaml # import pyyaml package 8 | 9 | # open the yaml file and load it into data 10 | with open('credentials.yaml') as f: 11 | data = yaml.safe_load(f) 12 | 13 | # Pull in the Cisco ISE SDK 14 | from ciscoisesdk import IdentityServicesEngineAPI 15 | 16 | # define our API 17 | api = IdentityServicesEngineAPI(username=data['ise_username'], 18 | password=data['ise_password'], 19 | uses_api_gateway=True, 20 | base_url='https://' + data['ise_hostname'], 21 | version=data['ise_version'], 22 | verify=data['ise_verify']) 23 | 24 | # First we have to get the information for all device admin policy sets 25 | policysets = api.device_administration_policy_set.get_device_admin_policy_sets() 26 | 27 | # Then we have to pull the policy ID for the Default policy set 28 | policyId = policysets.response.response[0].id 29 | 30 | # declare our rule and condition data because we have to pass it all in one big chunky dictionary/ 31 | # array/list/blob/whatever 32 | ruledata = { 33 | "default" : False, 34 | "name" : "AdminAccess", 35 | "rank" : 0, 36 | "condition" : { 37 | "conditionType" : "ConditionAttributes", 38 | "isNegate" : False, 39 | "dictionaryName" : "IdentityGroup", 40 | "attributeName" : "Name", 41 | "operator" : "equals", 42 | "attributeValue" : "User Identity Groups:netadmin" 43 | } 44 | } 45 | 46 | # Create our device administration authz rule 47 | api.device_administration_authorization_rules.create_device_admin_authorization_rule(policy_id=policyId, 48 | commands=[ "PermitAllCommands" ], 49 | profile="PermitAllShell", 50 | rule=ruledata) -------------------------------------------------------------------------------- /06_create_sgts.py: -------------------------------------------------------------------------------- 1 | # 06_create-sgts.py 2 | # 3 | # This file will create Scalable Group Tags (SGTs) in ISE that can be used for policy 4 | # with Cisco Software-Defined Access. 5 | # 6 | # In a typical SD-Access environment these SGTs would be created with Cisco DNA Center, so this 7 | # script isn't strictly necessary, however these SGTs need to exist before the authorization policies 8 | # are created. 9 | # 10 | # Either way, you should not need to modify this file - You should list your SGTs in policy.yaml 11 | import yaml # import pyyaml package 12 | 13 | # open the yaml file and load it into data 14 | with open('credentials.yaml') as f: 15 | data = yaml.safe_load(f) 16 | 17 | # open the policy.yaml file and load it into the policy variable 18 | with open('policy.yaml') as g: 19 | policy = yaml.safe_load(g) 20 | 21 | # Pull in the Cisco ISE SDK 22 | from ciscoisesdk import IdentityServicesEngineAPI 23 | 24 | # define our API 25 | api = IdentityServicesEngineAPI(username=data['ise_username'], 26 | password=data['ise_password'], 27 | uses_api_gateway=True, 28 | base_url='https://' + data['ise_hostname'], 29 | version=data['ise_version'], 30 | verify=data['ise_verify']) 31 | 32 | # iterate through the list of SGTs in policy.yaml and create each one 33 | for sgtlist in policy['sgtvars']: 34 | api.security_groups.create_security_group(name=sgtlist['sgtname'], 35 | description=sgtlist['sgtdesc'], 36 | value=-1) 37 | print("Creating SGT:", sgtlist['sgtname']) -------------------------------------------------------------------------------- /07_change_authentication_source.py: -------------------------------------------------------------------------------- 1 | # 07_change_authentication_source.py 2 | # 3 | # This file will simply change the identity source of the default and dot1x network access policy to use 4 | # internal users instead of all user stores, similar to 04_change_tacacs_authc_source.py 5 | # 6 | # It's not strictly necessary to do this, but it can sometimes speed up authc for a lab environment 7 | # that doesn't have to talk to an AD or LDAP for network access user information. 8 | # 9 | # This was the most annoying playbook to develop because there are some things that we need 10 | # from ISE (policy id and a bunch of rule/condition ids) in order to change the setting. To get these 11 | # values I had to create some really ugly and irritating JSON filters and use loops. 12 | # There was probably an easier way to get the values (narrator: THERE WAS), 13 | # but I'm new to all of this nonsense and it's all I could come up with on my own. 14 | # 15 | # ISE's API also required a bunch of seemingly random fields to be sent all so we can set 16 | # this: identitySourceName: "Internal Users" 17 | 18 | import yaml # import pyyaml package 19 | 20 | # open the yaml file and load it into data 21 | with open('credentials.yaml') as f: 22 | data = yaml.safe_load(f) 23 | 24 | # Pull in the Cisco ISE SDK 25 | from ciscoisesdk import IdentityServicesEngineAPI 26 | 27 | # define our API 28 | api = IdentityServicesEngineAPI(username=data['ise_username'], 29 | password=data['ise_password'], 30 | uses_api_gateway=True, 31 | base_url='https://' + data['ise_hostname'], 32 | version=data['ise_version'], 33 | verify=data['ise_verify']) 34 | 35 | # First we have to get the information for all device admin policy sets 36 | policysets = api.network_access_policy_set.get_network_access_policy_sets() 37 | 38 | # Then we have to pull the policy ID for the Default policy set 39 | policyId = policysets.response.response[0].id 40 | 41 | # Then we use the policy ID to grab all of the device administration authc rules 42 | rulesets = api.network_access_authentication_rules.get_network_access_authentication_rules(policy_id=policyId) 43 | 44 | # Now we need some condition and rule IDs 45 | # This is stupid, everything is stupid, I hate JSON 46 | # I am probably also doing this wrong (yep) 47 | # I don't care 48 | defaultruleId = [x['rule.id'] for x in rulesets.response.response if x['rule.name'] == 'Default'] 49 | dot1xruleId = [x['rule.id'] for x in rulesets.response.response if x['rule.name'] == 'Dot1X'] 50 | 51 | # These IDs are nested so we have to do some stupid crap to get them - I learned later on that there's a better 52 | # way to get these values, but I'm leaving it here for posterity 53 | # 54 | # iterate through all of the condition children in the response to find the conditions that we want 55 | # 56 | # I had to use try/except here because not every element had a "children" attribute, so we ignore those ones 57 | for z in range(len(rulesets.response.response)): 58 | try: 59 | wireddot1xcondId = [y['id'] for y in rulesets.response.response[z].rule.condition.children if y['name'] == 'Wired_802.1X'] 60 | wirelessdot1xcondId = [y['id'] for y in rulesets.response.response[z].rule.condition.children if y['name'] == 'Wireless_802.1X'] 61 | except AttributeError: pass 62 | 63 | # But wait, the ISE API needs some mandatory fields for some reason 64 | defaultruledata = { 65 | "default": True, 66 | "name": "Default", 67 | "rank": 2 68 | } 69 | 70 | dot1xruledata = { 71 | "default": False, 72 | "name": "Dot1X", 73 | "rank": 1, 74 | "condition" : { 75 | "conditionType" : "ConditionOrBlock", 76 | "isNegate" : False, 77 | "children" : [ { 78 | "conditionType" : "ConditionReference", 79 | "name" : "Wired_802.1X", 80 | "id" : wireddot1xcondId[0], 81 | }, { 82 | "conditionType" : "ConditionReference", 83 | "name" : "Wireless_802.1X", 84 | "id" : wirelessdot1xcondId[0], 85 | } ] 86 | } 87 | } 88 | 89 | # And now, after all of that work, we can change the identity source in the default and dot1x authc policies to "Internal Users" 90 | # I had to add the if_* rules because the ISE API changed between 3.1 and 3.1p1 and now they're mandatory 91 | # for some ridiculous reason 92 | api.network_access_authentication_rules.update_network_access_authentication_rule_by_id(id=defaultruleId[0], 93 | policy_id=policyId, 94 | if_auth_fail="REJECT", 95 | if_user_not_found="REJECT", 96 | if_process_fail="DROP", 97 | rule=defaultruledata, 98 | identity_source_name="Internal Users") 99 | 100 | api.network_access_authentication_rules.update_network_access_authentication_rule_by_id(id=dot1xruleId[0], 101 | policy_id=policyId, 102 | if_auth_fail="REJECT", 103 | if_user_not_found="REJECT", 104 | if_process_fail="DROP", 105 | rule=dot1xruledata, 106 | identity_source_name="Internal Users") 107 | -------------------------------------------------------------------------------- /08_create_authorization_profiles.py: -------------------------------------------------------------------------------- 1 | # 08_create_authorization_profiles.py 2 | # 3 | # This file will create authorization profiles for wired and wireless network access users. 4 | # The reason we need to have separate profiles for wired and wireless is because Cisco WLCs 5 | # accept a different RADIUS attribute for VLAN assignments than Cisco switches. Please don't ask me why. 6 | # 7 | # All data for the profiles (names and VLANs) is pulled from the policy.yaml file and then there will be 8 | # a wired and wireless profile created for each. 9 | # 10 | # I'm using a different way to wrap strings around a variable in the description field below 11 | # because apparently the way I've been doing it is some old way and Google failed me 12 | # 13 | # You should not have to edit this file unless you want to add more fields or customize further. 14 | 15 | import yaml # import pyyaml package 16 | 17 | # open the yaml file and load it into data 18 | with open('credentials.yaml') as f: 19 | data = yaml.safe_load(f) 20 | 21 | # open the policy.yaml file and load it into the policy variable 22 | with open('policy.yaml') as g: 23 | policy = yaml.safe_load(g) 24 | 25 | # Pull in the Cisco ISE SDK 26 | from ciscoisesdk import IdentityServicesEngineAPI 27 | 28 | # define our API 29 | api = IdentityServicesEngineAPI(username=data['ise_username'], 30 | password=data['ise_password'], 31 | uses_api_gateway=True, 32 | base_url='https://' + data['ise_hostname'], 33 | version=data['ise_version'], 34 | verify=data['ise_verify']) 35 | 36 | # We'll use this set this so that we don't try to create duplicate profiles (the script will break) 37 | already_created = set() 38 | 39 | # iterate through policies in policy.yaml to create each profile 40 | # I probably should have done this in one loop - I'll come back and fix that later. 41 | for policies in policy['policies']: 42 | if policies['policy'] not in already_created: # check for duplicate 43 | 44 | # some VLAN data for our command 45 | vlandata = { 46 | "nameID" : policies['vlan'], 47 | "tagID" : 1 48 | } 49 | 50 | # Create our wired profiles 51 | api.authorization_profile.create_authorization_profile(name=policies['policy'] + "_wired", 52 | description="wired profile for " + policies['policy'] + " users - automated", 53 | access_type="ACCESS_ACCEPT", 54 | authz_profile_type="SWITCH", 55 | easywired_session_candidate=False, 56 | profile_name="Cisco", 57 | service_template=False, 58 | track_movement=False, 59 | vlan=vlandata 60 | ) 61 | print("Creating Authorization Profile: " + policies['policy'] + "_wired") 62 | already_created.add(policies['policy']) # add the policy to our set for duplicate checking 63 | 64 | # We'll use this set this so that we don't try to create duplicate profiles (the script will break) 65 | already_created = set() 66 | 67 | # Iterate through policies in policy.yaml to create each profile 68 | for policies in policy['policies']: 69 | if policies['policy'] not in already_created: # check for duplicate 70 | 71 | # Some attribute data for our command 72 | attributedata = [{ 73 | "leftHandSideDictionaryAttribue" : { 74 | "AdvancedAttributeValueType" : "AdvancedDictionaryAttribute", 75 | "dictionaryName" : "Airespace", 76 | "attributeName" : "Airespace-Interface-Name" 77 | }, 78 | "rightHandSideAttribueValue" : { 79 | "AdvancedAttributeValueType" : "AttributeValue", 80 | "value" : policies['vlan'] 81 | } 82 | }] 83 | 84 | # Create our wireless profiles 85 | api.authorization_profile.create_authorization_profile(name=policies['policy'] + "_wireless", 86 | description="wireless profile for " + policies['policy'] + " users - automated", 87 | access_type="ACCESS_ACCEPT", 88 | authz_profile_type="SWITCH", 89 | easywired_session_candidate=False, 90 | profile_name="Cisco", 91 | service_template=False, 92 | track_movement=False, 93 | advanced_attributes=attributedata 94 | ) 95 | print("Creating Authorization Profile: " + policies['policy'] + "_wireless") 96 | already_created.add(policies['policy']) # add the policy to our set for duplicate checking -------------------------------------------------------------------------------- /09_create_authorization_policies.py: -------------------------------------------------------------------------------- 1 | # 09_create_authorization_policies.py 2 | # 3 | # This file will create authorization policies in the "Default" policy set for wired and wireless network 4 | # access users. 5 | # 6 | # All data for the profiles (names, groups, VLANs, and SGTs) is pulled from the policy.yaml file and there will be 7 | # a wired and wireless policy created for each entry. 8 | # 9 | # You should not have to edit this file unless you want to add more fields or customize further. 10 | 11 | import yaml # import pyyaml package 12 | 13 | # open the yaml file and load it into data 14 | with open('credentials.yaml') as f: 15 | data = yaml.safe_load(f) 16 | 17 | # open the policy.yaml file and load it into the policy variable 18 | with open('policy.yaml') as g: 19 | policy = yaml.safe_load(g) 20 | 21 | # Pull in the Cisco ISE SDK 22 | from ciscoisesdk import IdentityServicesEngineAPI 23 | 24 | # define our API 25 | api = IdentityServicesEngineAPI(username=data['ise_username'], 26 | password=data['ise_password'], 27 | uses_api_gateway=True, 28 | base_url='https://' + data['ise_hostname'], 29 | version=data['ise_version'], 30 | verify=data['ise_verify']) 31 | 32 | # First we have to get the information for all network access policy sets 33 | policysets = api.network_access_policy_set.get_network_access_policy_sets() 34 | 35 | # Then we have to pull the policy ID for the Default policy set 36 | policyId = policysets.response.response[0].id 37 | 38 | # Then we use the policy ID to grab all of the network access authc rules 39 | rulesets = api.network_access_authentication_rules.get_network_access_authentication_rules(policy_id=policyId) 40 | 41 | # Now we need some condition and rule IDs 42 | # This is still stupid, everything is still stupid, I still hate JSON 43 | # I am still probably doing this wrong (I was) 44 | # I still don't care 45 | defaultruleId = [x['rule.id'] for x in rulesets.response.response if x['rule.name'] == 'Default'] 46 | dot1xruleId = [x['rule.id'] for x in rulesets.response.response if x['rule.name'] == 'Dot1X'] 47 | 48 | # Some people, including Brooks from Twitter, think 49 | # that 50 | # multiline comments ma 51 | # ke things unreadable. 52 | 53 | # These IDs are nested so we have to do some stupid crap to get them - I learned later on that there's a better 54 | # way to get these values, but I'm leaving it here for posterity 55 | # 56 | # iterate through all of the condition children in the response to find the conditions that we want 57 | # 58 | # I had to use try/except here because not every element had a "children" attribute, so we ignore those ones 59 | for z in range(len(rulesets.response.response)): 60 | try: 61 | wireddot1xcondId = [y['id'] for y in rulesets.response.response[z].rule.condition.children if y['name'] == 'Wired_802.1X'] 62 | wirelessdot1xcondId = [y['id'] for y in rulesets.response.response[z].rule.condition.children if y['name'] == 'Wireless_802.1X'] 63 | except AttributeError: pass 64 | 65 | for policies in policy['policies']: 66 | 67 | # create our policy rule object, or array, or list, or dictionary, or whatever. 68 | 69 | wiredpolicyrule = { 70 | "default" : False, 71 | "name" : policies['usergroup'] + "_users_wired", 72 | "rank" : 1, 73 | "condition" : { 74 | "conditionType" : "ConditionAttributes", 75 | "isNegate" : False, 76 | "dictionaryName" : "IdentityGroup", 77 | "attributeName" : "Name", 78 | "operator" : "equals", 79 | "attributeValue" : "User Identity Groups:" + policies['usergroup'] 80 | } 81 | } 82 | 83 | wirelesspolicyrule = { 84 | "default" : False, 85 | "name" : policies['usergroup'] + "_users_wireless", 86 | "rank" : 0, 87 | "condition" : { 88 | "conditionType" : "ConditionAndBlock", 89 | "isNegate" : False, 90 | "children" : [ { 91 | "conditionType" : "ConditionAttributes", 92 | "isNegate" : False, 93 | "dictionaryName" : "IdentityGroup", 94 | "attributeName" : "Name", 95 | "operator" : "equals", 96 | "attributeValue" : "User Identity Groups:" + policies['usergroup'] 97 | }, { 98 | "conditionType" : "ConditionReference", 99 | "isNegate" : False, 100 | "name" : "Wireless_802.1X", 101 | "id" : wirelessdot1xcondId[0], 102 | } ] 103 | } 104 | } 105 | 106 | # Create rules for both wireless and wired users because some of our devices expect different RADIUS attributes 107 | api.network_access_authorization_rules.create_network_access_authorization_rule(policy_id=policyId, 108 | security_group=policies['sgt'], 109 | profile=[policies['policy'] + "_wireless"], 110 | rule=wirelesspolicyrule 111 | ) 112 | 113 | api.network_access_authorization_rules.create_network_access_authorization_rule(policy_id=policyId, 114 | security_group=policies['sgt'], 115 | profile=[policies['policy'] + "_wired"], 116 | rule=wiredpolicyrule 117 | ) -------------------------------------------------------------------------------- /10_create_guest_authz_profiles.py: -------------------------------------------------------------------------------- 1 | # 10_create_guest_authz_profiles.py 2 | # 3 | # This file will create authorization profiles required for a wired guest workflow in ISE. 4 | # 5 | # There are two profiles for this workflow: 6 | # 7 | # 1) The first profile (GuestRedirect) allows unauthenticted users/devices onto the network with limited 8 | # connectivity in the Guest VLAN allowing them to only connect to ISE to access the guest portal 9 | # 10 | # 2) Once the user authenticates with the guest portal, the second profile (GuestAccess) will allow them 11 | # full network access in the Guest VLAN. 12 | # 13 | # This workflow services two purposes: 14 | # 15 | # 1) Wired guest users will receive a guest portal to be allowed access to the network 16 | # 17 | # 2) Allow other devices limited network access so that ISE can profile them, specifically access points for the 18 | # later scripts 19 | # 20 | # You should not have to edit this file unless you want to add more fields or customize further. 21 | 22 | import yaml # import pyyaml package 23 | 24 | # open the yaml file and load it into data 25 | with open('credentials.yaml') as f: 26 | data = yaml.safe_load(f) 27 | 28 | # open the policy.yaml file and load it into the policy variable 29 | with open('policy.yaml') as g: 30 | policy = yaml.safe_load(g) 31 | 32 | # Pull in the Cisco ISE SDK 33 | from ciscoisesdk import IdentityServicesEngineAPI 34 | 35 | # define our API 36 | api = IdentityServicesEngineAPI(username=data['ise_username'], 37 | password=data['ise_password'], 38 | uses_api_gateway=True, 39 | base_url='https://' + data['ise_hostname'], 40 | version=data['ise_version'], 41 | verify=data['ise_verify']) 42 | 43 | # create our profile object, or array, or list, or dictionary, or whatever. 44 | 45 | vlandata = { 46 | "nameID" : policy['guest']['vlan'], 47 | "tagID" : 1 48 | } 49 | 50 | webredirectdata = { 51 | "WebRedirectionType" : "CentralizedWebAuth", 52 | "acl" : policy['guest']['redirectacl'], 53 | "portalName" : "Self-Registered Guest Portal (default)", 54 | "staticIPHostNameFQDN" : data['ise_hostname'], 55 | "displayCertificatesRenewalMessages" : True 56 | } 57 | 58 | # Create the Guest Redirect profile 59 | api.authorization_profile.create_authorization_profile(name="GuestRedirect", 60 | description="Guest Redirect profile", 61 | access_type="ACCESS_ACCEPT", 62 | authz_profile_type="SWITCH", 63 | easywired_session_candidate=False, 64 | profile_name="Cisco", 65 | service_template=False, 66 | track_movement=False, 67 | vlan=vlandata, 68 | web_redirection=webredirectdata 69 | ) 70 | 71 | # Create the Guest Access profile 72 | api.authorization_profile.create_authorization_profile(name="GuestAccess", 73 | description="Guest Access profile", 74 | access_type="ACCESS_ACCEPT", 75 | authz_profile_type="SWITCH", 76 | easywired_session_candidate=False, 77 | profile_name="Cisco", 78 | service_template=False, 79 | track_movement=False, 80 | vlan=vlandata, 81 | ) -------------------------------------------------------------------------------- /11_create_guest_authz_policies.py: -------------------------------------------------------------------------------- 1 | # 11_create_guest_authz_policies.py 2 | # 3 | # This file will create authorization policies required for a wired guest workflow in ISE. 4 | # 5 | # See the comments in 10_create_guest_authz_profiles.py for more info about the wired guest workflow. 6 | # 7 | # The way the rules are ordered, any wired non-authenticated devices or clients will hit the GuestRedirect rule 8 | # and get limited network access in the Guest VLAN. 9 | # 10 | # This will allow a wired user to access the guest portal and authenticate before being put back into 11 | # the process and hitting the GuestAccess rule. 12 | # 13 | # You should not have to edit this file unless you want to add more fields or customize further. 14 | 15 | import yaml # import pyyaml package 16 | 17 | # open the yaml file and load it into data 18 | with open('credentials.yaml') as f: 19 | data = yaml.safe_load(f) 20 | 21 | # open the policy.yaml file and load it into the policy variable 22 | with open('policy.yaml') as g: 23 | policy = yaml.safe_load(g) 24 | 25 | # Pull in the Cisco ISE SDK 26 | from ciscoisesdk import IdentityServicesEngineAPI 27 | 28 | # define our API 29 | api = IdentityServicesEngineAPI(username=data['ise_username'], 30 | password=data['ise_password'], 31 | uses_api_gateway=True, 32 | base_url='https://' + data['ise_hostname'], 33 | version=data['ise_version'], 34 | verify=data['ise_verify']) 35 | 36 | # First we have to get the information for all device admin policy sets 37 | policysets = api.network_access_policy_set.get_network_access_policy_sets() 38 | 39 | # Then we have to pull the policy ID for the Default policy set 40 | policyId = policysets.response.response[0].id 41 | 42 | # In earlier scripts I used some really convoluted ways to get this stupid condition ID. 43 | # This way is much simpler 44 | # Get the Wired_MAB network access condition ID and set it to a variable 45 | wiredmab = api.network_access_conditions.get_network_access_condition_by_name(name="Wired_MAB") 46 | 47 | # Oh crap I just remembered that I could have used this method for some of the previous scripts 48 | wiredmabId = wiredmab.response.response.id 49 | 50 | # create our policy rule object, or array, or list, or dictionary, or whatever. 51 | guestredirectrule = { 52 | "default" : False, 53 | "name" : "GuestRedirect", 54 | "rank" : 9, 55 | "condition" : { 56 | "conditionType" : "ConditionReference", 57 | "isNegate" : False, 58 | "name" : "Wired_MAB", 59 | "id" : wiredmabId 60 | } 61 | } 62 | 63 | guestaccessrule = { 64 | "default" : False, 65 | "name" : "GuestAccess", 66 | "rank" : 8, 67 | "condition" : { 68 | "conditionType" : "ConditionAndBlock", 69 | "isNegate" : False, 70 | "children" : [ { 71 | "conditionType" : "ConditionAttributes", 72 | "isNegate" : False, 73 | "dictionaryName" : "IdentityGroup", 74 | "attributeName" : "Name", 75 | "operator" : "equals", 76 | "attributeValue" : "Endpoint Identity Groups:GuestEndpoints" 77 | }, { 78 | "conditionType" : "ConditionReference", 79 | "isNegate" : False, 80 | "name" : "Wired_MAB", 81 | "id" : wiredmabId 82 | } ] 83 | } 84 | } 85 | 86 | # Create rules for Guest Redirect and Guest Access 87 | api.network_access_authorization_rules.create_network_access_authorization_rule(policy_id=policyId, 88 | security_group=policy['guest']['sgt'], 89 | profile=["GuestAccess"], 90 | rule=guestaccessrule 91 | ) 92 | 93 | api.network_access_authorization_rules.create_network_access_authorization_rule(policy_id=policyId, 94 | security_group=policy['guest']['sgt'], 95 | profile=["GuestRedirect"], 96 | rule=guestredirectrule 97 | ) 98 | -------------------------------------------------------------------------------- /12_access_point_profiling.py: -------------------------------------------------------------------------------- 1 | # 12_access_point_profiling.py 2 | # 3 | # This file will create authorization profiles and policies that will detect Cisco Access Points 4 | # and place them in a VLAN. 5 | # 6 | # It relies on the Guest workflow configured earlier in order to profile the AP during the GuestRedirect flow. 7 | # 8 | # You probably want to enable Change of Authorization (CoA) in ISE so that the AP can be re-authorized after it is 9 | # profiled. 10 | # 11 | # The VLAN that the AP will be placed on is defined in policy.yaml 12 | # 13 | # You should not have to edit this file unless you want to add more fields or customize further. 14 | 15 | import yaml # import pyyaml package 16 | 17 | # open the yaml file and load it into data 18 | with open('credentials.yaml') as f: 19 | data = yaml.safe_load(f) 20 | 21 | # open the policy.yaml file and load it into the policy variable 22 | with open('policy.yaml') as g: 23 | policy = yaml.safe_load(g) 24 | 25 | # Pull in the Cisco ISE SDK 26 | from ciscoisesdk import IdentityServicesEngineAPI 27 | 28 | # define our API 29 | api = IdentityServicesEngineAPI(username=data['ise_username'], 30 | password=data['ise_password'], 31 | uses_api_gateway=True, 32 | base_url='https://' + data['ise_hostname'], 33 | version=data['ise_version'], 34 | verify=data['ise_verify']) 35 | 36 | # First we have to get the information for all device admin policy sets 37 | policysets = api.network_access_policy_set.get_network_access_policy_sets() 38 | 39 | # Then we have to pull the policy ID for the Default policy set 40 | policyId = policysets.response.response[0].id 41 | 42 | # create our policy rule object, or array, or list, or dictionary, or whatever. 43 | # Jose told me to use underscores in my variable names 44 | # 45 | ap_policy_rule = { 46 | "default" : False, 47 | "name" : "CiscoAccessPoint", 48 | "rank" : 8, 49 | "condition" : { 50 | "conditionType" : "ConditionAttributes", 51 | "isNegate" : False, 52 | "dictionaryName" : "EndPoints", 53 | "attributeName" : "EndPointPolicy", 54 | "operator" : "equals", 55 | "attributeValue" : "Cisco-Device:Cisco-Access-Point" 56 | } 57 | } 58 | 59 | vlan_data = { 60 | "nameID" : policy['accesspoint']['vlan'], 61 | "tagID" : 1 62 | } 63 | 64 | # Create our Cisco Access point authz profile and rule 65 | api.authorization_profile.create_authorization_profile(name="CiscoAccessPoint", 66 | description="Cisco Access point profile", 67 | access_type="ACCESS_ACCEPT", 68 | authz_profile_type="SWITCH", 69 | easywired_session_candidate=False, 70 | profile_name="Cisco", 71 | service_template=False, 72 | track_movement=False, 73 | vlan=vlan_data, 74 | ) 75 | 76 | api.network_access_authorization_rules.create_network_access_authorization_rule(policy_id=policyId, 77 | profile=["CiscoAccessPoint"], 78 | rule=ap_policy_rule 79 | ) -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Python Scripts for Cisco Identity Services Engine (ISE) 2 | 3 | A set of [Python](https://www.python.org/) scripts to configure a freshly installed Cisco Identity Services Engine (ISE) for simple operation; in my case, a basic [Cisco Software-Defined Access](https://www.cisco.com/c/en/us/solutions/enterprise-networks/software-defined-access/index.html) environment. 4 | 5 | > **Note:** This repo is my second shot at automating ISE, and is mostly the same as my [Ansible project](https://github.com/eiddor/ise-automation-ansible) in terms of functionality. I even used the same YAML settings files so you can use either method without any modification. 6 | ### Features 7 | These scripts will configure the following in ISE: 8 | 9 | * local user groups (`01_add_groups.py`) 10 | * local user identities (`02_add_users.py`) 11 | * a simple TACACS profile and command set for privilege 15 access (`03_create_tacacs_profiles.py`) 12 | * TACACS policies in the default policy set (`05_create_tacacs_authz_policies.py`) 13 | * Scalable Group Tags (SGT) to allow our authentication rules to work (`06_create_sgts.py`) 14 | * network access authorization rules to places users in the appropriate VLANs (wired and wireless) (`08_create_authorization_profiles.py`) 15 | * network access policies to authorize users and assign SGTs (`09_create_authorization_policies.py`) 16 | * a complete wired guest workflow with redirection, portal, and SGT(`10_create_guest_authz_profiles.py` & `11_create_guest_authz_policies.py`) 17 | * Cisco access point profiling (using the wired guest flow) and authorization profiles (`12_access_point_profiling.py`) 18 | 19 | The ISE resources that are configured with these scripts are enough to support a basic Cisco SD-Access network including: 20 | 21 | * TACACS authentication for network devices 22 | * dot1x authentication and authorization for multiple users 23 | * wired guest access 24 | * multiple Scalable Group Tags (SGTs) 25 | * Cisco access point profiling and authorization 26 | 27 | ### Background 28 | 29 | I administer a lab environment that is used to demonstrate Cisco Software-Defined Access for customers. When new versions of [Cisco ISE](https://www.cisco.com/c/en/us/products/security/identity-services-engine/index.html) or [DNA Center](https://www.cisco.com/c/en/us/products/cloud-systems-management/dna-center/index.html) are released, I do a fresh installation of both so that I can test the new versions with the lab workflow. This involves installing each piece of software and then configuring them both to the point where I can start going through the lab guide. 30 | 31 | After watching a demo of the collections in [this repo](https://github.com/hosukw/20210928-IBN-Demo) that use Terraform and Ansible to spin-up and configure ISE in AWS, I was inspired to setup something similar to assist in my configuration process when testing new versions. 32 | 33 | I started with almost zero API experience beyond installing Postman on my workstation in the past and never using it. Prior to this project I had run exactly one Ansible playbook in my life, and that was six years ago. Needless to say, I was (and still am) completely green with this stuff, so it was a complete learning experience for me, especially not having a background in code or data structures. 34 | 35 | Once I got the [Ansible collection](https://github.com/eiddor/ise-automation-ansible) done, I decided to teach myself Python the hard way by converting everything into Python scripts. It was a challenge because I had zero Python experience, but I got it done in a couple of days with the help of Google. 36 | 37 | As a bonus: You will notice some snark in the script comments as well, which stemmed from some frustrations that I ran into while learning. Some, but not all, of these comments were copied from the companion Ansible playbooks, because the frustrations were mostly the same. 38 | ### Requirements 39 | 40 | #### Server 41 | * [Cisco Identity Services Engine](https://www.cisco.com/c/en/us/products/security/identity-services-engine/index.html) (ISE) 3.1 or higher 42 | 43 | > **Note:** Some of these scripts may work with ISE 3.0, but 3.1 is required for the policy stuff. 44 | #### Workstation 45 | * [Python](https://www.python.org/) 3.6+ 46 | * [Cisco ISE SDK v1.0.0](https://github.com/CiscoISE/ciscoisesdk)+ 47 | 48 | ### Quick Start 49 | 50 | If you just want to see these in action, you can run them against a Cisco DevNet [ISE 3.1 APIs, Ansible, and Automation](https://devnetsandbox.cisco.com/RM/Diagram/Index/ad4bb2ae-bb67-4d93-9f0d-2a6a04792e2e?diagramType=Topology) sandbox instance without any customization: 51 | 52 | * On your local workstation install [requirements](#requirements) above: 53 | 54 | **Cisco ISE SDK:** 55 | > `sudo pip install ciscoisesdk` 56 | 57 | * Reserve a sandbox in DevNet and connect to it per their instructions 58 | 59 | * In ISE, enable **ERS** and **Open API** settings in: _Administration | Settings | API Settings | API Service Settings_ 60 | 61 | ![ISE API Settings](images/ise_api.png) 62 | 63 | * Run the scripts one at a time like this: 64 | 65 | > `$ python 01_add_groups.py` 66 | 67 | > `$ python 02_add_users.py` 68 | 69 | > `$ python 03_create_tacacs_profiles.py` 70 | 71 | * You can verify the changes in the ISE GUI after each script if you're curious 72 | ### Usage Notes 73 | 74 | Although my use-case for these scripts involves a fresh deployment of ISE to support a Cisco SD-Access topology, they can absolutely be modified and used in a brownfield ISE environment without SDA. 75 | 76 | I'm going to try to make the project self-documenting via comments as best I can, but here's a rough guide to get started: 77 | 78 | `credentials.yaml` - Contains the ISE deployment information such as hostname, username, and password 79 | 80 | `groupsandusers.yaml` - Contains the internal identity groups and users that will be configured by the scripts 81 | 82 | `policy.yaml` - Contains the policy/profile information that will be configured by the scripts 83 | 84 | * I developed these scripts using Cisco DevNet's [ISE 3.1 APIs, Ansible, and Automation](https://devnetsandbox.cisco.com/RM/Diagram/Index/ad4bb2ae-bb67-4d93-9f0d-2a6a04792e2e?diagramType=Topology) sandbox, so they should be useable there without any modification (See [Quick Start](#quick-start) section) 85 | 86 | ### Other ISE Settings 87 | 88 | One day I will post a summary of some of the ISE settings that I change to make my life a little easier following an install. These settings will be pretty specific to a lab environment and not suggested for production. 89 | ### TODO 90 | 91 | * better documentation 92 | * better optimization of the scripts 93 | * result feedback from the scripts 94 | * error checking and handling 95 | * clean up the scripts to match the Python style guide (Hi, Jose!) 96 | * add more optional fields to make this useful in the real world 97 | * redo this whole mess in Python before I retire (**NOTE**: I DID IT) 98 | 99 | ### Acknowledgements 100 | 101 | Google. 102 | 103 | I also want to give a shoutout to the developers of the [Cisco ISE SDK](https://github.com/CiscoISE/ciscoisesdk). It made things much much easier for me. 104 | ### Questions? 105 | 106 | Please open an issue if you have any questions or suggestions. 107 | 108 | I developed these scripts for my own use, so I do want to keep them as clean as I can, but if you think they can be improved or optimized, feel free to submit a PR. 109 | 110 | -------------------------------------------------------------------------------- /credentials.yaml: -------------------------------------------------------------------------------- 1 | 2 | # credentials.yaml file describes the access details for your ISE instance 3 | # There are other ways to do this, including using the hosts file or encyrption, 4 | # so feel free to customize to suite your environment 5 | # 6 | # This file is referenced by default in all of the playbooks 7 | 8 | ise_hostname: 10.10.20.77 # the IP address or hostname of your ISE instance 9 | ise_username: admin # your ISE admin username (usually "admin" unless you've created an API user) 10 | ise_password: C1sco12345 # your ISE admin's password 11 | ise_version: 3.1.0 # your major ISE version - optional, defaults to 3.0.0 12 | ise_verify: False # verify your ISE SSL cert - optional, defaults to True -------------------------------------------------------------------------------- /groupsandusers.yaml: -------------------------------------------------------------------------------- 1 | # groupsandusers.yaml 2 | # 3 | # This file contains 4 | 5 | # Default password for users - I may provide an override for this in the user list in the future 6 | 7 | default_password: "C1sco12345" 8 | 9 | # List your group names and descriptions here. 10 | # These will be read by 01_add_groups.yaml and 02_add_users.yaml 11 | 12 | usergroups: 13 | - name: acct # name of the internal identity group to be created 14 | desc: Accounting for SSL # description for the identity group 15 | - name: finance 16 | desc: Finance users for SSL 17 | - name: hr 18 | desc: Human Resources for SSL 19 | - name: PCI 20 | desc: PCI users for SSL 21 | - name: assurance 22 | desc: Assurance for SSL 23 | - name: netadmin 24 | desc: Network Admins for SSL 25 | 26 | # List your usernames here - the "groups:" field must match with one of the groups above 27 | # These will be read by 02_add_users.yaml 28 | 29 | userlist: 30 | - name: acct1 # the actual username to be used for authentication 31 | groups: acct # the indentity group for the user - this should match one of the usergroups above 32 | firstname: Accounting # first name for identification purposes 33 | lastname: User 1 # last name for identification purposes 34 | description: User added with automation # description for the user (makes filtering easier) 35 | - name: acct2 36 | groups: acct 37 | firstname: Accounting 38 | lastname: User 2 39 | description: User added with automation 40 | - name: assur1 41 | groups: assurance 42 | firstname: Assurance 43 | lastname: User 1 44 | description: User added with automation 45 | - name: assur2 46 | groups: assurance 47 | firstname: Assurance 48 | lastname: User 2 49 | description: User added with automation 50 | - name: finance 51 | groups: finance 52 | firstname: Finance 53 | lastname: User 54 | description: User added with automation 55 | - name: hr1 56 | groups: hr 57 | firstname: HR 58 | lastname: User 1 59 | description: User added with automation 60 | - name: hr2 61 | groups: hr 62 | firstname: HR 63 | lastname: User 2 64 | description: User added with automation 65 | - name: netadmin 66 | groups: netadmin 67 | firstname: Network 68 | lastname: Administrator 69 | description: User added with automation 70 | - name: pci 71 | groups: PCI 72 | firstname: PCI 73 | lastname: User 74 | description: User added with automation 75 | -------------------------------------------------------------------------------- /images/ise_api.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eiddor/ise-automation-python/a1e99ff70a2aaae216df01629992ea1c132d3833/images/ise_api.png -------------------------------------------------------------------------------- /policy.yaml: -------------------------------------------------------------------------------- 1 | # policy.yaml 2 | # 3 | # This file contains all of the settings required for SGT, policy, and profile creation. It is read by a few 4 | # of the playbooks. 5 | # 6 | sgtvars: # Scalable Group Tags (SGTs) are defined here 7 | - sgtname: acct # SGT name 8 | sgtdesc: Accounting SGT for SDA # SGT description 9 | - sgtname: Campus_Quarantine 10 | sgtdesc: Campus_Quarantine SGT for SDA 11 | - sgtname: finance 12 | sgtdesc: Finance SGT for SDA 13 | - sgtname: hr 14 | sgtdesc: HR SGT for SDA 15 | - sgtname: PCI 16 | sgtdesc: PCI SGT for SDA 17 | 18 | policies: # settings defined here will be used to create authz profiles and rules for wired and wireless 19 | - policy: CampusUser # profile/rule names will be generated from this name 20 | usergroup: acct # this should match one of the indentity groups created in groupsandusers.yaml 21 | vlan: Campus # this will be the actual VLAN name on the switch 22 | sgt: acct # this should match one of the SGTs defined above 23 | - policy: CampusUser 24 | usergroup: hr 25 | vlan: Campus 26 | sgt: hr 27 | - policy: Restricted 28 | usergroup: PCI 29 | vlan: Restricted 30 | sgt: PCI 31 | - policy: Restricted 32 | usergroup: finance 33 | vlan: Restricted 34 | sgt: finance 35 | 36 | guest: # settings for guest users 37 | redirectacl: ACL_WEBAUTH_REDIRECT # this is the redirect ACL that is on the switch - Cisco DNA Center uses ACL_WEBAUTH_REDIRECT 38 | vlan: Guest # this will be the actual guest VLAN name on the switch 39 | sgt: Guests # this is the SGT to be used for guest - "Guests" is already defined in ISE by default 40 | 41 | accesspoint: # settings for Cisco access point 42 | vlan: AP_VLAN # this will be the actual VLAN name used on the switch for access points --------------------------------------------------------------------------------