-
Notifications
You must be signed in to change notification settings - Fork 4
/
paloalto_sync.go
346 lines (331 loc) · 11.5 KB
/
paloalto_sync.go
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
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
package paloalto
import (
"fmt"
"strconv"
"time"
"github.com/bl4ko/netbox-ssot/internal/constants"
"github.com/bl4ko/netbox-ssot/internal/netbox/inventory"
"github.com/bl4ko/netbox-ssot/internal/netbox/objects"
"github.com/bl4ko/netbox-ssot/internal/source/common"
"github.com/bl4ko/netbox-ssot/internal/utils"
)
// Sync device creates default device in netbox representing Paloalto firewall.
func (pas *PaloAltoSource) syncDevice(nbi *inventory.NetboxInventory) error {
deviceName := pas.SystemInfo["devicename"]
if deviceName == "" {
return fmt.Errorf("can't extract device name from system info")
}
deviceSerialNumber := pas.SystemInfo["serial"]
deviceModel := pas.SystemInfo["model"]
if deviceModel == "" {
pas.Logger.Warningf(pas.Ctx, "model field in system info is empty. Using fallback mechanism.")
deviceModel = constants.DefaultModel
}
deviceManufacturer, err := nbi.AddManufacturer(pas.Ctx, &objects.Manufacturer{
Name: "Palo Alto Networks, Inc.",
Slug: utils.Slugify("Palo Alto Networks, Inc."),
})
if err != nil {
return fmt.Errorf("failed adding manufacturer: %s", err)
}
deviceType, err := nbi.AddDeviceType(pas.Ctx, &objects.DeviceType{
Manufacturer: deviceManufacturer,
Model: deviceModel,
Slug: utils.Slugify(deviceManufacturer.Name + deviceModel),
})
if err != nil {
return fmt.Errorf("add device type: %s", err)
}
deviceTenant, err := common.MatchHostToTenant(pas.Ctx, nbi, deviceName, pas.HostTenantRelations)
if err != nil {
return fmt.Errorf("match host to tenant: %s", err)
}
deviceRole, err := nbi.AddDeviceRole(pas.Ctx, &objects.DeviceRole{
Name: constants.DeviceRoleFirewall,
Slug: utils.Slugify(constants.DeviceRoleFirewall),
Color: constants.DeviceRoleFirewallColor,
VMRole: false,
})
if err != nil {
return fmt.Errorf("add DeviceRole: %s", err)
}
deviceSite, err := common.MatchHostToSite(pas.Ctx, nbi, deviceName, pas.HostSiteRelations)
if err != nil {
return fmt.Errorf("match host to site: %s", err)
}
devicePlatformName := fmt.Sprintf("PAN-OS %s", pas.SystemInfo["sw-version"])
devicePlatform, err := nbi.AddPlatform(pas.Ctx, &objects.Platform{
Name: devicePlatformName,
Slug: utils.Slugify(devicePlatformName),
Manufacturer: deviceManufacturer,
})
if err != nil {
return fmt.Errorf("add platform: %s", err)
}
NBDevice, err := nbi.AddDevice(pas.Ctx, &objects.Device{
NetboxObject: objects.NetboxObject{
Tags: pas.SourceTags,
},
Name: deviceName,
Site: deviceSite,
DeviceRole: deviceRole,
Status: &objects.DeviceStatusActive,
DeviceType: deviceType,
Tenant: deviceTenant,
Platform: devicePlatform,
SerialNumber: deviceSerialNumber,
})
if err != nil {
return fmt.Errorf("add device: %s", err)
}
pas.NBFirewall = NBDevice
return nil
}
func (pas *PaloAltoSource) syncInterfaces(nbi *inventory.NetboxInventory) error {
for _, iface := range pas.Ifaces {
if iface.Name == "" {
pas.Logger.Debugf(pas.Ctx, "empty interface name. Skipping...")
continue
}
if utils.FilterInterfaceName(iface.Name, pas.SourceConfig.InterfaceFilter) {
pas.Logger.Debugf(pas.Ctx, "interface %s is filtered out with interface filter %s", iface.Name, pas.SourceConfig.InterfaceFilter)
continue
}
var ifaceLinkSpeed objects.InterfaceSpeed
ifaceType := &objects.OtherInterfaceType
if iface.LinkSpeed != "" {
speed, _ := strconv.Atoi(iface.LinkSpeed)
ifaceLinkSpeed = objects.InterfaceSpeed(speed)
if _, ok := objects.IfaceSpeed2IfaceType[objects.InterfaceSpeed(speed)]; ok {
ifaceType = objects.IfaceSpeed2IfaceType[objects.InterfaceSpeed(speed)]
}
}
var ifaceDuplex *objects.InterfaceDuplex
if iface.LinkDuplex != "" {
switch iface.LinkDuplex {
case "full":
ifaceDuplex = &objects.DuplexFull
case "auto":
ifaceDuplex = &objects.DuplexAuto
case "half":
ifaceDuplex = &objects.DuplexHalf
case "":
default:
pas.Logger.Debugf(pas.Ctx, "not implemented duplex value %s", iface.LinkDuplex)
}
}
var ifaceVdcs []*objects.VirtualDeviceContext
if vdc := pas.getVirtualDeviceContext(nbi, iface.Name); vdc != nil {
ifaceVdcs = []*objects.VirtualDeviceContext{vdc}
}
nbIface, err := nbi.AddInterface(pas.Ctx, &objects.Interface{
NetboxObject: objects.NetboxObject{
Tags: pas.SourceTags,
Description: iface.Comment,
},
Name: iface.Name,
Type: ifaceType,
Duplex: ifaceDuplex,
Device: pas.NBFirewall,
MTU: iface.Mtu,
Speed: ifaceLinkSpeed,
Vdcs: ifaceVdcs,
})
if err != nil {
return fmt.Errorf("add interface %s", err)
}
if len(iface.StaticIps) > 0 {
pas.syncIPs(nbi, nbIface, iface.StaticIps, nil)
}
for _, subIface := range pas.Iface2SubIfaces[iface.Name] {
subIfaceName := subIface.Name
if subIfaceName == "" {
continue
}
var subIfaceVlan *objects.Vlan
subIfaceVlans := []*objects.Vlan{}
var subifaceMode *objects.InterfaceMode
if subIface.Tag != 0 {
// Extract Vlan
vlanName := fmt.Sprintf("Vlan%d", subIface.Tag)
vlanGroup, err := common.MatchVlanToGroup(pas.Ctx, nbi, vlanName, pas.VlanGroupRelations)
if err != nil {
return fmt.Errorf("match vlan to group: %s", err)
}
vlanTenant, err := common.MatchVlanToTenant(pas.Ctx, nbi, vlanName, pas.VlanTenantRelations)
if err != nil {
return fmt.Errorf("match vlan to tenant: %s", err)
}
subIfaceVlan, err = nbi.AddVlan(pas.Ctx, &objects.Vlan{
NetboxObject: objects.NetboxObject{
Tags: pas.SourceTags,
Description: subIface.Comment,
},
Status: &objects.VlanStatusActive,
Name: fmt.Sprintf("Vlan%d", subIface.Tag),
Vid: subIface.Tag,
Tenant: vlanTenant,
Group: vlanGroup,
})
if err != nil {
return fmt.Errorf("add vlan: %s", err)
}
subIfaceVlans = append(subIfaceVlans, subIfaceVlan)
subifaceMode = &objects.InterfaceModeTagged
}
var vdcs []*objects.VirtualDeviceContext
if vdc := pas.getVirtualDeviceContext(nbi, subIfaceName); vdc != nil {
vdcs = []*objects.VirtualDeviceContext{vdc}
}
nbSubIface, err := nbi.AddInterface(pas.Ctx, &objects.Interface{
NetboxObject: objects.NetboxObject{
Tags: pas.SourceTags,
Description: subIface.Comment,
},
Name: subIface.Name,
Type: &objects.VirtualInterfaceType,
Device: pas.NBFirewall,
Mode: subifaceMode,
TaggedVlans: subIfaceVlans,
ParentInterface: nbIface,
MTU: subIface.Mtu,
Vdcs: vdcs,
})
if err != nil {
return fmt.Errorf("add subinterface: %s", err)
}
if len(subIface.StaticIps) > 0 {
pas.syncIPs(nbi, nbSubIface, subIface.StaticIps, subIfaceVlan)
}
}
}
return nil
}
// syncIPs adds all of the given ips to the given nbIface. It also
// Extracts prefixes from ips and connect them with prefix vlan.
func (pas *PaloAltoSource) syncIPs(nbi *inventory.NetboxInventory, nbIface *objects.Interface, ips []string, prefixVlan *objects.Vlan) {
for _, ipAddress := range ips {
if !utils.SubnetsContainIPAddress(ipAddress, pas.SourceConfig.IgnoredSubnets) {
dnsName := utils.ReverseLookup(ipAddress)
_, err := nbi.AddIPAddress(pas.Ctx, &objects.IPAddress{
NetboxObject: objects.NetboxObject{
Tags: pas.SourceTags,
CustomFields: map[string]interface{}{
constants.CustomFieldArpEntryName: false,
},
},
Address: ipAddress,
AssignedObjectID: nbIface.ID,
DNSName: dnsName,
AssignedObjectType: objects.AssignedObjectTypeDeviceInterface,
})
if err != nil {
pas.Logger.Errorf(pas.Ctx, "adding ip address %s failed with error: %s", ipAddress, err)
continue
}
prefix, mask, err := utils.GetPrefixAndMaskFromIPAddress(ipAddress)
if err != nil {
pas.Logger.Warningf(pas.Ctx, "extract prefix from address: %s", err)
} else if mask != constants.MaxIPv4MaskBits {
var prefixTenant *objects.Tenant
if prefixVlan != nil {
prefixTenant = prefixVlan.Tenant
}
_, err = nbi.AddPrefix(pas.Ctx, &objects.Prefix{
Prefix: prefix,
Tenant: prefixTenant,
Vlan: prefixVlan,
})
if err != nil {
pas.Logger.Errorf(pas.Ctx, "adding prefix: %s", err)
}
}
}
}
}
// syncSecurityZones syncs all security zones from palo alto as virtual device context in netbox.
// They are all added as part of main paloalto firewall device.
func (pas *PaloAltoSource) syncSecurityZones(nbi *inventory.NetboxInventory) error {
for _, securityZone := range pas.SecurityZones {
_, err := nbi.AddVirtualDeviceContext(pas.Ctx, &objects.VirtualDeviceContext{
NetboxObject: objects.NetboxObject{
Tags: pas.SourceTags,
},
Name: securityZone.Name,
Device: pas.NBFirewall,
Status: &objects.VDCStatusActive,
})
if err != nil {
return fmt.Errorf("add VirtualDeviceContext: %s", err)
}
}
return nil
}
// getVirtualDeviceContext retrieves the virtual device context associated with the given interface name.
func (pas *PaloAltoSource) getVirtualDeviceContext(nbi *inventory.NetboxInventory, ifaceName string) *objects.VirtualDeviceContext {
var virtualDeviceContext *objects.VirtualDeviceContext
zoneName := pas.Iface2SecurityZone[ifaceName]
if vdc, ok := nbi.VirtualDeviceContextsIndexByNameAndDeviceID[zoneName][pas.NBFirewall.ID]; ok {
virtualDeviceContext = vdc
}
return virtualDeviceContext
}
func (pas *PaloAltoSource) syncArpTable(nbi *inventory.NetboxInventory) error {
if !pas.SourceConfig.CollectArpData {
pas.Logger.Info(pas.Ctx, "skipping collecting of arp data")
return nil
}
// We tag it with special tag for arp data.
arpTag, err := nbi.AddTag(pas.Ctx, &objects.Tag{
Name: constants.DefaultArpTagName,
Slug: utils.Slugify(constants.DefaultArpTagName),
Color: constants.DefaultArpTagColor,
Description: "tag created for ip's collected from arp table",
})
if err != nil {
return fmt.Errorf("add tag: %s", err)
}
// We create custom field for tracking when was arp entry last seen
_, err = nbi.AddCustomField(pas.Ctx, &objects.CustomField{
Name: constants.CustomFieldArpIPLastSeenName,
Label: constants.CustomFieldArpIPLastSeenLabel,
Type: objects.CustomFieldTypeText,
FilterLogic: objects.FilterLogicLoose,
CustomFieldUIVisible: &objects.CustomFieldUIVisibleAlways,
CustomFieldUIEditable: &objects.CustomFieldUIEditableYes,
DisplayWeight: objects.DisplayWeightDefault,
Description: constants.CustomFieldArpIPLastSeenDescription,
SearchWeight: objects.SearchWeightDefault,
ObjectTypes: []objects.ObjectType{objects.ObjectTypeIpamIPAddress},
})
if err != nil {
return fmt.Errorf("add custom field: %s", err)
}
for _, entry := range pas.ArpData {
if entry.MAC != "(incomplete)" {
newTags := pas.SourceTags
newTags = append(newTags, arpTag)
currentTime := time.Now()
dnsName := utils.ReverseLookup(entry.IP)
defaultMask := 32
addressWithMask := fmt.Sprintf("%s/%d", entry.IP, defaultMask)
_, err = nbi.AddIPAddress(pas.Ctx, &objects.IPAddress{
NetboxObject: objects.NetboxObject{
Tags: newTags,
Description: fmt.Sprintf("IP collected from %s arp table", pas.SourceConfig.Name),
CustomFields: map[string]interface{}{
constants.CustomFieldArpIPLastSeenName: currentTime.Format(constants.ArpLastSeenFormat),
constants.CustomFieldArpEntryName: true,
},
},
Address: addressWithMask,
DNSName: dnsName,
Status: &objects.IPAddressStatusActive,
})
if err != nil {
return fmt.Errorf("add arp ip address: %s", err)
}
}
}
return nil
}