secrets-engine/tests/test_application_time.py

54 lines
3.1 KiB
Python
Raw Normal View History

from datetime import datetime, timezone, timedelta
from types import SimpleNamespace
import pytest
from railiance_clock.core import Window, unix_ns
from secrets_engine.application_time import read_window, validity_bounds, recheck_binding
from secrets_engine.approval_claim import validate_approval_claim
from secrets_engine.authorization import validate_decision_envelope
from secrets_engine.approval_consume import ConsumeBinding
from secrets_engine.errors import DecisionError
from tests.test_approval_claim import _claim, _binding, APPROVAL_ID, claim_binding_digest
from tests.test_decision_replay import pair
def window(lower, upper):
return Window(unix_ns(lower),unix_ns(upper),'test-authority','epoch','test-policy','sample')
def test_claim_uses_interval_not_workstation_time():
claim=_claim();start=datetime(2030,1,1,tzinfo=timezone.utc)
claim['validity']={'not_before':start.isoformat(),'expires_at':(start+timedelta(seconds=30)).isoformat()}
claim['freshness']['not_after']=(start+timedelta(seconds=10)).isoformat()
kw=dict(approval_id=APPROVAL_ID,expected_binding_digest=claim_binding_digest(**_binding()))
validate_approval_claim(claim,time_window=window(start+timedelta(seconds=1),start+timedelta(seconds=2)),**kw)
with pytest.raises(DecisionError):validate_approval_claim(claim,time_window=window(start-timedelta(seconds=1),start+timedelta(seconds=1)),**kw)
with pytest.raises(DecisionError):validate_approval_claim(claim,time_window=window(start+timedelta(seconds=9),start+timedelta(seconds=10)),**kw)
def test_producer_decision_interval_boundary():
request,decision=pair('rotate')
start=datetime.fromisoformat(decision['lifetime']['not_before'].replace('Z','+00:00'))
end=datetime.fromisoformat(decision['lifetime']['expires_at'].replace('Z','+00:00'))
kw=dict(accepted_policy_packages={'secrets-engine.catalog-lane.lifecycle'},accepted_policy_versions={'v2'})
validate_decision_envelope(decision,request,time_window=window(start,start+timedelta(seconds=1)),**kw)
with pytest.raises(DecisionError):validate_decision_envelope(decision,request,time_window=window(start-timedelta(microseconds=1),start+timedelta(seconds=1)),**kw)
with pytest.raises(DecisionError):validate_decision_envelope(decision,request,time_window=window(end-timedelta(seconds=1),end),**kw)
def test_configured_missing_trust_never_falls_back(tmp_path):
with pytest.raises(DecisionError):read_window(SimpleNamespace(clock_trust_file=tmp_path/'missing.json'))
assert read_window(SimpleNamespace(clock_trust_file=None)) is None
def test_no_mixing_shifted_now_and_interval():
now=datetime.now(timezone.utc)
with pytest.raises(DecisionError):validity_bounds(now,window(now,now))
def test_consume_rechecks_expiry(monkeypatch):
now=datetime.now(timezone.utc)
cfg=SimpleNamespace(clock_trust_file='configured')
binding=ConsumeBinding('approval','sha256:'+'a'*64,decision_expires_at=now.isoformat())
monkeypatch.setattr('secrets_engine.application_time.read_window',lambda cfg:window(now,now+timedelta(seconds=1)))
with pytest.raises(DecisionError):recheck_binding(cfg,binding)