Preserve revision-4 access failure and add safe founder-context diagnostics
This commit is contained in:
parent
1354a11f55
commit
fa81fcf88d
8 changed files with 176 additions and 16 deletions
|
|
@ -35,3 +35,5 @@ The founder executed approved revision3: the missing-fragment repair and171-repo
|
|||
[Revision4](native-r4/PLAN.md), packet SHA256 `64c73100f2347470fda1b8b3194dbf6a8009291d9d80223af463b3c5ecab52fd`, corrects numeric mode handling and exact known group-write restoration, reuses the existing verified immutable ConfigMap, and has a fresh execution/source gate. Eight shell image cases, a four-case171-repository/1539-hook controller run, three controller checks and runtime self-test passed. Candidate scanner/profile and six owner source hashes are unchanged. Await exact revision4 review; no native revision4 action has run. The proposed founder launcher is `/tmp/forge-scanner-rollout-r4.sh`.
|
||||
|
||||
Founder approval recorded for exact revision4 packet `64c73100f2347470fda1b8b3194dbf6a8009291d9d80223af463b3c5ecab52fd` via User: Approve exact revision4; request_user_input_async call_f68b7e7de81a4c089e8864b3d66773a9 item0,2026-11-02. Attended execution pending at `/tmp/forge-scanner-rollout-r4.sh`; approval receipt `docs/changes/2026-11-01-scanner-json-digest/native-r4-approval.json`. Earlier failures and exact approvals remain preserved; no execution success is inferred from approval.
|
||||
|
||||
Current revision4 execution attempt failed at first get:nodes, before mutation. Packet/approval are unchanged and its failed receipt is preserved in `attempt-r4-access/`. The local bridge/API transport responds, public server certificate is not expired, and a tested read-only access classifier is ready at `bash /tmp/forge-scanner-access-diagnose.sh`. Await selected founder-context status before any unchanged retry; no renewed approval is requested.
|
||||
|
|
|
|||
|
|
@ -0,0 +1,79 @@
|
|||
"""Founder-run selected access diagnostics, never config/credentials/raw errors.
|
||||
|
||||
No mutation or receipt reset. Reads selected node and self-identity metadata with
|
||||
the catalogued attended context. The rollout packet and failed receipt stay intact.
|
||||
"""
|
||||
from pathlib import Path
|
||||
import importlib.util
|
||||
import json
|
||||
import os
|
||||
import subprocess
|
||||
|
||||
HERE=Path(__file__).resolve().parent
|
||||
R4=HERE.parent/'native-r4'
|
||||
EXPECTED='64c73100f2347470fda1b8b3194dbf6a8009291d9d80223af463b3c5ecab52fd'
|
||||
ADMIN=Path('/home/worsch/.kube/config-railiance01')
|
||||
OUTPUT=Path('/tmp/forge-scanner-access-diagnostic-receipt.json')
|
||||
|
||||
|
||||
def category(returncode,stderr):
|
||||
if returncode==0:return 'passed'
|
||||
lower=stderr.lower()
|
||||
groups=[('authentication-required',[b'the server has asked for the client to provide credentials',b'must be logged in to the server',b'unauthorized',b'invalid bearer token']),
|
||||
('authorization-denied',[b'forbidden']),
|
||||
('certificate-validity-failure',[b'certificate has expired',b'certificate is not yet valid',b'not yet valid']),
|
||||
('certificate-verification-failure',[b'x509:',b'failed to verify certificate',b'certificate signed by unknown authority']),
|
||||
('client-certificate-required',[b'tls: certificate required',b'client certificate required']),
|
||||
('connection-refused',[b'connection refused']),
|
||||
('transport-timeout',[b'i/o timeout',b'context deadline exceeded',b'tls handshake timeout',b'timed out']),
|
||||
('name-resolution',[b'no such host']),
|
||||
('context-selection-failure',[b'current-context is not set',b'context was not found',b'no context exists',b'does not exist']),
|
||||
('configuration-unavailable',[b'error loading config file',b'no configuration has been provided',b'no such file or directory']),
|
||||
('configuration-parse-failure',[b'yaml:',b'invalid configuration']),
|
||||
('credential-plugin-failure',[b'getting credentials: exec',b'exec plugin',b'no auth provider found']),
|
||||
('api-unavailable',[b'serviceunavailable',b'bad gateway',b'currently unable to handle the request',b'connection reset by peer',b'eof']),
|
||||
('protocol-mismatch',[b'http response to https client'])]
|
||||
for name,needles in groups:
|
||||
if any(n in lower for n in needles):return name
|
||||
return 'unclassified-command-failure'
|
||||
|
||||
|
||||
def selected_nodes(raw,cfg):
|
||||
data=json.loads(raw);items=data.get('items',[])
|
||||
matched=len(items)==1 and items[0].get('metadata',{}).get('uid')==cfg['node_uid'] and any(x.get('address')=='92.205.62.239' for x in items[0].get('status',{}).get('addresses',[]))
|
||||
return {'node_count':len(items),'target_node_matches':matched}
|
||||
|
||||
|
||||
def selected_identity(raw):
|
||||
data=json.loads(raw);user=data.get('status',{}).get('userInfo',{})
|
||||
return {'self_identity_read':True,'identity_is_builder':user.get('username')=='ops-mason-builder',
|
||||
'identity_in_system_masters': 'system:masters' in user.get('groups',[])}
|
||||
|
||||
|
||||
def main():
|
||||
s=importlib.util.spec_from_file_location('reviewed_r4',R4/'rollout.py');m=importlib.util.module_from_spec(s);s.loader.exec_module(m)
|
||||
if m.verify_packet()!=EXPECTED:raise RuntimeError('reviewed-packet-drift')
|
||||
cfg=json.loads((R4.parent/'native-r2/inputs.json').read_bytes())
|
||||
env=dict(os.environ,KUBECONFIG=str(ADMIN))
|
||||
receipt={'read_only':True,'native_mutation':False,'rollout_packet_sha256':EXPECTED,
|
||||
'catalog_route':'kubernetes-railiance01-admin','admin_file_present':ADMIN.is_file(),
|
||||
'admin_file_readable':os.access(ADMIN,os.R_OK),'checks':[]}
|
||||
for label,args,select in [('get-nodes',['get','nodes','-o','json'],lambda raw:selected_nodes(raw,cfg)),
|
||||
('self-identity',['auth','whoami','-o','json'],selected_identity)]:
|
||||
try:
|
||||
q=subprocess.run(['kubectl','--request-timeout=8s',*args],capture_output=True,timeout=15,env=env)
|
||||
item={'operation':label,'returncode':q.returncode,'category':category(q.returncode,q.stderr)}
|
||||
if q.returncode==0:
|
||||
try:item.update(select(q.stdout))
|
||||
except (ValueError,TypeError,KeyError,AttributeError):item['category']='unexpected-response; raw withheld'
|
||||
except subprocess.TimeoutExpired:item={'operation':label,'category':'client-command-timeout'}
|
||||
receipt['checks'].append(item)
|
||||
OUTPUT.write_text(json.dumps(receipt,indent=2)+'\n')
|
||||
print(json.dumps(receipt,indent=2));print('receipt:',str(OUTPUT))
|
||||
|
||||
|
||||
if __name__=='__main__':
|
||||
try:main()
|
||||
except Exception as error:
|
||||
print(json.dumps({'result':'read-only-diagnostic-refused','reason':str(error) if isinstance(error,RuntimeError) else type(error).__name__}))
|
||||
raise SystemExit(1)
|
||||
|
|
@ -0,0 +1,15 @@
|
|||
{
|
||||
"proposal_id": "SYNC-SCANNER-JSON-DIGEST-20261101",
|
||||
"revision": 4,
|
||||
"events": [
|
||||
{
|
||||
"event": "command-failed",
|
||||
"operation": "get:nodes",
|
||||
"category": "command-failed"
|
||||
}
|
||||
],
|
||||
"native_mutation": false,
|
||||
"packet_sha256": "64c73100f2347470fda1b8b3194dbf6a8009291d9d80223af463b3c5ecab52fd",
|
||||
"reused_r2_packet_sha256": "d22b1f555af96c203b7d68efd25e80815ce18ca4e3bf0dd3ab96b6f699d50237",
|
||||
"mode": "apply"
|
||||
}
|
||||
|
|
@ -0,0 +1,22 @@
|
|||
{
|
||||
"proposal_id": "SYNC-SCANNER-JSON-DIGEST-20261101",
|
||||
"revision": 4,
|
||||
"packet_sha256": "64c73100f2347470fda1b8b3194dbf6a8009291d9d80223af463b3c5ecab52fd",
|
||||
"outcome": "failed at first Kubernetes read",
|
||||
"native_mutation": false,
|
||||
"reported_reason": "kubernetes-command-failed:get:nodes",
|
||||
"failed_receipt_sha256": "8c876dd2ac8d21a1c4b2a1823802f8a318328d8e16ebb7191160d87525bd50f6",
|
||||
"authorization": "exact revision4 approval remains valid; no code or scope change",
|
||||
"transport_observation": {
|
||||
"bridge": "connected",
|
||||
"anonymous_api_http_status": 401,
|
||||
"identity_verified": false,
|
||||
"server_certificate_expired": false,
|
||||
"server_certificate_not_after": "2027-03-10T00:47:00Z",
|
||||
"credentials_used": false
|
||||
},
|
||||
"next_step": "founder-run read-only access diagnostic; determine credentials/configuration/transport error without raw output",
|
||||
"diagnostic_sha256": "4df7a7a8d88abada354dcd59f6cd452e20d9bfe67aac0813991ea0e2545599c7",
|
||||
"diagnostic_tests": 3,
|
||||
"approved_packet_changed": false
|
||||
}
|
||||
|
|
@ -0,0 +1,26 @@
|
|||
import importlib.util
|
||||
from pathlib import Path
|
||||
import json
|
||||
import unittest
|
||||
|
||||
p=Path(__file__).resolve().parent/'diagnose_access.py';s=importlib.util.spec_from_file_location('access_probe',p);d=importlib.util.module_from_spec(s);s.loader.exec_module(d)
|
||||
|
||||
|
||||
class Tests(unittest.TestCase):
|
||||
def test_authentication_challenge_without_unauthorized_word(self):
|
||||
raw=b'could not get API group list: the server has asked for the client to provide credentials PRIVATE_SENTINEL'
|
||||
self.assertEqual(d.category(1,raw),'authentication-required')
|
||||
def test_categories_do_not_echo_raw_errors(self):
|
||||
for raw,expected in [(b'x509: certificate has expired PRIVATE_SENTINEL','certificate-validity-failure'),
|
||||
(b'Forbidden PRIVATE_SENTINEL','authorization-denied'),
|
||||
(b'connect: connection refused PRIVATE_SENTINEL','connection-refused'),
|
||||
(b'PRIVATE_SENTINEL','unclassified-command-failure')]:
|
||||
self.assertEqual(d.category(1,raw),expected)
|
||||
def test_selected_success_fields_do_not_emit_credentials_or_full_objects(self):
|
||||
raw=json.dumps({'items':[{'metadata':{'uid':'expected','annotations':{'unrelated':'PRIVATE_SENTINEL'}},'status':{'addresses':[{'address':'92.205.62.239'}]}}]}).encode()
|
||||
self.assertEqual(d.selected_nodes(raw,{'node_uid':'expected'}),{'node_count':1,'target_node_matches':True})
|
||||
raw=json.dumps({'status':{'userInfo':{'username':'ops-mason-builder','groups':['PRIVATE_SENTINEL']},'unrelated':'PRIVATE_SENTINEL'}}).encode()
|
||||
result=d.selected_identity(raw);self.assertTrue(result['identity_is_builder']);self.assertNotIn('PRIVATE_SENTINEL',json.dumps(result))
|
||||
|
||||
|
||||
if __name__=='__main__':unittest.main()
|
||||
Loading…
Add table
Add a link
Reference in a new issue