ā09-21-2021 05:47 PM - edited ā09-21-2021 05:52 PM
I'm trying to automate some deployments of FTD firewalls (on 7.0.0) for my company (It's the FP 1010 if it matters, and we're using FDM).
Due to the way we process them, the firewall get's the admin password changed (and the EULA accepted) before it's shipped to the site, mainly this is so the license is applied before being sent onsite. (Can't change this process, for.... reasons)
Once it's onsite, I have a python script I've written, which does..... a lot of stuff. (Sets inside / outside interfaces to routed mode, put's IP's on them, creates around 100 objects, creates some standard rules, set's the DNS/NTP/SNMP servers, etc, etc, etc...)
One last item I need to change the admin password to a locally entered one.
I can't seem to get this working correctly.
I'm pretty much using the Cisco Devnet example code for login
and I'm trying to call the Provision section there, but it's bombing out with:
{ "error" : { "severity" : "ERROR", "key" : "Validation", "messages" : [ { "description" : "The initial device setup is complete. You can now manage the device and change the configuration.", "code" : "DeviceSetupAlreadyDone", "location" : "" } ] } } Return code: 422
So here is a... neutered version of the script
#!/usr/bin/python3.9 import sys import requests import json import warnings import time import logging import getpass import ipaddress from bravado.client import SwaggerClient from bravado.requests_client import RequestsClient class FTDClient: '''This class acts as an FTD REST API client with a series of wrapper methods available along with a raw Bravado REST client if desired. ''' headers = { "Content-Type": "application/json", "Accept": "application/json" } def __init__(self, address, port, username, password, new_password): ''' Constructor used to initialize the bravado_client address: IP or hostname of the device to connect to port: Port number to connect to username: username to use (default 'admin') password: password to use (default 'Admin123') ''' # stash connectivity info in bravado_client self.server_address = address self.server_port = port self.username = username self.password = password self.new_password = new_password # WARNINGS requests.packages.urllib3.disable_warnings() # swagger doesn't like 'also_return_response' sent from FDM warnings.filterwarnings('ignore', 'config also_return_response is not a recognized config key') # after we auth we get an access token note that we have both normal (short session logins and custom where you can extend the length of the session) # both will leverage this same variable for now self.access_token = None self.bravado_client = None def login(self): ''' This is the normal login which will give you a ~30 minute session with no refresh. Should be fine for short lived work. Do not use for sessions that need to last longer than 30 minutes. ''' payload = '{{"grant_type": "password", "username": "{}", "password": "{}"}}'.format(self.username, self.password) auth_headers = {**FTDClient.headers, 'Authorization': 'Bearer '} #print ('Authentication Headers: %s', auth_headers) #print ('Authentication Payload is: %s', payload) r = requests.post("https://{}:{}/api/fdm/latest/fdm/token".format(self.server_address, self.server_port), data=payload, verify=False, headers=auth_headers) #print("response payload") #print(r.headers) #print(r.text) if r.status_code == 400: raise Exception("Error logging in: {}".format(r.content)) try: self.access_token = r.json()['access_token'] print ("**********************************") print ("Login Successful") # print ("Access Token:") # print (self.access_token) print ("**********************************") except: raise def logout(self): ''' Used for explicit session logout ''' logout_payload = {'grant_type': 'revoke_token', 'access_token': self.access_token, 'token_to_revoke': self.access_token} requests.post("https://{}:{}/api/fdm/latest/fdm/token".format(self.server_address, self.server_port), data=json.dumps(logout_payload), verify=False, headers=FTDClient.headers) self.access_token = None def provision(self): prov_headers = {**FTDClient.headers, 'Authorization': 'Bearer {}'.format(self.access_token)} provision_resp = requests.get("https://{}:{}/api/fdm/latest/devices/default/action/provision/default".format(self.server_address, self.server_port), headers=prov_headers, verify=False) resp_json = provision_resp.json() #resp_json['acceptEULA'] = 'true' resp_json['currentPassword']=self.password resp_json['newPassword']=self.new_password #print ("****Response from GET*****") #print(provision_resp) print ("**************************") print ("**** Provisioning ******") print ("**************************") #print ("*****Payload for POST*****") #print (json.dumps(resp_json)) resp = requests.post("https://{}:{}/api/fdm/latest/devices/default/action/provision".format(self.server_address, self.server_port), verify=False, headers=prov_headers, data=json.dumps(resp_json)) if resp.status_code == 200: print("Provisioning Successful") print("Your new FTD password is "+new_password) else: #print("Provisioning Complete") print(resp.text) print(resp.status_code) def deploy(self): deploy_headers = {**FTDClient.headers, 'Authorization': 'Bearer {}'.format(self.access_token)} init_deploy_resp = requests.post("https://{}:{}/api/fdm/latest/operational/deploy".format(self.server_address, self.server_port), verify=False, headers=deploy_headers) init_deploy_resp_json = init_deploy_resp.json() print("Deployment Started... This may take several minutes.") loop_counter = 0 while True: loop_counter = loop_counter + 1 deploy_resp = requests.get("https://{}:{}/api/fdm/latest/operational/deploy/{}".format(self.server_address, self.server_port, init_deploy_resp_json['id']), verify=False, headers=deploy_headers) deploy_resp_json = deploy_resp.json() time.sleep(10) print("Deployment in Progress [" + str(loop_counter) + "]") if deploy_resp_json['state'] == "DEPLOYED": print("Deployment Successful") break def get_client(self): ''' Returns a raw bravado_client to interact with Bravado using the Open API generated API ''' if self.bravado_client: return self.bravado_client # retrieve it if we don't already have it http_client = RequestsClient() http_client.session.verify = False http_client.session.headers = {**FTDClient.headers, 'Authorization': 'Bearer {}'.format(self.access_token)} # bravado will validate field type if it's in the JSON self.bravado_client = SwaggerClient.from_url('https://{}:{}/apispec/ngfw.json'.format(self.server_address, self.server_port), http_client=http_client, config={'validate_responses': False, 'validate_swagger_spec': False}) return self.bravado_client def sanitised_input(prompt, type_=None, min_=None, max_=None, range_=None, default_=""): if min_ is not None and max_ is not None and max_ < min_: raise ValueError("min_ must be less than or equal to max_.") while True: ui = input(prompt + " [" + default_ + "]: ") or default_ if type_ is not None: try: ui = type_(ui) except ValueError: print("Input type must be {0}.".format(type_.__name__)) continue if max_ is not None and ui > max_: print("Input must be less than or equal to {0}.".format(max_)) elif min_ is not None and ui < min_: print("Input must be greater than or equal to {0}.".format(min_)) elif range_ is not None and ui not in range_: if isinstance(range_, range): template = "Input must be between {0.start} and {0.stop}." print(template.format(range_)) else: template = "Input must be {0}." if len(range_) == 1: print(template.format(*range_)) else: expected = " or ".join(( ", ".join(str(x) for x in range_[:-1]), str(range_[-1]) )) print(template.format(expected)) else: return ui def update_admin_pass(client,oldPassword_,newPassword_): #find admin user user_ = client.User.getUserList(filter='name:admin').response().result['items'][0] print (user_) #update admin user password body = {'identitySourceId':user_['identitySourceId'], 'name':user_['name'], 'newPassword': newPassword_, 'password': oldPassword_, 'type': 'user', 'userServiceTypes': ['MGMT'], 'userPreferences': {'colorTheme': 'NORMAL_CISCO_IDENTITY', 'preferredTimeZone': '(UTC+00:00) UTC', 'type': 'userpreferences'}, 'version':user_['version']} return client.User.editUser(objId=user_['id'],body=body).response().result if __name__ == '__main__': # # DEFAULT INPUTS BEGIN # mgmt_ip = "192.168.45.45" mgmt_port = "443" username = "admin" password = "NotReallyThePassword" new_password = "" # DEFAULT INPUTS END # #Perform Login print("Enter device details to login") mgmt_ip = str(sanitised_input("Enter Firewall Management IP", ipaddress.ip_address, default_=mgmt_ip)) print("Firewall Management IP: " + mgmt_ip) mgmt_port = sanitised_input("Enter Management Port", str.lower, default_=mgmt_port, range_=('80', '443')) print("Management Port: " + mgmt_port) username = sanitised_input("Enter Username", str, default_=username) print("Username: " + username) password = sanitised_input("Enter Password", str, default_=password) print("Password: " + password) new_password = sanitised_input("Enter New Password", str) new_password1 = sanitised_input("Password Again", str) if new_password != new_password1: print("Passwords Do Not match. Exiting...") exit() http_client = FTDClient(mgmt_ip, mgmt_port, username, password, new_password) http_client.login() client = http_client.get_client() http_client.provision() #print(update_admin_pass(client,password, new_password)) #deploying the changes #http_client.deploy() #logout http_client.logout()
If I uncomment out the line to enable update_admin_pass, it uses my own password change function... which doesn't work either. (For lots of reasons, and stuff that is documented as not required, the function thinks it's required (looking at you userPreferences)
bravado.exception.HTTPUnprocessableEntity: 422 Unprocessable Entity: ErrorWrapper(error=ErrorResponse(key='Validation', messages=[NGFWErrorMessage(code='cannotChangeUserRoleOfUser', description='You cannot update the user role of a user.', location='userRole')], severity='ERROR'))
So... after the device has completed the initial provision, and I want to change the admin user password. How do I do this using Python and the API?
This is my first Python script, so be gentle.
Find answers to your questions by entering keywords or phrases in the Search bar above. New here? Use these resources to familiarize yourself with the community: