cancel
Showing results for 
Search instead for 
Did you mean: 
cancel
Announcements

543
Views
0
Helpful
0
Replies
Michael King
Beginner

Change Admin Password via API with Python

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.

0 REPLIES 0