diff --git a/controller/internal/routes/service_api_model.go b/controller/internal/routes/service_api_model.go index f646bc943..e78ef74d2 100644 --- a/controller/internal/routes/service_api_model.go +++ b/controller/internal/routes/service_api_model.go @@ -191,9 +191,12 @@ func MapServiceToRestModel(ae *env.AppEnv, rc *response.RequestContext, service isCheckPassing, _ = ae.Handlers.PostureResponse.Evaluate(rc.Identity.Id, rc.ApiSession.Id, postureCheck) validChecks[postureCheck.Id] = isCheckPassing } - timeoutSeconds := postureCheck.TimeoutSeconds(rc.ApiSession.Id, ae.Handlers.PostureResponse.PostureData(rc.Identity.Id)) + timeout := postureCheck.TimeoutSeconds() + timeoutRemaining := postureCheck.TimeoutRemainingSeconds(rc.ApiSession.Id, ae.Handlers.PostureResponse.PostureData(rc.Identity.Id)) + query.IsPassing = &isCheckPassing - query.Timeout = &timeoutSeconds + query.TimeoutRemaining = &timeoutRemaining + query.Timeout = &timeout querySet.PostureQueries = append(querySet.PostureQueries, query) if !isCheckPassing { diff --git a/controller/model/posture_check_model.go b/controller/model/posture_check_model.go index 11dcc4f97..32a06240f 100644 --- a/controller/model/posture_check_model.go +++ b/controller/model/posture_check_model.go @@ -42,7 +42,8 @@ type PostureCheckSubType interface { fillFrom(handler Handler, tx *bbolt.Tx, check *persistence.PostureCheck, subType persistence.PostureCheckSubType) error Evaluate(apiSessionId string, pd *PostureData) bool FailureValues(_ string, pd *PostureData) PostureCheckFailureValues - GetTimeoutSeconds(apiSessionId string, pd *PostureData) int64 + GetTimeoutSeconds() int64 + GetTimeoutRemainingSeconds(apiSessionId string, pd *PostureData) int64 } type PostureCheckFailureValues interface { @@ -141,6 +142,10 @@ func (entity *PostureCheck) Evaluate(apiSessionId string, pd *PostureData) (bool return true, nil } -func (entity *PostureCheck) TimeoutSeconds(apiSessionId string, pd *PostureData) int64 { - return entity.SubType.GetTimeoutSeconds(apiSessionId, pd) +func (entity *PostureCheck) TimeoutSeconds() int64 { + return entity.SubType.GetTimeoutSeconds() +} + +func (entity *PostureCheck) TimeoutRemainingSeconds(apiSessionId string, pd *PostureData) int64 { + return entity.SubType.GetTimeoutRemainingSeconds(apiSessionId, pd) } diff --git a/controller/model/posture_check_model_mac.go b/controller/model/posture_check_model_mac.go index 4d00802e8..2e1de8d9c 100644 --- a/controller/model/posture_check_model_mac.go +++ b/controller/model/posture_check_model_mac.go @@ -27,8 +27,11 @@ var _ PostureCheckSubType = &PostureCheckMacAddresses{} type PostureCheckMacAddresses struct { MacAddresses []string } +func (p *PostureCheckMacAddresses) GetTimeoutRemainingSeconds(_ string, _ *PostureData) int64 { + return PostureCheckNoTimeout +} -func (p *PostureCheckMacAddresses) GetTimeoutSeconds(_ string, _ *PostureData) int64 { +func (p *PostureCheckMacAddresses) GetTimeoutSeconds() int64 { return PostureCheckNoTimeout } diff --git a/controller/model/posture_check_model_mfa.go b/controller/model/posture_check_model_mfa.go index 9af87e167..b20f5a460 100644 --- a/controller/model/posture_check_model_mfa.go +++ b/controller/model/posture_check_model_mfa.go @@ -51,13 +51,22 @@ func (p *PostureCheckMfa) IsLegacyClient(apiSessionData *ApiSessionPostureData) return false } -func (p *PostureCheckMfa) GetTimeoutSeconds(apiSessionId string, pd *PostureData) int64 { +func (p *PostureCheckMfa) GetTimeoutSeconds() int64 { + if p.TimeoutSeconds > 0 { + return p.TimeoutSeconds + } + + return PostureCheckNoTimeout +} + +func (p *PostureCheckMfa) GetTimeoutRemainingSeconds(apiSessionId string, pd *PostureData) int64 { if p.TimeoutSeconds == PostureCheckNoTimeout { return PostureCheckNoTimeout } apiSessionData := pd.ApiSessions[apiSessionId] + // no MFA data, return 0 as we have a timeout of some value if apiSessionData == nil || apiSessionData.Mfa == nil || apiSessionData.Mfa.PassedMfaAt == nil { return 0 } @@ -69,36 +78,36 @@ func (p *PostureCheckMfa) GetTimeoutSeconds(apiSessionId string, pd *PostureData timeSinceLastMfa := time.Now().Sub(*apiSessionData.Mfa.PassedMfaAt) - timeout := p.TimeoutSeconds - int64(timeSinceLastMfa.Seconds()) - if timeout < 0 { + timeoutRemaining := p.TimeoutSeconds - int64(timeSinceLastMfa.Seconds()) + if timeoutRemaining <= 0 { return 0 } if p.PromptOnWake && apiSessionData.EndpointState != nil && apiSessionData.EndpointState.WokenAt != nil { onWakeTimeOut := int64(time.Now().Sub(apiSessionData.EndpointState.WokenAt.Add(MfaPromptGracePeriod)).Seconds()) - if onWakeTimeOut < 0 { + if onWakeTimeOut <= 0 { onWakeTimeOut = 0 } - if onWakeTimeOut < timeout { - timeout = onWakeTimeOut + if onWakeTimeOut < timeoutRemaining { + timeoutRemaining = onWakeTimeOut } } if p.PromptOnUnlock && apiSessionData.EndpointState != nil && apiSessionData.EndpointState.UnlockedAt != nil { onUnlockTimeOut := int64(time.Now().Sub(apiSessionData.EndpointState.UnlockedAt.Add(MfaPromptGracePeriod)).Seconds()) - if onUnlockTimeOut < 0 { + if onUnlockTimeOut <= 0 { onUnlockTimeOut = 0 } - if onUnlockTimeOut < timeout { - timeout = onUnlockTimeOut + if onUnlockTimeOut < timeoutRemaining { + timeoutRemaining = onUnlockTimeOut } } - return timeout + return timeoutRemaining } func (p *PostureCheckMfa) FailureValues(apiSessionId string, pd *PostureData) PostureCheckFailureValues { @@ -146,10 +155,10 @@ func (p *PostureCheckMfa) Evaluate(apiSessionId string, pd *PostureData) bool { return false } now := time.Now().UTC() - timeoutSeconds := p.GetTimeoutSeconds(apiSessionId, pd) + timeoutRemainingSeconds := p.GetTimeoutRemainingSeconds(apiSessionId, pd) - if timeoutSeconds != PostureCheckNoTimeout { - expiresAt := apiSessionData.Mfa.PassedMfaAt.Add(time.Duration(timeoutSeconds) * time.Second) + if timeoutRemainingSeconds != PostureCheckNoTimeout { + expiresAt := apiSessionData.Mfa.PassedMfaAt.Add(time.Duration(timeoutRemainingSeconds) * time.Second) if expiresAt.Before(now) { apiSessionData.Mfa.TimedOut = true } diff --git a/controller/model/posture_check_model_mfa_test.go b/controller/model/posture_check_model_mfa_test.go index fa0b3dea5..e31945685 100644 --- a/controller/model/posture_check_model_mfa_test.go +++ b/controller/model/posture_check_model_mfa_test.go @@ -407,7 +407,7 @@ func TestPostureCheckModelMfa(t *testing.T) { req.True(failureValues.ActualValue.TimedOutSeconds) }) }) - + t.Run("IsLegacyClient", func(t *testing.T) { t.Run("returns false for ziti-sdk-c and 0.25.0", func(t *testing.T) { mfaCheck, postureData := newMfaCheckAndPostureData() @@ -478,6 +478,80 @@ func TestPostureCheckModelMfa(t *testing.T) { req.False(result) }) }) + + t.Run("GetTimeoutSeconds", func(t *testing.T) { + t.Run("returns the static timeout value", func(t *testing.T) { + mfaCheck, _ := newMfaCheckAndPostureData() + + req := require.New(t) + req.Equal(mfaCheck.TimeoutSeconds, mfaCheck.GetTimeoutSeconds()) + }) + + t.Run("returns no timeout for negative numbers", func(t *testing.T) { + mfaCheck, _ := newMfaCheckAndPostureData() + mfaCheck.TimeoutSeconds = -100 + req := require.New(t) + req.Equal(PostureCheckNoTimeout, mfaCheck.GetTimeoutSeconds()) + }) + + t.Run("returns no timeout for 0", func(t *testing.T) { + mfaCheck, _ := newMfaCheckAndPostureData() + mfaCheck.TimeoutSeconds = 0 + req := require.New(t) + req.Equal(PostureCheckNoTimeout, mfaCheck.GetTimeoutSeconds()) + }) + }) + + t.Run("GetTimeoutRemainingSeconds", func(t *testing.T) { + t.Run("returns 0 if no ApiSessionData is present", func(t *testing.T) { + mfaCheck, postureData := newMfaCheckAndPostureData() + postureData.ApiSessions = map[string]*ApiSessionPostureData{} + req := require.New(t) + req.Equal(int64(0), mfaCheck.GetTimeoutRemainingSeconds(mfaTestApiSessionId, postureData)) + }) + + t.Run("returns 0 if no MFA data is present", func(t *testing.T) { + mfaCheck, postureData := newMfaCheckAndPostureData() + postureData.ApiSessions[mfaTestApiSessionId].Mfa = nil + req := require.New(t) + req.Equal(int64(0), mfaCheck.GetTimeoutRemainingSeconds(mfaTestApiSessionId, postureData)) + }) + + t.Run("returns 0 if no last MFA passedAt is nil", func(t *testing.T) { + mfaCheck, postureData := newMfaCheckAndPostureData() + postureData.ApiSessions[mfaTestApiSessionId].Mfa.PassedMfaAt = nil + req := require.New(t) + req.Equal(int64(0), mfaCheck.GetTimeoutRemainingSeconds(mfaTestApiSessionId, postureData)) + }) + + t.Run("returns near timeout if much time hasn't passed", func(t *testing.T) { + mfaCheck, postureData := newMfaCheckAndPostureData() + req := require.New(t) + + //time based, if on a slow machine this might slide + req.LessOrEqual(mfaCheck.GetTimeoutRemainingSeconds(mfaTestApiSessionId, postureData), mfaCheck.TimeoutSeconds) + req.GreaterOrEqual(mfaCheck.GetTimeoutRemainingSeconds(mfaTestApiSessionId, postureData), mfaCheck.TimeoutSeconds-5) + }) + + t.Run("returns not timeout for legacy clients if ignore legacy is true", func(t *testing.T) { + mfaCheck, postureData := newMfaCheckAndPostureData() + mfaCheck.IgnoreLegacyEndpoints = true + postureData.ApiSessions[mfaTestApiSessionId].SdkInfo.Version = "0.24.4" + + req := require.New(t) + req.Equal(PostureCheckNoTimeout, mfaCheck.GetTimeoutRemainingSeconds(mfaTestApiSessionId, postureData)) + }) + + t.Run("returns near timeout for legacy client if ignore legacy is false", func(t *testing.T) { + mfaCheck, postureData := newMfaCheckAndPostureData() + postureData.ApiSessions[mfaTestApiSessionId].SdkInfo.Version = "0.24.4" + req := require.New(t) + + //time based, if on a slow machine this might slide + req.LessOrEqual(mfaCheck.GetTimeoutRemainingSeconds(mfaTestApiSessionId, postureData), mfaCheck.TimeoutSeconds) + req.GreaterOrEqual(mfaCheck.GetTimeoutRemainingSeconds(mfaTestApiSessionId, postureData), mfaCheck.TimeoutSeconds-5) + }) + }) } // newMfaCheckAndPostureData returns a MFA posture check and posture data that will @@ -516,8 +590,8 @@ func newMfaCheckAndPostureData() (*PostureCheckMfa, *PostureData) { UnlockedAt: &unlockedAt, }, SdkInfo: &SdkInfo{ - Type: "ziti-sdk-c", - Version: "0.25.0", + Type: "ziti-sdk-c", + Version: "0.25.0", }, }, }, diff --git a/controller/model/posture_check_model_os.go b/controller/model/posture_check_model_os.go index 527c1f11f..5b982ec81 100644 --- a/controller/model/posture_check_model_os.go +++ b/controller/model/posture_check_model_os.go @@ -32,7 +32,11 @@ type PostureCheckOperatingSystem struct { OperatingSystems []OperatingSystem } -func (p *PostureCheckOperatingSystem) GetTimeoutSeconds(_ string, _ *PostureData) int64 { +func (p *PostureCheckOperatingSystem) GetTimeoutRemainingSeconds(_ string, _ *PostureData) int64 { + return PostureCheckNoTimeout +} + +func (p *PostureCheckOperatingSystem) GetTimeoutSeconds() int64 { return PostureCheckNoTimeout } diff --git a/controller/model/posture_check_model_process.go b/controller/model/posture_check_model_process.go index 05bf8532e..9484c3ca0 100644 --- a/controller/model/posture_check_model_process.go +++ b/controller/model/posture_check_model_process.go @@ -33,7 +33,11 @@ type PostureCheckProcess struct { Fingerprint string } -func (p *PostureCheckProcess) GetTimeoutSeconds(_ string, _ *PostureData) int64 { +func (p *PostureCheckProcess) GetTimeoutSeconds() int64 { + return PostureCheckNoTimeout +} + +func (p *PostureCheckProcess) GetTimeoutRemainingSeconds(_ string, _ *PostureData) int64 { return PostureCheckNoTimeout } diff --git a/controller/model/posture_check_model_process_,multi.go b/controller/model/posture_check_model_process_,multi.go index 0e299091a..617cf3327 100644 --- a/controller/model/posture_check_model_process_,multi.go +++ b/controller/model/posture_check_model_process_,multi.go @@ -32,7 +32,11 @@ type PostureCheckProcessMulti struct { Processes []*ProcessMulti } -func (p *PostureCheckProcessMulti) GetTimeoutSeconds(_ string, _ *PostureData) int64 { +func (p *PostureCheckProcessMulti) GetTimeoutSeconds() int64 { + return PostureCheckNoTimeout +} + +func (p *PostureCheckProcessMulti) GetTimeoutRemainingSeconds(_ string, _ *PostureData) int64 { return PostureCheckNoTimeout } diff --git a/controller/model/posture_check_model_windows_domain.go b/controller/model/posture_check_model_windows_domain.go index 10fd34270..fc3002a5e 100644 --- a/controller/model/posture_check_model_windows_domain.go +++ b/controller/model/posture_check_model_windows_domain.go @@ -29,7 +29,11 @@ type PostureCheckDomains struct { Domains []string } -func (p *PostureCheckDomains) GetTimeoutSeconds(_ string, _ *PostureData) int64 { +func (p *PostureCheckDomains) GetTimeoutSeconds() int64 { + return PostureCheckNoTimeout +} + +func (p *PostureCheckDomains) GetTimeoutRemainingSeconds(_ string, _ *PostureData) int64 { return PostureCheckNoTimeout } diff --git a/controller/model/posture_response_model.go b/controller/model/posture_response_model.go index 891c34321..a4c8f01aa 100644 --- a/controller/model/posture_response_model.go +++ b/controller/model/posture_response_model.go @@ -219,7 +219,7 @@ func (pc *PostureCache) SessionCreated(args ...interface{}) { if exist { pd = valueInMap.(*PostureData) } else { - pd = newPostureData() + pd = newValue.(*PostureData) } if pd.ApiSessions == nil { diff --git a/rest_client_api_server/embedded_spec.go b/rest_client_api_server/embedded_spec.go index d02b12f2d..c03afdb00 100644 --- a/rest_client_api_server/embedded_spec.go +++ b/rest_client_api_server/embedded_spec.go @@ -4703,7 +4703,8 @@ func init() { "required": [ "queryType", "isPassing", - "timeout" + "timeout", + "timeoutRemaining" ], "properties": { "isPassing": { @@ -4723,6 +4724,9 @@ func init() { }, "timeout": { "type": "integer" + }, + "timeoutRemaining": { + "type": "integer" } } } @@ -10040,7 +10044,8 @@ func init() { "required": [ "queryType", "isPassing", - "timeout" + "timeout", + "timeoutRemaining" ], "properties": { "isPassing": { @@ -10060,6 +10065,9 @@ func init() { }, "timeout": { "type": "integer" + }, + "timeoutRemaining": { + "type": "integer" } } } diff --git a/rest_management_api_server/embedded_spec.go b/rest_management_api_server/embedded_spec.go index f314dd5d6..a76e8be4d 100644 --- a/rest_management_api_server/embedded_spec.go +++ b/rest_management_api_server/embedded_spec.go @@ -17635,7 +17635,8 @@ func init() { "required": [ "queryType", "isPassing", - "timeout" + "timeout", + "timeoutRemaining" ], "properties": { "isPassing": { @@ -17655,6 +17656,9 @@ func init() { }, "timeout": { "type": "integer" + }, + "timeoutRemaining": { + "type": "integer" } } } @@ -36455,7 +36459,8 @@ func init() { "required": [ "queryType", "isPassing", - "timeout" + "timeout", + "timeoutRemaining" ], "properties": { "isPassing": { @@ -36475,6 +36480,9 @@ func init() { }, "timeout": { "type": "integer" + }, + "timeoutRemaining": { + "type": "integer" } } } diff --git a/rest_model/posture_query.go b/rest_model/posture_query.go index dcf5d2d17..d7b3c3cf9 100644 --- a/rest_model/posture_query.go +++ b/rest_model/posture_query.go @@ -62,6 +62,10 @@ type PostureQuery struct { // timeout // Required: true Timeout *int64 `json:"timeout"` + + // timeout remaining + // Required: true + TimeoutRemaining *int64 `json:"timeoutRemaining"` } // UnmarshalJSON unmarshals this object from a JSON structure @@ -84,6 +88,8 @@ func (m *PostureQuery) UnmarshalJSON(raw []byte) error { QueryType *PostureCheckType `json:"queryType"` Timeout *int64 `json:"timeout"` + + TimeoutRemaining *int64 `json:"timeoutRemaining"` } if err := swag.ReadJSON(raw, &dataAO1); err != nil { return err @@ -99,6 +105,8 @@ func (m *PostureQuery) UnmarshalJSON(raw []byte) error { m.Timeout = dataAO1.Timeout + m.TimeoutRemaining = dataAO1.TimeoutRemaining + return nil } @@ -121,6 +129,8 @@ func (m PostureQuery) MarshalJSON() ([]byte, error) { QueryType *PostureCheckType `json:"queryType"` Timeout *int64 `json:"timeout"` + + TimeoutRemaining *int64 `json:"timeoutRemaining"` } dataAO1.IsPassing = m.IsPassing @@ -133,6 +143,8 @@ func (m PostureQuery) MarshalJSON() ([]byte, error) { dataAO1.Timeout = m.Timeout + dataAO1.TimeoutRemaining = m.TimeoutRemaining + jsonDataAO1, errAO1 := swag.WriteJSON(dataAO1) if errAO1 != nil { return nil, errAO1 @@ -170,6 +182,10 @@ func (m *PostureQuery) Validate(formats strfmt.Registry) error { res = append(res, err) } + if err := m.validateTimeoutRemaining(formats); err != nil { + res = append(res, err) + } + if len(res) > 0 { return errors.CompositeValidationError(res...) } @@ -259,6 +275,15 @@ func (m *PostureQuery) validateTimeout(formats strfmt.Registry) error { return nil } +func (m *PostureQuery) validateTimeoutRemaining(formats strfmt.Registry) error { + + if err := validate.Required("timeoutRemaining", "body", m.TimeoutRemaining); err != nil { + return err + } + + return nil +} + // ContextValidate validate this posture query based on the context it is used func (m *PostureQuery) ContextValidate(ctx context.Context, formats strfmt.Registry) error { var res []error diff --git a/specs/client.yml b/specs/client.yml index f17529d13..5d93c6634 100644 --- a/specs/client.yml +++ b/specs/client.yml @@ -3379,6 +3379,7 @@ definitions: - queryType - isPassing - timeout + - timeoutRemaining properties: isPassing: type: boolean @@ -3392,6 +3393,8 @@ definitions: $ref: '#/definitions/postureCheckType' timeout: type: integer + timeoutRemaining: + type: integer postureQueryProcess: type: object properties: diff --git a/specs/management.yml b/specs/management.yml index 62cd94e62..52690e75a 100644 --- a/specs/management.yml +++ b/specs/management.yml @@ -12437,6 +12437,7 @@ definitions: - queryType - isPassing - timeout + - timeoutRemaining properties: isPassing: type: boolean @@ -12450,6 +12451,8 @@ definitions: $ref: '#/definitions/postureCheckType' timeout: type: integer + timeoutRemaining: + type: integer postureQueryProcess: type: object properties: diff --git a/specs/source/shared/posture-queries.yml b/specs/source/shared/posture-queries.yml index 0de4e5ce6..51698f29b 100644 --- a/specs/source/shared/posture-queries.yml +++ b/specs/source/shared/posture-queries.yml @@ -27,11 +27,14 @@ definitions: - queryType - isPassing - timeout + - timeoutRemaining properties: isPassing: type: boolean timeout: type: integer + timeoutRemaining: + type: integer queryType: $ref: 'posture-checks.yml#/definitions/postureCheckType' process: diff --git a/tests/posture_check_mfa_test.go b/tests/posture_check_mfa_test.go index 63de702d3..4f9b6e54f 100644 --- a/tests/posture_check_mfa_test.go +++ b/tests/posture_check_mfa_test.go @@ -333,7 +333,7 @@ func Test_PostureChecks_MFA(t *testing.T) { ctx.testContextChanged(t) ctx.Req.False(querySet[0].Path("isPassing").Data().(bool)) ctx.Req.False(postureQueries[0].Path("isPassing").Data().(bool)) - ctx.Req.Equal(0, int(postureQueries[0].Path("timeout").Data().(float64))) + ctx.Req.Equal(0, int(postureQueries[0].Path("timeoutRemaining").Data().(float64))) }) }) @@ -470,13 +470,11 @@ func Test_PostureChecks_MFA(t *testing.T) { ctx.testContextChanged(t) ctx.Req.True(querySet[0].Path("isPassing").Data().(bool)) ctx.Req.True(postureQueries[0].Path("isPassing").Data().(bool)) - ctx.Req.Greater(int(postureQueries[0].Path("timeout").Data().(float64)), 0) - ctx.Req.LessOrEqual(int(postureQueries[0].Path("timeout").Data().(float64)), 300) + ctx.Req.Greater(int(postureQueries[0].Path("timeoutRemaining").Data().(float64)), 0) + ctx.Req.LessOrEqual(int(postureQueries[0].Path("timeoutRemaining").Data().(float64)), 300) }) }) - }) - }) t.Run("a new api session can pass MFA auth to create service session if there are no endpoint state changes", func(t *testing.T) {