2026-10-09 20:12:44 +02:00
""" Offline tests for credential.py generate (RPF-WP-0054-T03) against a fake ``bao``.
Runs ` ` credential . py generate ` ` as a real subprocess , so the silence contract and
the delegation from credential . py are tested end to end . The fake records which
token each call used : KV calls must use the minted grant token , never the
caller ' s session.
"""
import hashlib
import json
import os
from pathlib import Path
import subprocess
import sys
import tempfile
import textwrap
import unittest
ROOT = Path ( __file__ ) . resolve ( ) . parents [ 1 ]
CLI = ROOT / ' scripts/credential.py '
GRANT_TOKEN = ' hvs.grant- ' + ' G7h8 ' * 6
FAKE_BAO = textwrap . dedent ( f ''' \
#!/usr/bin/env python3
import json , os , sys
sf = os . environ [ ' FAKE_BAO_STATE ' ]
st = json . load ( open ( sf ) )
a = sys . argv [ 1 : ]
tok = os . environ . get ( ' BAO_TOKEN ' , ' caller-session ' )
st [ ' calls ' ] . append ( { { ' args ' : a , ' token ' : tok } } )
def save ( ) : json . dump ( st , open ( sf , ' w ' ) )
def out ( obj ) : save ( ) ; print ( json . dumps ( obj ) ) ; sys . exit ( 0 )
def missing ( ) : save ( ) ; sys . stderr . write ( ' No value found at x \\ n ' ) ; sys . exit ( 2 )
if a [ : 2 ] == [ ' token ' , ' lookup ' ] : out ( { { ' data ' : { { ' policies ' : st [ ' caller_policies ' ] } } } } )
if a [ : 2 ] == [ ' token ' , ' create ' ] :
if st . get ( ' fail_create ' ) : save ( ) ; sys . exit ( 2 )
out ( { { ' auth ' : { { ' client_token ' : ' {GRANT_TOKEN} ' , ' accessor ' : ' acc ' } } } } )
if a [ : 2 ] == [ ' token ' , ' revoke ' ] : st [ ' revoked ' ] = tok ; save ( ) ; sys . exit ( 0 )
if a [ 0 ] == ' read ' :
rest = a [ - 1 ] . split ( ' / ' , 2 ) [ 2 ]
if rest not in st [ ' kv ' ] : missing ( )
n = len ( st [ ' kv ' ] [ rest ] )
out ( { { ' data ' : { { ' current_version ' : n , ' versions ' : { { str ( i + 1 ) : { { } } for i in range ( n ) } } } } } } )
if a [ 0 ] == ' write ' :
rest = a [ - 2 ] . split ( ' / ' , 2 ) [ 2 ]
body = json . loads ( sys . stdin . read ( ) )
st [ ' written ' ] = body
if st . get ( ' race ' ) : st [ ' kv ' ] [ rest ] = [ { { ' x ' : 1 } } ] ; save ( ) ; sys . exit ( 2 )
if st . get ( ' fail_write ' ) : save ( ) ; sys . exit ( 2 )
e = st [ ' kv ' ] . setdefault ( rest , [ ] )
if body [ ' options ' ] [ ' cas ' ] != len ( e ) : save ( ) ; sys . exit ( 2 )
e . append ( body [ ' data ' ] )
out ( { { ' data ' : { { ' version ' : len ( e ) } } } } )
save ( ) ; sys . exit ( 9 )
''' )
2026-10-11 18:46:21 +02:00
# Exact accepted Forgejo @1.1 shape; materialized only in this fake test.
CASE = ' # First KnownSecurityCase of oidc-browser-relying-party 1.1 (NK-WP-0048-T06). \n # Parameters are the request the founder accepted on 2026-10-08 \n # (key-cape docs/forgejo-oidc-client-request.yaml, KEY-WP-0038-T01); that \n # acceptance carries over as this case \' s acceptance. One difference: custody is \n # derived by the type (platform/workloads/keycape/forgejo), not the request \' s \n # proposed platform/workloads/forgejo/netkingdom-oidc-client. The founder \n # acknowledged that derivation with the type (memo r4, 2026-10-09). \n # Re-pinned from 1.0 to 1.1 on 2026-10-10 on the founder \' s go, before any \n # apply, so no 1.0 run overlaps (minor change: consumer-enforces-groups). \n apiVersion: netkingdom.io/security-case/v0.1 \n kind: SecurityCase \n spec: \n id: forgejo-netkingdom-oidc \n type: oidc-browser-relying-party@1.1 \n status: materialized \n acceptance: \n at: " 2026-10-08 " \n record: " key-cape docs/forgejo-oidc-client-request.yaml (status accepted-2026-10-08, KEY-WP-0038-T01); type acknowledged via informed-decision memo nk-wp-0048-t05-oidc-browser-relying-party-r4, hub decision e418263e " \n by: \n - { role: owner, name: " founder (for railiance-apps) " } \n - { role: founder, name: founder} \n owner: \n repo: railiance-apps \n person: founder \n parameters: \n client_id: forgejo \n display_name: Forgejo source forge \n redirect_uris: https://forgejo.coulomb.social/user/oauth2/netkingdom/callback \n allowed_scopes: openid,profile,email,groups \n client_type: confidential \n mfa_required: " true " \n access_groups: forge-admins,forge-coulomb-owners,forge-coulomb-readers \n initial_members: forge-admins=tegwick;forge-coulomb-owners=tegwick \n consuming_service: forgejo \n consumer_owner_repo: railiance-apps \n '
2026-10-09 20:12:44 +02:00
class Generate ( unittest . TestCase ) :
def setUp ( self ) :
self . tmp = Path ( tempfile . mkdtemp ( ) )
( self . tmp / ' bin ' ) . mkdir ( )
( self . tmp / ' bin/bao ' ) . write_text ( FAKE_BAO )
( self . tmp / ' bin/bao ' ) . chmod ( 0o755 )
self . state = { ' calls ' : [ ] , ' kv ' : { } ,
' caller_policies ' : [ ' default ' , ' credential-broker-keycape-client-secret-generate-issuer ' ] }
self . write_case ( CASE )
def write_case ( self , text ) :
self . case = self . tmp / ' case.yaml '
self . case . write_text ( text )
self . ref = ' forgejo-netkingdom-oidc@sha256: ' + hashlib . sha256 ( text . encode ( ) ) . hexdigest ( )
def run_cli ( self , * extra , client_id = ' forgejo ' , ref = None , env_extra = None ) :
( self . tmp / ' state.json ' ) . write_text ( json . dumps ( self . state ) )
receipt = self . tmp / f ' r { len ( list ( self . tmp . glob ( " r*.json " ) ) ) } .json '
env = { k : v for k , v in os . environ . items ( ) if k not in ( ' BAO_TOKEN ' , ' VAULT_TOKEN ' , ' WARDEN_AGENT_ID ' ) }
env . update ( PATH = f ' { self . tmp / " bin " } : { env [ " PATH " ] } ' , FAKE_BAO_STATE = str ( self . tmp / ' state.json ' ) ,
* * ( env_extra or { } ) )
proc = subprocess . run ( [ sys . executable , str ( CLI ) , ' generate ' , ' --grant ' , ' keycape/client-secret-generate ' ,
' --client-id ' , client_id , ' --case-file ' , str ( self . case ) ,
' --case-ref ' , ref or self . ref , ' --receipt ' , str ( receipt ) , * extra ] ,
env = env , capture_output = True , text = True )
self . assertEqual ( ( proc . stdout , proc . stderr ) , ( ' ' , ' ' ) )
self . state = json . loads ( ( self . tmp / ' state.json ' ) . read_text ( ) )
text = receipt . read_text ( )
self . assertNotIn ( GRANT_TOKEN , text )
written = self . state . get ( ' written ' )
if written :
self . assertNotIn ( written [ ' data ' ] [ ' CLIENT_SECRET ' ] , text )
return proc . returncode , json . loads ( text )
def kv_calls ( self ) :
return [ c for c in self . state [ ' calls ' ] if c [ ' args ' ] [ 0 ] in ( ' read ' , ' write ' ) ]
def test_generates_once_with_grant_token_and_revokes_it ( self ) :
code , receipt = self . run_cli ( )
self . assertEqual ( ( code , receipt [ ' outcome ' ] ) , ( 0 , ' generated ' ) )
self . assertEqual ( receipt [ ' kv_version ' ] , 1 )
self . assertTrue ( receipt [ ' grant_token_revoked ' ] )
self . assertEqual ( self . state [ ' revoked ' ] , GRANT_TOKEN )
self . assertTrue ( all ( c [ ' token ' ] == GRANT_TOKEN for c in self . kv_calls ( ) ) )
self . assertFalse ( any ( c [ ' args ' ] [ 0 ] == ' read ' and ' /data/ ' in c [ ' args ' ] [ - 1 ] for c in self . kv_calls ( ) ) )
value = self . state [ ' written ' ] [ ' data ' ] [ ' CLIENT_SECRET ' ]
self . assertEqual ( len ( value ) , 64 )
self . assertEqual ( self . state [ ' written ' ] [ ' options ' ] , { ' cas ' : 0 } )
def test_existing_entry_is_never_touched ( self ) :
self . state [ ' kv ' ] [ ' workloads/keycape/forgejo ' ] = [ { ' CLIENT_SECRET ' : ' old ' } ]
code , receipt = self . run_cli ( )
self . assertEqual ( ( code , receipt [ ' outcome ' ] ) , ( 3 , ' already_present ' ) )
self . assertNotIn ( ' written ' , self . state )
self . assertTrue ( receipt [ ' grant_token_revoked ' ] )
def test_lost_cas_race_reports_already_present ( self ) :
self . state [ ' race ' ] = True
code , receipt = self . run_cli ( )
self . assertEqual ( ( code , receipt [ ' outcome ' ] ) , ( 3 , ' already_present_lost_race ' ) )
def test_dry_run_writes_nothing ( self ) :
code , receipt = self . run_cli ( ' --dry-run ' )
self . assertEqual ( ( code , receipt [ ' outcome ' ] ) , ( 0 , ' dry_run_would_generate ' ) )
self . assertNotIn ( ' written ' , self . state )
def test_case_checks_refuse_before_any_bao_call ( self ) :
cases = [
( ' case_ref_mismatch ' , { ' ref ' : ' forgejo-netkingdom-oidc@sha256: ' + ' 0 ' * 64 } ) ,
( ' case_client_id_mismatch ' , { ' client_id ' : ' gitea ' } ) ,
( ' client_id_invalid_or_reserved ' , { ' client_id ' : ' secrets-engine-openbao ' } ) ,
( ' client_id_invalid_or_reserved ' , { ' client_id ' : ' ../forgejo ' } ) ,
( ' case_ref_malformed ' , { ' ref ' : ' forgejo ' } ) ,
]
for outcome , kwargs in cases :
with self . subTest ( outcome = outcome , * * kwargs ) :
self . state [ ' calls ' ] = [ ]
code , receipt = self . run_cli ( * * kwargs )
self . assertEqual ( ( code , receipt [ ' outcome ' ] ) , ( 2 , outcome ) )
self . assertEqual ( self . state [ ' calls ' ] , [ ] )
def test_unmaterialized_or_public_case_refused ( self ) :
for text , outcome in ( ( CASE . replace ( ' materialized ' , ' accepted ' ) , ' case_not_materialized ' ) ,
( CASE . replace ( ' confidential ' , ' public ' ) , ' case_client_not_confidential ' ) ) :
with self . subTest ( outcome = outcome ) :
self . write_case ( text )
code , receipt = self . run_cli ( )
self . assertEqual ( ( code , receipt [ ' outcome ' ] ) , ( 2 , outcome ) )
2026-10-11 18:46:21 +02:00
def test_only_exact_acknowledged_type_version_is_admitted ( self ) :
for type_ref in ( ' oidc-browser-relying-party@1.0 ' ,
' oidc-browser-relying-party@1.2 ' ,
' oidc-browser-relying-party@99.0 ' ,
' confidential-service-client@1.1 ' ) :
with self . subTest ( type_ref = type_ref ) :
self . write_case ( CASE . replace ( ' oidc-browser-relying-party@1.1 ' , type_ref ) )
self . state [ ' calls ' ] = [ ]
code , receipt = self . run_cli ( )
self . assertEqual ( ( code , receipt [ ' outcome ' ] ) , ( 2 , ' case_type_mismatch ' ) )
self . assertEqual ( self . state [ ' calls ' ] , [ ] )
def test_exact_accepted_case_still_requires_materialization ( self ) :
self . write_case ( CASE . replace ( ' status: materialized ' , ' status: accepted ' ) )
code , receipt = self . run_cli ( )
self . assertEqual ( ( code , receipt [ ' outcome ' ] ) , ( 2 , ' case_not_materialized ' ) )
self . assertEqual ( self . state [ ' calls ' ] , [ ] )
2026-10-09 20:12:44 +02:00
def test_agents_root_and_unbound_callers_refused ( self ) :
code , receipt = self . run_cli ( env_extra = { ' WARDEN_AGENT_ID ' : ' codex ' } )
self . assertEqual ( ( code , receipt [ ' outcome ' ] ) , ( 2 , ' agent_callers_refused ' ) )
self . state [ ' caller_policies ' ] = [ ' root ' ]
code , receipt = self . run_cli ( )
self . assertEqual ( ( code , receipt [ ' outcome ' ] ) , ( 2 , ' root_refused ' ) )
self . state [ ' caller_policies ' ] = [ ' default ' ]
code , receipt = self . run_cli ( )
self . assertEqual ( ( code , receipt [ ' outcome ' ] ) , ( 2 , ' caller_lacks_issuer_policy ' ) )
self . assertFalse ( any ( c [ ' args ' ] [ : 2 ] == [ ' token ' , ' create ' ] for c in self . state [ ' calls ' ] ) )
def test_write_failure_is_reported_and_token_revoked ( self ) :
self . state [ ' fail_write ' ] = True
code , receipt = self . run_cli ( )
self . assertEqual ( ( code , receipt [ ' outcome ' ] ) , ( 4 , ' write_failed ' ) )
self . assertTrue ( receipt [ ' grant_token_revoked ' ] )
if __name__ == ' __main__ ' :
unittest . main ( )