Skip to main content

Device Trust

This document describes device-trust logic in the zero-trust control plane backend: how devices are identifiable, revocable, and time-bound; policy evaluation (OPA/Rego) that decides when MFA is required and when to register or refresh trust; and configuration. For MFA flows (Login MFA branch, Refresh MFA branch, VerifyMFA, challenge/OTP, API), see mfa.md. Business logic lives in internal/identity/service/auth_service.go; policy evaluation is in internal/policy/engine/.

Audience: Developers integrating with or extending device trust or policy evaluation.

Overview

Device trust: Devices are identifiable by user_id, org_id, and fingerprint. Trust is revocable (a device can be marked revoked via revoked_at) and time-bound (trust can expire via trusted_until). After successful MFA, the backend may register the device as trusted for a configurable number of days, depending on policy. Effective trust is: Trusted && !RevokedAt && (TrustedUntil == nil || TrustedUntil > now). When a device is not effectively trusted, policy may require MFA on the next login or refresh; see mfa.md for the Login, Refresh, and VerifyMFA flows. On Refresh, the client may send device_fingerprint; the backend evaluates the same policy and, if MFA is required, revokes the current session and returns mfa_required or phone_required, so the user must complete MFA to obtain new tokens.


Architecture


Policy evaluation

Interface

The auth service depends on a PolicyEvaluator interface (internal/policy/engine/evaluator.go):

  • EvaluateMFA(ctx, platformSettings, orgSettings, device, user, isNewDevice) → (MFAResult, error)
  • MFAResult fields: MFARequired (bool), RegisterTrustAfterMFA (bool), TrustTTLDays (int).

Implementations decide whether MFA is required and, when the user completes MFA, whether to register the device as trusted and for how many days.

OPA implementation

The default implementation is OPAEvaluator (internal/policy/engine/opa_evaluator.go):

  • Loads enabled policies for the org from the policies table (Rego text in rules).
  • Builds an input map from platform settings, org settings, device state, user (e.g. has_phone), and is_new.
  • Compiles and evaluates Rego with OPA (v1 API). Queries: data.ztcp.device_trust.mfa_required, data.ztcp.device_trust.register_trust_after_mfa, data.ztcp.device_trust.trust_ttl_days.
  • If no org policies exist or evaluation fails, uses an embedded default Rego policy and safe defaults (e.g. MFA not required, register trust true, TTL 30 days).

Default Rego policy

When no org-specific policies are present, the engine uses an embedded default Rego policy: mfa_required is true if platform mandates always, or (device is new and org requires MFA for new devices), or (device is not effectively trusted and org requires MFA for untrusted). register_trust_after_mfa and trust_ttl_days come from org/platform settings. For the full default policy text and explanation, see Policy engine (OPA/Rego).

Input/output shape (OPA)

Input (JSON passed to OPA):

PathDescription
platform.mfa_required_alwaysbool
platform.default_trust_ttl_daysint
org.mfa_required_for_new_devicebool
org.mfa_required_for_untrustedbool
org.mfa_required_alwaysbool
org.register_trust_after_mfabool
org.trust_ttl_daysint
device.id, device.trusted, device.trusted_until, device.revoked_atdevice fields
device.is_newbool (first login for this device)
device.is_effectively_trustedbool (trusted and not revoked and not expired)
user.id, user.has_phoneuser fields

Output (from Rego, package ztcp.device_trust):

Rule/variableTypeMeaning
mfa_requiredboolWhether to require MFA before issuing session
register_trust_after_mfaboolWhether to mark device trusted after successful MFA
trust_ttl_daysintDevice trust TTL in days (used for trusted_until)

Settings sources

For full detail on OPA/Rego integration, policy structure, default policy text, and evaluation flow, see Policy engine (OPA/Rego).


Device trust

Domain

internal/device/domain/device.go:

  • Trusted (bool): whether the device is marked trusted.
  • TrustedUntil (*time.Time): optional expiry of trust; after this time the device is not effectively trusted.
  • RevokedAt (*time.Time): if set, the device has been revoked and is not trusted.
  • IsEffectivelyTrusted(now time.Time) bool: returns true only if Trusted && RevokedAt == nil && (TrustedUntil == nil || TrustedUntil.After(now)).

Registration after MFA

When VerifyMFA succeeds and policy returns RegisterTrustAfterMFA == true and TrustTTLDays > 0, the auth service calls createSessionAndResult(ctx, userID, orgID, deviceID, true, trustTTLDays), which sets trusted = true, trusted_until = now + trustTTLDays, and clears revoked_at via DeviceRepo.UpdateTrustedWithExpiry.

Revocation

The DeviceService exposes RevokeDevice (proto/device/device.proto, internal/device/handler/grpc.go): it sets the device to trusted = false, trusted_until = null, revoked_at = now. After revocation, the device is no longer effectively trusted, so on the next login policy may require MFA again (if org requires MFA for untrusted devices).


Configuration

VariableDescriptionDefault
DEFAULT_TRUST_TTL_DAYSDefault device trust TTL in days when platform_settings has no value.30

Platform-wide settings are stored in platform_settings (key-value). Org-level settings are in org_mfa_settings (one row per org). See database.md for schema.

For SMS and MFA challenge TTL configuration, see mfa.md.


See also

  • auth.md — Authentication overview, Register, Login, Refresh, Logout, and public methods.
  • database.md — Schema for platform_settings, org_mfa_settings, devices, and policies.
  • mfa.md — MFA flows, challenge/OTP, API, and SMS configuration.
  • policy-engine.md — OPA/Rego integration, policy structure, evaluation flow.