Releases: dannyota/fortimgr
Release list
v1.3.0
Large read-only FlatUI coverage expansion based on live GUI discovery. All
additions are backwards-compatible.
Added
- Device assigned package mapping via
ListDeviceAssignedPackages. - Device dashboard summary/install context via
DeviceSummary. - Explicit
Device.HATopologywhile preserving legacyDevice.HAMode. - Workflow step logs via
ListWorkflowLogs. - Firmware upgrade path matrix via
ListFirmwareUpgradePaths. - Device PSIRT/vulnerability summary via
DevicePSIRT. - Static IPv6 routes via
ListStaticRoutes6. - Device-scoped networking methods:
DeviceDNS,ListDeviceDDNS,
DeviceIPAM,SDWANSettings,ListSDWANMembers,ListSDWANServices, and
ListSDWANDuplication. - Firewall object methods:
ListAddresses6,ListAddressGroups6,
ListVirtualIPGroups,ListVirtualIPs6,ListVirtualIPGroups6,
ListIPPools6,ListIPPoolGroups,ListInternetServiceCustom,
ListInternetServiceCustomGroups,ListInternetServiceGroups,
ListInternetServiceNames,ListFDSDBInternetServices, and
ListScheduleGroups. - Live raw-vs-SDK capture test behind the
livecapturebuild tag. Captures are
written only under ignoredsamples/paths.
Changed
ListDeviceFirmwarenow maps the stable fields present in the live firmware
management rows, including model/platform IDs, upgrade keys, group name,
status, and model-device metadata.- Internal FlatUI transport code now shares the common POST/request handling
path forforwardandflatui_proxy. - Smoke coverage now includes the newly supported resources and avoids printing
the configured FortiManager address.
Compatibility notes for existing fields
Device.HAModeis unchanged and remains the legacy compatibility field using"standalone" / "master" / "slave". New code should use the addedDevice.HATopologyfield for HA topology andDevice.HARolefor role.DeviceFirmwarekeeps its existing fields, butListDeviceFirmwarenow maps the fuller live firmware-management row. Existing fields such asCurrentVersion,CurrentBuild,UpgradeVersion,CanUpgrade,Connected, andLicenseValidmay now be accompanied by additional populated metadata on the same struct.- No existing public fields were renamed or removed in this release.
v1.2.2
v1.2.1
Breaking Changes
Removed ListVDOMs and VDOM type
ListVDOMs used /dvmdb/device/{device}/vdom, a JSON-RPC endpoint that does not work through the FlatUI transport — callers always received a permission error regardless of account privileges.
Migration: VDOM names are available as string fields on existing types:
Interface.VDOMNormalizedInterfaceMapping.VDOMPackageInstallStatus.VDOM
Pass the vdom name directly to ListInterfaces(device, vdom) and ListStaticRoutes(device, vdom) as a string. Use "" for the global/default scope.
v1.2.0
What's New
Per-Policy Revision History
Two new methods expose FortiManager's per-object revision tracking (_objrev endpoint):
-
ListPolicyRevisions(ctx, adom, pkg, policyID)— returns the full revision history for a single firewall policy: who changed it, when, what action was taken, a human-readable change note, and the complete policy configuration snapshot at each revision point. -
ListPolicyRevisionCounts(ctx, adom, pkg)— returns a map of policy ID → revision count across all policies in a package, useful for identifying heavily-modified policies without fetching full histories.
Use Cases
- Field-level audit trail for individual firewall policies
- Detecting silent policy modifications (schedule extensions, address changes) bundled inside unrelated workflow sessions
- Compliance reporting on policy change frequency and authors
v1.1.0
v1.1.0 delivers three related themes: transparent pagination for 30 List methods, a change-management audit trail (revisions + workflow sessions + normalized interface abstraction), and device license metadata + credential-exclusion hardening on ListDevices. Every v1.0.3 symbol stays intact; all additions are backwards compatible.
Pagination (30 of 33 List* methods)
Every List method that queries a range-paginatable FortiManager endpoint now transparently fetches every page (1000 rows per forward request by default) and returns the concatenated result. Previously the SDK passed no range parameter at all — which silently truncated results on busy ADOMs where implicit row caps kicked in. If your deployment has ADOMs with thousands of policies, addresses, services, revisions, or workflow sessions, v1.1.0 is the first release that reliably returns all of them.
ListOption— new variadic option type accepted by 30 List methods.WithPageSize(n)— override the default page size (valid 1..10000).WithPageCallback(fn)— register a progress callback invoked after each page is fetched. Receives(cumulative count, 1-based page number).- Session expiry during pagination is handled transparently by the existing retry-on-expiry path — page 2 gets a re-login automatically.
- Termination is robust —
getPagedstops on 4 conditions: under-full page (normal), over-full page (endpoint ignored range), byte-identical page 2 (endpoint ignored offset on a same-size dataset), or a hard safety cap of 10000 iterations.
Three methods explicitly do NOT accept pagination options
| Method | Reason |
|---|---|
ListADOMs(ctx, all ...bool) |
Variadic collision with existing all ...bool; ADOM count is license-capped (~20–100 max) so paging is unnecessary. |
ListDeviceFirmware(ctx) |
Uses flatui_proxy with a grouping method call; not a range-paginatable list. |
ListPackageInstallStatus(ctx, adom, pkg) |
FortiManager design limitation: /pm/config/adom/{adom}/_package/status ignores the range parameter — passing range:[999999,1] still returns the full dataset. Accepting page options would be misleading. |
Example
```go
addrs, _ := client.ListAddresses(ctx, "root",
fortimgr.WithPageSize(500),
fortimgr.WithPageCallback(func(fetched, page int) {
log.Printf("fetched %d addresses so far (page %d)", fetched, page)
}),
)
```
Audit trail + ADOM interface abstraction (3 new methods)
ListADOMRevisions(ctx, adom, opts...)— returns the ADOM revision history from/dvmdb/adom/{adom}/revision. Each entry hasVersion,Name,Desc,CreatedBy,CreatedAt,Locked. Joins againstWorkflowSession.RevisionIDfor per-change-request traceability.ListWorkflowSessions(ctx, adom, opts...)— returns the workflow audit trail from/dvmdb/adom/{adom}/workflow. Captures who created each change request, who submitted it, who audited/approved it, which revision it produced, and a best-effortStatestring.ListNormalizedInterfaces(ctx, adom, opts...)— returns the ADOM-level normalized interface abstraction from/pm/config/adom/{adom}/obj/dynamic/interface. Each normalized name's_scopearray is fanned out into oneNormalizedInterfaceMappingper (device, vdom) pair for easy downstream iteration.
Device license metadata + ListDevices security hardening
Devicestruct — 10 new flatLicense*fields —LicenseExpire,LicenseOverdueSince,LicenseMaxCPU,LicenseMaxRAM,LicenseUTMEnabled,LicenseType,LicenseInstalledAt,LicenseLastSync,LicenseRegion,LicenseFlags. Populated from the same/dvmdb/adom/{adom}/deviceresponse — no new API calls. None of these fields carries the license activation key — FortiManager does not persist activation keys, only status, capacity, expiry, and region metadata.ListDevicesfields allowlist — switched togetPagedwith a server-sidefieldsallowlist on every per-page request. Encrypted device credentials (adm_pass,private_key,psk) never transit the wire. A compile-time-enforced test guards against future edits that might accidentally add a credential to the allowlist.
Tests
13 pagination subtests cover multi-page assembly, boundary conditions, callbacks, range injection, session expiry mid-pagination, endpoint-ignores-range (both over-full and exact-size variants), and error propagation. Dedicated per-method tests for revisions, workflow, and normalized interfaces cover edge cases like zero timestamps, unknown state ints, unmapped normalized interfaces, and scope fan-out.
Full changelog: v1.0.3...v1.1.0
v1.0.3
Downstream-requested improvements for a security-audit data warehouse — policy install status, richer device sync state, per-member HA role, and HA cluster member visibility. Fully backwards compatible: every v1.0.2 symbol stays intact, all additions go at the end of existing structs so positional literals keep compiling.
Added
-
ListPackageInstallStatus(ctx, adom, pkg string) ([]PackageInstallStatus, error)— new method hitting/pm/config/adom/{adom}/_package/status. Distinguishes package assignment (device on scope list) from actual installation (config pushed and running on the FortiGate).pkgis optional — empty returns every package in the ADOM, non-empty filters server-side via afilter: ["pkg","==",pkg]clause.PackageInstallStatusfields:ADOM,Package,Device,VDOM,Status("installed"/"modified"/"never"/"unknown"/"imported"). -
Devicestruct additions —Hostname,ConfStatus("unknown"/"insync"/"modified"),DevStatus("none"/"auto_updated"/"installed"/ 13 others),LastChecked(time.Time, zero whenlast_checked==0),LastResync(same),HARole(""/"master"/"slave"),HAMembers([]HAMember). All populated from the existing/dvmdb/adom/{adom}/deviceresponse — no new API calls.HARoleis derived by matching the device name againstha_slave[]. -
HAMembers+HAMembertype — surface every HA cluster member (including the standby) that FortiManager knows about for a given device record. FortiManager models each HA cluster as a single top-level device entry withName/Hostnameset to the primary's hostname —ListDeviceshas never returned standbys as separate rows and still doesn't.HAMembersis the only place where passive members appear. Each entry carriesName,SerialNumber,Role("master"/"slave"),Status("online"/"offline"), andConfStatus. Empty for standalone devices. -
getExtra[T]internal helper — private generic wrapper alongsideget[T]; forwards a GET whoseparams[0]merges extra fields (filter,option, …) into the payload. Used byListPackageInstallStatus; existing call sites ofget[T]are untouched. -
Enum maps —
confStatuses,devStatuses,haRoleswith raw-int passthrough for unmapped values (forward-compatible with future FortiManager schema additions). -
unixToTimehelper — convertsint/float64/stringUnix timestamps (includingnil/0) totime.Time, returning the zero value for "never" semantics.
Notes on HAMode (legacy field unchanged)
The existing Device.HAMode still maps the raw ha_mode int via the legacy deviceHAModes table ("0": "standalone", "1": "master", "2": "slave") — semantically this conflates topology and role, but behavior is preserved for v1.0.x callers. New code should prefer HARole for the per-member role, iterate HAMembers for the full cluster view, and treat HAMode as opaque until a future major version where HAMode is cleaned up to mean topology only ("standalone" / "a-p" / "a-a").
Known gaps (planned for v1.1.0)
The /pm/config/adom/{adom}/_package/status endpoint does not expose RevisionDeployed, RevisionLatest, LastInstallTime, ModifyState, or PendingChanges — the live FortiManager API only returns the aggregate status string. Callers that need those details should join against ADOM revision history (ListADOMRevisions — shipping in v1.1.0 along with workflow sessions, normalized interfaces, SDN connectors, ISDB, and traffic shapers).
Full changelog: v1.0.2...v1.0.3
v1.0.2
Friendlier IPsec naming matching the FortiGate GUI. Fully backwards compatible — no v1.0.1 symbol was renamed or removed.
Added
IPSecTunnel— zero-cost type alias forIPSecPhase1(type IPSecTunnel = IPSecPhase1). Values are interchangeable with no conversion.ListIPSecTunnels(ctx, adom)— one-line wrapper aroundListIPSecPhase1. Shares all logic via the type alias.IPSecSelector— new struct mirroringIPSecPhase2with one field rename:Phase1Name→Tunnel. Kept as a distinct type (not an alias) so renaming the field onIPSecPhase2isn't required and v1.0.1 callers of.Phase1Namekeep compiling.ListIPSecSelectors(ctx, adom)— delegates toListIPSecPhase2to reuse the HTTP/JSON/mapping path, then copies each result into anIPSecSelectorwith the renamed field. Single point of truth for the API call.
Rationale
Phase 1 / Phase 2 are IKE-RFC terms that force users to translate in their heads. The FortiGate GUI calls them "tunnel" and "selector", so matching that makes call sites read naturally:
```go
// Before
phase1, _ := c.ListIPSecPhase1(ctx, "root")
phase2, _ := c.ListIPSecPhase2(ctx, "root")
for _, p := range phase2 { fmt.Println(p.Phase1Name) }
// After
tunnels, _ := c.ListIPSecTunnels(ctx, "root")
selectors, _ := c.ListIPSecSelectors(ctx, "root")
for _, s := range selectors { fmt.Println(s.Tunnel) }
```
Full changelog: v1.0.1...v1.0.2
v1.0.1
Bug fixes discovered while running the SDK against a live restricted-admin session.
Fixes
ListADOMs()now filters to the session's accessible ADOMs by default. Previously it returned every ADOM on the box (including factory presets likeFortiAnalyzer,FortiMail,FortiWeb, etc.), which caused restricted admins to see ADOMs they had no scope for and fail on subsequent calls. PassListADOMs(ctx, true)to retain the global view for superadmin tooling. Uses/gui/sys/configto resolve the session scope (same endpoint asSystemStatus).ListInterfaces(ctx, device, "")(or"global") now uses the device-wide path/pm/config/device/<dev>/global/system/interface, returning every interface across all VDOMs in one call with each carrying its ownvdomfield. Restricted admins cannot read/dvmdb/device/<dev>/vdomto enumerate VDOMs first, so this is the only viable path for them. Callers can derive the VDOM set from the returned list. Passing a specific vdom still routes to the per-VDOM path.ListIPSecPhase1()/ListIPSecPhase2()corrected URL segment fromvpn.ipsec(dotted) tovpn/ipsec(slash). The dotted form returned-3 Object does not existon every FMG; the slash form is the valid path.
Smoke test
- Rewrote the device loop to skip
ListVDOMs(permission-denied for restricted admins) and instead fetch the global interface list per device, then derive the VDOM set from the result and callListStaticRoutesonce per derived VDOM. - Added an
ADOMs: N accessible / M totaldiagnostic line that calls bothListADOMs()andListADOMs(ctx, true)so the filtered/global comparison is visible at a glance.
Full changelog: v1.0.0...v1.0.1
v1.0.0
Changelog
v1.0.0
First stable release. Read-only Go SDK for FortiManager's FlatUI API.
Core
- Dual transport:
forwardfor config/device endpoints,proxyfor system endpoints - Session management with cookie jar and CSRF token handling
- Auto-relogin on session expiry (status code -6) for both transports
- Functional options:
WithCredentials,WithInsecureTLS,WithTimeout,WithTransport,WithHTTPClient,WithUserAgent,WithX509NegativeSerial - Sentinel errors:
ErrAuth,ErrNotLoggedIn,ErrPermission,ErrCertificate,ErrSessionExpired,ErrInvalidName - Input validation via
validName()to prevent path injection - Zero external dependencies (stdlib only)
Resource Methods (29 total)
System (proxy transport)
SystemStatus()— hostname, version, serial number, HA mode, platformListDeviceFirmware()— firmware status for all managed devices
Device Management
ListADOMs()— administrative domainsListDevices(adom)— managed FortiGate devices with firmware, HA, and statusListVDOMs(device)— virtual domains on a deviceListInterfaces(device, vdom)— network interfaces with role, mode, allow access, VLANListStaticRoutes(device, vdom)— static routing entries
Firewall Policy
ListPolicyPackages(adom)— policy packages with scope assignmentsListPolicies(adom, pkg)— firewall rules per package
Firewall Objects
ListAddresses(adom)— address objects (ipmask, iprange, fqdn, geography, wildcard)ListAddressGroups(adom)— address groupsListServices(adom)— custom service definitionsListServiceGroups(adom)— service groupsListVirtualIPs(adom)— virtual IP / port forwardingListIPPools(adom)— NAT IP poolsListZones(adom)— system zones with intrazone traffic setting
Scheduling
ListSchedulesRecurring(adom)— recurring schedulesListSchedulesOnetime(adom)— one-time schedules
Security Profiles
ListAntivirusProfiles(adom)— scan mode, feature set, logging optionsListIPSSensors(adom)— extended log, botnet scanning, malicious URL blockingListWebFilterProfiles(adom)— inspection mode, content logging, FTGD error loggingListAppControlProfiles(adom)— deep inspection, unknown/other app actionsListSSLSSHProfiles(adom)— cert mode, MAPI/RPC over HTTPS, ALPN support
User & Authentication
ListUsers(adom)— local usersListUserGroups(adom)— user groups with member listsListLDAPServers(adom)— LDAP server configurationsListRADIUSServers(adom)— RADIUS server configurations
VPN
ListIPSecPhase1(adom)— IPSec Phase 1 interfacesListIPSecPhase2(adom)— IPSec Phase 2 interfaces
Type Conversion
- Handles FortiManager's inconsistent JSON types (string, int, float64, array)
- Enum mapping from numeric API values to named FortiOS strings
- Subnet formatting with automatic dotted-mask to CIDR conversion
- Host addresses (/32) rendered without prefix length
v0.1.0: Initial Release
fortimgr v0.1.0
Read-only Go SDK for FortiManager using the FlatUI (Web UI) API.
Features
- Session auth with CSRF tokens, auto-relogin on session expiry
- Functional options —
WithCredentials,WithInsecureTLS,WithTimeout,WithTransport,WithHTTPClient,WithUserAgent,WithX509NegativeSerial - 12 resource methods across 8 categories:
ListADOMsListDevicesListPolicyPackages,ListPoliciesListAddresses,ListAddressGroupsListServices,ListServiceGroupsListSchedulesRecurring,ListSchedulesOnetimeListVirtualIPsListIPPools
- Enum mapping — numeric API values converted to named FortiOS strings
- Input validation — ADOM/package names restricted to safe characters
- Comprehensive tests — httptest mocks, table-driven conversion tests
Install
go get danny.vn/fortimgr@v0.1.0
Requires Go 1.24+.