CLI and Python API for Sophos Central (firewalls, licenses, alerts, firmware)
Project description
sfos-central-sdk
CLI and Python API for Sophos Central, including firewalls, licenses, alerts, and firmware management.
Requirements
- Python 3.12+
- Sophos Central API credentials (client ID and client secret)
Installation
pip install sfos-central-sdk
Configuration
Create a .env or credentials.env in your working directory with:
CENTRAL-CLIENT-ID– your Sophos Central API client IDCENTRAL-CLIENT-SECRET– your Sophos Central API client secret
Usage
CLI
After installation, the central-sync-to-db command syncs data to SQLite; use --export-xlsx to export all tables to Excel after sync (see Sync to DB).
The repo includes example.py: run python example.py (or uv run python example.py) from the project root with .env / credentials.env for a demo of firewalls, groups, licenses, and firmware checks via the Python API.
Python API (SDK)
Create a session with your credentials, authenticate, then call the API methods. Methods return typed containers (e.g. Firewalls, Licenses, Alerts) on success, or ReturnState on failure—check with isinstance(result, ReturnState).
Session and authentication
from central.session import CentralSession
from central.classes import ReturnState
central = CentralSession(client_id="your-client-id", client_secret="your-client-secret")
auth = central.authenticate()
if not auth.success:
raise SystemExit(auth.message)
# After auth, central.whoami is set (id, idType, apiHosts, etc.)
print(central.whoami.id, central.whoami.idType) # e.g. tenant vs partner
Firewalls
from central.session import CentralSession
from central.firewalls.methods import get_firewalls
from central.classes import ReturnState
central = CentralSession(client_id="...", client_secret="...")
central.authenticate()
# Current tenant (use whoami for tenant ID and data region URL)
url_base = central.whoami.data_region_url()
result = get_firewalls(central, tenant_id=central.whoami.id, url_base=url_base)
if isinstance(result, ReturnState) and not result.success:
print("Error:", result.message)
else:
for fw in result:
print(fw.id, fw.name, fw.healthStatus)
# Optional: filter by group or search
result = get_firewalls(central, tenant_id=central.whoami.id, url_base=url_base, group_id="...", search="...")
Firewall groups
from central.firewalls.groups.methods import get_firewall_groups
result = get_firewall_groups(
central,
tenant_id=central.whoami.id,
url_base=url_base,
recurseSubgroups=True,
search="...", # optional
)
for group in result:
print(group.id, group.name)
Licenses
from central.firewalls.licenses.methods import get_licenses
# Tenant-level licenses
result = get_licenses(
central,
tenant_id=central.whoami.id,
url_base=url_base,
)
if not isinstance(result, ReturnState):
for lic in result:
print(lic.id, lic.name, lic.status)
# Partner-level (only when central.whoami.idType == "partner")
result = get_licenses(central, partner_id=central.whoami.id)
Alerts
from central.alerts.methods import get_alerts, get_alert
# List alerts (firewall + other, optional time range)
result = get_alerts(
central,
tenant_id=central.whoami.id,
url_base=url_base,
product=["firewall", "other"],
from_time="2025-01-01T00:00:00Z", # ISO 8601
to_time="2025-03-01T00:00:00Z",
severity=["high", "medium"],
page_size=100,
)
if not isinstance(result, ReturnState):
for alert in result:
print(alert.id, alert.raisedAt, alert.severity)
# Single alert by ID
detail = get_alert(central, alert_id="...", tenant_id=central.whoami.id, url_base=url_base)
if not isinstance(detail, ReturnState):
print(detail.description, detail.products)
Firmware upgrade check
from central.firewalls.firmware.methods import firmware_upgrade_check
result = firmware_upgrade_check(
central,
firewall_ids=["fw-uuid-1", "fw-uuid-2"],
tenant_id=central.whoami.id,
url_base=url_base,
)
if not isinstance(result, ReturnState):
for fw in result.firewalls:
print(fw.id, fw.upgradeAvailable)
for ver in result.firmwareVersions:
print(ver.version, ver.releaseDate)
Firewall configuration import/export
The firewall configuration APIs wrap the upcoming Central firewall-config endpoints. Export and import operations are asynchronous: the initial call returns a transaction ID, then you poll the transaction endpoint until it finishes. Finished export transactions include a pre-signed download URL in transaction.response["url"].
from central.firewalls.config.methods import (
complete_firewall_config_import_upload,
export_firewall_config,
get_firewall_config_transaction,
start_firewall_config_import,
wait_for_firewall_config_transaction,
)
# Export all configuration for one firewall. For full exports, omit
# include_dependency and export_entities.
full_export_result = export_firewall_config(
central,
firewall_id="firewall-id",
full_export=True,
tenant_id=central.whoami.id,
url_base=central.whoami.data_region_url(),
)
full_export_transaction_id = full_export_result.value.data.transactionId
# Partial exports can include dependent objects and selected entity types.
export_result = export_firewall_config(
central,
firewall_id="firewall-id",
full_export=False,
include_dependency=True,
export_entities=["FirewallRule", "NATRule"],
tenant_id=central.whoami.id,
url_base=central.whoami.data_region_url(),
)
transaction_id = export_result.value.data.transactionId
# Wait for export completion. The default polling interval is 15 seconds.
# Optional callbacks receive each Transaction object.
export_transaction = wait_for_firewall_config_transaction(
central,
transaction_id=transaction_id,
tenant_id=central.whoami.id,
url_base=central.whoami.data_region_url(),
on_update=lambda tx: print(tx.status, tx.result),
on_complete=lambda tx: print("finished", tx.result),
)
download_url = export_transaction.value.data.response["url"]
download_method = export_transaction.value.data.response.get("method", "GET")
download_expires_at = export_transaction.value.data.response.get("expiresAt")
print(download_method, download_url, download_expires_at)
# Import starts by requesting a pre-signed upload URL.
upload_result = start_firewall_config_import(
central,
tenant_id=central.whoami.id,
url_base=central.whoami.data_region_url(),
)
upload = upload_result.value.data
print(upload.method, upload.url, upload.expiresAt)
# Upload the archive bytes to upload.url using upload.method outside the SDK,
# then notify Central that the upload has completed.
complete_result = complete_firewall_config_import_upload(
central,
transaction_id=upload.transactionId,
firewall_ids=["firewall-id"],
checksum_md5="d41d8cd98f00b204e9800998ecf8427e",
file_size_bytes=1024,
tenant_id=central.whoami.id,
url_base=central.whoami.data_region_url(),
)
# Poll a firewall config transaction once without waiting.
transaction = get_firewall_config_transaction(
central,
transaction_id=upload.transactionId,
tenant_id=central.whoami.id,
url_base=central.whoami.data_region_url(),
)
print(transaction.value.data.status, transaction.value.data.result)
The SDK data classes used by these methods are:
TransactionReference: returned byexport_firewall_config; containstransactionId.PresignedUpload: returned bystart_firewall_config_import; containstransactionId,url,method, andexpiresAt.Transaction: returned byget_firewall_config_transaction,wait_for_firewall_config_transaction, and import completion; containsid,status,result,createdAt,finishedAt,expiryAt,response, andrequest.ExportConfigRequest: maps Python arguments to Central fieldsfullExport,includeDependency, andexportEntities.ImportUploadCompleteRequest: maps Python arguments to Central fieldsfirewallIds,checksumMd5,fileSizeBytes,secureMasterKey, andperformPartialImport.
Partner: list tenants
# Only when central.whoami.idType == "partner"
tenants_result = central.get_tenants()
if isinstance(tenants_result, ReturnState) and not tenants_result.success:
print(tenants_result.message)
else:
for tenant in tenants_result:
print(tenant.id, tenant.name, tenant.apiHost)
# Use tenant.apiHost as url_base and tenant.id as tenant_id for tenant-scoped calls
Using credentials from env
import os
from dotenv import dotenv_values
creds = dotenv_values(".env") # or "credentials.env"
central = CentralSession(
creds["CENTRAL-CLIENT-ID"],
creds["CENTRAL-CLIENT-SECRET"],
)
central.authenticate()
# ... use SDK methods as above
Sync to DB
The central-sync-to-db command (module central.sync_to_db) syncs Sophos Central data into a local SQLite database: tenants, firewalls, licenses, alerts, alert details, and firmware upgrade/version info. Existing rows are updated, new ones inserted. Every synced table includes client_id (the CENTRAL-CLIENT-ID / OAuth client that last wrote the row), which helps when combining data from several credentials. Useful for reporting, dashboards, or offline analysis.
Invocation
central-sync-to-db [OPTIONS]
Or as a module:
uv run python -m central.sync_to_db [OPTIONS]
python -m central.sync_to_db [OPTIONS]
What gets synced
- Partner credentials (
idType == "partner"): all tenants, then per tenant: firewalls, licenses, alerts (firewall + other), alert details for new alerts, firmware upgrade check results; plus partner-level licenses. - Tenant credentials (
idType == "tenant"): single tenant (from whoami), firewalls, licenses, alerts, alert details, firmware upgrade info for that tenant.
Alerts are fetched incrementally when possible (from the latest raisedAt in the DB). Full alert details are fetched only for alerts that are new in the current run.
Options
| Option | Short | Description |
|---|---|---|
--log-level |
-l |
Log level: DEBUG, INFO, WARNING, ERROR. Default: INFO (or env LOG_LEVEL). |
--db |
-d |
Path to SQLite database file. Default: sophos_central.db in the current directory. |
--client-id |
— | Sophos Central client ID (must be used with --client-secret). |
--client-secret |
— | Sophos Central client secret (must be used with --client-id). |
--env |
-e |
Path to a .env file with CENTRAL-CLIENT-ID and CENTRAL-CLIENT-SECRET. Can be repeated for multiple credential sets. |
--export-xlsx |
-x |
After sync, export all summary tables to an Excel workbook (one sheet per table). Optional path; if omitted, uses <db-stem>.xlsx next to the DB. |
Credentials (in order of precedence)
- Inline:
--client-idand--client-secret(both required together). - Env file(s):
-e /path/to/file.env. Multiple-eruns sync once per file (multiple credential sets). - Default files:
./credentials.envor./.envin the current directory.
If no valid credentials are found, the script exits with an error.
Examples
# Default: use .env/credentials.env, write to sophos_central.db
central-sync-to-db
# Custom database path and log level
central-sync-to-db -d /data/sophos.db -l DEBUG
# Inline credentials
central-sync-to-db --client-id "..." --client-secret "..."
# Use a specific env file
central-sync-to-db -e ./prod.env
# Sync then export all tables to Excel (default path: sophos_central.xlsx)
central-sync-to-db -x
# Sync to a DB and export to a named Excel file
central-sync-to-db -d reports/sophos.db -x reports/sophos_export.xlsx
# Multiple env files = multiple sync runs into the same DB
central-sync-to-db -e tenant1.env -e tenant2.env
Output
- Logs: to stderr (or configured logging). Progress bar on a TTY.
- After each run:
sync_id, then a per-table summary of rows added/updated and timing. If-xis used, a message that the workbook was written.
Database schema
The script creates/updates tables: tenants, firewalls, licenses, license_subscriptions, alerts, alert_details, firmware_upgrades, firmware_versions. Schema is managed by central.db (e.g. init_schema). The same tables are exported when using --export-xlsx.
Programmatic sync (shared DB connection)
If you already have an open SQLite connection and credentials, import from central.sync_to_db:
from central.db import get_connection, init_schema
from central.sync_to_db import (
sync_client_credentials_to_database,
CentralSyncAuthError,
)
conn = get_connection("sophos_central.db")
init_schema(conn)
try:
result = sync_client_credentials_to_database(
conn, client_id, client_secret
) # default quiet=True: no progress bar or console log lines from the sync
# result.sync_id, result.summary, result.elapsed_by_table, result.total_elapsed
finally:
conn.close()
On auth failure, CentralSyncAuthError is raised. Use quiet=False (and optional progress=SyncProgress() from central.sync_to_db) to mirror CLI logging/progress.
Note (v0.2+): The sync CLI/API lives under central.sync_to_db. Replace former from sync_to_db import … with from central.sync_to_db import ….
Development
Install with dev dependencies:
pip install -e ".[dev]"
# or with uv (matches CI):
uv sync --extra dev
Run the linter:
ruff check .
Security scanning (no cost)
- Dependency vulnerabilities: pip-audit checks installed packages against the Python Packaging Advisory Database. Run:
uv run pip-audit
- Code security: Bandit is included in dev dependencies. Run:
uv run bandit -c pyproject.toml -r central example.py
- CI: The repo includes a GitHub Actions workflow (
.github/workflows/security.yml) that runs pip-audit and Bandit on push/PR tomaster/mainand weekly on a schedule. Free for public repos; private repos get a monthly Actions allowance.
Licensing
This project is licensed under the Apache License 2.0 (see License below). Third-party dependencies and their licenses are listed below.
Runtime dependencies
| Package | Version | License |
|---|---|---|
| dotenv | ≥0.9.9 | Unspecified on PyPI (thin wrapper; depends on python-dotenv, BSD-3-Clause) |
| openpyxl | ≥3.1.0 | MIT |
| requests | ≥2.32.5 | Apache-2.0 |
Dev dependencies
| Package | Version | License |
|---|---|---|
| ruff | ≥0.15.0 | MIT |
| pytest | ≥8.0.0 | MIT |
| pytest-cov | ≥5.0.0 | MIT |
| bandit[toml] | ≥1.7.0 | Apache-2.0 |
| pip-audit | ≥2.0.0 | Apache-2.0 |
License
APACHE 2.0
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file sfos_central_sdk-0.9.10.tar.gz.
File metadata
- Download URL: sfos_central_sdk-0.9.10.tar.gz
- Upload date:
- Size: 62.5 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: uv/0.11.7 {"installer":{"name":"uv","version":"0.11.7","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":null,"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
1d18058b5094fd544d85c1184cb662eba5329c3a220efd3abd4781472cddaedb
|
|
| MD5 |
ff0e76da1289f065e4b6dadd7f9f5bbd
|
|
| BLAKE2b-256 |
45d0b615b648a55d1b0e23f323c082b836f079277c423440a473638b828f8618
|
File details
Details for the file sfos_central_sdk-0.9.10-py3-none-any.whl.
File metadata
- Download URL: sfos_central_sdk-0.9.10-py3-none-any.whl
- Upload date:
- Size: 65.1 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: uv/0.11.7 {"installer":{"name":"uv","version":"0.11.7","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":null,"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
6f18be8c519b3d09c25829be1c5cdb361adc24c1354fd712ce2917455c40f876
|
|
| MD5 |
0daa934515d30832adf7b68a91a951e9
|
|
| BLAKE2b-256 |
f0a5ed5319e85e93d6901e9197338be9c7463a87b67949f6e14ebe4fc4f213e0
|