-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathdevice.go
More file actions
173 lines (160 loc) · 6.14 KB
/
Copy pathdevice.go
File metadata and controls
173 lines (160 loc) · 6.14 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
package fortimgr
import (
"context"
"fmt"
)
type apiDevice struct {
Name string `json:"name"`
OID int `json:"oid"`
SN string `json:"sn"`
Platform string `json:"platform_str"`
OSVer int `json:"os_ver"`
MrNum int `json:"mr"`
PatchNum int `json:"patch"`
BuildNum int `json:"build"`
HAMode any `json:"ha_mode"`
HACluster int `json:"ha_cluster"`
ConnState any `json:"conn_status"`
IP string `json:"ip"`
Hostname string `json:"hostname"`
ConfStatus any `json:"conf_status"`
DevStatus any `json:"dev_status"`
LastChecked any `json:"last_checked"`
LastResync any `json:"last_resync"`
HASlave []apiHASlave `json:"ha_slave"`
// v1.1.0 — license / subscription fields. These map to the 10 flat
// License* fields on Device. adm_pass, private_key, and psk are
// deliberately NOT declared here so they cannot be unmarshaled even
// if FortiManager includes them in the response.
VMLicExpire any `json:"vm_lic_expire"`
VMLicOverdueSince any `json:"vm_lic_overdue_since"`
FoslicCPU int `json:"foslic_cpu"`
FoslicRAM int `json:"foslic_ram"`
FoslicUTM int `json:"foslic_utm"`
FoslicType int `json:"foslic_type"`
FoslicDRSite int `json:"foslic_dr_site"`
FoslicInstTime any `json:"foslic_inst_time"`
FoslicLastSync any `json:"foslic_last_sync"`
LicFlags int `json:"lic_flags"`
LicRegion string `json:"lic_region"`
}
// deviceFields is the allowlist of fields ListDevices requests from
// /dvmdb/adom/{adom}/device. Explicitly excludes adm_pass, private_key,
// and psk so encrypted device credentials never transit the wire from
// FortiManager to the SDK process. Verified against a live FortiManager:
// requesting the allowlist returns 30 fields vs 102 without it, and
// none of the 4 known credential fields appear in the filtered response.
var deviceFields = []string{
// identity
"name", "hostname", "oid", "sn", "platform_str", "ip",
// firmware
"os_ver", "mr", "patch", "build",
// operational status
"conn_status", "conf_status", "dev_status", "last_checked", "last_resync",
// HA
"ha_mode", "ha_cluster", "ha_slave",
// license / subscription
"vm_lic_expire", "vm_lic_overdue_since",
"foslic_cpu", "foslic_ram", "foslic_utm", "foslic_type", "foslic_dr_site",
"foslic_inst_time", "foslic_last_sync",
"lic_flags", "lic_region",
}
// apiHASlave is the per-member entry in the dvmdb device's ha_slave array.
// Each entry describes one FortiGate in the HA cluster. role=1 is the
// primary/master; role=0 is the secondary/slave.
type apiHASlave struct {
Name string `json:"name"`
Role any `json:"role"`
SN string `json:"sn"`
Status any `json:"status"`
ConfStatus any `json:"conf_status"`
}
// ListDevices retrieves all FortiGate devices from an ADOM.
// An ADOM (Administrative Domain) is FortiManager's multi-tenancy scope.
// Use "root" for the default ADOM in single-tenant deployments.
//
// Pagination: fetches every page (1000 rows default) via getPaged,
// merging the deviceFields allowlist into every per-page request so
// encrypted credentials are never fetched from the wire. Override page
// size or observe progress via WithPageSize / WithPageCallback.
func (c *Client) ListDevices(ctx context.Context, adom string, opts ...ListOption) ([]Device, error) {
if !c.LoggedIn() {
return nil, ErrNotLoggedIn
}
if !validName(adom) {
return nil, fmt.Errorf("%w: %q", ErrInvalidName, adom)
}
apiURL := fmt.Sprintf("/dvmdb/adom/%s/device", adom)
items, err := getPaged[apiDevice](ctx, c, apiURL, map[string]any{
"fields": deviceFields,
}, buildListConfig(opts))
if err != nil {
return nil, err
}
devices := make([]Device, len(items))
for i, d := range items {
devices[i] = Device{
Name: d.Name,
DeviceID: fmt.Sprintf("%d", d.OID),
SerialNumber: d.SN,
Platform: d.Platform,
Firmware: fmt.Sprintf("%d.%d.%d-b%d", d.OSVer, d.MrNum, d.PatchNum, d.BuildNum),
HAMode: mapEnum(toString(d.HAMode), deviceHAModes),
HATopology: mapEnum(toString(d.HAMode), deviceHATopologies),
HAClusterID: fmt.Sprintf("%d", d.HACluster),
Status: mapEnum(toString(d.ConnState), deviceConnStates),
IPAddress: d.IP,
ADOM: adom,
Hostname: d.Hostname,
ConfStatus: mapEnum(toString(d.ConfStatus), confStatuses),
DevStatus: mapEnum(toString(d.DevStatus), devStatuses),
LastChecked: unixToTime(d.LastChecked),
LastResync: unixToTime(d.LastResync),
HARole: haRoleFromSlaves(d.Name, d.HASlave),
HAMembers: haMembers(d.HASlave),
LicenseExpire: unixToTime(d.VMLicExpire),
LicenseOverdueSince: unixToTime(d.VMLicOverdueSince),
LicenseMaxCPU: d.FoslicCPU,
LicenseMaxRAM: d.FoslicRAM,
LicenseUTMEnabled: d.FoslicUTM != 0,
LicenseType: d.FoslicType,
LicenseInstalledAt: unixToTime(d.FoslicInstTime),
LicenseLastSync: unixToTime(d.FoslicLastSync),
LicenseRegion: d.LicRegion,
LicenseFlags: d.LicFlags,
}
}
return devices, nil
}
// haMembers converts the raw ha_slave[] array into a slice of HAMember.
// Returns nil for standalone devices (empty ha_slave).
func haMembers(slaves []apiHASlave) []HAMember {
if len(slaves) == 0 {
return nil
}
members := make([]HAMember, len(slaves))
for i, s := range slaves {
members[i] = HAMember{
Name: s.Name,
SerialNumber: s.SN,
Role: mapEnum(toString(s.Role), haRoles),
Status: mapEnum(toString(s.Status), deviceConnStates),
ConfStatus: mapEnum(toString(s.ConfStatus), confStatuses),
}
}
return members
}
// haRoleFromSlaves returns the role of the named device within its HA
// cluster, derived from the ha_slave[] array. Empty string when the device
// is standalone or no matching slave entry exists.
func haRoleFromSlaves(name string, slaves []apiHASlave) string {
if len(slaves) == 0 {
return ""
}
for _, s := range slaves {
if s.Name == name {
return mapEnum(toString(s.Role), haRoles)
}
}
return ""
}