fmcclient.go 43 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622162316241625162616271628162916301631163216331634163516361637163816391640164116421643164416451646
  1. /*
  2. * This Source Code Form is subject to the terms of the Mozilla Public
  3. * License, v. 2.0. If a copy of the MPL was not distributed with this
  4. * file, You can obtain one at https://mozilla.org/MPL/2.0/.
  5. */
  6. package fmcclient
  7. // https://www.cisco.com/c/en/us/td/docs/security/firepower/620/api/REST/Firepower_Management_Center_REST_API_Quick_Start_Guide_620/Objects_in_the_REST_API.html#reference_jpj_jqc_bcb
  8. import (
  9. "bytes"
  10. "context"
  11. "encoding/json"
  12. "errors"
  13. "fmt"
  14. "io"
  15. "log"
  16. "net/http"
  17. "strings"
  18. "sync"
  19. "time"
  20. )
  21. const MaxAuthTokenMinutes = 60
  22. const MaxAuthTokenRefreshes = 3
  23. const MinutesBeforeRefreshDeadline = 3
  24. func NewAuthTokenExpiry(time.Time) time.Time {
  25. return time.Now().Add(MaxAuthTokenMinutes * time.Minute)
  26. }
  27. func withinAuthTokenRefreshWindow(expiry time.Time) bool {
  28. minUntilExpiry := time.Until(expiry).Minutes()
  29. return 0 < minUntilExpiry &&
  30. minUntilExpiry <= MinutesBeforeRefreshDeadline
  31. }
  32. func authTokenRefreshDeadlineHasPassed(
  33. expiry time.Time,
  34. ) bool {
  35. return time.Until(expiry) <= time.Duration(0)
  36. }
  37. func maxAuthTokenRefreshesHasBeenReached(cnt int) bool {
  38. return cnt >= MaxAuthTokenRefreshes
  39. }
  40. const (
  41. FMCStatusCodeOK int = 200
  42. FMCStatusCodeCreated int = 201
  43. FMCStatusCodeAccepted int = 202
  44. FMCStatusCodeNoContent int = 204
  45. FMCStatusCodeBadRequest int = 400
  46. FMCStatusCodeNotFound int = 404
  47. FMCStatusCodeMethodNotAllowed int = 405
  48. FMCStatusCodeUnprocessableEntity int = 422
  49. FMCStatusCodeTooManyRequests int = 429
  50. )
  51. func newFMCError(
  52. resp *http.Response,
  53. host,
  54. msg string,
  55. ) FMCError {
  56. fmcErr := &FMCErrorResponse{
  57. Error: FMCError{
  58. Host: host,
  59. HttpStatus: http.StatusText(resp.StatusCode),
  60. HttpStatusCode: resp.StatusCode,
  61. Err: msg,
  62. },
  63. }
  64. defer resp.Body.Close()
  65. body, err := io.ReadAll(resp.Body)
  66. if err != nil {
  67. return fmcErr.Error
  68. }
  69. json.Unmarshal(body, &fmcErr)
  70. return fmcErr.Error
  71. }
  72. type FMCError struct {
  73. Host string
  74. HttpStatus string
  75. HttpStatusCode int
  76. Category string
  77. Severity string
  78. Code string
  79. Details string
  80. Messages []map[string]string
  81. Err string
  82. }
  83. func (e FMCError) Error() string {
  84. status := http.StatusText(e.HttpStatusCode)
  85. if status != "" {
  86. status = " " + status
  87. }
  88. return fmt.Sprintf(
  89. "%s at FMC host %s: http status %d%s; category: %s; severity: %s; code: %s; details: %s; messages: %s",
  90. e.Err,
  91. e.Host,
  92. e.HttpStatusCode,
  93. status,
  94. e.Category,
  95. e.Severity,
  96. e.Code,
  97. e.Details,
  98. e.Messages,
  99. )
  100. }
  101. type FMCErrorResponse struct {
  102. Error FMCError
  103. }
  104. type Pager[A any] interface {
  105. Iter() <-chan *A
  106. LastErr() error
  107. }
  108. // Retrieve n items in the range [m*n, m*n + n - 1],
  109. // inclusive. Keep p pages buffered.
  110. type pager struct {
  111. fmc *FMC
  112. url string
  113. m uint64
  114. n uint64
  115. err error
  116. p int
  117. }
  118. /*
  119. From https://www.cisco.com/c/en/us/td/docs/security/firepower/620/api/REST/Firepower_Management_Center_REST_API_Quick_Start_Guide_620/Objects_in_the_REST_API.html:
  120. > The REST API will serve only 25 results per page. This can
  121. > be increased up to 1000 using the limit query parameter.
  122. */
  123. func newPager(url string, f *FMC, pageSize uint64) pager {
  124. if pageSize == 0 {
  125. pageSize = 25
  126. }
  127. if pageSize > 1000 {
  128. pageSize = 1000
  129. }
  130. return pager{
  131. fmc: f,
  132. url: url,
  133. m: 0,
  134. n: pageSize,
  135. p: 2,
  136. }
  137. }
  138. type fmcAggregate[T any] struct {
  139. Items []*T `json:"items"`
  140. }
  141. func iteratePages[A any](
  142. p *pager,
  143. f func() fmcAggregate[A],
  144. ) chan *fmcAggregate[A] {
  145. ch := make(chan *fmcAggregate[A], p.p)
  146. go func() {
  147. defer close(ch)
  148. for {
  149. q := fmt.Sprintf(
  150. "?expanded=true&limit=%d&offset=%d",
  151. p.n,
  152. p.m*p.n,
  153. )
  154. resp, err := p.fmc.get(p.url + q)
  155. if err != nil {
  156. p.err = fmt.Errorf("pager: iteratePages: get fmc url %v: %v", p.url+q, err)
  157. return
  158. }
  159. if resp.StatusCode != FMCStatusCodeOK {
  160. p.err = newFMCError(resp, p.fmc.host, err.Error())
  161. return
  162. }
  163. defer resp.Body.Close()
  164. body, err := io.ReadAll(resp.Body)
  165. if err != nil {
  166. p.err = fmt.Errorf("pager: iteratePages: read response body: %v", err)
  167. return
  168. }
  169. //log.Printf("DEBUG: %s\n", body)
  170. tmp := f()
  171. ptr := &tmp
  172. err = json.Unmarshal(body, ptr)
  173. if err != nil {
  174. log.Printf("ERROR: failed to parse '%s'\n", body)
  175. p.err = fmt.Errorf("pager: iteratePages: unmarshal fmc response: %v", err)
  176. return
  177. }
  178. ch <- ptr
  179. head := &fmcResponseBase{}
  180. err = json.Unmarshal(body, head)
  181. if err != nil {
  182. p.err = fmt.Errorf("pager: iteratePages: unmarshal fmc head: %v", err)
  183. return
  184. }
  185. //log.Printf("DEBUG: iteratePages: cnt=%d, m=%d, n=%d", head.Paging.Count, p.m, p.n)
  186. if (p.m*p.n + p.n) >= head.Paging.Count {
  187. return
  188. }
  189. p.m++
  190. }
  191. }()
  192. return ch
  193. }
  194. func iterateItems[A any](
  195. p *pager,
  196. f func() fmcAggregate[A],
  197. ) <-chan *A {
  198. ch := make(chan *A, p.n)
  199. go func() {
  200. defer close(ch)
  201. for resp := range iteratePages(p, f) {
  202. for i := 0; i < len(resp.Items); i++ {
  203. ch <- resp.Items[i]
  204. }
  205. if p.err != nil {
  206. p.err = fmt.Errorf("iterateItems: %s", p.err)
  207. return
  208. }
  209. }
  210. }()
  211. return ch
  212. }
  213. type FMCLinks struct {
  214. Self string `json:"self"`
  215. }
  216. type fmcResponseBase struct {
  217. Links FMCLinks `json:"links"`
  218. Paging struct {
  219. Count uint64 `json:"count"`
  220. Limit uint64 `json:"limit"`
  221. Offset uint64 `json:"offset"`
  222. Pages uint64 `json:"pages"`
  223. } `json:"paging"`
  224. }
  225. type FTDAutoNATRules struct {
  226. Items []*FTDAutoNATRule `json:"items"`
  227. }
  228. type FTDAutoNATRule struct {
  229. Id string `json:"id"`
  230. Type string `json:"type"` // FTDAutoNatRule
  231. Name string `json:"name"`
  232. Description string `json:"description,omitEmpty"`
  233. Version string `json:"version,omitEmpty"` // ?
  234. Section string `json:"section,omitEmpty"` // ?
  235. NATType string `json:"natType"` // STATIC | DYNAMIC
  236. SourceInterface *FTDInterfaceObjectRef `json:"sourceInterface,omitempty"`
  237. DestinationInterface *FTDInterfaceObjectRef `json:"destinationInterface,omitempty"`
  238. OriginalNetwork *FMCNetworkObjectRef `json:"originalNetwork"`
  239. TranslatedNetwork *FMCNetworkObjectRef `json:"translatedNetwork"`
  240. ServiceProtocol string `json:"serviceProtocol,omitEmpty"` // TCP | UDP
  241. OriginalPort *int `json:"originalPort,omitEmpty"`
  242. TranslatedPort *int `json:"translatedPort,omitEmpty"`
  243. PATOptions *FTDPATOptions `json:"patOptions,omitEmpty"`
  244. InterfaceInTranslatedNetwork bool `json:"interfaceInTranslatedNetwork"`
  245. Fallthrough bool `json:"fallThrough,omitempty"`
  246. NoProxyARP bool `json:"noProxyArp"`
  247. RouteLookup bool `json:"routeLookup"`
  248. NetToNet bool `json:"netToNet"`
  249. DNS bool `json:"dns,omitEmpty"`
  250. InterfaceIPv6 bool `json:"interfaceIpv6"`
  251. }
  252. type FTDInterfaceObjectRef struct {
  253. Id string `json:"id"`
  254. Type string `json:"type"` // SecurityZone | InterfaceGroup?
  255. Name string `json:"name"`
  256. }
  257. type FTDPATOptions struct {
  258. BlockAllocation bool `json:"blockAllocation"`
  259. PATPoolAddress *FMCNetworkObjectRef `json:"patPoolAddress"`
  260. IncludeReserve bool `json:"includeReserve,omitEmpty"`
  261. FlatPortRange bool `json:"flatPortRange"`
  262. InterfacePAT bool `json:"interfacePat"`
  263. ExtendedPAT bool `json:"extendedPat"`
  264. RoundRobin bool `json:"roundRobin"`
  265. }
  266. type FMCFTDNATPolicy struct {
  267. Id string `json:"id"`
  268. Type string `json:"type"` // FTDNatPolicy
  269. Name string `json:"name"`
  270. Description string `json:"description"`
  271. }
  272. type FMCPolicyRef struct {
  273. Id string `json:"id"`
  274. Type string `json:"type"` // FTDNatPolicy | AccessPolicy
  275. Name string `json:"name"`
  276. }
  277. type FMCPolicyAssignmentTarget struct {
  278. Id string `json:"id"`
  279. Type string `json:"type"` // Device
  280. Name string `json:"name"`
  281. KeepLocalEvents bool `json:"keepLocalEvents"`
  282. ProhibitPacketTransfer bool `json:"prohibitPacketTransfer"`
  283. }
  284. type FMCPolicyAssignment struct {
  285. Id string `json:"id"`
  286. Type string `json:"type"` // PolicyAssignment
  287. Policy *FMCPolicyRef `json:"policy"`
  288. Targets []*FMCPolicyAssignmentTarget `json:"targets"`
  289. }
  290. type FMCAccessPolicy struct {
  291. Links FMCLinks `json:"links"`
  292. Id string `json:"id"`
  293. Name string `json:"name"`
  294. Type string `json:"type"`
  295. }
  296. type FMCAccessPolicyRuleActionType string
  297. const (
  298. FMCAccessPolicyRuleActionAllow FMCAccessPolicyRuleActionType = "ALLOW"
  299. FMCAccessPolicyRuleActionTrust FMCAccessPolicyRuleActionType = "TRUST"
  300. FMCAccessPolicyRuleActionMonitor FMCAccessPolicyRuleActionType = "MONITOR"
  301. FMCAccessPolicyRuleActionBlock FMCAccessPolicyRuleActionType = "BLOCK"
  302. FMCAccessPolicyRuleActionBlockReset FMCAccessPolicyRuleActionType = "BLOCK_RESET"
  303. FMCAccessPolicyRuleActionBlockInteractive FMCAccessPolicyRuleActionType = "BLOCK_INTERACTIVE"
  304. FMCAccessPolicyRuleActionBlockResetInteractive FMCAccessPolicyRuleActionType = "BLOCK_RESET_INTERACTIVE"
  305. )
  306. type FMCAccessPolicyRuleComment struct {
  307. Comment string `json:"comment"`
  308. Date string `json:"date"`
  309. User struct {
  310. Name string `json:"name"`
  311. Type string `json:"type"`
  312. } `json:"user"`
  313. }
  314. type FMCSecurityZoneObject struct {
  315. Id string `json:"id"`
  316. Name string `json:"name,omitempty"`
  317. Type string `json:"type,omitempty"`
  318. }
  319. type FMCSyslogConfigObject struct {
  320. Id string `json:"id"`
  321. Name string `json:"name,omitempty"`
  322. Type string `json:"type,omitempty"`
  323. }
  324. type FMCAccessPolicyRuleMetadata struct {
  325. Category string `json:"category,omitempty"`
  326. Section string `json:"section,omitempty"`
  327. RuleIndex uint16 `json:"ruleIndex,omitempty"`
  328. }
  329. type FMCIPSPolicyRef struct {
  330. Id string `json:"id"`
  331. Name string `json:"name,omitempty"`
  332. Type string `json:"type,omitempty"` // IntrusionPolicy
  333. InspectionMode string `json:"inspectionMode,omitempty"` // DETECTION|PREVENTION
  334. }
  335. type FMCVariableSetRef struct {
  336. Id string `json:"id"`
  337. Name string `json:"name"`
  338. Type string `json:"type"` // VariableSet
  339. }
  340. type FMCAccessPolicyRuleZones struct {
  341. Objects []*FMCSecurityZoneObject `json:"objects"`
  342. }
  343. type FMCAccessPolicyRuleNetworks struct {
  344. Objects []*FMCNetworkObjectRef `json:"objects,omitempty"`
  345. Literals []*FMCNetworkLiteral `json:"literals,omitempty"`
  346. }
  347. type FMCPortObjectRef struct {
  348. Id string `json:"id"`
  349. Name string `json:"name,omitempty"`
  350. Type string `json:"type,omitempty"` // ICMPV4Object|ICMPV6Object|ProtocolPortObject|PortObjectGroup
  351. }
  352. // N.B.: the type 'ICMPV4Object' has an upper-case 'V', as
  353. // opposed to its "Literal" counterpart.
  354. type FMCProtocolPortObject struct {
  355. Id string `json:"id"`
  356. Name string `json:"name,omitempty"`
  357. Protocol string `json:"protocol,omitempty"`
  358. Port string `json:"port,omitempty"`
  359. ICMPType string `json:"icmpType,omitempty"`
  360. Code int `json:"code,omitempty"`
  361. Type string `json:"type,omitempty"` // ICMPV4Object|ICMPV6Object|ProtocolPortObject
  362. Overridable bool `json:"overridable,omitempty"`
  363. Description string `json:"description,omitempty"`
  364. }
  365. type FMCPortObjectGroup struct {
  366. Id string `json:"id"`
  367. Name string `json:"name,omitempty"`
  368. Type string `json:"type,omitempty"` // PortObjectGroup
  369. Objects []*FMCPortObjectRef `json:"objects"`
  370. Overridable bool `json:"overridable,omitempty"`
  371. Description string `json:"description,omitempty"`
  372. }
  373. /* Protocol -9999 represents 'All'. Incidentally, the FMC
  374. * API provides protocol names rather than IP protocol
  375. * numbers, which FMC will fail to recognize on put/post.
  376. * Moreover, ICMP port literals are provided without protocol
  377. * fields, which are required for put/post. This baffling
  378. * behavior should be handled as a matter of course.
  379. */
  380. type FMCPortLiteral struct {
  381. Type string `json:"type"` // ICMPv4PortLiteral|ICMPv6PortLiteral|PortLiteral
  382. Protocol string `json:"protocol"` // 0-255,-9999
  383. Port string `json:"port,omitempty"`
  384. ICMPType string `json:"icmpType,omitempty"`
  385. Code int `json:"code,omitempty"`
  386. }
  387. type FMCAccessPolicyRulePorts struct {
  388. Objects []*FMCPortObjectRef `json:"objects"`
  389. Literals []*FMCPortLiteral `json:"literals,omitempty"`
  390. }
  391. type FMCApplicationRef struct {
  392. Id string `json:"id"`
  393. Name string `json:"name"`
  394. Type string `json:"type"` // Application
  395. }
  396. type FMCAccessPolicyRuleApplications struct {
  397. Applications []*FMCApplicationRef `json:"applications"`
  398. }
  399. type FMCFilePolicyRef struct {
  400. Id string `json:"id"`
  401. Name string `json:"name"`
  402. Type string `json:"type"` // FilePolicy
  403. }
  404. type FMCURLLiteral struct {
  405. URL string `json:"url"`
  406. Type string `json:"type"` // Url
  407. }
  408. type FMCAccessPolicyRuleURLs struct {
  409. Literals []*FMCURLLiteral `json:"literals"`
  410. }
  411. /* "Intrusion Policy, File Policy and Variable Set cannot be
  412. * selected when rule action is Block, Trust, Block with
  413. * reset or monitor"
  414. *
  415. * It is possible for FMC to allow rules with `logFiles`
  416. * enabled and no file policy set. This will fail in
  417. * put/post.
  418. */
  419. type FMCAccessPolicyRule struct {
  420. Id string `json:"id,omitempty"`
  421. Name string `json:"name"`
  422. Type string `json:"type"`
  423. Metadata *FMCAccessPolicyRuleMetadata `json:"metadata,omitempty"`
  424. Action FMCAccessPolicyRuleActionType `json:"action"`
  425. Enabled bool `json:"enabled"`
  426. EnableSyslog bool `json:"enableSyslog"`
  427. SendEventsToFMC bool `json:"sendEventsToFMC"`
  428. IPSPolicy *FMCIPSPolicyRef `json:"ipsPolicy,omitempty"`
  429. FilePolicy *FMCFilePolicyRef `json:"filePolicy,omitempty"`
  430. VariableSet *FMCVariableSetRef `json:"variableSet,omitempty"`
  431. LogBegin bool `json:"logBegin"`
  432. LogEnd bool `json:"logEnd"`
  433. LogFiles bool `json:"logFiles"`
  434. SourceZones *FMCAccessPolicyRuleZones `json:"sourceZones"`
  435. DestinationZones *FMCAccessPolicyRuleZones `json:"destinationZones"`
  436. SourceNetworks *FMCAccessPolicyRuleNetworks `json:"sourceNetworks,omitempty"`
  437. DestinationNetworks *FMCAccessPolicyRuleNetworks `json:"destinationNetworks,omitempty"`
  438. SourcePorts *FMCAccessPolicyRulePorts `json:"sourcePorts,omitempty"`
  439. DestinationPorts *FMCAccessPolicyRulePorts `json:"destinationPorts,omitempty"`
  440. CommentHistoryList []*FMCAccessPolicyRuleComment `json:"commentHistoryList,omitempty"`
  441. Applications *FMCAccessPolicyRuleApplications `json:"applications,omitempty"`
  442. URLs *FMCAccessPolicyRuleURLs `json:"urls,omitempty"`
  443. }
  444. type FMCNetworkObjectRef struct {
  445. Id string `json:"id"`
  446. Name string `json:"name"`
  447. Type string `json:"type"` // Host|Network|Range|FQDN|NetworkGroup
  448. }
  449. type FMCNetworkObject struct {
  450. Id string `json:"id,omitempty"`
  451. Name string `json:"name,omitempty"`
  452. Type string `json:"type,omitempty"` // Host|Network|Range|FQDN
  453. Value string `json:"value,omitempty"`
  454. Overridable bool `json:"overridable,omitempty"`
  455. }
  456. type FMCNetworkGroupMetadata struct {
  457. Domain struct {
  458. Id string `json:"id,omitempty"`
  459. Name string `json:"name,omitempty"`
  460. Type string `json:"type,omitempty"` // Domain
  461. }
  462. }
  463. /* It has been observed that literals with type 'Host' can
  464. * have an erroneous/obsolete '/32' prefix length in FMC,
  465. * causing put/post to fail later. This must be accounted
  466. * for. */
  467. type FMCNetworkLiteral struct {
  468. Type string `json:"type"` // Network|Host
  469. Value string `json:"value"`
  470. }
  471. type FMCNetworkGroup struct {
  472. Id string `json:"id,omitempty"`
  473. Name string `json:"name,omitempty"`
  474. Type string `json:"type,omitempty"`
  475. Overridable bool `json:"overridable,omitempty"`
  476. Metadata *FMCNetworkGroupMetadata `json:"metadata,omitempty"`
  477. Literals []*FMCNetworkLiteral `json:"literals,omitempty"`
  478. Objects []*FMCNetworkObjectRef `json:"objects,omitempty"`
  479. }
  480. type nameToUUID struct {
  481. Name string
  482. UUID string
  483. }
  484. func New(
  485. ctx context.Context,
  486. cl *http.Client,
  487. host,
  488. user,
  489. pass string,
  490. ) (*FMC, error) {
  491. f := &FMC{
  492. ctx: ctx,
  493. client: cl,
  494. host: host,
  495. basePath: "/api/fmc_config/v1",
  496. user: user,
  497. pass: pass,
  498. authMutex: &sync.Mutex{},
  499. domains: make(map[string]string),
  500. }
  501. var err error
  502. if host == "" || user == "" || pass == "" {
  503. err = errors.New("Empty host, user, or pass")
  504. }
  505. return f, err
  506. }
  507. func checkForCiscoError(
  508. resp *http.Response,
  509. respBody []byte,
  510. ) error {
  511. /* According to https://www.cisco.com/c/en/us/td/docs/security/firepower/620/api/REST/Firepower_Management_Center_REST_API_Quick_Start_Guide_620/Objects_in_the_REST_API.html
  512. the API cannot accept a message with a payload greater
  513. than 20480 bytes (20 KB). This applies to both the REST
  514. API and to API Explorer. This is not a configurable
  515. parameter. If a message exceeds this limit, the API will
  516. give an HTTP 422 error.
  517. By contrast, https://www.cisco.com/c/en/us/td/docs/security/firepower/70/api/REST/firepower_management_center_rest_api_quick_start_guide_70/Objects_In_The_REST_API.html
  518. claims the API cannot accept a message with payload
  519. greater than 2048000 bytes (2 MB). I can only hope Cisco
  520. typo-ed the former when they meant the latter. */
  521. if resp.StatusCode == FMCStatusCodeUnprocessableEntity {
  522. return fmt.Errorf("server returned error: %+v: %s", resp, string(respBody))
  523. }
  524. /* From https://www.cisco.com/c/en/us/td/docs/security/firepower/620/api/REST/Firepower_Management_Center_REST_API_Quick_Start_Guide_620/Objects_in_the_REST_API.html:
  525. > The FMC REST API implements rate limiting to reduce
  526. > network load.
  527. >
  528. > The API will accept no more than 120 messages per minute
  529. > from an individual IP address. It will only allow 10
  530. > simultaneous connections *per IP address*. These are not
  531. > configurable parameters.
  532. >
  533. > If a client exceeds these limits, the API will give an
  534. > HTTP 429 error.
  535. However, https://www.cisco.com/c/en/us/td/docs/security/firepower/70/api/REST/firepower_management_center_rest_api_quick_start_guide_70/Objects_In_The_REST_API.html
  536. states the following:
  537. > – "Too Many Requests". Too many requests were sent to
  538. > the API. This error will occur if you send more than 120
  539. > requests per minute.
  540. > – Too many concurrent requests. The system cannot
  541. > accept more than 10 parallel requests *from all
  542. > clients*.
  543. > – Too many write operations per server. The API will
  544. > only allow one PUT, POST, or DELETE request *per user*
  545. > on a server at a time.
  546. If true, these latter limits constitute a substantial
  547. reduction in acceptable client behaviors.
  548. TODO: Prevent this occurring in the first place. */
  549. if resp.StatusCode == FMCStatusCodeTooManyRequests {
  550. return fmt.Errorf("server returned error: %+v: %s", resp, string(respBody))
  551. }
  552. if resp.StatusCode < 200 || 299 < resp.StatusCode {
  553. return fmt.Errorf("server returned error: %+v: %s", resp, string(respBody))
  554. }
  555. return nil
  556. }
  557. type Mode int
  558. const (
  559. ModeReadOnly Mode = iota
  560. ModeWrite
  561. )
  562. type FMC struct {
  563. ctx context.Context
  564. client *http.Client
  565. host string
  566. basePath string
  567. user string
  568. pass string
  569. authToken string
  570. refreshToken string
  571. refreshCount int
  572. tokenExpiry time.Time
  573. watchdogCancel context.CancelFunc
  574. authMutex *sync.Mutex
  575. domains map[string]string
  576. mode Mode
  577. }
  578. func (f *FMC) Arm() Mode {
  579. f.mode = ModeWrite
  580. return f.mode
  581. }
  582. func (f *FMC) Disarm() Mode {
  583. f.mode = ModeReadOnly
  584. return f.mode
  585. }
  586. func (f *FMC) authTokenInvalid() bool {
  587. if f.authToken == "" ||
  588. f.refreshCount > MaxAuthTokenRefreshes ||
  589. time.Until(f.tokenExpiry) <= time.Duration(0) {
  590. return true
  591. }
  592. return false
  593. }
  594. func (f *FMC) resetRefreshToken() {
  595. f.refreshToken = ""
  596. f.refreshCount = 0
  597. f.tokenExpiry = time.Time{}
  598. return
  599. }
  600. func (f *FMC) processAuthResponse(
  601. resp *http.Response,
  602. ) error {
  603. /* TODO: Is this actually a failure? For the life of me, I
  604. can't recall why this was necessary. A successful
  605. authentication with status 204 is presented here:
  606. https://www.cisco.com/c/en/us/support/docs/security/firepower-management-center/215918-how-to-generate-authentication-token-for.html
  607. */
  608. if resp.StatusCode != FMCStatusCodeNoContent {
  609. return newFMCError(resp, f.host, "authentication failure")
  610. }
  611. f.tokenExpiry = NewAuthTokenExpiry(time.Now())
  612. f.authToken = resp.Header.Get("X-Auth-Access-Token")
  613. f.refreshToken = resp.Header.Get("X-Auth-Refresh-Token")
  614. return nil
  615. }
  616. // See https://www.cisco.com/c/en/us/td/docs/security/firepower/620/api/REST/Firepower_Management_Center_REST_API_Quick_Start_Guide_620/Connecting_with_a_Client.html
  617. func (f *FMC) Authenticate() error {
  618. f.authMutex.Lock()
  619. defer f.authMutex.Unlock()
  620. if f.authTokenInvalid() ||
  621. maxAuthTokenRefreshesHasBeenReached(f.refreshCount) ||
  622. authTokenRefreshDeadlineHasPassed(f.tokenExpiry) {
  623. if f.watchdogCancel != nil {
  624. f.watchdogCancel()
  625. }
  626. f.resetRefreshToken()
  627. req, err := makeAuthAccessRequest(
  628. f.ctx,
  629. f.host,
  630. f.user,
  631. f.pass,
  632. )
  633. if err != nil {
  634. return err
  635. }
  636. resp, err := f.client.Do(req)
  637. if err != nil {
  638. return err
  639. }
  640. var domains []nameToUUID
  641. json.Unmarshal(
  642. []byte(resp.Header.Get("domains")),
  643. &domains,
  644. )
  645. for i := 0; i < len(domains); i++ {
  646. f.domains[domains[i].Name] = domains[i].UUID
  647. }
  648. if err = f.processAuthResponse(resp); err != nil {
  649. return err
  650. }
  651. info("authenticated to FMC")
  652. ctx, cancel := context.WithCancel(f.ctx)
  653. f.watchdogCancel = cancel
  654. f.startTokenWatchdog(ctx)
  655. } else {
  656. if !withinAuthTokenRefreshWindow(f.tokenExpiry) {
  657. return nil
  658. }
  659. req, err := makeAuthRefreshRequest(
  660. f.ctx,
  661. f.host,
  662. f.authToken,
  663. f.refreshToken,
  664. )
  665. if err != nil {
  666. return err
  667. }
  668. resp, err := f.client.Do(req)
  669. if err != nil {
  670. return err
  671. }
  672. if err = f.processAuthResponse(resp); err != nil {
  673. return err
  674. }
  675. info("refreshed FMC auth token")
  676. f.refreshCount++
  677. }
  678. return nil
  679. }
  680. func (f *FMC) startTokenWatchdog(ctx context.Context) {
  681. info("starting auth token watchdog")
  682. go func() {
  683. defer info("stopping auth token watchdog")
  684. for {
  685. select {
  686. case <-ctx.Done():
  687. return
  688. case <-time.After(100 * time.Millisecond):
  689. if f.authTokenInvalid() {
  690. info("auth token has invalidated")
  691. return
  692. }
  693. if err := f.Authenticate(); err != nil {
  694. warn(err.Error())
  695. return
  696. }
  697. }
  698. }
  699. }()
  700. return
  701. }
  702. func (f *FMC) get(url string) (
  703. resp *http.Response,
  704. err error,
  705. ) {
  706. req, err :=
  707. http.NewRequestWithContext(f.ctx, "GET", url, nil)
  708. if err != nil {
  709. return
  710. }
  711. req.Header.Add("X-Auth-Access-Token", f.authToken)
  712. resp, err = f.client.Do(req)
  713. return
  714. }
  715. func (f *FMC) post(
  716. url string,
  717. body io.Reader,
  718. ) (resp *http.Response, err error) {
  719. req, err :=
  720. http.NewRequestWithContext(f.ctx, "POST", url, body)
  721. if err != nil {
  722. return
  723. }
  724. req.Header.Add("Content-Type", "application/json")
  725. req.Header.Add("X-Auth-Access-Token", f.authToken)
  726. resp, err = f.client.Do(req)
  727. return
  728. }
  729. func (f *FMC) put(
  730. url string,
  731. body io.Reader,
  732. ) (resp *http.Response, err error) {
  733. req, err :=
  734. http.NewRequestWithContext(f.ctx, "PUT", url, body)
  735. if err != nil {
  736. return
  737. }
  738. req.Header.Add("Content-Type", "application/json")
  739. req.Header.Add("X-Auth-Access-Token", f.authToken)
  740. resp, err = f.client.Do(req)
  741. return
  742. }
  743. func (f *FMC) postGoThing(
  744. url string,
  745. goThing any,
  746. ) (respBody []byte, err error) {
  747. reqBody, err := json.Marshal(goThing)
  748. if err != nil {
  749. err = fmt.Errorf("failed to marshal json: %v: '%#v'", err, goThing)
  750. return
  751. }
  752. resp, err := f.post(url, bytes.NewReader(reqBody))
  753. if err != nil {
  754. err = fmt.Errorf("failed to post: %v: '%s'", err, reqBody)
  755. return
  756. }
  757. defer resp.Body.Close()
  758. respBody, err = io.ReadAll(resp.Body)
  759. if err != nil {
  760. err = fmt.Errorf("failed to read response body: %v", err)
  761. return
  762. }
  763. if err = checkForCiscoError(resp, respBody); err != nil {
  764. log.Printf("DEBUG: req body: %s", string(reqBody))
  765. return
  766. }
  767. return respBody, nil
  768. }
  769. func (f *FMC) putGoThing(
  770. url string,
  771. goThing any,
  772. ) (respBody []byte, err error) {
  773. reqBody, err := json.Marshal(goThing)
  774. if err != nil {
  775. err = fmt.Errorf("failed to marshal json: %v: '%#v'", err, goThing)
  776. return
  777. }
  778. resp, err := f.put(url, bytes.NewReader(reqBody))
  779. if err != nil {
  780. err = fmt.Errorf("failed to put: %v: '%s'", err, reqBody)
  781. return
  782. }
  783. defer resp.Body.Close()
  784. respBody, err = io.ReadAll(resp.Body)
  785. if err != nil {
  786. err = fmt.Errorf("failed to read response body: %v", err)
  787. return
  788. }
  789. if err = checkForCiscoError(resp, respBody); err != nil {
  790. log.Printf("DEBUG: req body: %s", string(reqBody))
  791. return
  792. }
  793. return respBody, nil
  794. }
  795. func (f *FMC) Domain(s string) (string, bool) {
  796. v, ok := f.domains[s]
  797. return v, ok
  798. }
  799. func (f *FMC) Domains() []string {
  800. keys := make([]string, 0, len(f.domains))
  801. for k := range f.domains {
  802. keys = append(keys, k)
  803. }
  804. return keys
  805. }
  806. type FMCAccessPoliciesPager struct {
  807. pager
  808. }
  809. func (
  810. p *FMCAccessPoliciesPager,
  811. ) Iter() <-chan *FMCAccessPolicy {
  812. return iterateItems(
  813. &p.pager,
  814. func() fmcAggregate[FMCAccessPolicy] {
  815. return fmcAggregate[FMCAccessPolicy]{}
  816. },
  817. )
  818. }
  819. func (p *FMCAccessPoliciesPager) LastErr() (err error) {
  820. if p.err != nil {
  821. err = fmt.Errorf("fmc access policies pager: %v", p.err)
  822. }
  823. return
  824. }
  825. func (f *FMC) AccessPolicies(
  826. domain string,
  827. pageSize uint64,
  828. ) (*FMCAccessPoliciesPager, error) {
  829. domainUUID, ok := f.domains[domain]
  830. if !ok {
  831. return nil, fmt.Errorf("access policies: unknown domain %v", domain)
  832. }
  833. url := fmt.Sprintf(
  834. "https://%s%s/domain/%s/policy/accesspolicies",
  835. f.host,
  836. f.basePath,
  837. domainUUID,
  838. )
  839. return &FMCAccessPoliciesPager{
  840. pager: newPager(url, f, pageSize),
  841. }, nil
  842. }
  843. type FMCAccessPolicyRulesPager struct {
  844. pager
  845. }
  846. func (
  847. p *FMCAccessPolicyRulesPager,
  848. ) Iter() <-chan *FMCAccessPolicyRule {
  849. return iterateItems(
  850. &p.pager,
  851. func() fmcAggregate[FMCAccessPolicyRule] {
  852. return fmcAggregate[FMCAccessPolicyRule]{}
  853. },
  854. )
  855. }
  856. func (p *FMCAccessPolicyRulesPager) LastErr() (err error) {
  857. if p.err != nil {
  858. err = fmt.Errorf("fmc access policy rules pager: %v", p.err)
  859. }
  860. return
  861. }
  862. func (f *FMC) AccessPolicyRules(
  863. domain,
  864. policyUUID string,
  865. pageSize uint64,
  866. ) (*FMCAccessPolicyRulesPager, error) {
  867. domainUUID, ok := f.domains[domain]
  868. if !ok {
  869. return nil, fmt.Errorf("access policy rules: unknown domain %v", domain)
  870. }
  871. url := fmt.Sprintf(
  872. "https://%s%s/domain/%s/policy/accesspolicies/%s/accessrules",
  873. f.host,
  874. f.basePath,
  875. domainUUID,
  876. policyUUID,
  877. )
  878. return &FMCAccessPolicyRulesPager{
  879. pager: newPager(url, f, pageSize),
  880. }, nil
  881. }
  882. func (f *FMC) AddAccessPolicyRules(
  883. domain,
  884. policyUUID string,
  885. rules []*FMCAccessPolicyRule,
  886. ) error {
  887. if f.mode != ModeWrite {
  888. return fmt.Errorf("add access policy rules: client is not armed; not allowed to change fmc")
  889. }
  890. domainUUID, ok := f.domains[domain]
  891. if !ok {
  892. return fmt.Errorf("add access policy rules: unknown domain %v", domain)
  893. }
  894. // TODO: permit specification of categories, sections
  895. //category := url.PathEscape(rule.Metadata.Category)
  896. url := fmt.Sprintf(
  897. "https://%s%s/domain/%s/policy/accesspolicies/%s/accessrules?bulk=true",
  898. f.host,
  899. f.basePath,
  900. domainUUID,
  901. policyUUID,
  902. )
  903. var err error
  904. for _, rule := range rules {
  905. rule.Id = ""
  906. rule.Metadata = nil
  907. if rule.SourceNetworks != nil &&
  908. rule.DestinationNetworks != nil {
  909. if len(rule.SourceNetworks.Objects) > 42 &&
  910. len(rule.SourceNetworks.Objects) < 50 ||
  911. len(rule.DestinationNetworks.Objects) > 42 &&
  912. len(rule.DestinationNetworks.Objects) < 50 {
  913. log.Printf("WARNING: network object count is approaching limit of 50: '%s'", rule.Name)
  914. }
  915. if len(rule.SourceNetworks.Objects) == 50 ||
  916. len(rule.DestinationNetworks.Objects) == 50 {
  917. log.Printf("WARNING: limit of 50 network objects reached: '%s'", rule.Name)
  918. }
  919. if len(rule.SourceNetworks.Objects) > 50 ||
  920. len(rule.DestinationNetworks.Objects) > 50 {
  921. log.Printf("ERROR: add access policy rules: unable to add rule: network object count exceeds 50-object limit: '%s'", rule.Name)
  922. err = fmt.Errorf("add access policy rules: unable to add rule: network object count exceeded 50-object limit")
  923. }
  924. }
  925. }
  926. if err != nil {
  927. return err
  928. }
  929. maxPost := 10
  930. for i := 0; i < len(rules); i += maxPost {
  931. log.Printf("DEBUG: add access policy rules: posting seq %d", i+1)
  932. last := min(i+maxPost, len(rules))
  933. _, err := f.postGoThing(url, rules[i:last])
  934. if err != nil {
  935. return err
  936. }
  937. }
  938. return nil
  939. }
  940. func (f *FMC) UpdateAccessPolicyRule(
  941. domain,
  942. policyUUID string,
  943. rule *FMCAccessPolicyRule,
  944. ) error {
  945. if f.mode != ModeWrite {
  946. return fmt.Errorf("update access policy rule: client is not armed; not allowed to change fmc")
  947. }
  948. domainUUID, ok := f.domains[domain]
  949. if !ok {
  950. return fmt.Errorf("update access policy rule: unknown domain %v", domain)
  951. }
  952. url := fmt.Sprintf(
  953. "https://%s%s/domain/%s/policy/accesspolicies/%s/accessrules/%s",
  954. f.host,
  955. f.basePath,
  956. domainUUID,
  957. policyUUID,
  958. rule.Id,
  959. )
  960. rule.Metadata = nil
  961. if rule.SourceNetworks != nil &&
  962. rule.DestinationNetworks != nil {
  963. if len(rule.SourceNetworks.Objects) > 42 &&
  964. len(rule.SourceNetworks.Objects) < 50 ||
  965. len(rule.DestinationNetworks.Objects) > 42 &&
  966. len(rule.DestinationNetworks.Objects) < 50 {
  967. log.Printf("WARNING: network object count is approaching limit of 50: '%s'", rule.Name)
  968. }
  969. if len(rule.SourceNetworks.Objects) == 50 ||
  970. len(rule.DestinationNetworks.Objects) == 50 {
  971. log.Printf("WARNING: limit of 50 network objects reached: '%s'", rule.Name)
  972. }
  973. if len(rule.SourceNetworks.Objects) > 50 ||
  974. len(rule.DestinationNetworks.Objects) > 50 {
  975. return fmt.Errorf("change access policy rule: unable to change rule: network object count exceeds 50-object limit: '%s'", rule.Name)
  976. }
  977. }
  978. _, err := f.putGoThing(url, rule)
  979. //log.Printf("DEBUG: post response body: %s", string(respBody))
  980. return err
  981. }
  982. func (f *FMC) FTDNATPolicies(
  983. domain string,
  984. pageSize uint64,
  985. ) (*FMCFTDNATPoliciesPager, error) {
  986. domainUUID, ok := f.domains[domain]
  987. if !ok {
  988. return nil, fmt.Errorf("ftd nat policies: unknown domain %v", domain)
  989. }
  990. url := fmt.Sprintf(
  991. "https://%s%s/domain/%s/policy/ftdnatpolicies?expanded=true",
  992. f.host,
  993. f.basePath,
  994. domainUUID,
  995. )
  996. return &FMCFTDNATPoliciesPager{
  997. pager: newPager(url, f, pageSize),
  998. }, nil
  999. }
  1000. type FMCFTDNATPoliciesPager struct {
  1001. pager
  1002. }
  1003. func (
  1004. p *FMCFTDNATPoliciesPager,
  1005. ) Iter() <-chan *FMCFTDNATPolicy {
  1006. return iterateItems(
  1007. &p.pager,
  1008. func() fmcAggregate[FMCFTDNATPolicy] {
  1009. return fmcAggregate[FMCFTDNATPolicy]{}
  1010. },
  1011. )
  1012. }
  1013. func (p *FMCFTDNATPoliciesPager) LastErr() (err error) {
  1014. if p.err != nil {
  1015. err = fmt.Errorf("fmc ftd nat policies pager: %v", p.err)
  1016. }
  1017. return
  1018. }
  1019. func (f *FMC) PolicyAssignments(
  1020. domain string,
  1021. pageSize uint64,
  1022. ) (*FMCPolicyAssignmentsPager, error) {
  1023. domainUUID, ok := f.domains[domain]
  1024. if !ok {
  1025. return nil, fmt.Errorf("policy assignments: unknown domain %v", domain)
  1026. }
  1027. url := fmt.Sprintf(
  1028. "https://%s%s/domain/%s/assignment/policyassignments",
  1029. f.host,
  1030. f.basePath,
  1031. domainUUID,
  1032. )
  1033. return &FMCPolicyAssignmentsPager{
  1034. pager: newPager(url, f, pageSize),
  1035. }, nil
  1036. }
  1037. type FMCPolicyAssignmentsPager struct {
  1038. pager
  1039. }
  1040. func (
  1041. p *FMCPolicyAssignmentsPager,
  1042. ) Iter() <-chan *FMCPolicyAssignment {
  1043. return iterateItems(
  1044. &p.pager,
  1045. func() fmcAggregate[FMCPolicyAssignment] {
  1046. return fmcAggregate[FMCPolicyAssignment]{}
  1047. },
  1048. )
  1049. }
  1050. func (p *FMCPolicyAssignmentsPager) LastErr() (err error) {
  1051. if p.err != nil {
  1052. err = fmt.Errorf("fmc policy assignments pager: %v", p.err)
  1053. }
  1054. return
  1055. }
  1056. func (f *FMC) UpdatePolicyAssignment(
  1057. domain string,
  1058. assignment *FMCPolicyAssignment,
  1059. ) error {
  1060. if f.mode != ModeWrite {
  1061. return fmt.Errorf("update policy assignment: client is not armed; not allowed to change fmc")
  1062. }
  1063. domainUUID, ok := f.domains[domain]
  1064. if !ok {
  1065. return fmt.Errorf("update policy assignment: unknown domain %v", domain)
  1066. }
  1067. url := fmt.Sprintf(
  1068. "https://%s%s/domain/%s/assignment/policyassignments/%s",
  1069. f.host,
  1070. f.basePath,
  1071. domainUUID,
  1072. assignment.Id,
  1073. )
  1074. //assignment.Metadata = nil
  1075. _, err := f.putGoThing(url, assignment)
  1076. return err
  1077. }
  1078. func (f *FMC) AddNetworkObjects(
  1079. domain string,
  1080. objects map[string]*FMCNetworkObject,
  1081. ) error {
  1082. if f.mode != ModeWrite {
  1083. return fmt.Errorf("add network host object: client is not armed; not allowed to change fmc")
  1084. }
  1085. domainUUID, ok := f.domains[domain]
  1086. if !ok {
  1087. return fmt.Errorf("add network host object: unknown domain %v", domain)
  1088. }
  1089. typeToPath := map[string]string{
  1090. "Host": "hosts",
  1091. "Network": "networks",
  1092. "Range": "ranges",
  1093. "FQDN": "fqdns",
  1094. }
  1095. url := func(suffix string) string {
  1096. return fmt.Sprintf(
  1097. "https://%s%s/domain/%s/object/%s?bulk=true",
  1098. f.host,
  1099. f.basePath,
  1100. domainUUID,
  1101. suffix,
  1102. )
  1103. }
  1104. objectsByType := make(map[string][]*FMCNetworkObject)
  1105. for k, _ := range typeToPath {
  1106. objectsByType[k] = make([]*FMCNetworkObject, 0, 32)
  1107. }
  1108. for _, e := range objects {
  1109. objectsByType[e.Type] = append(objectsByType[e.Type], e)
  1110. }
  1111. maxPost := 500
  1112. for k, os := range objectsByType {
  1113. suffix, ok := typeToPath[k]
  1114. if !ok {
  1115. return fmt.Errorf("add network object: unknown object type %v", k)
  1116. }
  1117. for i := 0; i < len(os); i += maxPost {
  1118. log.Printf("DEBUG: add network objects: posting type %s, block %d", k, i+1)
  1119. last := min(i+maxPost, len(os))
  1120. _, err := f.postGoThing(url(suffix), os[i:last])
  1121. if err != nil {
  1122. return err
  1123. }
  1124. }
  1125. }
  1126. return nil
  1127. }
  1128. func min(a, b int) int {
  1129. if a < b {
  1130. return a
  1131. }
  1132. return b
  1133. }
  1134. func (f *FMC) AddNetworkGroups(
  1135. domain string,
  1136. groups map[string]*FMCNetworkGroup,
  1137. ) error {
  1138. if f.mode != ModeWrite {
  1139. return fmt.Errorf("add network host object: client is not armed; not allowed to change fmc")
  1140. }
  1141. domainUUID, ok := f.domains[domain]
  1142. if !ok {
  1143. return fmt.Errorf("add network host object: unknown domain %v", domain)
  1144. }
  1145. url := fmt.Sprintf(
  1146. "https://%s%s/domain/%s/object/networkgroups?bulk=true",
  1147. f.host,
  1148. f.basePath,
  1149. domainUUID,
  1150. )
  1151. unsorted := make(map[string]bool)
  1152. for k, _ := range groups {
  1153. unsorted[k] = true
  1154. }
  1155. antichains := make([][]*FMCNetworkGroup, 0, 10)
  1156. antichains =
  1157. append(antichains, make([]*FMCNetworkGroup, 0, 32))
  1158. nextAntichain := make(map[string]bool)
  1159. separable := func(key string) bool {
  1160. for _, e := range groups[key].Objects {
  1161. if e.Type != "NetworkGroup" {
  1162. continue
  1163. }
  1164. if unsorted[strings.ToLower(e.Name)] ||
  1165. nextAntichain[strings.ToLower(e.Name)] {
  1166. return false
  1167. }
  1168. }
  1169. return true
  1170. }
  1171. lastCnt := -1
  1172. for {
  1173. depth := len(antichains) - 1
  1174. lastCnt = len(unsorted)
  1175. for k, _ := range unsorted {
  1176. if separable(k) {
  1177. groups[k].Id = ""
  1178. groups[k].Metadata = nil
  1179. antichains[depth] = append(antichains[depth], groups[k])
  1180. nextAntichain[k] = true
  1181. delete(unsorted, k)
  1182. }
  1183. }
  1184. if len(unsorted) == lastCnt {
  1185. break
  1186. }
  1187. if len(unsorted) > 0 {
  1188. nextAntichain = make(map[string]bool)
  1189. antichains =
  1190. append(antichains, make([]*FMCNetworkGroup, 0, 32))
  1191. }
  1192. }
  1193. if len(unsorted) > 0 {
  1194. return fmt.Errorf("WARN: add network groups: provided group set contains cycles: culprits %+v", unsorted)
  1195. }
  1196. posted := make(map[string]string)
  1197. maxPost := 50
  1198. for j, a := range antichains {
  1199. for _, e := range a {
  1200. for _, r := range e.Objects {
  1201. if r.Type == "NetworkGroup" {
  1202. if uuid, ok := posted[strings.ToLower(r.Name)]; ok {
  1203. r.Id = uuid
  1204. }
  1205. }
  1206. }
  1207. }
  1208. for i := 0; i < len(a); i += maxPost {
  1209. log.Printf("DEBUG: add network groups: posting antichain %d, seq %d", j, i+1)
  1210. last := min(i+maxPost, len(a))
  1211. respBody, err := f.postGoThing(url, a[i:last])
  1212. if err != nil {
  1213. return err
  1214. }
  1215. response := &FMCAddNetworkGroupsResponse{}
  1216. err = json.Unmarshal(respBody, response)
  1217. if err != nil {
  1218. log.Printf("ERROR: add network groups: failed to parse '%s'\n", respBody)
  1219. return fmt.Errorf("pager: iteratePages: unmarshal fmc response: %v", err)
  1220. }
  1221. for _, i := range response.Items {
  1222. posted[strings.ToLower(i.Name)] = i.Id
  1223. }
  1224. }
  1225. }
  1226. return nil
  1227. }
  1228. type FMCAddNetworkGroupsResponse struct {
  1229. Items []*FMCNetworkGroup `json:"items"`
  1230. }
  1231. func (f *FMC) AddNetworkHostObject(
  1232. domain string,
  1233. object *FMCNetworkObject,
  1234. ) error {
  1235. if f.mode != ModeWrite {
  1236. return fmt.Errorf("add network host object: client is not armed; not allowed to change fmc")
  1237. }
  1238. domainUUID, ok := f.domains[domain]
  1239. if !ok {
  1240. return fmt.Errorf("add network host object: unknown domain %v", domain)
  1241. }
  1242. url := fmt.Sprintf(
  1243. "https://%s%s/domain/%s/object/hosts?bulk=true",
  1244. f.host,
  1245. f.basePath,
  1246. domainUUID,
  1247. )
  1248. _, err := f.postGoThing(url, object)
  1249. //log.Printf("DEBUG: post response body: %s", string(respBody))
  1250. return err
  1251. }
  1252. func (f *FMC) NetworkHostObjects(
  1253. domain string,
  1254. pageSize uint64,
  1255. ) (*FMCNetworkObjectsPager, error) {
  1256. domainUUID, ok := f.domains[domain]
  1257. if !ok {
  1258. return nil, fmt.Errorf("network host objects: unknown domain %v", domain)
  1259. }
  1260. url := fmt.Sprintf(
  1261. "https://%s%s/domain/%s/object/hosts",
  1262. f.host,
  1263. f.basePath,
  1264. domainUUID,
  1265. )
  1266. return &FMCNetworkObjectsPager{
  1267. pager: newPager(url, f, pageSize),
  1268. }, nil
  1269. }
  1270. type FMCNetworkObjectsPager struct {
  1271. pager
  1272. }
  1273. func (
  1274. p *FMCNetworkObjectsPager,
  1275. ) Iter() <-chan *FMCNetworkObject {
  1276. return iterateItems(
  1277. &p.pager,
  1278. func() fmcAggregate[FMCNetworkObject] {
  1279. return fmcAggregate[FMCNetworkObject]{}
  1280. },
  1281. )
  1282. }
  1283. func (p *FMCNetworkObjectsPager) LastErr() (err error) {
  1284. if p.err != nil {
  1285. err = fmt.Errorf("fmc network objects pager: %v", p.err)
  1286. }
  1287. return
  1288. }
  1289. /* Retrieve both network and host objects
  1290. */
  1291. func (f *FMC) NetworkAddresses(
  1292. domain string,
  1293. pageSize uint64,
  1294. ) (*FMCNetworkObjectsPager, error) {
  1295. domainUUID, ok := f.domains[domain]
  1296. if !ok {
  1297. return nil, fmt.Errorf("network addresses: unknown domain %v", domain)
  1298. }
  1299. url := fmt.Sprintf(
  1300. "https://%s%s/domain/%s/object/networkaddresses",
  1301. f.host,
  1302. f.basePath,
  1303. domainUUID,
  1304. )
  1305. return &FMCNetworkObjectsPager{
  1306. pager: newPager(url, f, pageSize),
  1307. }, nil
  1308. }
  1309. type FMCNetworkGroupsPager struct {
  1310. pager
  1311. }
  1312. func (
  1313. p *FMCNetworkGroupsPager,
  1314. ) Iter() <-chan *FMCNetworkGroup {
  1315. return iterateItems(
  1316. &p.pager,
  1317. func() fmcAggregate[FMCNetworkGroup] {
  1318. return fmcAggregate[FMCNetworkGroup]{}
  1319. },
  1320. )
  1321. }
  1322. func (p *FMCNetworkGroupsPager) LastErr() (err error) {
  1323. if p.err != nil {
  1324. err = fmt.Errorf("fmc network groups pager: %v", p.err)
  1325. }
  1326. return
  1327. }
  1328. /* Retrieve network groups
  1329. */
  1330. func (f *FMC) NetworkGroups(
  1331. domain string,
  1332. pageSize uint64,
  1333. ) (*FMCNetworkGroupsPager, error) {
  1334. domainUUID, ok := f.domains[domain]
  1335. if !ok {
  1336. return nil, fmt.Errorf("network groups: unknown domain %v", domain)
  1337. }
  1338. url := fmt.Sprintf(
  1339. "https://%s%s/domain/%s/object/networkgroups",
  1340. f.host,
  1341. f.basePath,
  1342. domainUUID,
  1343. )
  1344. return &FMCNetworkGroupsPager{
  1345. pager: newPager(url, f, pageSize),
  1346. }, nil
  1347. }
  1348. type FMCSecurityZoneObjectsPager struct {
  1349. pager
  1350. }
  1351. func (
  1352. p *FMCSecurityZoneObjectsPager,
  1353. ) Iter() <-chan *FMCSecurityZoneObject {
  1354. return iterateItems(
  1355. &p.pager,
  1356. func() fmcAggregate[FMCSecurityZoneObject] {
  1357. return fmcAggregate[FMCSecurityZoneObject]{}
  1358. },
  1359. )
  1360. }
  1361. func (p *FMCSecurityZoneObjectsPager) LastErr() (err error) {
  1362. if p.err != nil {
  1363. err = fmt.Errorf("fmc security zone objects pager: %v", p.err)
  1364. }
  1365. return
  1366. }
  1367. func (f *FMC) SecurityZones(
  1368. domain string,
  1369. pageSize uint64,
  1370. ) (*FMCSecurityZoneObjectsPager, error) {
  1371. domainUUID, ok := f.domains[domain]
  1372. if !ok {
  1373. return nil, fmt.Errorf("security zones: unknown domain %v", domain)
  1374. }
  1375. url := fmt.Sprintf(
  1376. "https://%s%s/domain/%s/object/securityzones",
  1377. f.host,
  1378. f.basePath,
  1379. domainUUID,
  1380. )
  1381. return &FMCSecurityZoneObjectsPager{
  1382. pager: newPager(url, f, pageSize),
  1383. }, nil
  1384. }
  1385. type FMCProtocolPortObjectsPager struct {
  1386. pager
  1387. }
  1388. func (
  1389. p *FMCProtocolPortObjectsPager,
  1390. ) Iter() <-chan *FMCProtocolPortObject {
  1391. return iterateItems(
  1392. &p.pager,
  1393. func() fmcAggregate[FMCProtocolPortObject] {
  1394. return fmcAggregate[FMCProtocolPortObject]{}
  1395. },
  1396. )
  1397. }
  1398. func (p *FMCProtocolPortObjectsPager) LastErr() (err error) {
  1399. if p.err != nil {
  1400. err = fmt.Errorf("fmc protocol-port objects pager: %v", p.err)
  1401. }
  1402. return
  1403. }
  1404. func (f *FMC) Ports(
  1405. domain string,
  1406. pageSize uint64,
  1407. ) (*FMCProtocolPortObjectsPager, error) {
  1408. domainUUID, ok := f.domains[domain]
  1409. if !ok {
  1410. return nil, fmt.Errorf("port objects: unknown domain %v", domain)
  1411. }
  1412. url := fmt.Sprintf(
  1413. "https://%s%s/domain/%s/object/ports",
  1414. f.host,
  1415. f.basePath,
  1416. domainUUID,
  1417. )
  1418. return &FMCProtocolPortObjectsPager{
  1419. pager: newPager(url, f, pageSize),
  1420. }, nil
  1421. }
  1422. type FMCPortObjectGroupsPager struct {
  1423. pager
  1424. }
  1425. func (
  1426. p *FMCPortObjectGroupsPager,
  1427. ) Iter() <-chan *FMCPortObjectGroup {
  1428. return iterateItems(
  1429. &p.pager,
  1430. func() fmcAggregate[FMCPortObjectGroup] {
  1431. return fmcAggregate[FMCPortObjectGroup]{}
  1432. },
  1433. )
  1434. }
  1435. func (p *FMCPortObjectGroupsPager) LastErr() (err error) {
  1436. if p.err != nil {
  1437. err = fmt.Errorf("fmc port object groups pager: %v", p.err)
  1438. }
  1439. return
  1440. }
  1441. func (f *FMC) PortGroups(
  1442. domain string,
  1443. pageSize uint64,
  1444. ) (*FMCPortObjectGroupsPager, error) {
  1445. domainUUID, ok := f.domains[domain]
  1446. if !ok {
  1447. return nil, fmt.Errorf("port object groups: unknown domain %v", domain)
  1448. }
  1449. url := fmt.Sprintf(
  1450. "https://%s%s/domain/%s/object/portobjectgroups",
  1451. f.host,
  1452. f.basePath,
  1453. domainUUID,
  1454. )
  1455. return &FMCPortObjectGroupsPager{
  1456. pager: newPager(url, f, pageSize),
  1457. }, nil
  1458. }
  1459. // See https://www.cisco.com/c/en/us/td/docs/security/firepower/620/api/REST/Firepower_Management_Center_REST_API_Quick_Start_Guide_620/Connecting_with_a_Client.html
  1460. func makeAuthAccessRequest(
  1461. ctx context.Context,
  1462. host,
  1463. user,
  1464. pass string,
  1465. ) (*http.Request, error) {
  1466. url := fmt.Sprintf(
  1467. "https://%s/api/fmc_platform/v1/auth/generatetoken",
  1468. host,
  1469. )
  1470. req, err :=
  1471. http.NewRequestWithContext(ctx, "POST", url, nil)
  1472. if err != nil {
  1473. return req, err
  1474. }
  1475. req.SetBasicAuth(user, pass)
  1476. return req, nil
  1477. }
  1478. // See https://www.cisco.com/c/en/us/td/docs/security/firepower/620/api/REST/Firepower_Management_Center_REST_API_Quick_Start_Guide_620/Connecting_with_a_Client.html
  1479. func makeAuthRefreshRequest(
  1480. ctx context.Context,
  1481. host,
  1482. authToken,
  1483. refreshToken string,
  1484. ) (*http.Request, error) {
  1485. url := fmt.Sprintf(
  1486. "https://%s/api/fmc_platform/v1/auth/refreshtoken",
  1487. host,
  1488. )
  1489. req, err :=
  1490. http.NewRequestWithContext(ctx, "POST", url, nil)
  1491. if err != nil {
  1492. return req, err
  1493. }
  1494. req.Header.Add("X-Auth-Access-Token", authToken)
  1495. req.Header.Add("X-Auth-Refresh-Token", refreshToken)
  1496. return req, nil
  1497. }
  1498. func warn(m string) {
  1499. log.Printf("Warn: %s\n", m)
  1500. return
  1501. }
  1502. func info(m string) {
  1503. log.Printf("info: %s\n", m)
  1504. return
  1505. }
  1506. func debug(m string) {
  1507. log.Printf("debug: %s\n", m)
  1508. return
  1509. }