"""Draft-only offline exact-path lane scaffolding; no execution surface.""" from __future__ import annotations from copy import deepcopy from datetime import date import hashlib import json from pathlib import Path import re import sys import tempfile import jsonschema import yaml REPO_DIR = Path(__file__).resolve().parents[1] MAX_INPUT_BYTES = 128 * 1024 SECRET_MARKERS = ('AGE-SECRET-KEY-1', '-----BEGIN', 'hvs.', 'hvb.', 'hvc.', 'ghp_', 'github_pat_', 'npm_', 'BAO_TOKEN=', 'VAULT_TOKEN=', 'OPENBAO_ROOT_TOKEN=') class UniqueLoader(yaml.SafeLoader): pass def unique_mapping(loader, node): result = {} for key_node, value_node in node.value: key = loader.construct_object(key_node) if not isinstance(key, str) or key in result: raise ValueError('Invalid or duplicate metadata key') result[key] = loader.construct_object(value_node) return result UniqueLoader.add_constructor(yaml.resolver.BaseResolver.DEFAULT_MAPPING_TAG, unique_mapping) def read_metadata(path): with Path(path).open('rb') as stream: raw = stream.read(MAX_INPUT_BYTES + 1) if len(raw) > MAX_INPUT_BYTES: raise ValueError('Metadata input exceeds size limit') text = raw.decode('utf-8') if any(marker in text for marker in SECRET_MARKERS) or re.search(r'(? 900: raise ValueError('Prospective Kubernetes token TTL exceeds fifteen minutes') eso = data['eso'] if len(auth['service_account_names']) != 1 or len(auth['service_account_namespaces']) != 1: raise ValueError('One explicit ESO auth identity is supported') keys = [item['secret_key'] for item in eso['data']] properties = [item['property'] for item in eso['data']] if len(set(keys)) != len(keys) or len(set(properties)) != len(properties) or set(properties) != set(bao['fields']): raise ValueError('ESO mappings must cover the explicit field set exactly once') def verify_catalog_join(request, catalog): # Warden validates its own full input; platform checks only the shared seam. if not isinstance(catalog, dict): raise ValueError('Catalog pointer manifest must be an object') for field in ('schema_version', 'lane_id', 'title', 'consumer_repo', 'owner_repo'): if catalog.get(field) != request[field]: raise ValueError('Cross-owner fixture identity mismatch') if catalog.get('risk') != request['risk']['classification']: raise ValueError('Cross-owner risk mismatch') if catalog.get('canon_ref') != 'railiance-platform:credential-change-requests/' + request['ccr_id'] + '.yaml': raise ValueError('Catalog CCR destination pointer mismatch') ref = catalog.get('platform_request_ref') if not isinstance(ref, str) or not re.fullmatch(r'railiance-platform:[a-zA-Z0-9_./-]+', ref) or '..' in ref: raise ValueError('Catalog needs a non-secret owner request pointer') def render_artifacts(data, policy_renderer, auth_renderer, request_digest, catalog_digest=None): validate_request(data) data = deepcopy(data) bao = data['openbao'] auth = bao['auth'] lane = data['lane_id'] policy_file = 'openbao/policies/' + bao['policy_name'] + '.hcl' ccr = {'id': data['ccr_id'], 'kind': 'credential-change-request', 'schema_version': 1, 'request_type': 'workload-kv-read', 'title': data['title'], 'status': 'proposed', 'created': data['date'], 'updated': data['date'], 'requester': data['requester'], 'review': {'required': True, 'required_approvers': data['review_approvers'], 'comments': []}, 'target': data['target'], 'openbao': {'mount': bao['mount'], 'kv_path': bao['kv_path'], 'fields': bao['fields'], 'policy_name': bao['policy_name'], 'policy_file': policy_file, 'metadata_read': False, 'auth': {'method': 'kubernetes', 'mount': auth['mount'], 'role': auth['role'], 'audience': auth['audience'], 'ttl': auth['ttl'], 'token_max_ttl': auth['ttl'], 'token_explicit_max_ttl': auth['ttl'], 'token_no_default_policy': True, 'policies': [bao['policy_name']], 'bound_claims': {'service_account_names': auth['service_account_names'], 'service_account_namespaces': auth['service_account_namespaces']}, 'bound_claims_confirmed': False}}, 'access_frontdoor': {'type': 'external-secrets', 'catalog_id': lane, 'readiness': 'pending-review', 'resolvable': False}, 'risk': data['risk'], 'verification': {'positive': ['Owner-reviewed native exact identity and exact path proof required'], 'negative': ['Owner-reviewed wrong identity, sibling GET and parent LIST denials required'], 'activation_conditions': ['Exact CCR approval, confirmed identity, attended custody and native receipt acceptance required']}, 'lifecycle': {'deactivate': 'Owner-reviewed delivery withdrawal and token revocation; exact rollback receipt required', 'rotate': 'Separate version-guarded owner approval and contained value delivery required', 'compromised': 'Owner incident process; no automatic read, reseed or cleanup'}, 'scaffold': {'draft_only': True, 'consumer_repo': data['consumer_repo'], 'owner_repo': data['owner_repo'], 'request_sha256': request_digest, 'catalog_request_sha256': catalog_digest}} annotation = {'railiance.io/credential-change': data['ccr_id'], 'railiance.io/admission': 'pending-review'} eso = data['eso'] store = {'apiVersion': 'external-secrets.io/v1', 'kind': 'ClusterSecretStore', 'metadata': {'name': eso['store_name'], 'annotations': deepcopy(annotation)}, 'spec': {'provider': {'vault': {'server': eso['server'], 'path': bao['mount'], 'version': 'v2', 'auth': {'kubernetes': {'mountPath': auth['mount'], 'role': auth['role'], 'audiences': [auth['audience']], 'serviceAccountRef': {'name': auth['service_account_names'][0], 'namespace': auth['service_account_namespaces'][0]}}}}}, 'conditions': [{'namespaces': [eso['namespace']]}]}} secret = {'apiVersion': 'external-secrets.io/v1', 'kind': 'ExternalSecret', 'metadata': {'name': eso['external_secret_name'], 'namespace': eso['namespace'], 'annotations': deepcopy(annotation)}, 'spec': {'refreshInterval': eso['refresh_interval'], 'secretStoreRef': {'kind': 'ClusterSecretStore', 'name': eso['store_name']}, 'target': {'name': eso['secret_name'], 'creationPolicy': 'Owner', 'deletionPolicy': 'Retain', 'template': {'metadata': {'annotations': deepcopy(annotation)}}}, 'data': [{'secretKey': item['secret_key'], 'remoteRef': {'key': bao['kv_path'][len(bao['mount']) + 1:], 'property': item['property']}} for item in eso['data']]}} evidence = {'schema_version': 1, 'lane_id': lane, 'ccr_id': data['ccr_id'], 'status': 'not-executed', 'draft_only': True, 'verified': False, 'request_sha256': request_digest, 'required_owner_checks': ['Exact revision/digest and required owner reviews', 'Identity/auth claims confirmed separately; no inferred workload identity', 'Metadata/capability readback through reviewed output-suppressing owner tool', 'No Secret bodies, values or raw subprocess errors in evidence', 'Native scoped positive/negative, cleanup/revocation and rollback receipts after approval']} artifacts = {'credential-change-requests/' + data['ccr_id'] + '.yaml': yaml.safe_dump(ccr, sort_keys=False), policy_file: '# DRAFT ONLY: exact CCR review and confirmed identity required.\n' + policy_renderer(ccr), 'openbao/auth/' + auth['role'] + '.draft.json': json.dumps({'draft_only': True, 'status': 'proposed', 'bound_claims_confirmed': False, 'auth_payload': auth_renderer(ccr)}, indent=2) + '\n', 'manifests/' + lane + '.draft.yaml': '# DRAFT REVIEW ARTIFACT. Do not apply. No custody/admission supplied.\n' + yaml.safe_dump_all([store, secret], sort_keys=False), 'docs/evidence/' + lane + '.checklist.json': json.dumps(evidence, indent=2) + '\n', 'README.md': 'Draft-only review bundle. No admission, approval, confirmed identity, credential value or executed receipt.\n' 'Review the exact request digest and owner destinations before promoting any file.\n' 'ESO target metadata is explicit to contain source last-applied annotations.\n'} return artifacts def scaffold(input_path, output_dir, policy_renderer, auth_renderer, catalog_manifest=None): data, digest = read_metadata(input_path) catalog_digest = None if catalog_manifest is not None: catalog, catalog_digest = read_metadata(catalog_manifest) validate_request(data) verify_catalog_join(data, catalog) artifacts = render_artifacts(data, policy_renderer, auth_renderer, digest, catalog_digest) output = Path(output_dir).absolute() if output.is_symlink() or output.exists() or output.resolve().is_relative_to(REPO_DIR.resolve()): raise ValueError('Output must be a new staging directory outside the owner repository') if any((p / '.git').exists() for p in output.resolve().parents if p != Path(tempfile.gettempdir()).resolve()): raise ValueError('Output staging must not be inside a repository') output.mkdir(mode=0o700, parents=False) for relative, content in artifacts.items(): path = output / relative path.parent.mkdir(parents=True, exist_ok=True) with path.open('x', encoding='utf-8') as stream: stream.write(content) return artifacts def command_scaffold(args, policy_renderer, auth_renderer): try: artifacts = scaffold(args.input, args.output_dir, policy_renderer, auth_renderer, args.catalog_manifest) except (ValueError, OSError, UnicodeError, RecursionError): print('Scaffold refused: invalid metadata, mismatched owner pointers, or unsafe staging output.', file=sys.stderr) return 2 print('Prepared ' + str(len(artifacts)) + ' draft review artifacts; nothing admitted or executed.') return 0