package toil import ( "context" "encoding/json" "net/http" "time" ) func (f *Client) authTokenInvalid() bool { if f.authToken == "" || f.refreshCount > MaxAuthTokenRefreshes || time.Until(f.tokenExpiry) <= time.Duration(0) { return true } return false } func (f *Client) resetRefreshToken() { f.refreshToken = "" f.refreshCount = 0 f.tokenExpiry = time.Time{} return } func (f *Client) 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 *Client) 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 *Client) 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 }