For Forward Enterprise on-prem customers running air-gapped instances, maintaining up-to-date security data can offer challenges. This Python script addresses those challenges by automating the daily update of the CVE (Common Vulnerabilities and Exposures) database, ensuring your instance reflects the latest threat intelligence—even when it’s offline. You can perform these updates in the Forward Enterprise UI, but this script offers an automated solution to ensure you’re running the most up-to-date definitions. In addition to CVEs, you can modify this script to include custom definitions to identify threats and compliance requirements specific to your industry or sector.
Designed for Security and Compliance Teams
This script is particularly valuable for:
- Security Operations Center (SOC) teams monitoring for zero-day vulnerabilities
- Audit and compliance professionals needing accurate, up-to-date security data
- Any on-prem customer managing Forward Enterprise in a secure or regulated environment
By removing the need for daily manual updates, it saves time and reduces the risk of human error in high-stakes environments.
Automate Download to Upload
Here’s how the script works:
- Connect to Forward’s web platform using your credentials (or ideally, an API key).
- Downloads the latest CVE database as a compressed file.
- Uploads the file to your on-prem instance, using its local credentials and endpoint information.
- Runs on a schedule (via cron or task scheduler), so the update process is hands-off after setup.
Typically, this script runs on a bastion host—any machine that can access both the internet and the internal network. It could be your laptop or a dedicated server bridging the two environments
Expand Beyond CVEs with Minimal Effort
While the script’s primary use case is daily CVE updates, it’s flexible enough to support other workflows:
- Upload custom or third-party compliance files
- Sync internal datasets not available via API
- Extend the logic to support multiple destination hosts
With only minor tweaks to the source URL and upload target, the same foundation can support a broader set of update tasks.
Running the script requires:
- A modern Python environment
- The requests library (a common dependency)
- A Forward user account with download access to the CVE database
- On-prem instance credentials (or support for insecure uploads if no certificate is installed)
Python Script
#!/usr/bin/env python3
import argparse
import logging
import os
import re
import sys
import requests
from requests.auth import HTTPBasicAuth
from requests.adapters import HTTPAdapter
from urllib3.util.retry import Retry
def setup_logging(verbose):
level = logging.DEBUG if verbose else logging.INFO
logging.basicConfig(
level=level,
format='%(asctime)s - %(levelname)s - %(message)s'
)
def create_session():
session = requests.Session()
retries = Retry(
total=3,
backoff_factor=1,
status_forcelist=[502, 503, 504],
allowed_methods=["GET", "PUT"]
)
session.mount('https://', HTTPAdapter(max_retries=retries))
return session
def download_file(session, url, auth):
logging.info(f"Downloading from {url}...")
response = session.get(url, auth=auth)
if response.status_code != 200:
logging.error(f"Failed to download file. Status code: {response.status_code}")
sys.exit(1)
# Extract filename from Content-Disposition
content_disposition = response.headers.get('Content-Disposition', '')
filename = "cve-index.bin.gz" # default
if 'filename=' in content_disposition:
filename = content_disposition.split('filename=')[-1].strip('";')
logging.info(f"Download successful. Filename: {filename}")
return response.content, filename
def safe_filename(name):
return re.sub(r'[^\w.-]', '_', name)
def save_file_locally(data, filename='cve-index.bin.gz'):
safe_name = safe_filename(filename)
path = os.path.join('/tmp', safe_name)
logging.debug(f"Saving file to {path} for debugging purposes.")
with open(path, 'wb') as f:
f.write(data)
logging.info(f"File saved as {path}.")
def upload_file(session, data, upload_url, auth, verify=True):
headers = {'Content-Type': 'application/x-gzip'}
logging.info(f"Uploading to {upload_url}...")
response = session.put(upload_url, headers=headers, auth=auth, data=data, verify=verify)
if response.status_code not in (200, 201, 204):
logging.error(f"Failed to upload file. Status code: {response.status_code}")
logging.debug(response.text)
sys.exit(1)
logging.info("Upload successful.")
def main():
parser = argparse.ArgumentParser(description='Download and forward CVE index file.')
parser.add_argument('--fwd-user', required=True, help='Username for fwd.app')
parser.add_argument('--fwd-pass', required=True, help='Password for fwd.app')
parser.add_argument('--target-host', required=True, help='Target host (e.g., portal.forwardnetworks.your.instance.url)')
parser.add_argument('--target-user', required=True, help='Username for target host')
parser.add_argument('--target-pass', required=True, help='Password for target host')
parser.add_argument('--insecure-upload', action='store_true', help='Disable SSL verification on upload')
parser.add_argument('--verbose', action='store_true', help='Enable verbose logging')
args = parser.parse_args()
setup_logging(args.verbose)
source_url = 'https://fwd.app/api/cve-index'
target_url = f'https://{args.target_host}/api/cve-index'
session = create_session()
file_data, filename = download_file(session, source_url, HTTPBasicAuth(args.fwd_user, args.fwd_pass))
save_file_locally(file_data, filename)
upload_file(
session,
file_data,
target_url,
HTTPBasicAuth(args.target_user, args.target_pass),
verify=not args.insecure_upload
)
if __name__ == "__main__":
main()