Close the second round of review findings: revoke, reinstate, expiry
This commit is contained in:
parent
65cea0d927
commit
db9a450f0c
@ -89,6 +89,58 @@ def enrol(
|
|||||||
return fingerprint
|
return fingerprint
|
||||||
|
|
||||||
|
|
||||||
|
def is_revoked(row, now: Optional[float] = None) -> bool:
|
||||||
|
"""Is this agent row currently revoked?
|
||||||
|
|
||||||
|
A revocation with an end time has lapsed and the agent is back in force.
|
||||||
|
Anything still valid reads as revoked, so a malformed row fails closed.
|
||||||
|
"""
|
||||||
|
if row["revoked_at"] is None:
|
||||||
|
return False
|
||||||
|
until = row["revoked_until"] if "revoked_until" in row.keys() else None
|
||||||
|
if until is None:
|
||||||
|
return True
|
||||||
|
return (now if now is not None else time.time()) < until
|
||||||
|
|
||||||
|
|
||||||
|
def revoke(store: Store, agent: str, until: Optional[int] = None,
|
||||||
|
reason: Optional[str] = None, actor: str = "system") -> int:
|
||||||
|
"""Revoke an agent, optionally until a time.
|
||||||
|
|
||||||
|
`until=None` is permanent. A timestamp suspends the identity until then,
|
||||||
|
which is a real distinction: an incident response that ends should not need
|
||||||
|
a re-enrolment, and a permanent death should not be undone by a clock.
|
||||||
|
|
||||||
|
Revocation is an administrative act, so it is recorded with `actor` rather
|
||||||
|
than pretending an agent revoked itself.
|
||||||
|
"""
|
||||||
|
_validate_agent_id(agent)
|
||||||
|
if store.get_agent(agent) is None:
|
||||||
|
raise errors.UnknownAgent(f"{agent} is not enrolled")
|
||||||
|
killed = store.revoke_agent(agent, until, reason)
|
||||||
|
store.record(
|
||||||
|
"identity.revoked",
|
||||||
|
actor=actor,
|
||||||
|
subject=agent,
|
||||||
|
detail={
|
||||||
|
"until": until,
|
||||||
|
"permanent": until is None,
|
||||||
|
"reason": reason,
|
||||||
|
"sessions_revoked": killed,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
return killed
|
||||||
|
|
||||||
|
|
||||||
|
def reinstate(store: Store, agent: str, actor: str = "system") -> None:
|
||||||
|
"""Lift a revocation. Audited, because it undoes a security decision."""
|
||||||
|
_validate_agent_id(agent)
|
||||||
|
if store.get_agent(agent) is None:
|
||||||
|
raise errors.UnknownAgent(f"{agent} is not enrolled")
|
||||||
|
store.reinstate(agent)
|
||||||
|
store.record("identity.reinstated", actor=actor, subject=agent)
|
||||||
|
|
||||||
|
|
||||||
def challenge(store: Store, agent: str, ttl: int = DEFAULT_NONCE_TTL,
|
def challenge(store: Store, agent: str, ttl: int = DEFAULT_NONCE_TTL,
|
||||||
limit: Optional[int] = None, window: Optional[int] = None) -> dict:
|
limit: Optional[int] = None, window: Optional[int] = None) -> dict:
|
||||||
"""Issue a single-use nonce for an agent to sign."""
|
"""Issue a single-use nonce for an agent to sign."""
|
||||||
@ -98,7 +150,7 @@ def challenge(store: Store, agent: str, ttl: int = DEFAULT_NONCE_TTL,
|
|||||||
row = store.get_agent(agent)
|
row = store.get_agent(agent)
|
||||||
if row is None:
|
if row is None:
|
||||||
raise errors.UnknownAgent(f"{agent} is not enrolled")
|
raise errors.UnknownAgent(f"{agent} is not enrolled")
|
||||||
if row["revoked_at"] is not None:
|
if is_revoked(row):
|
||||||
raise errors.UnknownAgent(f"{agent} is revoked")
|
raise errors.UnknownAgent(f"{agent} is revoked")
|
||||||
nonce = secrets.token_urlsafe(24)
|
nonce = secrets.token_urlsafe(24)
|
||||||
expires = int(time.time()) + ttl
|
expires = int(time.time()) + ttl
|
||||||
@ -156,7 +208,7 @@ def verify(
|
|||||||
agent_row = store.get_agent(agent)
|
agent_row = store.get_agent(agent)
|
||||||
if agent_row is None:
|
if agent_row is None:
|
||||||
raise errors.UnknownAgent(f"{agent} is not enrolled")
|
raise errors.UnknownAgent(f"{agent} is not enrolled")
|
||||||
if agent_row["revoked_at"] is not None:
|
if is_revoked(agent_row):
|
||||||
raise errors.UnknownAgent(f"{agent} is revoked")
|
raise errors.UnknownAgent(f"{agent} is revoked")
|
||||||
|
|
||||||
public = keys.from_public_bytes(agent_row["pubkey"])
|
public = keys.from_public_bytes(agent_row["pubkey"])
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user