Bind wrapped issuance to native metadata and preserve immutable recipient attempts
Assistant: codex Assistant-Model: gpt-6.1-sol Assistant-Session: 01a126e1-76f8-7643-8cf3-e93336539ade
This commit is contained in:
parent
58129de8f1
commit
0db6adef69
8 changed files with 288 additions and 46 deletions
|
|
@ -65,13 +65,17 @@ def test_wrap_kv_writes_0600_file_and_omits_token_from_result(tmp_path):
|
|||
assert WRAP_ACCESSOR not in result.wrap_handle
|
||||
|
||||
|
||||
def test_wrap_rejects_repo_paths():
|
||||
def test_wrap_rejects_repo_paths(tmp_path):
|
||||
import subprocess
|
||||
repository = tmp_path / "repository"
|
||||
repository.mkdir()
|
||||
subprocess.run(["git", "init", str(repository)], check=True, capture_output=True)
|
||||
entry = validate_entry(copy.deepcopy(VALID))
|
||||
with pytest.raises(ProvisioningError, match="Git worktree"):
|
||||
write_wrapped_handoff(
|
||||
FakeWrapClient(),
|
||||
entry,
|
||||
out_file=__import__("pathlib").Path("wrap.token"),
|
||||
out_file=repository / "wrap.token",
|
||||
ttl="15m",
|
||||
)
|
||||
|
||||
|
|
@ -96,6 +100,7 @@ def test_parse_wrap_accepts_wrap_info_only():
|
|||
"token": WRAP_TOKEN,
|
||||
"accessor": WRAP_ACCESSOR,
|
||||
"ttl": 900,
|
||||
"creation_path": "secret/x",
|
||||
}
|
||||
}
|
||||
)
|
||||
|
|
@ -116,7 +121,7 @@ def test_wrap_kv_get_uses_wrap_ttl_flag(monkeypatch):
|
|||
return SimpleNamespace(
|
||||
returncode=0,
|
||||
stdout=json.dumps(
|
||||
{"wrap_info": {"token": WRAP_TOKEN, "accessor": WRAP_ACCESSOR}}
|
||||
{"wrap_info": {"token": WRAP_TOKEN, "accessor": WRAP_ACCESSOR, "ttl": 900, "creation_path": "secret/data/test/team/thing"}}
|
||||
),
|
||||
stderr="",
|
||||
)
|
||||
|
|
@ -158,3 +163,93 @@ def test_production_wrap_fails_closed(tmp_path, monkeypatch):
|
|||
)
|
||||
with pytest.raises(DecisionError, match="production action 'wrap'"):
|
||||
cli.cmd_wrap(cfg, args)
|
||||
|
||||
@pytest.mark.parametrize('change', [
|
||||
{'creation_path':'secret/data/other-recipient'}, {'creation_path':None},
|
||||
{'ttl':901}, {'ttl':60}, {'ttl':True}, {'ttl':None},
|
||||
{'token':42}, {'token':'value with whitespace'}, {'accessor':None},
|
||||
])
|
||||
def test_native_wrap_binding_cannot_be_inferred_from_request(change):
|
||||
client=OpenBaoClient(addr='http://example.invalid',token='t',bao_bin='bao')
|
||||
value={'token':WRAP_TOKEN,'accessor':WRAP_ACCESSOR,'ttl':900,'creation_path':'secret/data/test/team/thing'}
|
||||
value.update(change)
|
||||
with pytest.raises(BackendError) as raised:
|
||||
client._parse_wrap_response(json.dumps({'wrap_info':value}),ttl='15m',creation_path='secret/data/test/team/thing')
|
||||
assert WRAP_TOKEN not in str(raised.value)
|
||||
|
||||
|
||||
def test_valid_wrap_info_cannot_mask_unwrapped_payload():
|
||||
client=OpenBaoClient(addr='http://example.invalid',token='t',bao_bin='bao')
|
||||
value={'wrap_info':{'token':WRAP_TOKEN,'accessor':WRAP_ACCESSOR,'ttl':900,'creation_path':'secret/x'},'data':{'secret':'NEVER-LOG-ME'}}
|
||||
with pytest.raises(BackendError) as raised:
|
||||
client._parse_wrap_response(json.dumps(value),ttl='15m',creation_path='secret/x')
|
||||
assert 'NEVER-LOG-ME' not in str(raised.value)
|
||||
|
||||
|
||||
def test_existing_output_prevents_mint_and_preserves_original(tmp_path):
|
||||
out=tmp_path/'wrap.token';out.write_text('previous trial');out.chmod(0o600)
|
||||
client=FakeWrapClient()
|
||||
with pytest.raises(ProvisioningError,match='already exists'):
|
||||
write_wrapped_handoff(client,validate_entry(copy.deepcopy(VALID)),out_file=out,ttl='60s')
|
||||
assert client.calls==[] and out.read_text()=='previous trial'
|
||||
|
||||
|
||||
def test_leaf_and_parent_symlink_cannot_redirect_handoff(tmp_path):
|
||||
from pathlib import Path
|
||||
target=tmp_path/'retained';target.write_text('private existing');target.chmod(0o600)
|
||||
out=tmp_path/'wrap.token';out.symlink_to(target)
|
||||
client=FakeWrapClient();entry=validate_entry(copy.deepcopy(VALID))
|
||||
with pytest.raises(ProvisioningError):write_wrapped_handoff(client,entry,out_file=out,ttl='60s')
|
||||
folder=tmp_path/'private';folder.mkdir(mode=0o700)
|
||||
parent=tmp_path/'redirect';parent.symlink_to(folder,target_is_directory=True)
|
||||
with pytest.raises(ProvisioningError):write_wrapped_handoff(client,entry,out_file=parent/'wrap.token',ttl='60s')
|
||||
assert client.calls==[] and target.read_text()=='private existing'
|
||||
|
||||
|
||||
def test_backend_failure_retains_private_reservation_and_prevents_remint(tmp_path):
|
||||
class Failed(FakeWrapClient):
|
||||
def wrap_kv_get(self,*args,**kwargs):
|
||||
self.calls.append('attempt');raise BackendError('unverified native mint')
|
||||
client=Failed();out=tmp_path/'wrap.token';entry=validate_entry(copy.deepcopy(VALID))
|
||||
with pytest.raises(BackendError):write_wrapped_handoff(client,entry,out_file=out,ttl='60s')
|
||||
assert out.exists() and out.read_bytes()==b'' and out.stat().st_mode&0o077==0
|
||||
with pytest.raises(ProvisioningError):write_wrapped_handoff(client,entry,out_file=out,ttl='60s')
|
||||
assert client.calls==['attempt']
|
||||
|
||||
|
||||
def test_shared_output_directory_refuses_before_backend(tmp_path):
|
||||
parent=tmp_path/'shared';parent.mkdir(mode=0o755);client=FakeWrapClient()
|
||||
with pytest.raises(ProvisioningError,match='owned and private'):
|
||||
write_wrapped_handoff(client,validate_entry(copy.deepcopy(VALID)),out_file=parent/'wrap.token',ttl='60s')
|
||||
assert client.calls==[]
|
||||
|
||||
|
||||
def test_concurrent_handoff_reserves_before_only_one_backend_mint(tmp_path):
|
||||
from concurrent.futures import ThreadPoolExecutor
|
||||
import threading
|
||||
entered,release=threading.Event(),threading.Event()
|
||||
class Paused(FakeWrapClient):
|
||||
def wrap_kv_get(self,*args,**kwargs):
|
||||
entered.set();assert release.wait(5)
|
||||
return super().wrap_kv_get(*args,**kwargs)
|
||||
client=Paused();entry=validate_entry(copy.deepcopy(VALID));out=tmp_path/'wrap.token'
|
||||
with ThreadPoolExecutor(max_workers=2) as pool:
|
||||
first=pool.submit(write_wrapped_handoff,client,entry,out_file=out,ttl='60s')
|
||||
assert entered.wait(5)
|
||||
try:
|
||||
with pytest.raises(ProvisioningError):write_wrapped_handoff(client,entry,out_file=out,ttl='60s')
|
||||
finally:release.set()
|
||||
first.result()
|
||||
assert len(client.calls)==1 and out.read_text().strip()==WRAP_TOKEN
|
||||
|
||||
|
||||
def test_output_replacement_during_backend_call_does_not_overwrite_other_recipient(tmp_path):
|
||||
out=tmp_path/'wrap.token';other=tmp_path/'other';other.write_text('retained recipient')
|
||||
class Replaced(FakeWrapClient):
|
||||
def wrap_kv_get(self,*args,**kwargs):
|
||||
out.unlink();out.symlink_to(other)
|
||||
return super().wrap_kv_get(*args,**kwargs)
|
||||
client=Replaced()
|
||||
with pytest.raises(ProvisioningError,match='reservation changed'):
|
||||
write_wrapped_handoff(client,validate_entry(copy.deepcopy(VALID)),out_file=out,ttl='60s')
|
||||
assert other.read_text()=='retained recipient' and len(client.calls)==1
|
||||
|
|
|
|||
24
tests/test_wrapped_issuer_native.py
Normal file
24
tests/test_wrapped_issuer_native.py
Normal file
|
|
@ -0,0 +1,24 @@
|
|||
"""Real wrapping semantics in a disposable local OpenBao, not estate authority."""
|
||||
import json
|
||||
import re
|
||||
|
||||
from secrets_engine.openbao import OpenBaoClient
|
||||
from tests.test_integration_bao import bao_dev, pytestmark # noqa: F401
|
||||
|
||||
|
||||
def test_native_wrap_path_ttl_and_one_time_unwrap(bao_dev):
|
||||
written=bao_dev._run(['kv','put','-format=json','secret/d3-wrap-fixture',
|
||||
'marker=synthetic-wrapped-integration-value'])
|
||||
assert written.returncode==0
|
||||
wrapped=bao_dev.wrap_kv_get('secret','d3-wrap-fixture',ttl='60s')
|
||||
assert wrapped.creation_path=='secret/data/d3-wrap-fixture'
|
||||
assert wrapped.ttl=='60'
|
||||
assert wrapped.wrap_token not in repr(wrapped) and wrapped.wrap_token not in json.dumps(wrapped.evidence())
|
||||
recipient=OpenBaoClient(addr=bao_dev.addr,token=wrapped.wrap_token,bao_bin=bao_dev.bao_bin)
|
||||
first=recipient._run(['unwrap','-format=json'])
|
||||
assert first.returncode==0
|
||||
assert json.loads(first.stdout)['data']['data']['marker']=='synthetic-wrapped-integration-value'
|
||||
replay=recipient._run(['unwrap','-format=json'])
|
||||
assert replay.returncode==2 and not replay.stdout.strip()
|
||||
assert re.search(r'Code:\s*400\b',replay.stderr)
|
||||
assert '* wrapping token is not valid or does not exist' in replay.stderr
|
||||
Loading…
Add table
Add a link
Reference in a new issue