199 lines
11 KiB
Diff
199 lines
11 KiB
Diff
|
|
--- a/sso-mfa/k8s/keycape/keycape-client.py
|
||
|
|
+++ b/sso-mfa/k8s/keycape/keycape-client.py
|
||
|
|
@@ -200,10 +200,10 @@
|
||
|
|
|
||
|
|
def check_config(binary, config_text, env_names, policy_store):
|
||
|
|
"""KeyCape's startup checks on one registry: names and messages only, no values."""
|
||
|
|
- command = [binary, 'check-config', '--config', '/dev/stdin', '--env-names', ','.join(env_names)]
|
||
|
|
+ command = [str(Path(binary).resolve()), 'check-config', '--config', '/dev/stdin', '--env-names', ','.join(env_names)]
|
||
|
|
if policy_store:
|
||
|
|
command.append('--policy-store')
|
||
|
|
- result = subprocess.run(command, input=config_text.encode(), capture_output=True, timeout=60)
|
||
|
|
+ result = subprocess.run(command, input=config_text.encode(), capture_output=True, timeout=60, env={})
|
||
|
|
report = json.loads(result.stdout)
|
||
|
|
require(isinstance(report, dict) and isinstance(report.get('valid'), bool)
|
||
|
|
and report['valid'] == (result.returncode == 0), 'check_config_output_shape')
|
||
|
|
--- /dev/null
|
||
|
|
+++ b/sso-mfa/k8s/keycape/test_keycape_checker_environment.py
|
||
|
|
@@ -0,0 +1,66 @@
|
||
|
|
+"""Opt-in real-checker boundary regressions; no production inputs or checker stub.
|
||
|
|
+
|
||
|
|
+Set KEYCAPE_TEST_CHECKER_BINARY and KEYCAPE_TEST_CHECKER_SHA256 to an independently
|
||
|
|
+built artifact. Ordinary helper-only runs skip this class when none is supplied.
|
||
|
|
+"""
|
||
|
|
+from contextlib import chdir
|
||
|
|
+import hashlib
|
||
|
|
+import os
|
||
|
|
+from pathlib import Path
|
||
|
|
+import tempfile
|
||
|
|
+import unittest
|
||
|
|
+from unittest.mock import patch
|
||
|
|
+
|
||
|
|
+from test_keycape_client import kc
|
||
|
|
+
|
||
|
|
+VALID = '''issuer: https://issuer.example.invalid
|
||
|
|
+port: 8080
|
||
|
|
+privateKeyPem: /nonexistent/synthetic-key.pem
|
||
|
|
+clients:
|
||
|
|
+ - clientId: synthetic-client
|
||
|
|
+ displayName: Synthetic client
|
||
|
|
+ redirectUris: [https://client.example.invalid/cb]
|
||
|
|
+ allowedScopes: [openid]
|
||
|
|
+ grantTypes: [authorization_code]
|
||
|
|
+ clientType: public
|
||
|
|
+'''
|
||
|
|
+
|
||
|
|
+
|
||
|
|
+class RealCheckerEnvironmentTests(unittest.TestCase):
|
||
|
|
+ @classmethod
|
||
|
|
+ def setUpClass(cls):
|
||
|
|
+ source = os.environ.get('KEYCAPE_TEST_CHECKER_BINARY')
|
||
|
|
+ if not source:
|
||
|
|
+ raise unittest.SkipTest('exact real checker artifact not supplied')
|
||
|
|
+ data = Path(source).read_bytes()
|
||
|
|
+ expected = os.environ.get('KEYCAPE_TEST_CHECKER_SHA256')
|
||
|
|
+ if expected != 'sha256:' + hashlib.sha256(data).hexdigest():
|
||
|
|
+ raise ValueError('real checker artifact digest differs')
|
||
|
|
+ cls.directory = tempfile.TemporaryDirectory()
|
||
|
|
+ cls.addClassCleanup(cls.directory.cleanup)
|
||
|
|
+ cls.binary = Path(cls.directory.name) / 'keycape'
|
||
|
|
+ cls.binary.write_bytes(data)
|
||
|
|
+ cls.binary.chmod(0o700)
|
||
|
|
+
|
||
|
|
+ def check(self, raw, binary=None):
|
||
|
|
+ return kc.check_config(str(binary or self.binary), raw, [], False)
|
||
|
|
+
|
||
|
|
+ def test_ambient_overrides_cannot_reject_supplied_valid_config(self):
|
||
|
|
+ with patch.dict(os.environ, {'KEYCAPE_ACCOUNT_PORTAL_URL': 'http://polluted.example.invalid',
|
||
|
|
+ 'KEYCAPE_BROWSER_LOGOUT_URL': 'http://polluted.example.invalid/logout'}):
|
||
|
|
+ self.assertTrue(self.check(VALID)['valid'])
|
||
|
|
+
|
||
|
|
+ def test_ambient_overrides_cannot_admit_supplied_invalid_config(self):
|
||
|
|
+ invalid = VALID + 'accountPortalURL: http://invalid.example.invalid\nbrowserLogoutURL: http://invalid.example.invalid/logout\n'
|
||
|
|
+ with patch.dict(os.environ, {'KEYCAPE_ACCOUNT_PORTAL_URL': 'https://polluted.example.invalid',
|
||
|
|
+ 'KEYCAPE_BROWSER_LOGOUT_URL': 'https://polluted.example.invalid/logout'}):
|
||
|
|
+ self.assertFalse(self.check(invalid)['valid'])
|
||
|
|
+
|
||
|
|
+ def test_relative_selected_artifact_is_not_replaced_by_path_decoy(self):
|
||
|
|
+ decoy = Path(self.directory.name) / 'decoy'
|
||
|
|
+ decoy.mkdir()
|
||
|
|
+ trap = decoy / 'keycape'
|
||
|
|
+ trap.write_text('#!/bin/sh\nexit 27\n')
|
||
|
|
+ trap.chmod(0o700)
|
||
|
|
+ with chdir(self.directory.name), patch.dict(os.environ, {'PATH': str(decoy)}):
|
||
|
|
+ self.assertTrue(self.check(VALID, binary=Path('keycape'))['valid'])
|
||
|
|
--- a/sso-mfa/k8s/keycape/registration-adapter.md
|
||
|
|
+++ b/sso-mfa/k8s/keycape/registration-adapter.md
|
||
|
|
@@ -13,7 +13,12 @@
|
||
|
|
and its complete local helper import closure together with the exact reviewed
|
||
|
|
`check-config` executable digest. Changing that digest changes the descriptor
|
||
|
|
source pin and descriptor bytes, requiring independently reviewed admission.
|
||
|
|
-The fixed executable is rehashed before each attempt. The caller constructs the
|
||
|
|
+The fixed executable is rehashed before each attempt. The helper resolves the
|
||
|
|
+selected file to an absolute executable path and supplies an empty checker
|
||
|
|
+environment, so ambient PATH and KeyCape URL overrides cannot select another
|
||
|
|
+executable or alter validation of supplied bytes. Path resolution does not
|
||
|
|
+establish immutable storage or eliminate replacement races; executable custody
|
||
|
|
+and native runtime admission remain separate requirements. The caller constructs the
|
||
|
|
proposed invocation from exact source inputs, installs the fixed adapter once,
|
||
|
|
and calls normal `Executor.admit`; independently derived admission must match.
|
||
|
|
No callable replacement after admission is required or supported by this recipe.
|
||
|
|
--- a/workplans/NK-WP-0048-security-case-types.md
|
||
|
|
+++ b/workplans/NK-WP-0048-security-case-types.md
|
||
|
|
@@ -323,7 +323,7 @@
|
||
|
|
```task
|
||
|
|
id: NK-WP-0048-T06
|
||
|
|
status: wait
|
||
|
|
-depends_on: [NK-WP-0048-T05, NK-WP-0048-T08, NK-WP-0048-T09, NK-WP-0048-T12, NK-WP-0048-T13, NK-WP-0048-T14, NK-WP-0048-T15, NK-WP-0048-T16, MASON-WP-0010-T03, WARDEN-WP-0044-T03]
|
||
|
|
+depends_on: [NK-WP-0048-T05, NK-WP-0048-T08, NK-WP-0048-T09, NK-WP-0048-T12, NK-WP-0048-T13, NK-WP-0048-T14, NK-WP-0048-T15, NK-WP-0048-T16, NK-WP-0048-T17, NK-WP-0048-T18, MASON-WP-0010-T03, WARDEN-WP-0044-T03]
|
||
|
|
blocking_reason: "Type1.2 architecture has a confirmed source-only founder acknowledgment; canonical1.1 remains unchanged/default and the1.2 reference case stays proposed. Needs explicit version selection/reference-source integration without normal admission bypass, closed owner adapters plus global/native receipt verifier (WARDEN-WP-0044-T03), independently verified six-object structural materialization and CCR42 claims/created-only provenance, exact broker/case/source reconciliation, then separately authorized native mint/delivery and attended registry/consumer verification. Source acknowledgment, a bundle or local tests do not prove implemented/materialized/live status."
|
||
|
|
priority: high
|
||
|
|
state_hub_task_id: "d9b26bcf-a7c6-59a2-92a0-f913d76a203c"
|
||
|
|
@@ -734,10 +734,11 @@
|
||
|
|
```
|
||
|
|
|
||
|
|
Owner: NetKingdom for descriptor/type source and exact checker artifact; Warden
|
||
|
|
-independently owns global provenance/admission. Next action: identify the exact
|
||
|
|
-reviewed checker executable artifact and native owner evidence/session callback
|
||
|
|
-contracts, then assemble the complete step descriptor set and a proposed explicit
|
||
|
|
-type version selection. T13's retained descriptor pins a synthetic checker only.
|
||
|
|
+independently owns global provenance/admission. Next action: obtain review of the
|
||
|
|
+exact checker source/artifact proposed by T17 and identify native owner evidence/
|
||
|
|
+session callback contracts, then assemble the complete step descriptor set and
|
||
|
|
+a proposed explicit type version selection. T13's historical descriptor retains
|
||
|
|
+its synthetic checker; T17 supplies an artifact-bound proposal, not admission.
|
||
|
|
Completion requires separate owner source review of exact machine descriptors,
|
||
|
|
artifact/source/config pins, normal type/version admission and independent
|
||
|
|
evidence-verification boundaries. Warden d97652a refreshes producer validator
|
||
|
|
@@ -746,3 +747,70 @@
|
||
|
|
change. Platform owns structural/mint/broker/delivery evidence; Apps owns actual
|
||
|
|
consumer login/group enforcement. Neither their assent nor native acceptance is
|
||
|
|
supplied by this task.
|
||
|
|
+
|
||
|
|
+## T17 — Build and exercise the exact offline config-checker artifact
|
||
|
|
+
|
||
|
|
+```task
|
||
|
|
+id: NK-WP-0048-T17
|
||
|
|
+status: done
|
||
|
|
+priority: high
|
||
|
|
+```
|
||
|
|
+
|
||
|
|
+Completed 2026-10-16, isolated artifact/source fixture proof only. Built the
|
||
|
|
+existing `key-cape/src/cmd/keycape` command from published clean source revision
|
||
|
|
+`3c71226a1a7b8a2bdb710e9c497e1a1f7f8a3987`, source tree
|
||
|
|
+`f0010b9cab17dbc3a787628a29657d98b900320f`, with cached Go 1.23.0,
|
||
|
|
+CGO disabled, Linux/amd64 v1, trimmed paths, disabled VCS stamping and offline
|
||
|
|
+read-only module selection. The full retained closure pins 108 source files,
|
||
|
|
+9876 toolchain files and ten used module source directories. Three isolated
|
||
|
|
+build locations produced identical 13633409-byte artifacts:
|
||
|
|
+`sha256:be37738fe1c08259fb4f9ca0daf0f9301bfffc237e5bb38435525dfbf11ccff5`.
|
||
|
|
+
|
||
|
|
+Eight disposable checks use the actual executable and maintained helper/kernel,
|
||
|
|
+with complete synthetic configs and a minimal environment: add/created-only
|
||
|
|
+removal, normal pinned producer admission, rollback, lost-readback uncertainty,
|
||
|
|
+stale revisions, substituted checker refusal, invalid startup config and missing
|
||
|
|
+secret-env refusal. No checker stub or producer-pin override remains. The
|
||
|
|
+artifact-bound descriptor proposal is
|
||
|
|
+`sha256:76b1153d188062ece53fcd5e8d35290fdbfd02a69dd79d4b001fc42781afcdc3`.
|
||
|
|
+Evidence: Custodian
|
||
|
|
+`docs/changes/netkingdom-target-2026-10-16/browser-checker-artifact-next/`.
|
||
|
|
+The first two build attempts succeeded but their closure collection failed;
|
||
|
|
+both failures are retained, as is refusal of the older incomplete synthetic
|
||
|
|
+fixture. No production config, credentials or cluster mutation was used.
|
||
|
|
+
|
||
|
|
+This extracts completed build/fixture scope from T16 without claiming KeyCape
|
||
|
|
+owner endorsement, native checker selection/storage custody, acknowledged type
|
||
|
|
+integration, native evidence/session callbacks or attended browser acceptance.
|
||
|
|
+T16 retains exact owner review and composition/admission scope. The attended
|
||
|
|
+registration procedure remains unexercised.
|
||
|
|
+
|
||
|
|
+## T18 — Make checker input and executable selection deterministic
|
||
|
|
+
|
||
|
|
+```task
|
||
|
|
+id: NK-WP-0048-T18
|
||
|
|
+status: done
|
||
|
|
+priority: high
|
||
|
|
+```
|
||
|
|
+
|
||
|
|
+Completed 2026-10-16, isolated source correction and real-artifact fixture proof
|
||
|
|
+only; independent integration/publication remain separately recorded. KeyCape
|
||
|
|
+source review identified non-secret `KEYCAPE_ACCOUNT_PORTAL_URL` and
|
||
|
|
+`KEYCAPE_BROWSER_LOGOUT_URL` overrides in config.Load. The original helper
|
||
|
|
+inherited ambient environment, allowing valid supplied configuration to be
|
||
|
|
+rejected or invalid supplied URL configuration to be admitted. A bare relative
|
||
|
|
+checker filename could also be replaced by PATH lookup after the adapter had
|
||
|
|
+hashed the selected local file. All three counterexamples are retained against
|
||
|
|
+the unchanged predecessor.
|
||
|
|
+
|
||
|
|
+The existing helper now resolves the selected checker file to an absolute path
|
||
|
|
+and passes an explicit empty subprocess environment. Three opt-in real-checker
|
||
|
|
+regressions verify both URL-verdict directions and relative-file selection under
|
||
|
|
+a non-checker PATH decoy. All 39 affected checks pass with the exact supplied
|
||
|
|
+artifact, as do 11 real-artifact helper/kernel scenarios. Source evidence:
|
||
|
|
+Custodian `docs/changes/netkingdom-target-2026-10-16/browser-checker-environment-r2/`.
|
||
|
|
+Historical descriptors remain retained; the helper correction changes successor
|
||
|
|
+source-composition pins and requires their separate review/admission. An absolute
|
||
|
|
+path is not immutable custody and does not eliminate file replacement races;
|
||
|
|
+T16 retains native executable storage/custody and callback/type admission.
|
||
|
|
+No production configuration, credentials or cluster mutation was used.
|