security: record KeyCape custody caller reconciliation and native config proof
This commit is contained in:
parent
873c595bbb
commit
bb3e2db07e
4 changed files with 269 additions and 0 deletions
23
docs/changes/CUST-WP-0080/keycape_bootstrap_native_test.go
Normal file
23
docs/changes/CUST-WP-0080/keycape_bootstrap_native_test.go
Normal file
|
|
@ -0,0 +1,23 @@
|
|||
package config
|
||||
|
||||
import "testing"
|
||||
|
||||
func TestCustodyBootstrapNativeLoader(t *testing.T) {
|
||||
cfg, err := Load("../../bootstrap-fixture.json")
|
||||
if err != nil {
|
||||
t.Fatal("bootstrap output failed native config parsing")
|
||||
}
|
||||
if cfg.PrivacyIDEA.AdminToken != "" || cfg.PrivacyIDEA.AdminTokenFile != "/etc/keycape-factor/admin-token" {
|
||||
t.Fatal("credential source mismatch")
|
||||
}
|
||||
if cfg.PrivacyIDEA.Realm != "coulomb" || !cfg.PrivacyIDEA.RequireForAll {
|
||||
t.Fatal("factor policy mismatch")
|
||||
}
|
||||
clients, err := cfg.Registrations()
|
||||
if err != nil || len(clients) != 5 {
|
||||
t.Fatal("bootstrap registrations did not survive parsing")
|
||||
}
|
||||
if cfg.LLDAP.BindPW == "" || cfg.Authelia.ClientSecret == "" {
|
||||
t.Fatal("bootstrap credential fields missing")
|
||||
}
|
||||
}
|
||||
69
docs/changes/CUST-WP-0080/verify_keycape_bootstrap.py
Normal file
69
docs/changes/CUST-WP-0080/verify_keycape_bootstrap.py
Normal file
|
|
@ -0,0 +1,69 @@
|
|||
#!/usr/bin/env python3
|
||||
"""Verify the synthetic bootstrap output with KeyCape's current native loader.
|
||||
|
||||
Only local source, synthetic credentials and disposable files are used. Requires
|
||||
pytest, PyYAML, OpenSSL and Go with dependencies already cached. No kubectl from
|
||||
PATH is executed: the fixture installs its recording stub in a private PATH.
|
||||
"""
|
||||
import argparse
|
||||
import base64
|
||||
import hashlib
|
||||
import importlib.util
|
||||
import json
|
||||
import os
|
||||
from pathlib import Path
|
||||
import shutil
|
||||
import subprocess
|
||||
import tempfile
|
||||
|
||||
|
||||
def digest(path):
|
||||
return hashlib.sha256(path.read_bytes()).hexdigest()
|
||||
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument('--net-kingdom', type=Path, required=True)
|
||||
parser.add_argument('--key-cape', type=Path, required=True)
|
||||
args = parser.parse_args()
|
||||
fixture = args.net_kingdom / 'tests/test_keycape_bootstrap.py'
|
||||
spec = importlib.util.spec_from_file_location('bootstrap_fixture', fixture)
|
||||
module = importlib.util.module_from_spec(spec)
|
||||
spec.loader.exec_module(module)
|
||||
with tempfile.TemporaryDirectory(prefix='keycape-bootstrap-native-') as directory:
|
||||
root = Path(directory)
|
||||
case_root = root / 'fixture'; case_root.mkdir()
|
||||
case = module.setup.__wrapped__(case_root)
|
||||
result = module.invoke(case)
|
||||
if result.returncode:
|
||||
raise RuntimeError('synthetic bootstrap failed')
|
||||
writes = [c for c in module.calls(case) if c['args'][0] == 'create']
|
||||
if len(writes) != 1:
|
||||
raise RuntimeError('unexpected synthetic operation count')
|
||||
manifest = json.loads(writes[0]['stdin'])
|
||||
checkout = root / 'consumer'; checkout.mkdir()
|
||||
shutil.copytree(args.key_cape / 'src', checkout / 'src',
|
||||
ignore=shutil.ignore_patterns('.git', '__pycache__', '.pytest_cache'))
|
||||
# Existing native config tests read only these public example fixtures.
|
||||
(checkout / 'config').mkdir()
|
||||
for name in ('dev-config.yaml', 'service-clients.example.yaml'):
|
||||
shutil.copyfile(args.key_cape / 'config' / name, checkout / 'config' / name)
|
||||
(checkout / 'src/bootstrap-fixture.json').write_bytes(base64.b64decode(manifest['data']['config.yaml']))
|
||||
native_test = Path(__file__).with_name('keycape_bootstrap_native_test.go')
|
||||
shutil.copyfile(native_test, checkout / 'src/internal/config/custody_bootstrap_test.go')
|
||||
tested = subprocess.run(['go', 'test', '-count=1', './internal/config', './internal/adapters/privacyidea'],
|
||||
cwd=checkout / 'src', capture_output=True, timeout=120,
|
||||
env={**os.environ, 'GOPROXY': 'off', 'GOTOOLCHAIN': 'auto'})
|
||||
if tested.returncode:
|
||||
raise RuntimeError('native loader/adapter verification failed')
|
||||
sources = ['src/internal/config/config.go', 'src/internal/adapters/privacyidea/config.go',
|
||||
'src/internal/adapters/privacyidea/adapter.go',
|
||||
'src/internal/adapters/privacyidea/credential_file_test.go']
|
||||
print(json.dumps({'result': 'passed', 'bootstrap_fixture_sha256': digest(fixture),
|
||||
'native_test_sha256': digest(native_test),
|
||||
'consumer_sources': [{'path': p, 'sha256': digest(args.key_cape / p)} for p in sources],
|
||||
'scope': 'Generated synthetic YAML parsed by native loader; local config and adapter suites. No live cluster or provider.'}, indent=2))
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
Loading…
Add table
Add a link
Reference in a new issue