Renew T03 requests using Railiance time and bind native execution
Assistant: codex Assistant-Model: gpt-6-astra Assistant-Session: 01a09cbb-87c6-7900-a145-4ce53ba9f1a6
This commit is contained in:
parent
3bd3a2e87b
commit
5b3041cfb5
7 changed files with 219 additions and 14 deletions
25
docs/credential-lane-designs/t03-renewed-execution.md
Normal file
25
docs/credential-lane-designs/t03-renewed-execution.md
Normal file
|
|
@ -0,0 +1,25 @@
|
|||
# T03 continuation after Railiance Clock deployment
|
||||
|
||||
The original requests expired unconsumed. Replacement request IDs and immutable
|
||||
memo version 2 are recorded in the creation receipt and Secrets Engine workplan.
|
||||
The exact apply/verify/exec action requests and checker pins are unchanged.
|
||||
|
||||
Run the requester and execution worker with
|
||||
`/home/worsch/secrets-engine/.venv/bin/python`; this environment includes Clock,
|
||||
JSON Schema, JWT, and YAML dependencies. The requester requires
|
||||
`--clock-trust-file` and validates full token validity against that interval.
|
||||
The native execution worker requires `SECRETS_ENGINE_CLOCK_TRUST_FILE`.
|
||||
Refresh the boot-bound admission via the independently verified Clock public key
|
||||
and SSH owner epoch readback immediately before attended execution. An expired
|
||||
trust file fails closed; never extend it or use workstation wall-time fallback.
|
||||
|
||||
Use `http://127.0.0.1:18200` for the admitted OpenBao relay and the existing
|
||||
`operator-browser` PATH helper on WSL when no browser launcher is installed.
|
||||
The outer lane remains secrets-engine-approval-client-login; its reviewed
|
||||
`t03-attended-delivery.py` invokes the separate contained platform-admin lane.
|
||||
Both retain their own self-revocation and private runtime cleanup.
|
||||
|
||||
Human review: https://decisions.coulomb.social/review?memo_id=SECRETS-WP-0010-T03-apply
|
||||
(and identifiers ending -verify and -exec). Version 2 is required. The requester
|
||||
has no approval or consume scope; no human entries are copied from version 1.
|
||||
No execution may begin before the new approvals pass native claim/PDP checks.
|
||||
147
docs/evidence/2026-09-15-t03-renewed-approval-requests.json
Normal file
147
docs/evidence/2026-09-15-t03-renewed-approval-requests.json
Normal file
|
|
@ -0,0 +1,147 @@
|
|||
{
|
||||
"observed_at": "2026-09-15T22:28:23.009075+00:00",
|
||||
"status": "created",
|
||||
"phase": "three_unapproved_requests_created",
|
||||
"requests": [
|
||||
{
|
||||
"memo_id": "SECRETS-WP-0010-T03-apply",
|
||||
"memo_version": 2,
|
||||
"action": "apply",
|
||||
"approval": {
|
||||
"binding": {
|
||||
"action": "apply",
|
||||
"actor": "secrets-engine",
|
||||
"digest": "sha256:03cc5b37437f14e86b686ce4054f976556334eff3fa260b03e8014ed3d9217e5",
|
||||
"human_control": true,
|
||||
"pdp_digest": "sha256:933427642c58c54f65dd4781b8e4c8e2f358eb96497ae35870fff4bd593d2b84",
|
||||
"pdp_path": true,
|
||||
"principal": "secrets-engine",
|
||||
"purpose": "IR-WP-0004 read-only OpenRouter key authentication check; no inference",
|
||||
"target": {
|
||||
"attributes": {
|
||||
"auth_targets": [
|
||||
"se-prod-openrouter-llm-connect"
|
||||
],
|
||||
"fields": [],
|
||||
"policy_targets": [
|
||||
"se-prod-openrouter-llm-connect"
|
||||
],
|
||||
"stage": "prod"
|
||||
},
|
||||
"id": "catalog:openrouter-llm-connect",
|
||||
"system": "secrets-engine",
|
||||
"type": "secret-catalog-lane"
|
||||
}
|
||||
},
|
||||
"created_at": "2026-09-15T22:28:26+00:00",
|
||||
"entries": [],
|
||||
"id": "9935335c-8e9a-566e-a48e-6a5b5f4882eb",
|
||||
"required_count": 1,
|
||||
"status": "requested",
|
||||
"superseded_by": null,
|
||||
"updated_at": "2026-09-15T22:28:26+00:00",
|
||||
"validity": {
|
||||
"expires_at": "2026-09-16T22:28:26.260445+00:00",
|
||||
"not_before": "2026-09-15T22:28:26.260445+00:00"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"memo_id": "SECRETS-WP-0010-T03-verify",
|
||||
"memo_version": 2,
|
||||
"action": "verify",
|
||||
"approval": {
|
||||
"binding": {
|
||||
"action": "verify",
|
||||
"actor": "secrets-engine",
|
||||
"digest": "sha256:72d9267d038c17107c880ffc9009793ce9c70defbddfe2219628854b811a04b2",
|
||||
"human_control": true,
|
||||
"pdp_digest": "sha256:9e36cdbf3890cc5a7e550fd57191f9c0f2a6b2180909aac302aa3b999e7e6b25",
|
||||
"pdp_path": true,
|
||||
"principal": "secrets-engine",
|
||||
"purpose": "IR-WP-0004 read-only OpenRouter key authentication check; no inference",
|
||||
"target": {
|
||||
"attributes": {
|
||||
"auth_targets": [
|
||||
"se-prod-openrouter-llm-connect"
|
||||
],
|
||||
"fields": [
|
||||
"OPENROUTER_API_KEY"
|
||||
],
|
||||
"policy_targets": [
|
||||
"se-prod-openrouter-llm-connect"
|
||||
],
|
||||
"stage": "prod"
|
||||
},
|
||||
"id": "catalog:openrouter-llm-connect",
|
||||
"system": "secrets-engine",
|
||||
"type": "secret-catalog-lane"
|
||||
}
|
||||
},
|
||||
"created_at": "2026-09-15T22:28:26+00:00",
|
||||
"entries": [],
|
||||
"id": "273d6882-6253-5dc9-ac54-544f92ef5e56",
|
||||
"required_count": 1,
|
||||
"status": "requested",
|
||||
"superseded_by": null,
|
||||
"updated_at": "2026-09-15T22:28:26+00:00",
|
||||
"validity": {
|
||||
"expires_at": "2026-09-16T22:28:26.464110+00:00",
|
||||
"not_before": "2026-09-15T22:28:26.464110+00:00"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"memo_id": "SECRETS-WP-0010-T03-exec",
|
||||
"memo_version": 2,
|
||||
"action": "exec",
|
||||
"approval": {
|
||||
"binding": {
|
||||
"action": "exec",
|
||||
"actor": "secrets-engine",
|
||||
"digest": "sha256:f2cf0fb53b740900756ccde5587c3578fcff44beef2f1edab17b9a198a4033d8",
|
||||
"human_control": true,
|
||||
"pdp_digest": "sha256:9f17812750fc8962b0fc58fc09df136850c9935a864fafa998a1c030dfb75bd9",
|
||||
"pdp_path": true,
|
||||
"principal": "secrets-engine",
|
||||
"purpose": "IR-WP-0004 read-only OpenRouter key authentication check; no inference",
|
||||
"target": {
|
||||
"attributes": {
|
||||
"auth_targets": [
|
||||
"se-prod-openrouter-llm-connect"
|
||||
],
|
||||
"fields": [
|
||||
"OPENROUTER_API_KEY"
|
||||
],
|
||||
"policy_targets": [
|
||||
"se-prod-openrouter-llm-connect"
|
||||
],
|
||||
"stage": "prod"
|
||||
},
|
||||
"id": "catalog:openrouter-llm-connect",
|
||||
"system": "secrets-engine",
|
||||
"type": "secret-catalog-lane"
|
||||
}
|
||||
},
|
||||
"created_at": "2026-09-15T22:28:26+00:00",
|
||||
"entries": [],
|
||||
"id": "7ba0c13b-68cd-5b3e-9481-42ba9e385e68",
|
||||
"required_count": 1,
|
||||
"status": "requested",
|
||||
"superseded_by": null,
|
||||
"updated_at": "2026-09-15T22:28:26+00:00",
|
||||
"validity": {
|
||||
"expires_at": "2026-09-16T22:28:26.786252+00:00",
|
||||
"not_before": "2026-09-15T22:28:26.786252+00:00"
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
"credential_values_emitted": false,
|
||||
"signature_verified": true,
|
||||
"excess_scopes_refused": true,
|
||||
"wrong_secret_refused": true,
|
||||
"reader_scope_verified": true,
|
||||
"human_entries_created": false,
|
||||
"approvals_consumed": false
|
||||
}
|
||||
|
|
@ -1,5 +1,5 @@
|
|||
"""Silent, single-attempt creation of the three operator-reviewed T03 requests."""
|
||||
import base64,json,os,stat,subprocess,time
|
||||
import argparse,base64,json,os,stat,subprocess,time,sys
|
||||
from pathlib import Path
|
||||
from datetime import datetime,timezone,timedelta
|
||||
from urllib.request import Request,build_opener,ProxyHandler,HTTPRedirectHandler
|
||||
|
|
@ -7,7 +7,7 @@ from urllib.parse import urlencode
|
|||
from urllib.error import HTTPError
|
||||
import jwt
|
||||
ROOT=Path('/home/worsch/railiance-platform')
|
||||
RECEIPT=ROOT/'docs/evidence/2026-09-14-t03-native-approval-requests.json'
|
||||
RECEIPT=ROOT/'docs/evidence/2026-09-15-t03-renewed-approval-requests.json'
|
||||
POLICY='workload-kv-read-secrets-engine-requester-client'
|
||||
KV='platform/data/workloads/secrets-engine/approval-requester'
|
||||
ISSUER='https://kc.coulomb.social'
|
||||
|
|
@ -55,7 +55,7 @@ def main(receipt):
|
|||
if bao('token','capabilities','-format=json',path)!=expected:raise ValueError('reader_scope_failed')
|
||||
helper=Path.home()/'.vault-token';info=helper.lstat()
|
||||
if not stat.S_ISREG(info.st_mode) or stat.S_IMODE(info.st_mode)!=0o600 or info.st_uid!=os.getuid():raise ValueError('private_helper_required')
|
||||
status,data=http('https://bao.coulomb.social/v1/'+KV+'?version=1',headers={'X-Vault-Token':helper.read_text().strip()})
|
||||
status,data=http('http://127.0.0.1:18200/v1/'+KV+'?version=1',headers={'X-Vault-Token':helper.read_text().strip()})
|
||||
if status!=200 or data['data']['metadata']['version']!=1:raise ValueError('requester_delivery_failed')
|
||||
secret=data['data']['data']['CLIENT_SECRET'];del data
|
||||
def exchange(scope,credential=secret):
|
||||
|
|
@ -68,7 +68,9 @@ def main(receipt):
|
|||
header=jwt.get_unverified_header(token)
|
||||
keys=[key for key in jwks['keys'] if key['kid']==header.get('kid')]
|
||||
if header.get('alg')!='RS256' or len(keys)!=1:raise ValueError('signing_key_failed')
|
||||
claims=jwt.decode(token,jwt.PyJWK.from_dict(keys[0]).key,algorithms=['RS256'],issuer=ISSUER,audience='approval-engine',options={'strict_aud':True,'require':['sub','iat','exp','iss','aud']})
|
||||
claims=jwt.decode(token,jwt.PyJWK.from_dict(keys[0]).key,algorithms=['RS256'],issuer=ISSUER,audience='approval-engine',options={'strict_aud':True,'require':['sub','iat','exp','iss','aud'],'verify_iat':False,'verify_exp':False,'verify_nbf':False})
|
||||
from t03_request_time import validate_token_time
|
||||
validate_token_time(claims, CLOCK.read())
|
||||
expected={'sub':'secrets-engine','tenant':'tenant:platform','principal_type':'service','scope':'approval:create','roles':['secrets-engine-requester'],'groups':[]}
|
||||
if any(claims.get(k)!=v for k,v in expected.items()) or claims['exp']-claims['iat']!=900:raise ValueError('requester_claims_failed')
|
||||
for scope in ('approval:approve','approval:consume','approval:read'):
|
||||
|
|
@ -76,16 +78,20 @@ def main(receipt):
|
|||
if exchange('approval:create','invalid-synthetic-credential')[0]!=401:raise ValueError('wrong_secret_not_refused')
|
||||
receipt.update(phase='requester_verified',signature_verified=True,excess_scopes_refused=True,wrong_secret_refused=True,reader_scope_verified=True)
|
||||
for memo,record,pdp_digest in prepared:
|
||||
now=datetime.now(timezone.utc)
|
||||
now=datetime.fromtimestamp(CLOCK.read().lower_ns/1e9,timezone.utc)
|
||||
body={'id':record['approval_id'],'binding':record['binding'],'validity':{'not_before':now.isoformat(),'expires_at':(now+timedelta(hours=24)).isoformat()},'required_count':1,'human_control':True,'pdp_path':True,'pdp_digest':pdp_digest}
|
||||
receipt['phase']='create_attempt:'+record['action'];RECEIPT.write_text(json.dumps(receipt,indent=2)+'\n')
|
||||
status,result=http('http://127.0.0.1:18281/v1/approvals',body=json.dumps(body).encode(),headers={'Authorization':'Bearer '+token,'Content-Type':'application/json'})
|
||||
if status!=201 or result['status']!='requested' or result['entries'] or result['binding']['digest']!=record['binding_digest'] or result['binding']['human_control'] is not True or result['binding']['pdp_digest']!=pdp_digest:raise ValueError('request_creation_requires_reconciliation')
|
||||
receipt['requests'].append({'memo_id':memo.removeprefix('memo:'),'action':record['action'],'approval':result})
|
||||
receipt['requests'].append({'memo_id':memo.removeprefix('memo:'),'memo_version':record['memo_version'],'action':record['action'],'approval':result})
|
||||
RECEIPT.write_text(json.dumps(receipt,indent=2)+'\n')
|
||||
receipt.update(status='created',phase='three_unapproved_requests_created',human_entries_created=False,approvals_consumed=False)
|
||||
|
||||
if __name__=='__main__':
|
||||
p=argparse.ArgumentParser();p.add_argument('--clock-trust-file',type=Path,required=True);a=p.parse_args()
|
||||
sys.path.insert(0,'/home/worsch/railiance-clock/src')
|
||||
from railiance_clock.client import Clock,FileTrust
|
||||
CLOCK=Clock(FileTrust(a.clock_trust_file));CLOCK.read()
|
||||
if RECEIPT.exists():raise SystemExit(1)
|
||||
receipt={'observed_at':datetime.now(timezone.utc).isoformat(),'status':'failed','phase':'preflight','requests':[],'credential_values_emitted':False}
|
||||
try:main(receipt)
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ from datetime import datetime,timezone
|
|||
from urllib.request import Request,build_opener,ProxyHandler,HTTPRedirectHandler
|
||||
ROOT=Path('/home/worsch/railiance-platform')
|
||||
spec=importlib.util.spec_from_file_location('preflight',ROOT/'scripts/approval-client-reader-preflight.py');pre=importlib.util.module_from_spec(spec);spec.loader.exec_module(pre)
|
||||
RECEIPT=ROOT/'docs/evidence/2026-09-14-t03-attended-delivery.json'
|
||||
RECEIPT=ROOT/'docs/evidence/2026-09-15-t03-attended-delivery.json'
|
||||
class NoRedirect(HTTPRedirectHandler):
|
||||
def redirect_request(self,*args,**kwargs):return None
|
||||
|
||||
|
|
@ -15,7 +15,7 @@ def private(path,directory=False):
|
|||
st=path.lstat();require(st.st_uid==os.getuid() and stat.S_IMODE(st.st_mode)==(0o700 if directory else 0o600) and (stat.S_ISDIR(st.st_mode) if directory else stat.S_ISREG(st.st_mode)),'private_path_required')
|
||||
def main(receipt):
|
||||
require(Path.home().parent.name=='.warden-attended-login' and not os.getenv('BAO_TOKEN') and not os.getenv('VAULT_TOKEN'),'attended_reader_required')
|
||||
require(not Path('/home/worsch/secrets-engine/docs/evidence/2026-09-14-t03-native-execution.json').exists(),'execution_receipt_requires_reconciliation')
|
||||
require(not Path('/home/worsch/secrets-engine/docs/evidence/2026-09-15-t03-native-execution.json').exists(),'execution_receipt_requires_reconciliation')
|
||||
identity=pre.bao('token','lookup','-format=json')['data'];pre.validate_identity(identity)
|
||||
for path,expected in pre.EXPECTED.items():require(sorted(pre.bao('token','capabilities','-format=json',path))==expected,'reader_scope_failed')
|
||||
runtime=Path('/run/user')/str(os.getuid());private(runtime,True)
|
||||
|
|
@ -24,7 +24,7 @@ def main(receipt):
|
|||
helper=Path.home()/'.vault-token';private(helper)
|
||||
with tempfile.TemporaryDirectory(prefix='t03-native-',dir=runtime) as name:
|
||||
directory=Path(name);private(directory,True)
|
||||
req=Request('https://bao.coulomb.social/v1/platform/data/workloads/secrets-engine/approval-client?version=1',headers={'X-Vault-Token':helper.read_text().strip()})
|
||||
req=Request('http://127.0.0.1:18200/v1/platform/data/workloads/secrets-engine/approval-client?version=1',headers={'X-Vault-Token':helper.read_text().strip()})
|
||||
with build_opener(ProxyHandler({}),NoRedirect()).open(req,timeout=20) as response:
|
||||
data=response.read(65537);require(len(data)<=65536,'response_bound_exceeded')
|
||||
data=json.loads(data);require(data['data']['metadata']['version']==1,'custody_version_mismatch')
|
||||
|
|
@ -34,7 +34,7 @@ def main(receipt):
|
|||
del value,data,req
|
||||
receipt.update(phase='scoped_reader_delivered',reader_effective_policy_verified=True,existing_kv_version=1)
|
||||
RECEIPT.write_text(json.dumps(receipt,indent=2)+'\n')
|
||||
p=subprocess.run(['python3',str(ROOT/'scripts/openbao-attended-exec.py'),'--','python3','-B',str(ROOT/'scripts/t03-native-execution.py'),'admin','--directory',str(directory),'--negative-token-file',str(helper)],capture_output=True,timeout=780)
|
||||
p=subprocess.run(['python3',str(ROOT/'scripts/openbao-attended-exec.py'),'--','/home/worsch/secrets-engine/.venv/bin/python','-B',str(ROOT/'scripts/t03-native-execution.py'),'admin','--directory',str(directory),'--negative-token-file',str(helper)],capture_output=True,timeout=780)
|
||||
# Warden's output remains inside this envelope, never forwarded or stored.
|
||||
require(p.returncode==0,'attended_admin_failed')
|
||||
receipt.update(status='passed',phase='native_actions_completed',admin_warden_exited_0=True)
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ from dataclasses import replace
|
|||
import yaml
|
||||
ROOT=Path('/home/worsch/secrets-engine')
|
||||
sys.path.insert(0,str(ROOT/'src'))
|
||||
sys.path.insert(0,'/home/worsch/railiance-clock/src')
|
||||
from secrets_engine.config import Config
|
||||
from secrets_engine.catalog import get_entry
|
||||
from secrets_engine.authorization import build_action_request,digest_material
|
||||
|
|
@ -15,10 +16,10 @@ from secrets_engine.decisions import resolve_decision,require_approved
|
|||
from secrets_engine.openbao import OpenBaoClient
|
||||
from secrets_engine.exec_owner import validate_delivery_target
|
||||
from secrets_engine.plan import build_plan
|
||||
RECEIPT=ROOT/'docs/evidence/2026-09-14-t03-native-execution.json'
|
||||
RECEIPT=ROOT/'docs/evidence/2026-09-15-t03-native-execution.json'
|
||||
KUBE=['kubectl','--kubeconfig','/home/worsch/.kube/config-railiance01']
|
||||
ROLE='se-prod-openrouter-llm-connect'; LANE='openrouter-llm-connect'
|
||||
IDS={'apply':'09592588-ab15-53e7-89b8-c4e9f29aaacf','verify':'9416fa31-fa9e-5603-8289-f35bc9625409','exec':'da678b61-35be-598e-8d95-a7aefa2fdc73'}
|
||||
IDS={'apply':'9935335c-8e9a-566e-a48e-6a5b5f4882eb','verify':'273d6882-6253-5dc9-ac54-544f92ef5e56','exec':'7ba0c13b-68cd-5b3e-9481-42ba9e385e68'}
|
||||
|
||||
def require(value,code):
|
||||
if not value:raise ValueError(code)
|
||||
|
|
@ -34,7 +35,7 @@ def prepare(directory):
|
|||
for action,approval in IDS.items():
|
||||
folder=directory/action;folder.mkdir();doc=copy.deepcopy(source);doc['approval']['authorization_id']=approval
|
||||
(folder/(LANE+'.yaml')).write_text(yaml.safe_dump(doc,sort_keys=False))
|
||||
cfg=replace(Config.load(),catalog_dir=folder,bao_addr='https://bao.coulomb.social',approval_url='http://127.0.0.1:18281',approval_token_file=None,approval_client_secret_file=directory/'client-secret',keycape_token_url='https://kc.coulomb.social/token',keycape_issuer='https://kc.coulomb.social',keycape_client_secret_file=None,openbao_jwt_login_file=None,authorization_subject_id='secrets-engine',authorization_subject_type='service',authorization_policy_package='secrets-engine.catalog-lane.lifecycle',authorization_policy_version='v2',authorization_min_approvals=1,pdp_url='http://127.0.0.1:18282',pdp_token_file=directory/'pdp-caller')
|
||||
cfg=replace(Config.load(),catalog_dir=folder,bao_addr='http://127.0.0.1:18200',approval_url='http://127.0.0.1:18281',approval_token_file=None,approval_client_secret_file=directory/'client-secret',keycape_token_url='https://kc.coulomb.social/token',keycape_issuer='https://kc.coulomb.social',keycape_client_secret_file=None,openbao_jwt_login_file=None,authorization_subject_id='secrets-engine',authorization_subject_type='service',authorization_policy_package='secrets-engine.catalog-lane.lifecycle',authorization_policy_version='v2',authorization_min_approvals=1,pdp_url='http://127.0.0.1:18282',pdp_token_file=directory/'pdp-caller')
|
||||
entry=get_entry(folder,LANE);fields=() if action=='apply' else tuple(entry.fields)
|
||||
actual=build_action_request(entry,action,subject_id='secrets-engine',subject_type='service',purpose=entry.approval['purpose'],fields=fields,policy_targets=(entry.policy_name,),auth_targets=(entry.role_name,))
|
||||
expected=json.loads((ROOT/f'docs/evidence/2026-09-14-openrouter-final-{action}-request.json').read_text())
|
||||
|
|
@ -73,6 +74,9 @@ def admin(directory,negative):
|
|||
require('platform-admin' in policies and 'root' not in policies and identity.get('entity_id') and 0<identity['ttl']<=3600,'attended_operator_required')
|
||||
require(bao('read','-format=json','sys/auth')['data'].get('approle/') is not None,'existing_approle_mount_required')
|
||||
configs,entries,command=prepare(directory)
|
||||
from secrets_engine.application_time import read_window
|
||||
require(all(cfg.clock_trust_file for cfg in configs.values()),'admitted_railiance_clock_required')
|
||||
for cfg in configs.values():read_window(cfg)
|
||||
receipt['health_before']=health()
|
||||
for ns,label,image,port in [('approval-engine','approval-engine','251941a5cb2724b57cc32cff6b693b1ab0be695bee4f56f02d51961189c0fa49',18281),('flex-auth','flex-auth-secrets-engine','05a03a8790c2210c48ea92391441c77ddf640d0cd32f5ec09838f5393171fcbd',18282)]:
|
||||
pods=json.loads(run(*KUBE,'-n',ns,'get','pods','-l','app.kubernetes.io/name='+label,'-o','json'))['items']
|
||||
|
|
@ -93,7 +97,7 @@ def admin(directory,negative):
|
|||
auth=authorize_action(configs[action],entries[action],action,fields=() if action=='apply' else tuple(entries[action].fields),policy_targets=(ROLE,),auth_targets=(ROLE,))
|
||||
require(auth is not None,'native_authorization_missing')
|
||||
receipt['phase']='all_claims_and_pdp_checks_passed';save(receipt)
|
||||
client=OpenBaoClient.resolve('https://bao.coulomb.social')
|
||||
client=OpenBaoClient.resolve('http://127.0.0.1:18200')
|
||||
existing=client.read_policy(ROLE)
|
||||
require(existing is None and not client.approle_exists(ROLE),'existing_native_objects_require_reconciliation')
|
||||
for action in IDS:
|
||||
|
|
|
|||
11
scripts/t03_request_time.py
Normal file
11
scripts/t03_request_time.py
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
"""Exact requester token validity against an admitted Railiance interval."""
|
||||
def validate_token_time(claims, window):
|
||||
for field in ("iat", "exp"):
|
||||
if type(claims.get(field)) is not int:
|
||||
raise ValueError("invalid_token_time")
|
||||
if "nbf" in claims and type(claims["nbf"]) is not int:
|
||||
raise ValueError("invalid_token_time")
|
||||
if (claims["exp"] <= claims["iat"] or claims["iat"] * 1000000000 > window.lower_ns
|
||||
or claims.get("nbf", claims["iat"]) * 1000000000 > window.lower_ns
|
||||
or claims["exp"] * 1000000000 <= window.upper_ns):
|
||||
raise ValueError("token_does_not_contain_railiance_interval")
|
||||
12
tests/test_t03_request_time.py
Normal file
12
tests/test_t03_request_time.py
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
from pathlib import Path
|
||||
from types import SimpleNamespace
|
||||
import importlib.util
|
||||
import unittest
|
||||
p=Path(__file__).resolve().parents[1]/'scripts/t03_request_time.py'
|
||||
s=importlib.util.spec_from_file_location('request_time',p);m=importlib.util.module_from_spec(s);s.loader.exec_module(m)
|
||||
class TokenTimeTests(unittest.TestCase):
|
||||
def test_full_interval(self):m.validate_token_time({'iat':10,'exp':13},SimpleNamespace(lower_ns=11000000000,upper_ns=12000000000))
|
||||
def test_boundaries(self):
|
||||
for claims in [{'iat':12,'exp':20},{'iat':10,'exp':12},{'iat':10,'exp':13,'nbf':12},{'iat':True,'exp':13}]:
|
||||
with self.subTest(claims=claims),self.assertRaises(ValueError):m.validate_token_time(claims,SimpleNamespace(lower_ns=11000000000,upper_ns=12000000000))
|
||||
if __name__=='__main__':unittest.main()
|
||||
Loading…
Add table
Add a link
Reference in a new issue