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: 422So 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.
Solved! Go to Solution.
10-24-2024 08:23 AM
Going through some old threads I opened. I fixed my issue with this code:
def change_password(client,oldPassword_,new_password):
print ("Changing Password")
try:
user_info = client.User.getUserList(filter='name:admin').response().result['items'][0]
command_body = {
"id": user_info["id"],
"type": "user",
"userServiceTypes": ["MGMT"],
"password": oldPassword_,
"newPassword": new_password,
"userRole": "ROLE_ADMIN",
"identitySourceId": user_info["identitySourceId"],
"userPreferences": {
"colorTheme": "NORMAL_CISCO_IDENTITY",
"preferredTimeZone": "(UTC+00:00) UTC",
"type": "userpreferences"
},
"name": "admin",
"version": user_info["version"]
}
response = client.User.editUser(objId=user_info["id"], body=command_body).response().result
print("Password changed successfully.")
return response
except IndexError:
print("Error: Admin user not found.")
except Exception as e:
print(f"***************************************************")
print(f"Password does not match Password Policy.")
print(f"***************************************************")
print(f"The following errors were detected:\n{e}")
sys.exit(1)
10-24-2024 08:23 AM
Going through some old threads I opened. I fixed my issue with this code:
def change_password(client,oldPassword_,new_password):
print ("Changing Password")
try:
user_info = client.User.getUserList(filter='name:admin').response().result['items'][0]
command_body = {
"id": user_info["id"],
"type": "user",
"userServiceTypes": ["MGMT"],
"password": oldPassword_,
"newPassword": new_password,
"userRole": "ROLE_ADMIN",
"identitySourceId": user_info["identitySourceId"],
"userPreferences": {
"colorTheme": "NORMAL_CISCO_IDENTITY",
"preferredTimeZone": "(UTC+00:00) UTC",
"type": "userpreferences"
},
"name": "admin",
"version": user_info["version"]
}
response = client.User.editUser(objId=user_info["id"], body=command_body).response().result
print("Password changed successfully.")
return response
except IndexError:
print("Error: Admin user not found.")
except Exception as e:
print(f"***************************************************")
print(f"Password does not match Password Policy.")
print(f"***************************************************")
print(f"The following errors were detected:\n{e}")
sys.exit(1)
Discover and save your favorite ideas. Come back to expert answers, step-by-step guides, recent topics, and more.
New here? Get started with these tips. How to use Community New member guide