diff --git a/service/cziti/sdk.go b/service/cziti/sdk.go index 431a8743..2334cc82 100644 --- a/service/cziti/sdk.go +++ b/service/cziti/sdk.go @@ -54,6 +54,7 @@ import ( "os" "strings" "sync" + "sync/atomic" "time" "unsafe" ) @@ -82,17 +83,19 @@ type BulkServiceChange struct { ServicesToAdd []*dto.Service MfaMinTimeout int32 MfaMaxTimeout int32 + MfaMinTimeoutRem int32 + MfaMaxTimeoutRem int32 MfaLastUpdatedTime time.Time } type NotificationMessage struct { - IdentityName string - Fingerprint string - Message string + IdentityName string + Fingerprint string + Message string MfaMinimumTimeout int32 MfaMaximumTimeout int32 MfaTimeDuration int - Severity string + Severity string } type TunnelNotificationEvent struct { @@ -156,6 +159,8 @@ type ZIdentity struct { MfaEnabled bool MfaMinTimeout int32 MfaMaxTimeout int32 + MfaMinTimeoutRem int32 + MfaMaxTimeoutRem int32 MfaLastUpdatedTime time.Time } @@ -164,8 +169,10 @@ func NewZid(statusChange func(int)) *ZIdentity { zid.Services = sync.Map{} zid.Options = (*C.ziti_options)(C.calloc(1, C.sizeof_ziti_options)) zid.StatusChanges = statusChange - zid.MfaMinTimeout = -1 + zid.MfaMinTimeoutRem = -1 + zid.MfaMaxTimeoutRem = -1 zid.MfaMaxTimeout = -1 + zid.MfaMinTimeout = -1 zid.MfaLastUpdatedTime = time.Now() return zid } @@ -247,7 +254,7 @@ func (zid *ZIdentity) Shutdown() { } func (zid *ZIdentity) MfaRefreshNeeded() bool { - return !(zid.MfaMinTimeout == -1 && zid.MfaMaxTimeout == -1) + return !(zid.MfaMinTimeoutRem == -1 && zid.MfaMaxTimeoutRem == -1) } func (zid *ZIdentity) GetRemainingTime(timeout int32) int32 { @@ -262,11 +269,11 @@ func (zid *ZIdentity) GetRemainingTime(timeout int32) int32 { func (zid *ZIdentity) GetMFAState(interval int32) int { var mfaState int - if (zid.MfaMaxTimeout > -1) && (zid.MfaMaxTimeout-int32(time.Since(zid.MfaLastUpdatedTime).Seconds())) < 0 { + if (zid.MfaMaxTimeoutRem > -1) && (zid.MfaMaxTimeoutRem-int32(time.Since(zid.MfaLastUpdatedTime).Seconds())) < 0 { mfaState = constants.MfaAllSvcTimeout - } else if (zid.MfaMinTimeout - int32(time.Since(zid.MfaLastUpdatedTime).Seconds())) < 0 { + } else if (zid.MfaMinTimeoutRem - int32(time.Since(zid.MfaLastUpdatedTime).Seconds())) < 0 { mfaState = constants.MfaFewSvcTimeout - } else if (zid.MfaMinTimeout - int32(time.Since(zid.MfaLastUpdatedTime).Seconds())) < interval { + } else if (zid.MfaMinTimeoutRem - int32(time.Since(zid.MfaLastUpdatedTime).Seconds())) < interval { mfaState = constants.MfaNearingTimeout } else { mfaState = -1 @@ -274,6 +281,31 @@ func (zid *ZIdentity) GetMFAState(interval int32) int { return mfaState } +func (zid *ZIdentity) UpdateMFATime() { + + zid.Services.Range(func(key interface{}, value interface{}) bool { + //string, ZService + val := value.(*ZService) + + if zid.MfaRefreshNeeded() && val.Service.Timeout > 0 { + var svcTimeout int32 = -1 + for _, pc := range val.Service.PostureChecks { + if svcTimeout == -1 || svcTimeout > int32(pc.Timeout) { + svcTimeout = int32(pc.Timeout) + } + break + } + atomic.StoreInt32(&val.Service.TimeoutRemaining, svcTimeout) + } + + return true + }) + zid.MfaLastUpdatedTime = time.Now() + zid.MfaMaxTimeoutRem = zid.MfaMaxTimeout + zid.MfaMinTimeoutRem = zid.MfaMinTimeout + log.Debugf("updated Mfa Maximum Timeout Remaining to %d and Mfa Minimum Timeout Remaining to %d", zid.MfaMaxTimeoutRem, zid.MfaMinTimeoutRem) +} + //export doZitiShutdown func doZitiShutdown(async *C.uv_async_t) { ctx := C.ziti_context(async.data) @@ -366,6 +398,7 @@ func serviceCB(ziti_ctx C.ziti_context, service *C.ziti_service, status C.int, z //if any posture query sets pass - that will grant the user access to that service hasAccess := false timeout := -1 + timeoutRemaining := -1 //find all posture checks sets... for setIdx := 0; true; setIdx++ { @@ -395,7 +428,7 @@ func serviceCB(ziti_ctx C.ziti_context, service *C.ziti_service, status C.int, z var pcId string pcId = C.GoString(pq.id) - log.Infof("Posture query %s, timeout %d", C.GoString(pq.id), int(pq.timeout)) + log.Infof("Posture query %s, timeout %d, timeoutRemaining %d", C.GoString(pq.id), int(pq.timeout), int(pq.timeoutRemaining)) _, found := pcIds[pcId] if found { @@ -405,18 +438,23 @@ func serviceCB(ziti_ctx C.ziti_context, service *C.ziti_service, status C.int, z if timeout == -1 || timeout > int(pq.timeout) { timeout = int(pq.timeout) } + if timeoutRemaining == -1 || timeoutRemaining > int(pq.timeoutRemaining) { + timeoutRemaining = int(pq.timeoutRemaining) + } break } } } else { pcIds[C.GoString(pq.id)] = false pc := dto.PostureCheck{ - IsPassing: bool(pq.is_passing), - QueryType: C.GoString(pq.query_type), - Id: pcId, - Timeout: int(pq.timeout), + IsPassing: bool(pq.is_passing), + QueryType: C.GoString(pq.query_type), + Id: pcId, + Timeout: int(pq.timeout), + TimeoutRemaining: int(pq.timeoutRemaining), } timeout = pc.Timeout + timeoutRemaining = pc.TimeoutRemaining postureChecks = append(postureChecks, pc) } @@ -424,15 +462,16 @@ func serviceCB(ziti_ctx C.ziti_context, service *C.ziti_service, status C.int, z } svc = &dto.Service{ - Name: name, - Id: svcId, - Protocols: protocols, - Addresses: addresses, - Ports: portRanges, - OwnsIntercept: true, - PostureChecks: postureChecks, - IsAccessable: hasAccess, - Timeout: int32(timeout), + Name: name, + Id: svcId, + Protocols: protocols, + Addresses: addresses, + Ports: portRanges, + OwnsIntercept: true, + PostureChecks: postureChecks, + IsAccessable: hasAccess, + Timeout: int32(timeout), + TimeoutRemaining: int32(timeoutRemaining), } added := ZService{ Name: name, @@ -584,6 +623,8 @@ func eventCB(ztx C.ziti_context, event *C.ziti_event_t) { } var minimumTimeout int32 = -1 var maximumTimeout int32 = -1 + var minimumTimeoutRem int32 = -1 + var maximumTimeoutRem int32 = -1 noTimeoutSvc := false if len(servicesToAdd) > 0 { zid.Services.Range(func(key interface{}, value interface{}) bool { @@ -600,17 +641,32 @@ func eventCB(ztx C.ziti_context, event *C.ziti_event_t) { } else { noTimeoutSvc = true } + if val.Service.TimeoutRemaining >= 0 { + if minimumTimeoutRem == -1 || minimumTimeoutRem > val.Service.TimeoutRemaining { + minimumTimeoutRem = val.Service.TimeoutRemaining + } + if maximumTimeoutRem == -1 || maximumTimeoutRem < val.Service.TimeoutRemaining { + maximumTimeoutRem = val.Service.TimeoutRemaining + } + } else { + noTimeoutSvc = true + } return true }) if noTimeoutSvc { maximumTimeout = -1 + maximumTimeoutRem = -1 } } + now := time.Now() zid.MfaMinTimeout = minimumTimeout zid.MfaMaxTimeout = maximumTimeout + zid.MfaMinTimeoutRem = minimumTimeoutRem + zid.MfaMaxTimeoutRem = maximumTimeoutRem + zid.MfaLastUpdatedTime = now svcChange := BulkServiceChange{ Fingerprint: zid.Fingerprint, @@ -620,7 +676,9 @@ func eventCB(ztx C.ziti_context, event *C.ziti_event_t) { ServicesToRemove: servicesToRemove, MfaMinTimeout: minimumTimeout, MfaMaxTimeout: maximumTimeout, - MfaLastUpdatedTime: time.Now(), + MfaMinTimeoutRem: minimumTimeoutRem, + MfaMaxTimeoutRem: maximumTimeoutRem, + MfaLastUpdatedTime: now, } if len(BulkServiceChanges) == cap(BulkServiceChanges) { diff --git a/service/ziti-tunnel/constants/consts.go b/service/ziti-tunnel/constants/consts.go index 4496b4f1..6571b0aa 100644 --- a/service/ziti-tunnel/constants/consts.go +++ b/service/ziti-tunnel/constants/consts.go @@ -25,7 +25,7 @@ const ( MinimumFrequency = 5 // minimum notification window MaximumFrequency = 20 // maximum notification window - MfaAllSvcTimeout = 0 - MfaFewSvcTimeout = 1 + MfaAllSvcTimeout = 0 + MfaFewSvcTimeout = 1 MfaNearingTimeout = 2 ) diff --git a/service/ziti-tunnel/dto/dtos.go b/service/ziti-tunnel/dto/dtos.go index 20c88f2c..17544e60 100644 --- a/service/ziti-tunnel/dto/dtos.go +++ b/service/ziti-tunnel/dto/dtos.go @@ -32,15 +32,16 @@ type AddIdentity struct { } type Service struct { - Name string - Id string - Protocols []string - Addresses []Address - Ports []PortRange - OwnsIntercept bool - PostureChecks []PostureCheck - IsAccessable bool - Timeout int32 + Name string + Id string + Protocols []string + Addresses []Address + Ports []PortRange + OwnsIntercept bool + PostureChecks []PostureCheck + IsAccessable bool + Timeout int32 + TimeoutRemaining int32 } type Address struct { @@ -56,10 +57,11 @@ type PortRange struct { } type PostureCheck struct { - IsPassing bool - QueryType string - Id string - Timeout int + IsPassing bool + QueryType string + Id string + Timeout int + TimeoutRemaining int } type ServiceOwner struct { @@ -84,6 +86,8 @@ type Identity struct { Tags []string `json:",omitempty"` MfaMinTimeout int32 MfaMaxTimeout int32 + MfaMinTimeoutRem int32 + MfaMaxTimeoutRem int32 MfaLastUpdatedTime time.Time } type Metrics struct { diff --git a/service/ziti-tunnel/service/ipc.go b/service/ziti-tunnel/service/ipc.go index 7ee17ce4..b5440f29 100644 --- a/service/ziti-tunnel/service/ipc.go +++ b/service/ziti-tunnel/service/ipc.go @@ -1196,31 +1196,31 @@ func handleEvents(isInitialized chan struct{}) { var notificationMinTimeout int32 = 0 var notificationMaxTimeout int32 = -1 - switch mfaState := id.CId.GetMFAState(int32((constants.MaximumFrequency+rts.state.NotificationFrequency)*60)); mfaState { + switch mfaState := id.CId.GetMFAState(int32((constants.MaximumFrequency + rts.state.NotificationFrequency) * 60)); mfaState { case constants.MfaAllSvcTimeout: notificationMessage = fmt.Sprintf("All of the services of identity %s are timed out", id.Name) case constants.MfaFewSvcTimeout: notificationMessage = fmt.Sprintf("Some of the services of identity %s are timed out", id.Name) case constants.MfaNearingTimeout: - notificationMinTimeout = id.CId.GetRemainingTime(id.CId.MfaMinTimeout) + notificationMinTimeout = id.CId.GetRemainingTime(id.CId.MfaMinTimeoutRem) notificationMessage = fmt.Sprintf("Some of the services of identity %s are timing out in %s", id.Name, secondsToReadableFmt(notificationMinTimeout)) default: // do nothing } if len(notificationMessage) > 0 { - if id.CId.MfaMaxTimeout > -1 { - notificationMaxTimeout = id.CId.GetRemainingTime(id.CId.MfaMaxTimeout) + if id.CId.MfaMaxTimeoutRem > -1 { + notificationMaxTimeout = id.CId.GetRemainingTime(id.CId.MfaMaxTimeoutRem) } - notificationMinTimeout = id.CId.GetRemainingTime(id.CId.MfaMinTimeout) + notificationMinTimeout = id.CId.GetRemainingTime(id.CId.MfaMinTimeoutRem) cleanNotifications = append(cleanNotifications, cziti.NotificationMessage{ - Fingerprint: id.FingerPrint, - IdentityName: id.Name, - Severity: "major", + Fingerprint: id.FingerPrint, + IdentityName: id.Name, + Severity: "major", MfaMinimumTimeout: notificationMinTimeout, MfaMaximumTimeout: notificationMaxTimeout, - Message: notificationMessage, + Message: notificationMessage, MfaTimeDuration: int(time.Since(id.CId.MfaLastUpdatedTime).Seconds()), }) } @@ -1272,18 +1272,18 @@ func Clean(src *Id) dto.Identity { //string, ZService val := value.(*cziti.ZService) - if src.CId.MfaRefreshNeeded() && val.Service.Timeout > 0 { + if src.CId.MfaRefreshNeeded() && val.Service.TimeoutRemaining > 0 { var svcTimeout int32 = -1 for _, pc := range val.Service.PostureChecks { - if svcTimeout == -1 || svcTimeout > int32(pc.Timeout) { - svcTimeout = int32(pc.Timeout) + if svcTimeout == -1 || svcTimeout > int32(pc.TimeoutRemaining) { + svcTimeout = int32(pc.TimeoutRemaining) } break } if (svcTimeout - int32(time.Since(src.CId.MfaLastUpdatedTime).Seconds())) < 0 { - atomic.StoreInt32(&val.Service.Timeout, 0) + atomic.StoreInt32(&val.Service.TimeoutRemaining, 0) } else { - atomic.StoreInt32(&val.Service.Timeout, svcTimeout-int32(time.Since(src.CId.MfaLastUpdatedTime).Seconds())) + atomic.StoreInt32(&val.Service.TimeoutRemaining, svcTimeout-int32(time.Since(src.CId.MfaLastUpdatedTime).Seconds())) } } @@ -1292,11 +1292,13 @@ func Clean(src *Id) dto.Identity { }) nid.MfaMinTimeout = src.CId.MfaMinTimeout nid.MfaMaxTimeout = src.CId.MfaMaxTimeout + nid.MfaMinTimeoutRem = src.CId.MfaMinTimeoutRem + nid.MfaMaxTimeoutRem = src.CId.MfaMaxTimeoutRem if !src.CId.MfaRefreshNeeded() { nid.MfaLastUpdatedTime = time.Now() } else { nid.MfaLastUpdatedTime = src.CId.MfaLastUpdatedTime - if (nid.MfaMaxTimeout-int32(time.Since(nid.MfaLastUpdatedTime).Seconds())) < 0 && nid.MfaEnabled { + if (nid.MfaMaxTimeoutRem-int32(time.Since(nid.MfaLastUpdatedTime).Seconds())) < 0 && nid.MfaEnabled { nid.MfaNeeded = true } } @@ -1322,8 +1324,7 @@ func authMfa(out *json.Encoder, fingerprint string, code string) { id := rts.Find(fingerprint) result := cziti.AuthMFA(id.CId, code) if result == nil { - id.CId.MfaNeeded = false - id.CId.MfaLastUpdatedTime = time.Now() + id.CId.UpdateMFATime() respond(out, dto.Response{Message: "AuthMFA complete", Code: SUCCESS, Error: "", Payload: fingerprint}) } else { respondWithError(out, fmt.Sprintf("AuthMFA failed. the supplied code [%s] was not valid: %s", code, result), 1, result) diff --git a/service/ziti-tunnel/service/state.go b/service/ziti-tunnel/service/state.go index d53419b3..ad8406bf 100644 --- a/service/ziti-tunnel/service/state.go +++ b/service/ziti-tunnel/service/state.go @@ -163,6 +163,8 @@ func (t *RuntimeState) ToMetrics() dto.TunnelStatus { MfaNeeded: id.MfaNeeded, MfaMinTimeout: id.MfaMinTimeout, MfaMaxTimeout: id.MfaMaxTimeout, + MfaMinTimeoutRem: id.MfaMinTimeoutRem, + MfaMaxTimeoutRem: id.MfaMaxTimeoutRem, MfaLastUpdatedTime: id.MfaLastUpdatedTime, } i++