Fortigate config management in Github

After Github opened its free repository function to free users, I’m using Github private repository to store lots of my applications config file. I usually don’t use version management because they usually never changes after initial deployment. However, especially while I write blog post I need to make changes just to check the functionality. And sometimes I forgot to rollback config and need to check manually on the device.

In this post, I show you how to integrate Fortigate config backup script and Github API. And in the next post I will deploy them in CloudFunction so that it can be invoked by Fortigate automation stitch.


1. Fortigate Config Copy

I’m going to deploy this in Google Cloud Function in the end, so I don’t want to store any files in there. I modified config_backup() function and created backup_copy(), both are almost the same, but copy function returns config text instead of save one.

import requests
import urllib3 # disable security warning for SSL certificate
urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning) # disable security warning for SSL certificate


def config_copy(ipaddr, api_token):
    '''
    input: ipaddr(string) - target ip address of fortigate
    input: api_token(string) - api_token for api user(accprofile should have sysgrp.mnt)
    output: config text
    Tested on: Fortigate OnDemand on AWS - FortiOS6.0.4
    '''
    base_url = f'https://{ipaddr}/api/v2/'
    headers = {'Authorization': f'Bearer {api_token}'}
    params = {'scope': 'global'}
    uri = 'monitor/system/config/backup/'
    
    rep = requests.get(base_url + uri, headers=headers, params=params, verify=False)

    if rep.status_code != 200:
        print(f'Something went wrong. status_code: {rep.status_code}')
        return ''

    return rep.text

2. Github file update

You need to provide API token to use this function, which you can generate at personal access tokens. Please make sure that repository you are going to use is private. I save this script as ‘github_custom.py’.

import datetime

def update_file(token, repo, filename, content, comment=datetime.datetime.now().strftime('%Y%m%d%H%M%S')):
    '''
    input: token(string) - Github API token
    input: repo(string) - repository name
    input: filename(string) - file name to be pushed to github. this file must exist in github prior to update
    input: content(string) - content of the file
    input: comment(string, optional) - commit comment.
    output: True if commit successful. False if not.
    '''
    import github

    g = github.Github(token)
    try:
        repo = g.get_repo(repo)
        repo_ok = True
        file_obj = repo.get_contents(filename)
    except github.GithubException:
        if repo_ok:
            print(f'File {filename} is not found in {repo}. Make sure the file is in the repository prior to update.')
        else:
            print(f'{repo} is not found. Make sure repo name and your api token has sufficient access.')
        return False
    repo.update_file(file_obj.path, comment, content, file_obj.sha)

    return True

3. Test

To test this, I use interactive mode.

>>> import re
>>>
>>> import fortigate
>>> import github_custom
>>> 
>>> latest_config = fortigate.config_copy('54.xxx.xxx.xxx', FORTIGATE_TOKEN)
>>> hostname = re.search(r'set hostname\s"(.*)"\n', latest_config).group(1)
>>> 
>>> github_custom.update_file(GITHUB_TOKEN, '*****/config_archive', f'{hostname}.conf', latest_config)
True
>>> 

Once completed, you can log into Github.com and check if the file is successfully updated. And you can compare the latest config with the previous version.

Fortigateのコンフィグの取得とGithub上での世代管理ができるようになります。