12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622162316241625162616271628162916301631163216331634163516361637163816391640164116421643164416451646 |
- /*
- * This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at https://mozilla.org/MPL/2.0/.
- */
- package fmcclient
- // 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
- import (
- "bytes"
- "context"
- "encoding/json"
- "errors"
- "fmt"
- "io"
- "log"
- "net/http"
- "strings"
- "sync"
- "time"
- )
- const MaxAuthTokenMinutes = 60
- const MaxAuthTokenRefreshes = 3
- const MinutesBeforeRefreshDeadline = 3
- func NewAuthTokenExpiry(time.Time) time.Time {
- return time.Now().Add(MaxAuthTokenMinutes * time.Minute)
- }
- func withinAuthTokenRefreshWindow(expiry time.Time) bool {
- minUntilExpiry := time.Until(expiry).Minutes()
- return 0 < minUntilExpiry &&
- minUntilExpiry <= MinutesBeforeRefreshDeadline
- }
- func authTokenRefreshDeadlineHasPassed(
- expiry time.Time,
- ) bool {
- return time.Until(expiry) <= time.Duration(0)
- }
- func maxAuthTokenRefreshesHasBeenReached(cnt int) bool {
- return cnt >= MaxAuthTokenRefreshes
- }
- const (
- FMCStatusCodeOK int = 200
- FMCStatusCodeCreated int = 201
- FMCStatusCodeAccepted int = 202
- FMCStatusCodeNoContent int = 204
- FMCStatusCodeBadRequest int = 400
- FMCStatusCodeNotFound int = 404
- FMCStatusCodeMethodNotAllowed int = 405
- FMCStatusCodeUnprocessableEntity int = 422
- FMCStatusCodeTooManyRequests int = 429
- )
- func newFMCError(
- resp *http.Response,
- host,
- msg string,
- ) FMCError {
- fmcErr := &FMCErrorResponse{
- Error: FMCError{
- Host: host,
- HttpStatus: http.StatusText(resp.StatusCode),
- HttpStatusCode: resp.StatusCode,
- Err: msg,
- },
- }
- defer resp.Body.Close()
- body, err := io.ReadAll(resp.Body)
- if err != nil {
- return fmcErr.Error
- }
- json.Unmarshal(body, &fmcErr)
- return fmcErr.Error
- }
- type FMCError struct {
- Host string
- HttpStatus string
- HttpStatusCode int
- Category string
- Severity string
- Code string
- Details string
- Messages []map[string]string
- Err string
- }
- func (e FMCError) Error() string {
- status := http.StatusText(e.HttpStatusCode)
- if status != "" {
- status = " " + status
- }
- return fmt.Sprintf(
- "%s at FMC host %s: http status %d%s; category: %s; severity: %s; code: %s; details: %s; messages: %s",
- e.Err,
- e.Host,
- e.HttpStatusCode,
- status,
- e.Category,
- e.Severity,
- e.Code,
- e.Details,
- e.Messages,
- )
- }
- type FMCErrorResponse struct {
- Error FMCError
- }
- type Pager[A any] interface {
- Iter() <-chan *A
- LastErr() error
- }
- // Retrieve n items in the range [m*n, m*n + n - 1],
- // inclusive. Keep p pages buffered.
- type pager struct {
- fmc *FMC
- url string
- m uint64
- n uint64
- err error
- p int
- }
- /*
- 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:
- > The REST API will serve only 25 results per page. This can
- > be increased up to 1000 using the limit query parameter.
- */
- func newPager(url string, f *FMC, pageSize uint64) pager {
- if pageSize == 0 {
- pageSize = 25
- }
- if pageSize > 1000 {
- pageSize = 1000
- }
- return pager{
- fmc: f,
- url: url,
- m: 0,
- n: pageSize,
- p: 2,
- }
- }
- type fmcAggregate[T any] struct {
- Items []*T `json:"items"`
- }
- func iteratePages[A any](
- p *pager,
- f func() fmcAggregate[A],
- ) chan *fmcAggregate[A] {
- ch := make(chan *fmcAggregate[A], p.p)
- go func() {
- defer close(ch)
- for {
- q := fmt.Sprintf(
- "?expanded=true&limit=%d&offset=%d",
- p.n,
- p.m*p.n,
- )
- resp, err := p.fmc.get(p.url + q)
- if err != nil {
- p.err = fmt.Errorf("pager: iteratePages: get fmc url %v: %v", p.url+q, err)
- return
- }
- if resp.StatusCode != FMCStatusCodeOK {
- p.err = newFMCError(resp, p.fmc.host, err.Error())
- return
- }
- defer resp.Body.Close()
- body, err := io.ReadAll(resp.Body)
- if err != nil {
- p.err = fmt.Errorf("pager: iteratePages: read response body: %v", err)
- return
- }
- //log.Printf("DEBUG: %s\n", body)
- tmp := f()
- ptr := &tmp
- err = json.Unmarshal(body, ptr)
- if err != nil {
- log.Printf("ERROR: failed to parse '%s'\n", body)
- p.err = fmt.Errorf("pager: iteratePages: unmarshal fmc response: %v", err)
- return
- }
- ch <- ptr
- head := &fmcResponseBase{}
- err = json.Unmarshal(body, head)
- if err != nil {
- p.err = fmt.Errorf("pager: iteratePages: unmarshal fmc head: %v", err)
- return
- }
- //log.Printf("DEBUG: iteratePages: cnt=%d, m=%d, n=%d", head.Paging.Count, p.m, p.n)
- if (p.m*p.n + p.n) >= head.Paging.Count {
- return
- }
- p.m++
- }
- }()
- return ch
- }
- func iterateItems[A any](
- p *pager,
- f func() fmcAggregate[A],
- ) <-chan *A {
- ch := make(chan *A, p.n)
- go func() {
- defer close(ch)
- for resp := range iteratePages(p, f) {
- for i := 0; i < len(resp.Items); i++ {
- ch <- resp.Items[i]
- }
- if p.err != nil {
- p.err = fmt.Errorf("iterateItems: %s", p.err)
- return
- }
- }
- }()
- return ch
- }
- type FMCLinks struct {
- Self string `json:"self"`
- }
- type fmcResponseBase struct {
- Links FMCLinks `json:"links"`
- Paging struct {
- Count uint64 `json:"count"`
- Limit uint64 `json:"limit"`
- Offset uint64 `json:"offset"`
- Pages uint64 `json:"pages"`
- } `json:"paging"`
- }
- type FTDAutoNATRules struct {
- Items []*FTDAutoNATRule `json:"items"`
- }
- type FTDAutoNATRule struct {
- Id string `json:"id"`
- Type string `json:"type"` // FTDAutoNatRule
- Name string `json:"name"`
- Description string `json:"description,omitEmpty"`
- Version string `json:"version,omitEmpty"` // ?
- Section string `json:"section,omitEmpty"` // ?
- NATType string `json:"natType"` // STATIC | DYNAMIC
- SourceInterface *FTDInterfaceObjectRef `json:"sourceInterface,omitempty"`
- DestinationInterface *FTDInterfaceObjectRef `json:"destinationInterface,omitempty"`
- OriginalNetwork *FMCNetworkObjectRef `json:"originalNetwork"`
- TranslatedNetwork *FMCNetworkObjectRef `json:"translatedNetwork"`
- ServiceProtocol string `json:"serviceProtocol,omitEmpty"` // TCP | UDP
- OriginalPort *int `json:"originalPort,omitEmpty"`
- TranslatedPort *int `json:"translatedPort,omitEmpty"`
- PATOptions *FTDPATOptions `json:"patOptions,omitEmpty"`
- InterfaceInTranslatedNetwork bool `json:"interfaceInTranslatedNetwork"`
- Fallthrough bool `json:"fallThrough,omitempty"`
- NoProxyARP bool `json:"noProxyArp"`
- RouteLookup bool `json:"routeLookup"`
- NetToNet bool `json:"netToNet"`
- DNS bool `json:"dns,omitEmpty"`
- InterfaceIPv6 bool `json:"interfaceIpv6"`
- }
- type FTDInterfaceObjectRef struct {
- Id string `json:"id"`
- Type string `json:"type"` // SecurityZone | InterfaceGroup?
- Name string `json:"name"`
- }
- type FTDPATOptions struct {
- BlockAllocation bool `json:"blockAllocation"`
- PATPoolAddress *FMCNetworkObjectRef `json:"patPoolAddress"`
- IncludeReserve bool `json:"includeReserve,omitEmpty"`
- FlatPortRange bool `json:"flatPortRange"`
- InterfacePAT bool `json:"interfacePat"`
- ExtendedPAT bool `json:"extendedPat"`
- RoundRobin bool `json:"roundRobin"`
- }
- type FMCFTDNATPolicy struct {
- Id string `json:"id"`
- Type string `json:"type"` // FTDNatPolicy
- Name string `json:"name"`
- Description string `json:"description"`
- }
- type FMCPolicyRef struct {
- Id string `json:"id"`
- Type string `json:"type"` // FTDNatPolicy | AccessPolicy
- Name string `json:"name"`
- }
- type FMCPolicyAssignmentTarget struct {
- Id string `json:"id"`
- Type string `json:"type"` // Device
- Name string `json:"name"`
- KeepLocalEvents bool `json:"keepLocalEvents"`
- ProhibitPacketTransfer bool `json:"prohibitPacketTransfer"`
- }
- type FMCPolicyAssignment struct {
- Id string `json:"id"`
- Type string `json:"type"` // PolicyAssignment
- Policy *FMCPolicyRef `json:"policy"`
- Targets []*FMCPolicyAssignmentTarget `json:"targets"`
- }
- type FMCAccessPolicy struct {
- Links FMCLinks `json:"links"`
- Id string `json:"id"`
- Name string `json:"name"`
- Type string `json:"type"`
- }
- type FMCAccessPolicyRuleActionType string
- const (
- FMCAccessPolicyRuleActionAllow FMCAccessPolicyRuleActionType = "ALLOW"
- FMCAccessPolicyRuleActionTrust FMCAccessPolicyRuleActionType = "TRUST"
- FMCAccessPolicyRuleActionMonitor FMCAccessPolicyRuleActionType = "MONITOR"
- FMCAccessPolicyRuleActionBlock FMCAccessPolicyRuleActionType = "BLOCK"
- FMCAccessPolicyRuleActionBlockReset FMCAccessPolicyRuleActionType = "BLOCK_RESET"
- FMCAccessPolicyRuleActionBlockInteractive FMCAccessPolicyRuleActionType = "BLOCK_INTERACTIVE"
- FMCAccessPolicyRuleActionBlockResetInteractive FMCAccessPolicyRuleActionType = "BLOCK_RESET_INTERACTIVE"
- )
- type FMCAccessPolicyRuleComment struct {
- Comment string `json:"comment"`
- Date string `json:"date"`
- User struct {
- Name string `json:"name"`
- Type string `json:"type"`
- } `json:"user"`
- }
- type FMCSecurityZoneObject struct {
- Id string `json:"id"`
- Name string `json:"name,omitempty"`
- Type string `json:"type,omitempty"`
- }
- type FMCSyslogConfigObject struct {
- Id string `json:"id"`
- Name string `json:"name,omitempty"`
- Type string `json:"type,omitempty"`
- }
- type FMCAccessPolicyRuleMetadata struct {
- Category string `json:"category,omitempty"`
- Section string `json:"section,omitempty"`
- RuleIndex uint16 `json:"ruleIndex,omitempty"`
- }
- type FMCIPSPolicyRef struct {
- Id string `json:"id"`
- Name string `json:"name,omitempty"`
- Type string `json:"type,omitempty"` // IntrusionPolicy
- InspectionMode string `json:"inspectionMode,omitempty"` // DETECTION|PREVENTION
- }
- type FMCVariableSetRef struct {
- Id string `json:"id"`
- Name string `json:"name"`
- Type string `json:"type"` // VariableSet
- }
- type FMCAccessPolicyRuleZones struct {
- Objects []*FMCSecurityZoneObject `json:"objects"`
- }
- type FMCAccessPolicyRuleNetworks struct {
- Objects []*FMCNetworkObjectRef `json:"objects,omitempty"`
- Literals []*FMCNetworkLiteral `json:"literals,omitempty"`
- }
- type FMCPortObjectRef struct {
- Id string `json:"id"`
- Name string `json:"name,omitempty"`
- Type string `json:"type,omitempty"` // ICMPV4Object|ICMPV6Object|ProtocolPortObject|PortObjectGroup
- }
- // N.B.: the type 'ICMPV4Object' has an upper-case 'V', as
- // opposed to its "Literal" counterpart.
- type FMCProtocolPortObject struct {
- Id string `json:"id"`
- Name string `json:"name,omitempty"`
- Protocol string `json:"protocol,omitempty"`
- Port string `json:"port,omitempty"`
- ICMPType string `json:"icmpType,omitempty"`
- Code int `json:"code,omitempty"`
- Type string `json:"type,omitempty"` // ICMPV4Object|ICMPV6Object|ProtocolPortObject
- Overridable bool `json:"overridable,omitempty"`
- Description string `json:"description,omitempty"`
- }
- type FMCPortObjectGroup struct {
- Id string `json:"id"`
- Name string `json:"name,omitempty"`
- Type string `json:"type,omitempty"` // PortObjectGroup
- Objects []*FMCPortObjectRef `json:"objects"`
- Overridable bool `json:"overridable,omitempty"`
- Description string `json:"description,omitempty"`
- }
- /* Protocol -9999 represents 'All'. Incidentally, the FMC
- * API provides protocol names rather than IP protocol
- * numbers, which FMC will fail to recognize on put/post.
- * Moreover, ICMP port literals are provided without protocol
- * fields, which are required for put/post. This baffling
- * behavior should be handled as a matter of course.
- */
- type FMCPortLiteral struct {
- Type string `json:"type"` // ICMPv4PortLiteral|ICMPv6PortLiteral|PortLiteral
- Protocol string `json:"protocol"` // 0-255,-9999
- Port string `json:"port,omitempty"`
- ICMPType string `json:"icmpType,omitempty"`
- Code int `json:"code,omitempty"`
- }
- type FMCAccessPolicyRulePorts struct {
- Objects []*FMCPortObjectRef `json:"objects"`
- Literals []*FMCPortLiteral `json:"literals,omitempty"`
- }
- type FMCApplicationRef struct {
- Id string `json:"id"`
- Name string `json:"name"`
- Type string `json:"type"` // Application
- }
- type FMCAccessPolicyRuleApplications struct {
- Applications []*FMCApplicationRef `json:"applications"`
- }
- type FMCFilePolicyRef struct {
- Id string `json:"id"`
- Name string `json:"name"`
- Type string `json:"type"` // FilePolicy
- }
- type FMCURLLiteral struct {
- URL string `json:"url"`
- Type string `json:"type"` // Url
- }
- type FMCAccessPolicyRuleURLs struct {
- Literals []*FMCURLLiteral `json:"literals"`
- }
- /* "Intrusion Policy, File Policy and Variable Set cannot be
- * selected when rule action is Block, Trust, Block with
- * reset or monitor"
- *
- * It is possible for FMC to allow rules with `logFiles`
- * enabled and no file policy set. This will fail in
- * put/post.
- */
- type FMCAccessPolicyRule struct {
- Id string `json:"id,omitempty"`
- Name string `json:"name"`
- Type string `json:"type"`
- Metadata *FMCAccessPolicyRuleMetadata `json:"metadata,omitempty"`
- Action FMCAccessPolicyRuleActionType `json:"action"`
- Enabled bool `json:"enabled"`
- EnableSyslog bool `json:"enableSyslog"`
- SendEventsToFMC bool `json:"sendEventsToFMC"`
- IPSPolicy *FMCIPSPolicyRef `json:"ipsPolicy,omitempty"`
- FilePolicy *FMCFilePolicyRef `json:"filePolicy,omitempty"`
- VariableSet *FMCVariableSetRef `json:"variableSet,omitempty"`
- LogBegin bool `json:"logBegin"`
- LogEnd bool `json:"logEnd"`
- LogFiles bool `json:"logFiles"`
- SourceZones *FMCAccessPolicyRuleZones `json:"sourceZones"`
- DestinationZones *FMCAccessPolicyRuleZones `json:"destinationZones"`
- SourceNetworks *FMCAccessPolicyRuleNetworks `json:"sourceNetworks,omitempty"`
- DestinationNetworks *FMCAccessPolicyRuleNetworks `json:"destinationNetworks,omitempty"`
- SourcePorts *FMCAccessPolicyRulePorts `json:"sourcePorts,omitempty"`
- DestinationPorts *FMCAccessPolicyRulePorts `json:"destinationPorts,omitempty"`
- CommentHistoryList []*FMCAccessPolicyRuleComment `json:"commentHistoryList,omitempty"`
- Applications *FMCAccessPolicyRuleApplications `json:"applications,omitempty"`
- URLs *FMCAccessPolicyRuleURLs `json:"urls,omitempty"`
- }
- type FMCNetworkObjectRef struct {
- Id string `json:"id"`
- Name string `json:"name"`
- Type string `json:"type"` // Host|Network|Range|FQDN|NetworkGroup
- }
- type FMCNetworkObject struct {
- Id string `json:"id,omitempty"`
- Name string `json:"name,omitempty"`
- Type string `json:"type,omitempty"` // Host|Network|Range|FQDN
- Value string `json:"value,omitempty"`
- Overridable bool `json:"overridable,omitempty"`
- }
- type FMCNetworkGroupMetadata struct {
- Domain struct {
- Id string `json:"id,omitempty"`
- Name string `json:"name,omitempty"`
- Type string `json:"type,omitempty"` // Domain
- }
- }
- /* It has been observed that literals with type 'Host' can
- * have an erroneous/obsolete '/32' prefix length in FMC,
- * causing put/post to fail later. This must be accounted
- * for. */
- type FMCNetworkLiteral struct {
- Type string `json:"type"` // Network|Host
- Value string `json:"value"`
- }
- type FMCNetworkGroup struct {
- Id string `json:"id,omitempty"`
- Name string `json:"name,omitempty"`
- Type string `json:"type,omitempty"`
- Overridable bool `json:"overridable,omitempty"`
- Metadata *FMCNetworkGroupMetadata `json:"metadata,omitempty"`
- Literals []*FMCNetworkLiteral `json:"literals,omitempty"`
- Objects []*FMCNetworkObjectRef `json:"objects,omitempty"`
- }
- type nameToUUID struct {
- Name string
- UUID string
- }
- func New(
- ctx context.Context,
- cl *http.Client,
- host,
- user,
- pass string,
- ) (*FMC, error) {
- f := &FMC{
- ctx: ctx,
- client: cl,
- host: host,
- basePath: "/api/fmc_config/v1",
- user: user,
- pass: pass,
- authMutex: &sync.Mutex{},
- domains: make(map[string]string),
- }
- var err error
- if host == "" || user == "" || pass == "" {
- err = errors.New("Empty host, user, or pass")
- }
- return f, err
- }
- func checkForCiscoError(
- resp *http.Response,
- respBody []byte,
- ) error {
- /* 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
- the API cannot accept a message with a payload greater
- than 20480 bytes (20 KB). This applies to both the REST
- API and to API Explorer. This is not a configurable
- parameter. If a message exceeds this limit, the API will
- give an HTTP 422 error.
- 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
- claims the API cannot accept a message with payload
- greater than 2048000 bytes (2 MB). I can only hope Cisco
- typo-ed the former when they meant the latter. */
- if resp.StatusCode == FMCStatusCodeUnprocessableEntity {
- return fmt.Errorf("server returned error: %+v: %s", resp, string(respBody))
- }
- /* 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:
- > The FMC REST API implements rate limiting to reduce
- > network load.
- >
- > The API will accept no more than 120 messages per minute
- > from an individual IP address. It will only allow 10
- > simultaneous connections *per IP address*. These are not
- > configurable parameters.
- >
- > If a client exceeds these limits, the API will give an
- > HTTP 429 error.
- 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
- states the following:
- > – "Too Many Requests". Too many requests were sent to
- > the API. This error will occur if you send more than 120
- > requests per minute.
- > – Too many concurrent requests. The system cannot
- > accept more than 10 parallel requests *from all
- > clients*.
- > – Too many write operations per server. The API will
- > only allow one PUT, POST, or DELETE request *per user*
- > on a server at a time.
- If true, these latter limits constitute a substantial
- reduction in acceptable client behaviors.
- TODO: Prevent this occurring in the first place. */
- if resp.StatusCode == FMCStatusCodeTooManyRequests {
- return fmt.Errorf("server returned error: %+v: %s", resp, string(respBody))
- }
- if resp.StatusCode < 200 || 299 < resp.StatusCode {
- return fmt.Errorf("server returned error: %+v: %s", resp, string(respBody))
- }
- return nil
- }
- type Mode int
- const (
- ModeReadOnly Mode = iota
- ModeWrite
- )
- type FMC struct {
- ctx context.Context
- client *http.Client
- host string
- basePath string
- user string
- pass string
- authToken string
- refreshToken string
- refreshCount int
- tokenExpiry time.Time
- watchdogCancel context.CancelFunc
- authMutex *sync.Mutex
- domains map[string]string
- mode Mode
- }
- func (f *FMC) Arm() Mode {
- f.mode = ModeWrite
- return f.mode
- }
- func (f *FMC) Disarm() Mode {
- f.mode = ModeReadOnly
- return f.mode
- }
- func (f *FMC) authTokenInvalid() bool {
- if f.authToken == "" ||
- f.refreshCount > MaxAuthTokenRefreshes ||
- time.Until(f.tokenExpiry) <= time.Duration(0) {
- return true
- }
- return false
- }
- func (f *FMC) resetRefreshToken() {
- f.refreshToken = ""
- f.refreshCount = 0
- f.tokenExpiry = time.Time{}
- return
- }
- func (f *FMC) processAuthResponse(
- resp *http.Response,
- ) error {
- /* TODO: Is this actually a failure? For the life of me, I
- can't recall why this was necessary. A successful
- authentication with status 204 is presented here:
- https://www.cisco.com/c/en/us/support/docs/security/firepower-management-center/215918-how-to-generate-authentication-token-for.html
- */
- if resp.StatusCode != FMCStatusCodeNoContent {
- return newFMCError(resp, f.host, "authentication failure")
- }
- f.tokenExpiry = NewAuthTokenExpiry(time.Now())
- f.authToken = resp.Header.Get("X-Auth-Access-Token")
- f.refreshToken = resp.Header.Get("X-Auth-Refresh-Token")
- return nil
- }
- // 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
- func (f *FMC) Authenticate() error {
- f.authMutex.Lock()
- defer f.authMutex.Unlock()
- if f.authTokenInvalid() ||
- maxAuthTokenRefreshesHasBeenReached(f.refreshCount) ||
- authTokenRefreshDeadlineHasPassed(f.tokenExpiry) {
- if f.watchdogCancel != nil {
- f.watchdogCancel()
- }
- f.resetRefreshToken()
- req, err := makeAuthAccessRequest(
- f.ctx,
- f.host,
- f.user,
- f.pass,
- )
- if err != nil {
- return err
- }
- resp, err := f.client.Do(req)
- if err != nil {
- return err
- }
- var domains []nameToUUID
- json.Unmarshal(
- []byte(resp.Header.Get("domains")),
- &domains,
- )
- for i := 0; i < len(domains); i++ {
- f.domains[domains[i].Name] = domains[i].UUID
- }
- if err = f.processAuthResponse(resp); err != nil {
- return err
- }
- info("authenticated to FMC")
- ctx, cancel := context.WithCancel(f.ctx)
- f.watchdogCancel = cancel
- f.startTokenWatchdog(ctx)
- } else {
- if !withinAuthTokenRefreshWindow(f.tokenExpiry) {
- return nil
- }
- req, err := makeAuthRefreshRequest(
- f.ctx,
- f.host,
- f.authToken,
- f.refreshToken,
- )
- if err != nil {
- return err
- }
- resp, err := f.client.Do(req)
- if err != nil {
- return err
- }
- if err = f.processAuthResponse(resp); err != nil {
- return err
- }
- info("refreshed FMC auth token")
- f.refreshCount++
- }
- return nil
- }
- func (f *FMC) startTokenWatchdog(ctx context.Context) {
- info("starting auth token watchdog")
- go func() {
- defer info("stopping auth token watchdog")
- for {
- select {
- case <-ctx.Done():
- return
- case <-time.After(100 * time.Millisecond):
- if f.authTokenInvalid() {
- info("auth token has invalidated")
- return
- }
- if err := f.Authenticate(); err != nil {
- warn(err.Error())
- return
- }
- }
- }
- }()
- return
- }
- func (f *FMC) get(url string) (
- resp *http.Response,
- err error,
- ) {
- req, err :=
- http.NewRequestWithContext(f.ctx, "GET", url, nil)
- if err != nil {
- return
- }
- req.Header.Add("X-Auth-Access-Token", f.authToken)
- resp, err = f.client.Do(req)
- return
- }
- func (f *FMC) post(
- url string,
- body io.Reader,
- ) (resp *http.Response, err error) {
- req, err :=
- http.NewRequestWithContext(f.ctx, "POST", url, body)
- if err != nil {
- return
- }
- req.Header.Add("Content-Type", "application/json")
- req.Header.Add("X-Auth-Access-Token", f.authToken)
- resp, err = f.client.Do(req)
- return
- }
- func (f *FMC) put(
- url string,
- body io.Reader,
- ) (resp *http.Response, err error) {
- req, err :=
- http.NewRequestWithContext(f.ctx, "PUT", url, body)
- if err != nil {
- return
- }
- req.Header.Add("Content-Type", "application/json")
- req.Header.Add("X-Auth-Access-Token", f.authToken)
- resp, err = f.client.Do(req)
- return
- }
- func (f *FMC) postGoThing(
- url string,
- goThing any,
- ) (respBody []byte, err error) {
- reqBody, err := json.Marshal(goThing)
- if err != nil {
- err = fmt.Errorf("failed to marshal json: %v: '%#v'", err, goThing)
- return
- }
- resp, err := f.post(url, bytes.NewReader(reqBody))
- if err != nil {
- err = fmt.Errorf("failed to post: %v: '%s'", err, reqBody)
- return
- }
- defer resp.Body.Close()
- respBody, err = io.ReadAll(resp.Body)
- if err != nil {
- err = fmt.Errorf("failed to read response body: %v", err)
- return
- }
- if err = checkForCiscoError(resp, respBody); err != nil {
- log.Printf("DEBUG: req body: %s", string(reqBody))
- return
- }
- return respBody, nil
- }
- func (f *FMC) putGoThing(
- url string,
- goThing any,
- ) (respBody []byte, err error) {
- reqBody, err := json.Marshal(goThing)
- if err != nil {
- err = fmt.Errorf("failed to marshal json: %v: '%#v'", err, goThing)
- return
- }
- resp, err := f.put(url, bytes.NewReader(reqBody))
- if err != nil {
- err = fmt.Errorf("failed to put: %v: '%s'", err, reqBody)
- return
- }
- defer resp.Body.Close()
- respBody, err = io.ReadAll(resp.Body)
- if err != nil {
- err = fmt.Errorf("failed to read response body: %v", err)
- return
- }
- if err = checkForCiscoError(resp, respBody); err != nil {
- log.Printf("DEBUG: req body: %s", string(reqBody))
- return
- }
- return respBody, nil
- }
- func (f *FMC) Domain(s string) (string, bool) {
- v, ok := f.domains[s]
- return v, ok
- }
- func (f *FMC) Domains() []string {
- keys := make([]string, 0, len(f.domains))
- for k := range f.domains {
- keys = append(keys, k)
- }
- return keys
- }
- type FMCAccessPoliciesPager struct {
- pager
- }
- func (
- p *FMCAccessPoliciesPager,
- ) Iter() <-chan *FMCAccessPolicy {
- return iterateItems(
- &p.pager,
- func() fmcAggregate[FMCAccessPolicy] {
- return fmcAggregate[FMCAccessPolicy]{}
- },
- )
- }
- func (p *FMCAccessPoliciesPager) LastErr() (err error) {
- if p.err != nil {
- err = fmt.Errorf("fmc access policies pager: %v", p.err)
- }
- return
- }
- func (f *FMC) AccessPolicies(
- domain string,
- pageSize uint64,
- ) (*FMCAccessPoliciesPager, error) {
- domainUUID, ok := f.domains[domain]
- if !ok {
- return nil, fmt.Errorf("access policies: unknown domain %v", domain)
- }
- url := fmt.Sprintf(
- "https://%s%s/domain/%s/policy/accesspolicies",
- f.host,
- f.basePath,
- domainUUID,
- )
- return &FMCAccessPoliciesPager{
- pager: newPager(url, f, pageSize),
- }, nil
- }
- type FMCAccessPolicyRulesPager struct {
- pager
- }
- func (
- p *FMCAccessPolicyRulesPager,
- ) Iter() <-chan *FMCAccessPolicyRule {
- return iterateItems(
- &p.pager,
- func() fmcAggregate[FMCAccessPolicyRule] {
- return fmcAggregate[FMCAccessPolicyRule]{}
- },
- )
- }
- func (p *FMCAccessPolicyRulesPager) LastErr() (err error) {
- if p.err != nil {
- err = fmt.Errorf("fmc access policy rules pager: %v", p.err)
- }
- return
- }
- func (f *FMC) AccessPolicyRules(
- domain,
- policyUUID string,
- pageSize uint64,
- ) (*FMCAccessPolicyRulesPager, error) {
- domainUUID, ok := f.domains[domain]
- if !ok {
- return nil, fmt.Errorf("access policy rules: unknown domain %v", domain)
- }
- url := fmt.Sprintf(
- "https://%s%s/domain/%s/policy/accesspolicies/%s/accessrules",
- f.host,
- f.basePath,
- domainUUID,
- policyUUID,
- )
- return &FMCAccessPolicyRulesPager{
- pager: newPager(url, f, pageSize),
- }, nil
- }
- func (f *FMC) AddAccessPolicyRules(
- domain,
- policyUUID string,
- rules []*FMCAccessPolicyRule,
- ) error {
- if f.mode != ModeWrite {
- return fmt.Errorf("add access policy rules: client is not armed; not allowed to change fmc")
- }
- domainUUID, ok := f.domains[domain]
- if !ok {
- return fmt.Errorf("add access policy rules: unknown domain %v", domain)
- }
- // TODO: permit specification of categories, sections
- //category := url.PathEscape(rule.Metadata.Category)
- url := fmt.Sprintf(
- "https://%s%s/domain/%s/policy/accesspolicies/%s/accessrules?bulk=true",
- f.host,
- f.basePath,
- domainUUID,
- policyUUID,
- )
- var err error
- for _, rule := range rules {
- rule.Id = ""
- rule.Metadata = nil
- if rule.SourceNetworks != nil &&
- rule.DestinationNetworks != nil {
- if len(rule.SourceNetworks.Objects) > 42 &&
- len(rule.SourceNetworks.Objects) < 50 ||
- len(rule.DestinationNetworks.Objects) > 42 &&
- len(rule.DestinationNetworks.Objects) < 50 {
- log.Printf("WARNING: network object count is approaching limit of 50: '%s'", rule.Name)
- }
- if len(rule.SourceNetworks.Objects) == 50 ||
- len(rule.DestinationNetworks.Objects) == 50 {
- log.Printf("WARNING: limit of 50 network objects reached: '%s'", rule.Name)
- }
- if len(rule.SourceNetworks.Objects) > 50 ||
- len(rule.DestinationNetworks.Objects) > 50 {
- log.Printf("ERROR: add access policy rules: unable to add rule: network object count exceeds 50-object limit: '%s'", rule.Name)
- err = fmt.Errorf("add access policy rules: unable to add rule: network object count exceeded 50-object limit")
- }
- }
- }
- if err != nil {
- return err
- }
- maxPost := 10
- for i := 0; i < len(rules); i += maxPost {
- log.Printf("DEBUG: add access policy rules: posting seq %d", i+1)
- last := min(i+maxPost, len(rules))
- _, err := f.postGoThing(url, rules[i:last])
- if err != nil {
- return err
- }
- }
- return nil
- }
- func (f *FMC) UpdateAccessPolicyRule(
- domain,
- policyUUID string,
- rule *FMCAccessPolicyRule,
- ) error {
- if f.mode != ModeWrite {
- return fmt.Errorf("update access policy rule: client is not armed; not allowed to change fmc")
- }
- domainUUID, ok := f.domains[domain]
- if !ok {
- return fmt.Errorf("update access policy rule: unknown domain %v", domain)
- }
- url := fmt.Sprintf(
- "https://%s%s/domain/%s/policy/accesspolicies/%s/accessrules/%s",
- f.host,
- f.basePath,
- domainUUID,
- policyUUID,
- rule.Id,
- )
- rule.Metadata = nil
- if rule.SourceNetworks != nil &&
- rule.DestinationNetworks != nil {
- if len(rule.SourceNetworks.Objects) > 42 &&
- len(rule.SourceNetworks.Objects) < 50 ||
- len(rule.DestinationNetworks.Objects) > 42 &&
- len(rule.DestinationNetworks.Objects) < 50 {
- log.Printf("WARNING: network object count is approaching limit of 50: '%s'", rule.Name)
- }
- if len(rule.SourceNetworks.Objects) == 50 ||
- len(rule.DestinationNetworks.Objects) == 50 {
- log.Printf("WARNING: limit of 50 network objects reached: '%s'", rule.Name)
- }
- if len(rule.SourceNetworks.Objects) > 50 ||
- len(rule.DestinationNetworks.Objects) > 50 {
- return fmt.Errorf("change access policy rule: unable to change rule: network object count exceeds 50-object limit: '%s'", rule.Name)
- }
- }
- _, err := f.putGoThing(url, rule)
- //log.Printf("DEBUG: post response body: %s", string(respBody))
- return err
- }
- func (f *FMC) FTDNATPolicies(
- domain string,
- pageSize uint64,
- ) (*FMCFTDNATPoliciesPager, error) {
- domainUUID, ok := f.domains[domain]
- if !ok {
- return nil, fmt.Errorf("ftd nat policies: unknown domain %v", domain)
- }
- url := fmt.Sprintf(
- "https://%s%s/domain/%s/policy/ftdnatpolicies?expanded=true",
- f.host,
- f.basePath,
- domainUUID,
- )
- return &FMCFTDNATPoliciesPager{
- pager: newPager(url, f, pageSize),
- }, nil
- }
- type FMCFTDNATPoliciesPager struct {
- pager
- }
- func (
- p *FMCFTDNATPoliciesPager,
- ) Iter() <-chan *FMCFTDNATPolicy {
- return iterateItems(
- &p.pager,
- func() fmcAggregate[FMCFTDNATPolicy] {
- return fmcAggregate[FMCFTDNATPolicy]{}
- },
- )
- }
- func (p *FMCFTDNATPoliciesPager) LastErr() (err error) {
- if p.err != nil {
- err = fmt.Errorf("fmc ftd nat policies pager: %v", p.err)
- }
- return
- }
- func (f *FMC) PolicyAssignments(
- domain string,
- pageSize uint64,
- ) (*FMCPolicyAssignmentsPager, error) {
- domainUUID, ok := f.domains[domain]
- if !ok {
- return nil, fmt.Errorf("policy assignments: unknown domain %v", domain)
- }
- url := fmt.Sprintf(
- "https://%s%s/domain/%s/assignment/policyassignments",
- f.host,
- f.basePath,
- domainUUID,
- )
- return &FMCPolicyAssignmentsPager{
- pager: newPager(url, f, pageSize),
- }, nil
- }
- type FMCPolicyAssignmentsPager struct {
- pager
- }
- func (
- p *FMCPolicyAssignmentsPager,
- ) Iter() <-chan *FMCPolicyAssignment {
- return iterateItems(
- &p.pager,
- func() fmcAggregate[FMCPolicyAssignment] {
- return fmcAggregate[FMCPolicyAssignment]{}
- },
- )
- }
- func (p *FMCPolicyAssignmentsPager) LastErr() (err error) {
- if p.err != nil {
- err = fmt.Errorf("fmc policy assignments pager: %v", p.err)
- }
- return
- }
- func (f *FMC) UpdatePolicyAssignment(
- domain string,
- assignment *FMCPolicyAssignment,
- ) error {
- if f.mode != ModeWrite {
- return fmt.Errorf("update policy assignment: client is not armed; not allowed to change fmc")
- }
- domainUUID, ok := f.domains[domain]
- if !ok {
- return fmt.Errorf("update policy assignment: unknown domain %v", domain)
- }
- url := fmt.Sprintf(
- "https://%s%s/domain/%s/assignment/policyassignments/%s",
- f.host,
- f.basePath,
- domainUUID,
- assignment.Id,
- )
- //assignment.Metadata = nil
- _, err := f.putGoThing(url, assignment)
- return err
- }
- func (f *FMC) AddNetworkObjects(
- domain string,
- objects map[string]*FMCNetworkObject,
- ) error {
- if f.mode != ModeWrite {
- return fmt.Errorf("add network host object: client is not armed; not allowed to change fmc")
- }
- domainUUID, ok := f.domains[domain]
- if !ok {
- return fmt.Errorf("add network host object: unknown domain %v", domain)
- }
- typeToPath := map[string]string{
- "Host": "hosts",
- "Network": "networks",
- "Range": "ranges",
- "FQDN": "fqdns",
- }
- url := func(suffix string) string {
- return fmt.Sprintf(
- "https://%s%s/domain/%s/object/%s?bulk=true",
- f.host,
- f.basePath,
- domainUUID,
- suffix,
- )
- }
- objectsByType := make(map[string][]*FMCNetworkObject)
- for k, _ := range typeToPath {
- objectsByType[k] = make([]*FMCNetworkObject, 0, 32)
- }
- for _, e := range objects {
- objectsByType[e.Type] = append(objectsByType[e.Type], e)
- }
- maxPost := 500
- for k, os := range objectsByType {
- suffix, ok := typeToPath[k]
- if !ok {
- return fmt.Errorf("add network object: unknown object type %v", k)
- }
- for i := 0; i < len(os); i += maxPost {
- log.Printf("DEBUG: add network objects: posting type %s, block %d", k, i+1)
- last := min(i+maxPost, len(os))
- _, err := f.postGoThing(url(suffix), os[i:last])
- if err != nil {
- return err
- }
- }
- }
- return nil
- }
- func min(a, b int) int {
- if a < b {
- return a
- }
- return b
- }
- func (f *FMC) AddNetworkGroups(
- domain string,
- groups map[string]*FMCNetworkGroup,
- ) error {
- if f.mode != ModeWrite {
- return fmt.Errorf("add network host object: client is not armed; not allowed to change fmc")
- }
- domainUUID, ok := f.domains[domain]
- if !ok {
- return fmt.Errorf("add network host object: unknown domain %v", domain)
- }
- url := fmt.Sprintf(
- "https://%s%s/domain/%s/object/networkgroups?bulk=true",
- f.host,
- f.basePath,
- domainUUID,
- )
- unsorted := make(map[string]bool)
- for k, _ := range groups {
- unsorted[k] = true
- }
- antichains := make([][]*FMCNetworkGroup, 0, 10)
- antichains =
- append(antichains, make([]*FMCNetworkGroup, 0, 32))
- nextAntichain := make(map[string]bool)
- separable := func(key string) bool {
- for _, e := range groups[key].Objects {
- if e.Type != "NetworkGroup" {
- continue
- }
- if unsorted[strings.ToLower(e.Name)] ||
- nextAntichain[strings.ToLower(e.Name)] {
- return false
- }
- }
- return true
- }
- lastCnt := -1
- for {
- depth := len(antichains) - 1
- lastCnt = len(unsorted)
- for k, _ := range unsorted {
- if separable(k) {
- groups[k].Id = ""
- groups[k].Metadata = nil
- antichains[depth] = append(antichains[depth], groups[k])
- nextAntichain[k] = true
- delete(unsorted, k)
- }
- }
- if len(unsorted) == lastCnt {
- break
- }
- if len(unsorted) > 0 {
- nextAntichain = make(map[string]bool)
- antichains =
- append(antichains, make([]*FMCNetworkGroup, 0, 32))
- }
- }
- if len(unsorted) > 0 {
- return fmt.Errorf("WARN: add network groups: provided group set contains cycles: culprits %+v", unsorted)
- }
- posted := make(map[string]string)
- maxPost := 50
- for j, a := range antichains {
- for _, e := range a {
- for _, r := range e.Objects {
- if r.Type == "NetworkGroup" {
- if uuid, ok := posted[strings.ToLower(r.Name)]; ok {
- r.Id = uuid
- }
- }
- }
- }
- for i := 0; i < len(a); i += maxPost {
- log.Printf("DEBUG: add network groups: posting antichain %d, seq %d", j, i+1)
- last := min(i+maxPost, len(a))
- respBody, err := f.postGoThing(url, a[i:last])
- if err != nil {
- return err
- }
- response := &FMCAddNetworkGroupsResponse{}
- err = json.Unmarshal(respBody, response)
- if err != nil {
- log.Printf("ERROR: add network groups: failed to parse '%s'\n", respBody)
- return fmt.Errorf("pager: iteratePages: unmarshal fmc response: %v", err)
- }
- for _, i := range response.Items {
- posted[strings.ToLower(i.Name)] = i.Id
- }
- }
- }
- return nil
- }
- type FMCAddNetworkGroupsResponse struct {
- Items []*FMCNetworkGroup `json:"items"`
- }
- func (f *FMC) AddNetworkHostObject(
- domain string,
- object *FMCNetworkObject,
- ) error {
- if f.mode != ModeWrite {
- return fmt.Errorf("add network host object: client is not armed; not allowed to change fmc")
- }
- domainUUID, ok := f.domains[domain]
- if !ok {
- return fmt.Errorf("add network host object: unknown domain %v", domain)
- }
- url := fmt.Sprintf(
- "https://%s%s/domain/%s/object/hosts?bulk=true",
- f.host,
- f.basePath,
- domainUUID,
- )
- _, err := f.postGoThing(url, object)
- //log.Printf("DEBUG: post response body: %s", string(respBody))
- return err
- }
- func (f *FMC) NetworkHostObjects(
- domain string,
- pageSize uint64,
- ) (*FMCNetworkObjectsPager, error) {
- domainUUID, ok := f.domains[domain]
- if !ok {
- return nil, fmt.Errorf("network host objects: unknown domain %v", domain)
- }
- url := fmt.Sprintf(
- "https://%s%s/domain/%s/object/hosts",
- f.host,
- f.basePath,
- domainUUID,
- )
- return &FMCNetworkObjectsPager{
- pager: newPager(url, f, pageSize),
- }, nil
- }
- type FMCNetworkObjectsPager struct {
- pager
- }
- func (
- p *FMCNetworkObjectsPager,
- ) Iter() <-chan *FMCNetworkObject {
- return iterateItems(
- &p.pager,
- func() fmcAggregate[FMCNetworkObject] {
- return fmcAggregate[FMCNetworkObject]{}
- },
- )
- }
- func (p *FMCNetworkObjectsPager) LastErr() (err error) {
- if p.err != nil {
- err = fmt.Errorf("fmc network objects pager: %v", p.err)
- }
- return
- }
- /* Retrieve both network and host objects
- */
- func (f *FMC) NetworkAddresses(
- domain string,
- pageSize uint64,
- ) (*FMCNetworkObjectsPager, error) {
- domainUUID, ok := f.domains[domain]
- if !ok {
- return nil, fmt.Errorf("network addresses: unknown domain %v", domain)
- }
- url := fmt.Sprintf(
- "https://%s%s/domain/%s/object/networkaddresses",
- f.host,
- f.basePath,
- domainUUID,
- )
- return &FMCNetworkObjectsPager{
- pager: newPager(url, f, pageSize),
- }, nil
- }
- type FMCNetworkGroupsPager struct {
- pager
- }
- func (
- p *FMCNetworkGroupsPager,
- ) Iter() <-chan *FMCNetworkGroup {
- return iterateItems(
- &p.pager,
- func() fmcAggregate[FMCNetworkGroup] {
- return fmcAggregate[FMCNetworkGroup]{}
- },
- )
- }
- func (p *FMCNetworkGroupsPager) LastErr() (err error) {
- if p.err != nil {
- err = fmt.Errorf("fmc network groups pager: %v", p.err)
- }
- return
- }
- /* Retrieve network groups
- */
- func (f *FMC) NetworkGroups(
- domain string,
- pageSize uint64,
- ) (*FMCNetworkGroupsPager, error) {
- domainUUID, ok := f.domains[domain]
- if !ok {
- return nil, fmt.Errorf("network groups: unknown domain %v", domain)
- }
- url := fmt.Sprintf(
- "https://%s%s/domain/%s/object/networkgroups",
- f.host,
- f.basePath,
- domainUUID,
- )
- return &FMCNetworkGroupsPager{
- pager: newPager(url, f, pageSize),
- }, nil
- }
- type FMCSecurityZoneObjectsPager struct {
- pager
- }
- func (
- p *FMCSecurityZoneObjectsPager,
- ) Iter() <-chan *FMCSecurityZoneObject {
- return iterateItems(
- &p.pager,
- func() fmcAggregate[FMCSecurityZoneObject] {
- return fmcAggregate[FMCSecurityZoneObject]{}
- },
- )
- }
- func (p *FMCSecurityZoneObjectsPager) LastErr() (err error) {
- if p.err != nil {
- err = fmt.Errorf("fmc security zone objects pager: %v", p.err)
- }
- return
- }
- func (f *FMC) SecurityZones(
- domain string,
- pageSize uint64,
- ) (*FMCSecurityZoneObjectsPager, error) {
- domainUUID, ok := f.domains[domain]
- if !ok {
- return nil, fmt.Errorf("security zones: unknown domain %v", domain)
- }
- url := fmt.Sprintf(
- "https://%s%s/domain/%s/object/securityzones",
- f.host,
- f.basePath,
- domainUUID,
- )
- return &FMCSecurityZoneObjectsPager{
- pager: newPager(url, f, pageSize),
- }, nil
- }
- type FMCProtocolPortObjectsPager struct {
- pager
- }
- func (
- p *FMCProtocolPortObjectsPager,
- ) Iter() <-chan *FMCProtocolPortObject {
- return iterateItems(
- &p.pager,
- func() fmcAggregate[FMCProtocolPortObject] {
- return fmcAggregate[FMCProtocolPortObject]{}
- },
- )
- }
- func (p *FMCProtocolPortObjectsPager) LastErr() (err error) {
- if p.err != nil {
- err = fmt.Errorf("fmc protocol-port objects pager: %v", p.err)
- }
- return
- }
- func (f *FMC) Ports(
- domain string,
- pageSize uint64,
- ) (*FMCProtocolPortObjectsPager, error) {
- domainUUID, ok := f.domains[domain]
- if !ok {
- return nil, fmt.Errorf("port objects: unknown domain %v", domain)
- }
- url := fmt.Sprintf(
- "https://%s%s/domain/%s/object/ports",
- f.host,
- f.basePath,
- domainUUID,
- )
- return &FMCProtocolPortObjectsPager{
- pager: newPager(url, f, pageSize),
- }, nil
- }
- type FMCPortObjectGroupsPager struct {
- pager
- }
- func (
- p *FMCPortObjectGroupsPager,
- ) Iter() <-chan *FMCPortObjectGroup {
- return iterateItems(
- &p.pager,
- func() fmcAggregate[FMCPortObjectGroup] {
- return fmcAggregate[FMCPortObjectGroup]{}
- },
- )
- }
- func (p *FMCPortObjectGroupsPager) LastErr() (err error) {
- if p.err != nil {
- err = fmt.Errorf("fmc port object groups pager: %v", p.err)
- }
- return
- }
- func (f *FMC) PortGroups(
- domain string,
- pageSize uint64,
- ) (*FMCPortObjectGroupsPager, error) {
- domainUUID, ok := f.domains[domain]
- if !ok {
- return nil, fmt.Errorf("port object groups: unknown domain %v", domain)
- }
- url := fmt.Sprintf(
- "https://%s%s/domain/%s/object/portobjectgroups",
- f.host,
- f.basePath,
- domainUUID,
- )
- return &FMCPortObjectGroupsPager{
- pager: newPager(url, f, pageSize),
- }, nil
- }
- // 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
- func makeAuthAccessRequest(
- ctx context.Context,
- host,
- user,
- pass string,
- ) (*http.Request, error) {
- url := fmt.Sprintf(
- "https://%s/api/fmc_platform/v1/auth/generatetoken",
- host,
- )
- req, err :=
- http.NewRequestWithContext(ctx, "POST", url, nil)
- if err != nil {
- return req, err
- }
- req.SetBasicAuth(user, pass)
- return req, nil
- }
- // 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
- func makeAuthRefreshRequest(
- ctx context.Context,
- host,
- authToken,
- refreshToken string,
- ) (*http.Request, error) {
- url := fmt.Sprintf(
- "https://%s/api/fmc_platform/v1/auth/refreshtoken",
- host,
- )
- req, err :=
- http.NewRequestWithContext(ctx, "POST", url, nil)
- if err != nil {
- return req, err
- }
- req.Header.Add("X-Auth-Access-Token", authToken)
- req.Header.Add("X-Auth-Refresh-Token", refreshToken)
- return req, nil
- }
- func warn(m string) {
- log.Printf("Warn: %s\n", m)
- return
- }
- func info(m string) {
- log.Printf("info: %s\n", m)
- return
- }
- func debug(m string) {
- log.Printf("debug: %s\n", m)
- return
- }
|