Prepare verified Forgejo public-PKCE build profile revision 2 for scoped review
This commit is contained in:
parent
bab3dd1c9d
commit
17635cb0a7
42 changed files with 1632 additions and 5 deletions
|
|
@ -0,0 +1,116 @@
|
|||
# Forgejo working sign-in — proposed build profile, revision 2
|
||||
|
||||
Proposal `CUST80-FORGEJO-PUBLIC-BUILD-20261018`, revision 2.
|
||||
SHA-256 of [proposal.json](proposal.json):
|
||||
`fcea2cd32b8ee2634d2ebeb9ef4b659b6b85f8a4878413ed96e5d06cb09892c3`.
|
||||
|
||||
**Proposed; no live change has been executed.** The founder's good-case-first
|
||||
direction permits preparing this option; a separate explicit go and independent
|
||||
NetKingdom, Apps and KeyCape reviews are required before activation. Previous
|
||||
scanner approval and the confidential-client case acceptance do not authorize it.
|
||||
|
||||
## Working flow and the deliberate build-mode choice
|
||||
|
||||
Connect the existing Forgejo release to the existing KeyCape issuer using an exact
|
||||
HTTPS callback, authorization-code flow with S256 PKCE, and required MFA. For this
|
||||
temporary profile the client is public: it has no client secret. Existing Forgejo
|
||||
accounts retain their local permissions and require explicit account linking;
|
||||
automatic new-account registration stays disabled. Local sign-in and local 2FA
|
||||
remain available.
|
||||
|
||||
The accepted confidential-client profile remains the target. Its secret
|
||||
generation, issuer/consumer delivery and complete case lifecycle stay in
|
||||
CUST-WP-0082-T20 and the existing owner workplans. Full ongoing current-identity,
|
||||
group-policy, revocation and isolation acceptance also stays in WP-82. This
|
||||
candidate needs explicit acceptance of that temporary security deferral. It
|
||||
does not make the original SecurityCaseType implemented or the confidential case
|
||||
materialized. No grant is inferred from this preparation.
|
||||
|
||||
This uses the maintained NetKingdom client-registration capability and the
|
||||
unmodified Forgejo CLI, chart and OIDC client. There is no custom backend build,
|
||||
new registration service, evaluator or credential-distribution mechanism.
|
||||
|
||||
## Evidence from preparation
|
||||
|
||||
| Check | Observed result |
|
||||
| --- | --- |
|
||||
| Live issuer client | `forgejo` is unknown; registration is needed. [Probe](issuer-start-probe.json). |
|
||||
| Existing registration/delivery | Confidential entry absent, env binding absent, consumer delivery absent. [Inspection](registration-inspection.json), [delivery metadata](delivery-metadata.json). |
|
||||
| Existing Forgejo sources | No authentication sources. Local sign-in is available. [Inventory](forgejo-auth-source-inventory.json). |
|
||||
| Actual issuer + upstream client | HTTPS token exchange and UserInfo passed without a client secret; wrong PKCE and replay refused. MFA/human authorization was a synthetic session seed. [Result](protocol-result.json). |
|
||||
| Unchanged Forgejo image | Disposable local SQLite migration and source creation passed. Browser login page showed NetKingdom and retained the local form; redirect carried the intended issuer, scopes, state and S256 challenge. [Result](upstream-release-result.json). This is local proof, not deployed sign-in. |
|
||||
| Chart 12.5.0 | Source postimage renders the same public CLI parameters; scanner guard/template source stays byte-equivalent. [Render check](helm-render-validation.json). |
|
||||
| Startup checker | Reproduced the exact previously owner-reviewed `be37738f…` binary. [Reproduction](checker-reproduction.json). |
|
||||
| Native registration preview | Both live and proposed registries pass the checker; server dry-run preserves unrelated registry/config data. [Dry-run](public-registration-dry-run.json). |
|
||||
|
||||
The first two source-protocol attempts failed during offline fixture cache setup.
|
||||
Their receipts/logs are retained; attempt 3 passed. No runtime security code was
|
||||
changed to obtain the passing result. Real human sign-in and account continuity
|
||||
remain T43; actual agent Git/authenticated API use remains T44.
|
||||
|
||||
## Exact activation scope
|
||||
|
||||
The binding proposal includes all source hashes, current deployment identities,
|
||||
registry UID/resourceVersion, command arguments, verification and rollback.
|
||||
[execution-steps.json](execution-steps.json) contains the four native actions:
|
||||
|
||||
1. Register only the proposed public `forgejo` client through the maintained
|
||||
helper, with exact registry CAS and the reproduced startup checker.
|
||||
2. Restart `sso/keycape` once and verify Ready, discovery and the authorization
|
||||
start before proceeding.
|
||||
3. Create only the `netkingdom` Forgejo authentication source through the existing
|
||||
upstream CLI, with an empty client secret and the exact discovery/scopes.
|
||||
4. Restart `forgejo/forgejo-gitea` once and verify Ready, the sign-in option,
|
||||
exact HTTPS callback and PKCE redirect, and preserved local sign-in.
|
||||
|
||||
Publish the exact Apps values postimage and NetKingdom build-profile declaration
|
||||
through their existing file-scoped publication/consistency path. Source is
|
||||
reviewed against the pinned owner revisions; any drift stops the action.
|
||||
The source declarations make future normal reconciliation reproduce the profile.
|
||||
No Helm upgrade, image, resource, runner, scanner-hook or network-policy change
|
||||
is part of this activation. The existing Recreate strategies keep one replica
|
||||
each; no extra pod capacity is needed. Current node CPU requests are 3605m/4000m.
|
||||
|
||||
Both restarts briefly interrupt their service and in-flight sign-in. The readiness
|
||||
budget is 180 seconds per phase, with the founder present. No unattended or later
|
||||
standing execution grant is requested. Expected extra infrastructure spend is
|
||||
EUR 0; recovery planning estimate is EUR 300. Temporary identity-profile exposure
|
||||
is not monetized by that estimate and requires explicit founder acceptance.
|
||||
|
||||
## Verification, rollback and human trial
|
||||
|
||||
Verify every source/input pin and the observed native preconditions immediately
|
||||
before effects. Record actual source ID and registry before/after revisions;
|
||||
compare pod templates allowing only the approved restart annotation. Preserve
|
||||
the existing images, resources, scanner template/guard/hooks and unrelated data.
|
||||
Stop at a failed or ambiguous phase; retain the failure and do not replay blindly.
|
||||
|
||||
Before human linking, rollback removes only the proved-created source, then
|
||||
restarts Forgejo if needed; removes only the exact public registry entry through
|
||||
the same helper, validates the result and restarts KeyCape. At most one additional
|
||||
restart per changed deployment is allowed for rollback. Unexpected links,
|
||||
creation uncertainty or drift requires owner reconciliation before deletion.
|
||||
Revert only this trial's source declarations; preserve others' work.
|
||||
|
||||
After successful activation, the actual human trial is:
|
||||
|
||||
1. Sign in to the existing Forgejo account using its current local path.
|
||||
2. Open [Security settings](https://forgejo.coulomb.social/user/settings/security),
|
||||
choose **Link account**, then **netkingdom**, and complete the actual KeyCape
|
||||
sign-in/MFA. Confirm the intended existing account was linked.
|
||||
3. Sign out, use **netkingdom** on the login page, and confirm the same username,
|
||||
repositories, organisations and expected rights. Report the result without
|
||||
passwords, codes, cookies or token material.
|
||||
|
||||
These instructions are prepared from the upstream account-link template; they
|
||||
are not a claim that the live option already exists. Credentials remain with the
|
||||
human. T44 then verifies the coding agent's supported Git and authenticated API
|
||||
flow with its actual build profile stated.
|
||||
|
||||
Revision 2 represents the same public source hashes as typed `path`/`sha256` records.
|
||||
The ordinary scanner had classified two filename-as-key hash values in revision 1
|
||||
as generic API keys; those values exactly match the actual source files. The
|
||||
profile, commands and input hashes are unchanged, and revision 2 scans clean
|
||||
under the same rules. [Revision history](revision-history.json) retains the
|
||||
original digest, local original and exact reconstruction from committed public
|
||||
data. No rule, exemption or secret-encoding bypass was added.
|
||||
|
|
@ -0,0 +1,104 @@
|
|||
"""Disposable local validation of the unchanged Forgejo runtime and CLI."""
|
||||
from pathlib import Path
|
||||
import json
|
||||
import secrets
|
||||
import subprocess
|
||||
import time
|
||||
import urllib.error
|
||||
import urllib.parse
|
||||
import urllib.request
|
||||
|
||||
packet = Path(__file__).resolve().parent
|
||||
work = Path('/tmp/custodian-forgejo-public-release-r1')
|
||||
work.mkdir(exist_ok=False)
|
||||
data = work/'data'
|
||||
data.mkdir()
|
||||
image = 'code.forgejo.org/forgejo/forgejo@sha256:41649dff5311664097d29b4d80620403f0b27e7c0eee0b8af79b132a37f3807b'
|
||||
container = 'custodian-forgejo-public-release-r1'
|
||||
config = '''APP_NAME = Synthetic Forgejo trial
|
||||
RUN_MODE = prod
|
||||
[server]
|
||||
PROTOCOL = http
|
||||
HTTP_ADDR = 0.0.0.0
|
||||
HTTP_PORT = 3000
|
||||
ROOT_URL = http://127.0.0.1:18092/
|
||||
DISABLE_SSH = true
|
||||
[database]
|
||||
DB_TYPE = sqlite3
|
||||
PATH = /data/fixture.db
|
||||
[security]
|
||||
INSTALL_LOCK = true
|
||||
SECRET_KEY = {secret}
|
||||
[service]
|
||||
DISABLE_REGISTRATION = true
|
||||
[oauth2_client]
|
||||
ENABLE_AUTO_REGISTRATION = false
|
||||
ACCOUNT_LINKING = login
|
||||
OPENID_CONNECT_SCOPES = openid profile email groups
|
||||
[session]
|
||||
PROVIDER = memory
|
||||
[log]
|
||||
MODE = console
|
||||
LEVEL = Error
|
||||
'''
|
||||
(data/'fixture.ini').write_text(config.format(secret=secrets.token_hex(32)))
|
||||
(data/'fixture.ini').chmod(0o600)
|
||||
common = ['docker','run','--rm','--read-only','--tmpfs','/tmp:rw,size=128m',
|
||||
'--mount',f'type=bind,source={data},target=/data',
|
||||
'--env','GITEA_WORK_DIR=/data','--env','GITEA_CUSTOM=/data/gitea',
|
||||
'--entrypoint','/usr/local/bin/gitea']
|
||||
receipt = {'image':image,'profile_approved':False,'production_mutation':False,
|
||||
'database':'disposable local SQLite only','commands':[]}
|
||||
for name, args in [('migrate',['migrate']),('add-oauth',['admin','auth','add-oauth',
|
||||
'--name','netkingdom','--provider','openidConnect','--key','forgejo',
|
||||
'--secret','','--auto-discover-url','https://kc.coulomb.social/.well-known/openid-configuration',
|
||||
'--scopes','openid,profile,email,groups'])]:
|
||||
command = common+[image,'--config','/data/fixture.ini',*args]
|
||||
result = subprocess.run(command,capture_output=True,timeout=90)
|
||||
(work/(name+'.log')).write_bytes(result.stdout+result.stderr)
|
||||
receipt['commands'].append({'name':name,'exit_code':result.returncode})
|
||||
if result.returncode:
|
||||
(packet/'upstream-release-result.json').write_text(json.dumps(receipt,indent=2)+'\n')
|
||||
raise SystemExit('local upstream CLI check failed; private log retained')
|
||||
command = common[:3]+['--name',container,'--detach','-p','127.0.0.1:18092:3000']+common[3:]+[image,'--config','/data/fixture.ini','web']
|
||||
started = subprocess.run(command,capture_output=True,timeout=30)
|
||||
assert started.returncode == 0, 'local server could not start'
|
||||
class NoRedirect(urllib.request.HTTPRedirectHandler):
|
||||
def redirect_request(self,*args):
|
||||
return None
|
||||
try:
|
||||
for _ in range(50):
|
||||
try:
|
||||
with urllib.request.urlopen('http://127.0.0.1:18092/user/login',timeout=2) as r:
|
||||
login=r.read(200000).decode()
|
||||
break
|
||||
except (OSError,urllib.error.URLError):
|
||||
time.sleep(0.2)
|
||||
else:
|
||||
raise RuntimeError('local server readiness failed')
|
||||
receipt['netkingdom_option_visible'] = '/user/oauth2/netkingdom' in login
|
||||
receipt['local_signin_form_preserved'] = 'user_name' in login
|
||||
opener=urllib.request.build_opener(NoRedirect)
|
||||
try:
|
||||
with opener.open('http://127.0.0.1:18092/user/oauth2/netkingdom',timeout=10) as r:
|
||||
status=r.status; location=r.headers.get('Location','')
|
||||
except urllib.error.HTTPError as e:
|
||||
status=e.code; location=e.headers.get('Location','')
|
||||
redirect=urllib.parse.urlsplit(location);query=urllib.parse.parse_qs(redirect.query)
|
||||
receipt['authorization_start']={'http_status':status,'issuer_host':redirect.hostname,
|
||||
'path':redirect.path,'client_id':query.get('client_id'),
|
||||
'callback':query.get('redirect_uri'),'scope':query.get('scope'),
|
||||
'pkce_method':query.get('code_challenge_method'),
|
||||
'pkce_challenge_present':bool(query.get('code_challenge')),
|
||||
'state_present':bool(query.get('state'))}
|
||||
receipt['passed'] = (receipt['netkingdom_option_visible'] and receipt['local_signin_form_preserved']
|
||||
and status in [302,303,307] and redirect.hostname=='kc.coulomb.social'
|
||||
and query.get('client_id')==['forgejo']
|
||||
and query.get('code_challenge_method')==['S256'] and bool(query.get('code_challenge')))
|
||||
finally:
|
||||
stopped=subprocess.run(['docker','stop','--time','5',container],capture_output=True,timeout=20)
|
||||
receipt['local_container_stopped']=stopped.returncode==0
|
||||
(packet/'upstream-release-result.json').write_text(json.dumps(receipt,indent=2)+'\n')
|
||||
print(json.dumps(receipt))
|
||||
if not receipt['passed']:
|
||||
raise SystemExit(1)
|
||||
|
|
@ -0,0 +1,9 @@
|
|||
{
|
||||
"verified": true,
|
||||
"artifact": "/tmp/custodian-forgejo-public-checker-r1/keycape",
|
||||
"sha256": "be37738fe1c08259fb4f9ca0daf0f9301bfffc237e5bb38435525dfbf11ccff5",
|
||||
"source_revision": "3c71226a1a7b8a2bdb710e9c497e1a1f7f8a3987",
|
||||
"byte_identical_to_existing_owner_review": true,
|
||||
"seconds": 17.647,
|
||||
"real_runtime_configuration_access": false
|
||||
}
|
||||
|
|
@ -0,0 +1,13 @@
|
|||
clientId: forgejo
|
||||
displayName: Forgejo source forge
|
||||
redirectUris:
|
||||
- https://forgejo.coulomb.social/user/oauth2/netkingdom/callback
|
||||
allowedScopes:
|
||||
- openid
|
||||
- profile
|
||||
- email
|
||||
- groups
|
||||
grantTypes:
|
||||
- authorization_code
|
||||
clientType: public
|
||||
mfaRequired: true
|
||||
|
|
@ -0,0 +1,14 @@
|
|||
clientId: forgejo
|
||||
displayName: Forgejo source forge
|
||||
redirectUris:
|
||||
- https://forgejo.coulomb.social/user/oauth2/netkingdom/callback
|
||||
allowedScopes:
|
||||
- openid
|
||||
- profile
|
||||
- email
|
||||
- groups
|
||||
grantTypes:
|
||||
- authorization_code
|
||||
clientType: confidential
|
||||
secretRef: env:KEYCAPE_FORGEJO_CLIENT_SECRET
|
||||
mfaRequired: true
|
||||
|
|
@ -0,0 +1,18 @@
|
|||
{
|
||||
"workplan_id": "dfadb19b-445e-59c6-bcd6-48e87b912c52",
|
||||
"id": "ad88edeb-7c7c-486f-a695-1a7abae5b20c",
|
||||
"topic_id": "cee7bedf-2b48-46ef-8601-006474f2ad7a",
|
||||
"title": "WP-80: admit temporary Forgejo public-PKCE build profile, revision 2",
|
||||
"description": "Pending actual founder go for CUST80-FORGEJO-PUBLIC-BUILD-20261018@2, SHA-256 fcea2cd32b8ee2634d2ebeb9ef4b659b6b85f8a4878413ed96e5d06cb09892c3. Packet /home/worsch/the-custodian/docs/changes/netkingdom-target-2026-10-18/forgejo-good-case-preparation-r1/README.md. Same temporary public/S256-PKCE/MFA profile, source publication and four native actions as r1: register one client, create one Forgejo source and restart each existing deployment once; one additional restart each only for rollback. Confidential target/full security acceptance remain in CUST-WP-0082. Conditional on independent NetKingdom/Apps/KeyCape reviews; authorize those reviewer agents. Revision2 changes only the representation of public source hashes after the scanner refused the filename-as-key layout; no scanner rule or source/execution semantic changed. Original revision1 digest 61eff8b488674e75510133fe118de86d895be04c033e109c3597c03ed2ecf30e retained in supervision/revision-history. No native action or actual approval yet.",
|
||||
"decision_type": "pending",
|
||||
"status": "open",
|
||||
"rationale": null,
|
||||
"decided_by": null,
|
||||
"decided_at": null,
|
||||
"deadline": null,
|
||||
"escalation_note": null,
|
||||
"superseded_by": null,
|
||||
"created_at": "2026-10-18T17:19:22.260406Z",
|
||||
"updated_at": "2026-10-18T17:27:12.192435Z",
|
||||
"workstream_id": "dfadb19b-445e-59c6-bcd6-48e87b912c52"
|
||||
}
|
||||
|
|
@ -0,0 +1,18 @@
|
|||
{
|
||||
"workplan_id": "dfadb19b-445e-59c6-bcd6-48e87b912c52",
|
||||
"id": "ad88edeb-7c7c-486f-a695-1a7abae5b20c",
|
||||
"topic_id": "cee7bedf-2b48-46ef-8601-006474f2ad7a",
|
||||
"title": "WP-80: admit temporary Forgejo public-PKCE build profile, revision 1",
|
||||
"description": "Pending actual founder decision for CUST80-FORGEJO-PUBLIC-BUILD-20261018@1; proposal SHA-256 61eff8b488674e75510133fe118de86d895be04c033e109c3597c03ed2ecf30e. Packet: /home/worsch/the-custodian/docs/changes/netkingdom-target-2026-10-18/forgejo-good-case-preparation-r1/README.md. Temporary public authorization-code/S256-PKCE client forgejo, MFA required, no client secret; confidential-client target/full security acceptance retained in CUST-WP-0082. Exact scope: publish two owner source declarations, register one issuer client, create one Forgejo auth source, restart each existing deployment once, verify; one extra restart each only for rollback. Preserve local login, images/resources/scanner/data. Execution conditional on independent NetKingdom, Apps and KeyCape approvals bound to the exact revision. Founder must authorize those reviewer agents. No native action has run; no source acceptance or prior scanner approval is carried forward as this go. Expected new infrastructure cost EUR0; recovery planning EUR300; temporary profile exposure needs explicit acceptance.",
|
||||
"decision_type": "pending",
|
||||
"status": "open",
|
||||
"rationale": null,
|
||||
"decided_by": null,
|
||||
"decided_at": null,
|
||||
"deadline": null,
|
||||
"escalation_note": null,
|
||||
"superseded_by": null,
|
||||
"created_at": "2026-10-18T17:19:22.260406Z",
|
||||
"updated_at": "2026-10-18T17:19:22.260406Z",
|
||||
"workstream_id": "dfadb19b-445e-59c6-bcd6-48e87b912c52"
|
||||
}
|
||||
|
|
@ -0,0 +1,124 @@
|
|||
{
|
||||
"sso": {
|
||||
"externalsecrets": [
|
||||
{
|
||||
"name": "keycape-approval-engine-operator-client",
|
||||
"uid": "962bce90-349d-4406-9da8-9fb9d145d1e6",
|
||||
"conditions": [
|
||||
{
|
||||
"type": "Ready",
|
||||
"status": "True",
|
||||
"reason": "SecretSynced"
|
||||
}
|
||||
],
|
||||
"target": "keycape-approval-engine-operator-client",
|
||||
"secretStoreRef": {
|
||||
"kind": "ClusterSecretStore",
|
||||
"name": "openbao-keycape-approval-engine-operator"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "keycape-factor-read",
|
||||
"uid": "c348de2a-4c3f-44d5-b20b-c1939db33df7",
|
||||
"conditions": [
|
||||
{
|
||||
"type": "Ready",
|
||||
"status": "True",
|
||||
"reason": "SecretSynced"
|
||||
}
|
||||
],
|
||||
"target": "keycape-factor-read",
|
||||
"secretStoreRef": {
|
||||
"kind": "ClusterSecretStore",
|
||||
"name": "openbao-keycape-factor-read"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "keycape-informed-decision-sitting-requester-client",
|
||||
"uid": "44a79a03-7ce7-4da3-92c3-7e674fd2f7a6",
|
||||
"conditions": [
|
||||
{
|
||||
"type": "Ready",
|
||||
"status": "True",
|
||||
"reason": "SecretSynced"
|
||||
}
|
||||
],
|
||||
"target": "keycape-informed-decision-sitting-requester-client",
|
||||
"secretStoreRef": {
|
||||
"kind": "ClusterSecretStore",
|
||||
"name": "openbao-keycape-informed-decision-sitting-requester"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "keycape-secrets-engine-approval-client",
|
||||
"uid": "2b7cda9e-0310-4c8c-9bec-f3c5a4f39507",
|
||||
"conditions": [
|
||||
{
|
||||
"type": "Ready",
|
||||
"status": "True",
|
||||
"reason": "SecretSynced"
|
||||
}
|
||||
],
|
||||
"target": "keycape-secrets-engine-approval-client",
|
||||
"secretStoreRef": {
|
||||
"kind": "ClusterSecretStore",
|
||||
"name": "openbao-keycape-secrets-engine-approval"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "keycape-secrets-engine-openbao-client",
|
||||
"uid": "2a6f6c12-2c09-4e13-ae09-8e6d7a99265d",
|
||||
"conditions": [
|
||||
{
|
||||
"type": "Ready",
|
||||
"status": "True",
|
||||
"reason": "SecretSynced"
|
||||
}
|
||||
],
|
||||
"target": "keycape-secrets-engine-openbao-client",
|
||||
"secretStoreRef": {
|
||||
"kind": "ClusterSecretStore",
|
||||
"name": "openbao-keycape-secrets-engine-openbao"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "keycape-secrets-engine-requester-client",
|
||||
"uid": "5e397d7c-180d-4ea6-9f45-08ba63cf52a5",
|
||||
"conditions": [
|
||||
{
|
||||
"type": "Ready",
|
||||
"status": "True",
|
||||
"reason": "SecretSynced"
|
||||
}
|
||||
],
|
||||
"target": "keycape-secrets-engine-requester-client",
|
||||
"secretStoreRef": {
|
||||
"kind": "ClusterSecretStore",
|
||||
"name": "openbao-keycape-secrets-engine-requester"
|
||||
}
|
||||
}
|
||||
],
|
||||
"secretstores": []
|
||||
},
|
||||
"forgejo": {
|
||||
"externalsecrets": [
|
||||
{
|
||||
"name": "forgejo-mailer",
|
||||
"uid": "cbbea71e-70a8-4010-aca9-9136920f2f58",
|
||||
"conditions": [
|
||||
{
|
||||
"type": "Ready",
|
||||
"status": "True",
|
||||
"reason": "SecretSynced"
|
||||
}
|
||||
],
|
||||
"target": "forgejo-mailer",
|
||||
"secretStoreRef": {
|
||||
"kind": "ClusterSecretStore",
|
||||
"name": "openbao-forgejo"
|
||||
}
|
||||
}
|
||||
],
|
||||
"secretstores": []
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,34 @@
|
|||
[
|
||||
{
|
||||
"namespace": "sso",
|
||||
"name": "keycape",
|
||||
"uid": "99ddd83c-cb3f-4847-bcf8-35f1aa87627f",
|
||||
"generation": 65,
|
||||
"resource_version": "75372295",
|
||||
"replicas": 1,
|
||||
"strategy": {
|
||||
"type": "Recreate"
|
||||
},
|
||||
"ready_replicas": 1,
|
||||
"images": {
|
||||
"keycape": "forgejo.coulomb.social/coulomb/key-cape@sha256:cdec9dd7c515f7e0a6169fc3fa560ee2cc47bf40d899755dec808c362f5ce010"
|
||||
},
|
||||
"pod_template_sha256": "e7d56eb38923824299934f92f5b5768f93bbf6bd4042a06a1fad0d86f05439a7"
|
||||
},
|
||||
{
|
||||
"namespace": "forgejo",
|
||||
"name": "forgejo-gitea",
|
||||
"uid": "e8eff0aa-64bb-4b44-9a80-a66612986355",
|
||||
"generation": 15,
|
||||
"resource_version": "77118297",
|
||||
"replicas": 1,
|
||||
"strategy": {
|
||||
"type": "Recreate"
|
||||
},
|
||||
"ready_replicas": 1,
|
||||
"images": {
|
||||
"gitea": "code.forgejo.org/forgejo/forgejo:11.0.3-rootless"
|
||||
},
|
||||
"pod_template_sha256": "fedc046d4512be337cf0a66427a8393843ba214007f043ee52e7cd9123ad5421"
|
||||
}
|
||||
]
|
||||
|
|
@ -0,0 +1,94 @@
|
|||
{
|
||||
"apply": [
|
||||
{
|
||||
"step": "register_public_client",
|
||||
"argv": [
|
||||
"/home/worsch/repo-manager/.venv/bin/python",
|
||||
"/home/worsch/net-kingdom/sso-mfa/k8s/keycape/keycape-client.py",
|
||||
"apply",
|
||||
"--client-file",
|
||||
"/home/worsch/the-custodian/docs/changes/netkingdom-target-2026-10-18/forgejo-good-case-preparation-r1/client-public-build.yaml",
|
||||
"--check-config",
|
||||
"/tmp/custodian-forgejo-public-checker-r1/keycape",
|
||||
"--expected-uid",
|
||||
"2e94519d-1550-41c7-9701-2efe47fe1fd3",
|
||||
"--expected-resource-version",
|
||||
"75372162",
|
||||
"--case-acceptance",
|
||||
"<ACTUAL_RESOLVED_FOUNDER_DECISION_FOR_THIS_BUILD_PROFILE>"
|
||||
]
|
||||
},
|
||||
{
|
||||
"step": "restart_keycape_once",
|
||||
"argv": [
|
||||
"ssh",
|
||||
"railiance01",
|
||||
"kubectl -n sso rollout restart deployment/keycape"
|
||||
]
|
||||
},
|
||||
{
|
||||
"step": "add_forgejo_source",
|
||||
"argv": [
|
||||
"ssh",
|
||||
"railiance01",
|
||||
"kubectl -n forgejo exec deployment/forgejo-gitea -c gitea -- /usr/local/bin/gitea --config /data/gitea/conf/app.ini admin auth add-oauth --name netkingdom --provider openidConnect --key forgejo --secret '' --auto-discover-url https://kc.coulomb.social/.well-known/openid-configuration --scopes openid,profile,email,groups"
|
||||
]
|
||||
},
|
||||
{
|
||||
"step": "restart_forgejo_once",
|
||||
"argv": [
|
||||
"ssh",
|
||||
"railiance01",
|
||||
"kubectl -n forgejo rollout restart deployment/forgejo-gitea"
|
||||
]
|
||||
}
|
||||
],
|
||||
"rollback": [
|
||||
{
|
||||
"step": "remove_only_this_proved_created_source",
|
||||
"argv": [
|
||||
"ssh",
|
||||
"railiance01",
|
||||
"kubectl -n forgejo exec deployment/forgejo-gitea -c gitea -- /usr/local/bin/gitea --config /data/gitea/conf/app.ini admin auth delete --id '<ID_PROVED_CREATED_BY_THIS_EXECUTION>'"
|
||||
],
|
||||
"condition": "Only before human linking; uncertain creation, drift or possible new links requires owner reconciliation, never blind deletion."
|
||||
},
|
||||
{
|
||||
"step": "restart_forgejo_if_source_was_removed",
|
||||
"argv": [
|
||||
"ssh",
|
||||
"railiance01",
|
||||
"kubectl -n forgejo rollout restart deployment/forgejo-gitea"
|
||||
]
|
||||
},
|
||||
{
|
||||
"step": "remove_only_exact_created_public_client",
|
||||
"argv": [
|
||||
"/home/worsch/repo-manager/.venv/bin/python",
|
||||
"/home/worsch/net-kingdom/sso-mfa/k8s/keycape/keycape-client.py",
|
||||
"apply",
|
||||
"--operation",
|
||||
"remove",
|
||||
"--client-file",
|
||||
"/home/worsch/the-custodian/docs/changes/netkingdom-target-2026-10-18/forgejo-good-case-preparation-r1/client-public-build.yaml",
|
||||
"--check-config",
|
||||
"/tmp/custodian-forgejo-public-checker-r1/keycape",
|
||||
"--expected-uid",
|
||||
"2e94519d-1550-41c7-9701-2efe47fe1fd3",
|
||||
"--expected-resource-version",
|
||||
"<VERIFIED_CURRENT_REGISTRY_REVISION>",
|
||||
"--case-acceptance",
|
||||
"<SAME_ACTUAL_RESOLVED_FOUNDER_DECISION>"
|
||||
]
|
||||
},
|
||||
{
|
||||
"step": "restart_keycape_if_registry_was_reverted",
|
||||
"argv": [
|
||||
"ssh",
|
||||
"railiance01",
|
||||
"kubectl -n sso rollout restart deployment/keycape"
|
||||
]
|
||||
}
|
||||
],
|
||||
"dynamic_arguments": "Only actual approval reference, proved-created source ID and verified registry resourceVersion are filled from real receipts. They do not authorize changed profile/source inputs."
|
||||
}
|
||||
|
|
@ -0,0 +1,8 @@
|
|||
{
|
||||
"exit_code": 0,
|
||||
"header_recognized": true,
|
||||
"sources": [],
|
||||
"raw_output_discarded": true,
|
||||
"configuration_values_emitted": false,
|
||||
"mode": "read-only authentication-source inventory"
|
||||
}
|
||||
|
|
@ -0,0 +1,138 @@
|
|||
# Non-secret Forgejo Helm values (gitea-charts/gitea chart + Forgejo image).
|
||||
# Layer helm/forgejo-secrets.sops.yaml for database password and admin credentials.
|
||||
# Recreate avoids leveldb queue lock fights on the shared RWO PVC during upgrades.
|
||||
strategy:
|
||||
type: Recreate
|
||||
|
||||
service:
|
||||
http:
|
||||
type: ClusterIP
|
||||
|
||||
image:
|
||||
registry: code.forgejo.org
|
||||
repository: forgejo/forgejo
|
||||
tag: "11.0.3"
|
||||
rootless: true
|
||||
|
||||
persistence:
|
||||
enabled: true
|
||||
size: 10Gi
|
||||
|
||||
postgresql:
|
||||
enabled: false
|
||||
|
||||
postgresql-ha:
|
||||
enabled: false
|
||||
|
||||
redis-cluster:
|
||||
enabled: false
|
||||
|
||||
valkey-cluster:
|
||||
enabled: false
|
||||
|
||||
# File subPath keeps Git from copying ConfigMap projection symlinks.
|
||||
extraVolumes:
|
||||
- name: secret-scan-template
|
||||
configMap:
|
||||
name: forgejo-secret-template-832f6dc6b809
|
||||
defaultMode: 365
|
||||
extraContainerVolumeMounts:
|
||||
- &id001
|
||||
name: secret-scan-template
|
||||
mountPath: /opt/forge-secret-template/hooks/pre-receive.d/10-custodian-secret-scan
|
||||
subPath: hook
|
||||
readOnly: true
|
||||
extraInitVolumeMounts:
|
||||
- *id001
|
||||
|
||||
# Verify admitted scanner custody after Kubernetes fsGroup volume processing.
|
||||
# Only the existing security subtree is mounted; no credentials are needed.
|
||||
preExtraInitContainers:
|
||||
- name: verify-secret-scanner
|
||||
image: code.forgejo.org/forgejo/forgejo@sha256:691657c7e64af7730a2e885e6d022cf16ae60724931c5392bd61838afadcaeba
|
||||
imagePullPolicy: IfNotPresent
|
||||
command:
|
||||
- sh
|
||||
- -c
|
||||
- "#!/bin/sh\nset +vx\nset -eu\ntest \"$(id -u)\" = 1000\ntest -d /data && test ! -L /data\ntest -d /data/security && test ! -L /data/security\ntest -d /data/security/receive-secret-scan && test ! -L /data/security/receive-secret-scan\ntest -d /data/security/receive-secret-scan/20261012-57a0642b6251 && test ! -L /data/security/receive-secret-scan/20261012-57a0642b6251\ntest -f /data/security/receive-secret-scan/20261012-57a0642b6251/receive-secret-scan && test ! -L /data/security/receive-secret-scan/20261012-57a0642b6251/receive-secret-scan\ntest \"$(stat -c %u /data/security/receive-secret-scan/20261012-57a0642b6251/receive-secret-scan)\" = 1000\ntest \"$(sha256sum /data/security/receive-secret-scan/20261012-57a0642b6251/receive-secret-scan | cut -d \" \" -f 1)\" = 57a0642b625105440fe146fceb4d244fd2dd08a75beb316831675643e6cc27ec\ncase \"$(stat -c %a /data/security/receive-secret-scan/20261012-57a0642b6251/receive-secret-scan)\" in 755|775) ;; *) exit 21;; esac\ntest -f /data/security/receive-secret-scan/20261012-57a0642b6251/gitleaks\
|
||||
\ && test ! -L /data/security/receive-secret-scan/20261012-57a0642b6251/gitleaks\ntest \"$(stat -c %u /data/security/receive-secret-scan/20261012-57a0642b6251/gitleaks)\" = 1000\ntest \"$(sha256sum /data/security/receive-secret-scan/20261012-57a0642b6251/gitleaks | cut -d \" \" -f 1)\" = 88f91962aa2f93ac6ab281d553b9e125f5197bbbce38f9f2437f7299c32e5509\ncase \"$(stat -c %a /data/security/receive-secret-scan/20261012-57a0642b6251/gitleaks)\" in 755|775) ;; *) exit 21;; esac\nif [ \"${1:-check}\" = apply ]; then\n chmod 755 /data/security/receive-secret-scan/20261012-57a0642b6251/receive-secret-scan\n test \"$(stat -c %a /data/security/receive-secret-scan/20261012-57a0642b6251/receive-secret-scan)\" = 755\n chmod 755 /data/security/receive-secret-scan/20261012-57a0642b6251/gitleaks\n test \"$(stat -c %a /data/security/receive-secret-scan/20261012-57a0642b6251/gitleaks)\" = 755\nfi\n\n#!/bin/sh\nset +vx\nset -eu\ntest \"$(id -u)\" = 1000\ntest -d /data && test ! -L /data\ntest -d /data/security\
|
||||
\ && test ! -L /data/security\ntest -d /data/security/receive-secret-scan && test ! -L /data/security/receive-secret-scan\ntest -d /data/security/receive-secret-scan/20261018-73ee04078786 && test ! -L /data/security/receive-secret-scan/20261018-73ee04078786\ntest -f /data/security/receive-secret-scan/20261018-73ee04078786/receive-secret-scan && test ! -L /data/security/receive-secret-scan/20261018-73ee04078786/receive-secret-scan\ntest \"$(stat -c %u /data/security/receive-secret-scan/20261018-73ee04078786/receive-secret-scan)\" = 1000\ntest \"$(sha256sum /data/security/receive-secret-scan/20261018-73ee04078786/receive-secret-scan | cut -d \" \" -f 1)\" = 73ee04078786801fc74d80d2bef122b7764c93856e403e8de5b078336d7f9e1a\ncase \"$(stat -c %a /data/security/receive-secret-scan/20261018-73ee04078786/receive-secret-scan)\" in 755|775) ;; *) exit 21;; esac\ntest -f /data/security/receive-secret-scan/20261018-73ee04078786/gitleaks && test ! -L /data/security/receive-secret-scan/20261018-73ee04078786/gitleaks\n\
|
||||
test \"$(stat -c %u /data/security/receive-secret-scan/20261018-73ee04078786/gitleaks)\" = 1000\ntest \"$(sha256sum /data/security/receive-secret-scan/20261018-73ee04078786/gitleaks | cut -d \" \" -f 1)\" = 88f91962aa2f93ac6ab281d553b9e125f5197bbbce38f9f2437f7299c32e5509\ncase \"$(stat -c %a /data/security/receive-secret-scan/20261018-73ee04078786/gitleaks)\" in 755|775) ;; *) exit 21;; esac\nif [ \"${1:-check}\" = apply ]; then\n chmod 755 /data/security/receive-secret-scan/20261018-73ee04078786/receive-secret-scan\n test \"$(stat -c %a /data/security/receive-secret-scan/20261018-73ee04078786/receive-secret-scan)\" = 755\n chmod 755 /data/security/receive-secret-scan/20261018-73ee04078786/gitleaks\n test \"$(stat -c %a /data/security/receive-secret-scan/20261018-73ee04078786/gitleaks)\" = 755\nfi\n"
|
||||
- sh
|
||||
- apply
|
||||
securityContext:
|
||||
runAsUser: 1000
|
||||
runAsGroup: 1000
|
||||
runAsNonRoot: true
|
||||
allowPrivilegeEscalation: false
|
||||
readOnlyRootFilesystem: true
|
||||
capabilities:
|
||||
drop:
|
||||
- ALL
|
||||
resources:
|
||||
requests:
|
||||
cpu: 10m
|
||||
memory: 32Mi
|
||||
limits:
|
||||
cpu: 100m
|
||||
memory: 64Mi
|
||||
volumeMounts:
|
||||
- name: data
|
||||
mountPath: /data/security
|
||||
subPath: security
|
||||
terminationMessagePath: /dev/termination-log
|
||||
terminationMessagePolicy: File
|
||||
|
||||
gitea:
|
||||
oauth:
|
||||
- name: netkingdom
|
||||
provider: openidConnect
|
||||
key: forgejo
|
||||
secret: ""
|
||||
autoDiscoverUrl: https://kc.coulomb.social/.well-known/openid-configuration
|
||||
scopes: openid,profile,email,groups
|
||||
admin:
|
||||
username: forgejo_admin
|
||||
email: admin@coulomb.social
|
||||
passwordMode: initialOnlyRequireReset
|
||||
# Mailer password from OpenBao via ExternalSecret forgejo-mailer (not Helm/SOPS).
|
||||
additionalConfigFromEnvs:
|
||||
- name: GITEA__GIT_0X2E_CONFIG__INIT_0X2E_TEMPLATEDIR
|
||||
value: /opt/forge-secret-template
|
||||
- name: GITEA__mailer__PASSWD
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: forgejo-mailer
|
||||
key: MAILER_PASSWD
|
||||
config:
|
||||
oauth2_client:
|
||||
ENABLE_AUTO_REGISTRATION: false
|
||||
ACCOUNT_LINKING: login
|
||||
OPENID_CONNECT_SCOPES: openid profile email groups
|
||||
database:
|
||||
DB_TYPE: postgres
|
||||
HOST: forgejo-db-rw.databases.svc.cluster.local:5432
|
||||
NAME: forgejo
|
||||
USER: forgejo
|
||||
server:
|
||||
DOMAIN: forgejo.coulomb.social
|
||||
SSH_DOMAIN: forgejo.coulomb.social
|
||||
SSH_PORT: 22
|
||||
SSH_LISTEN_PORT: 2222
|
||||
DISABLE_SSH: false
|
||||
service:
|
||||
DISABLE_REGISTRATION: true
|
||||
security:
|
||||
INSTALL_LOCK: true
|
||||
session:
|
||||
COOKIE_SECURE: true
|
||||
mailer:
|
||||
ENABLED: true
|
||||
FROM: forgejo@coulomb.social
|
||||
# IONOS submission port 587 uses STARTTLS, not implicit TLS (smtps).
|
||||
PROTOCOL: smtp+starttls
|
||||
SMTP_ADDR: smtp.ionos.de
|
||||
SMTP_PORT: 587
|
||||
USER: forgejo@coulomb.social
|
||||
actions:
|
||||
ENABLED: true
|
||||
|
|
@ -0,0 +1,12 @@
|
|||
{
|
||||
"verified": true,
|
||||
"chart": "gitea 12.5.0",
|
||||
"chart_sha256": "2d6f35f3d1ff0cfcab0f22142d20a1d6405fa974a79d7a18000904d233131823",
|
||||
"public_add_oauth_command": "gitea admin auth add-oauth --auto-discover-url \"https://kc.coulomb.social/.well-known/openid-configuration\" --key \"forgejo\" --name \"netkingdom\" --provider \"openidConnect\" --scopes \"openid,profile,email,groups\" --secret \"\"",
|
||||
"rendered_resource_count": 8,
|
||||
"real_credentials_used": false,
|
||||
"private_render_discarded": true,
|
||||
"apps_base_sha256": "64ee8cbac800d9c5743ccdba47eb019064d23afdbc2d92c2568c7eafb107c18f",
|
||||
"apps_postimage_sha256": "210e6e450ad9fbe7e25ad7ec8ae5d4e9bf9bbc9e7f65d52ac670a66a9dd79d06",
|
||||
"installed_scanner_bytes_preserved": true
|
||||
}
|
||||
|
|
@ -0,0 +1,31 @@
|
|||
{
|
||||
"scope": "Read-only public discovery and unauthenticated authorization-start probe; no factor entry, admin access or config mutation",
|
||||
"issuer": "https://kc.coulomb.social",
|
||||
"authorization_endpoint_path": "/authorize",
|
||||
"client_id": "forgejo",
|
||||
"callback_uri": "https://forgejo.coulomb.social/user/oauth2/netkingdom/callback",
|
||||
"requested_scopes": [
|
||||
"openid",
|
||||
"profile",
|
||||
"email",
|
||||
"groups"
|
||||
],
|
||||
"pkce": "S256",
|
||||
"http_status": 400,
|
||||
"known_oauth_error_markers": [],
|
||||
"redirect_host": null,
|
||||
"redirect_path": "",
|
||||
"login_form_present": false,
|
||||
"tokens_or_codes_obtained": false,
|
||||
"raw_html_or_state_published": false,
|
||||
"interpretation": "The live issuer refuses the declared forgejo client as unknown client_id. Native client registration remains a real prerequisite, separate from deferred hardening.",
|
||||
"followup_closed_message_matches": {
|
||||
"unknown client_id": true,
|
||||
"invalid redirect_uri": false,
|
||||
"redirect_uri not allowed": false,
|
||||
"unsupported response_type": false,
|
||||
"invalid scope": false,
|
||||
"MFA required": false,
|
||||
"PKCE required": false
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,12 @@
|
|||
{
|
||||
"exit_code": 0,
|
||||
"safe_settings": {
|
||||
"ROOT_URL": "https://forgejo.coulomb.social/",
|
||||
"DISABLE_REGISTRATION": "true"
|
||||
},
|
||||
"unrecognized_output_discarded": false,
|
||||
"default_enable_auto_registration": false,
|
||||
"default_account_linking": "login",
|
||||
"default_openid_scopes": "use the source scopes",
|
||||
"native_changes": false
|
||||
}
|
||||
|
|
@ -0,0 +1,15 @@
|
|||
# Temporary build profile; activation requires the exact CUST80-FORGEJO-PUBLIC-BUILD-20261018 approval.
|
||||
# The accepted confidential-client case remains the target; full acceptance is CUST-WP-0082-T20.
|
||||
clientId: forgejo
|
||||
displayName: Forgejo source forge
|
||||
redirectUris:
|
||||
- https://forgejo.coulomb.social/user/oauth2/netkingdom/callback
|
||||
allowedScopes:
|
||||
- openid
|
||||
- profile
|
||||
- email
|
||||
- groups
|
||||
grantTypes:
|
||||
- authorization_code
|
||||
clientType: public
|
||||
mfaRequired: true
|
||||
|
|
@ -0,0 +1,14 @@
|
|||
{
|
||||
"verified": true,
|
||||
"proposal_sha256": "61eff8b488674e75510133fe118de86d895be04c033e109c3597c03ed2ecf30e",
|
||||
"profile_approved": false,
|
||||
"native_effects_executed": false,
|
||||
"source_input_pins_matched": 12,
|
||||
"upstream_protocol_and_release": "passed",
|
||||
"native_registry_dry_run": "passed without mutation",
|
||||
"original_task_bindings_preserved": 40,
|
||||
"current_task_bindings": 69,
|
||||
"unrelated_work_preserved": 8,
|
||||
"T41": "done preparation only",
|
||||
"T42": "wait actual scoped founder go and three authorized owner reviews"
|
||||
}
|
||||
|
|
@ -0,0 +1,18 @@
|
|||
{
|
||||
"verified": true,
|
||||
"proposal_sha256": "fcea2cd32b8ee2634d2ebeb9ef4b659b6b85f8a4878413ed96e5d06cb09892c3",
|
||||
"profile_approved": false,
|
||||
"native_effects_executed": false,
|
||||
"source_input_pins_matched": 12,
|
||||
"upstream_protocol_and_release": "passed",
|
||||
"native_registry_dry_run": "passed without mutation",
|
||||
"original_task_bindings_preserved": 40,
|
||||
"current_task_bindings": 69,
|
||||
"unrelated_work_preserved": 8,
|
||||
"T41": "done preparation only",
|
||||
"T42": "wait actual scoped founder go and three authorized owner reviews",
|
||||
"proposal_revision": 2,
|
||||
"original_revision_retained": true,
|
||||
"source_pins_semantically_unchanged": true,
|
||||
"proposal_scan_with_unchanged_rules": "clean"
|
||||
}
|
||||
|
|
@ -0,0 +1,136 @@
|
|||
{
|
||||
"proposal_id": "CUST80-FORGEJO-PUBLIC-BUILD-20261018",
|
||||
"revision": 2,
|
||||
"state": "proposed_not_approved",
|
||||
"purpose": "Deliver actual Forgejo NetKingdom sign-in using the existing upstream release under a temporary public PKCE build profile.",
|
||||
"decision_basis": "Founder good-case-first direction9663d4ca; this new native/profile action needs separate explicit go.",
|
||||
"mode_of_authority": "ADMINISTER @ realm:kubernetes/railiance01; activation only after actual scoped APPROVED disposition",
|
||||
"target": {
|
||||
"cluster_node_ip": "92.205.62.239",
|
||||
"issuer_config": "sso/keycape-config",
|
||||
"issuer_client_id": "forgejo",
|
||||
"issuer_config_uid": "2e94519d-1550-41c7-9701-2efe47fe1fd3",
|
||||
"issuer_config_resource_version": "75372162",
|
||||
"forgejo_auth_source": "netkingdom",
|
||||
"callback_uri": "https://forgejo.coulomb.social/user/oauth2/netkingdom/callback"
|
||||
},
|
||||
"profile": {
|
||||
"client_type": "public",
|
||||
"authorization_code_pkce": "S256",
|
||||
"scopes": [
|
||||
"openid",
|
||||
"profile",
|
||||
"email",
|
||||
"groups"
|
||||
],
|
||||
"mfa_required": true,
|
||||
"client_secret_generated_or_distributed": false,
|
||||
"automatic_new_account_registration": false,
|
||||
"existing_account_linking": "login",
|
||||
"local_signin_and_local_2fa": "preserved",
|
||||
"role_or_group_automation": "none; existing Forgejo account permissions retained"
|
||||
},
|
||||
"explicit_deferrals": [
|
||||
"Confidential-client credential generation and issuer/consumer delivery remain the unchanged target in CUST-WP-0082-T20 and existing owner plans.",
|
||||
"This scoped build-profile action does not claim the full SecurityCaseType lifecycle, protected executor/kernel, approval/PDP or independent audit acceptance.",
|
||||
"Full current KeyCape checks on each API/Git/SSH operation, any-of group policy, revocation and isolation remain unaccepted hardening work."
|
||||
],
|
||||
"admission": {
|
||||
"founder": "Actual explicit go bound to this revision/digest, never inferred from previous scanner or sequencing decisions.",
|
||||
"independent_reviews_required": [
|
||||
"net-kingdom",
|
||||
"railiance-apps",
|
||||
"key-cape"
|
||||
],
|
||||
"reviewer_authorization": "Founder must authorize those owner agents; no new worker has been spawned.",
|
||||
"each_review_must_bind": "This proposal digest and exact producer/consumer source pins, declared build-profile limits, activation and rollback.",
|
||||
"owner_reviews_status": "not requested or written yet"
|
||||
},
|
||||
"source_pins": [
|
||||
{
|
||||
"path": "/home/worsch/net-kingdom/sso-mfa/k8s/keycape/keycape-registration-adapter.py",
|
||||
"sha256": "a53beea4dbbe373ce7a5b84d55e79be8407ab9688037c7ee057afcc7be576047"
|
||||
},
|
||||
{
|
||||
"path": "/home/worsch/net-kingdom/sso-mfa/k8s/keycape/keycape-client.py",
|
||||
"sha256": "4b63161cf3365d34dd684a3116e2c935baaf7f6aa4b30983f6883546db3d6591"
|
||||
},
|
||||
{
|
||||
"path": "/home/worsch/net-kingdom/sso-mfa/k8s/keycape/portal-client-rollout.py",
|
||||
"sha256": "158e1d36eb881cdd0a2eda91b472adea5e8fde5061e00d26dda368580aeb871f"
|
||||
},
|
||||
{
|
||||
"path": "/home/worsch/net-kingdom/sso-mfa/k8s/keycape/openbao-client-config.py",
|
||||
"sha256": "1b908b4c36134212d78f9fbe7a2a201eb3ced06d31bb1dcea4a59d80689d84b1"
|
||||
},
|
||||
{
|
||||
"path": "/home/worsch/net-kingdom/sso-mfa/k8s/keycape/register-user-engine-portal.py",
|
||||
"sha256": "67173edc44f5457e7b6a3e0fb2321b398159cfce5dea8ead01eabf835fb38d76"
|
||||
},
|
||||
{
|
||||
"path": "/tmp/custodian-forgejo-public-checker-r1/keycape",
|
||||
"sha256": "be37738fe1c08259fb4f9ca0daf0f9301bfffc237e5bb38435525dfbf11ccff5"
|
||||
},
|
||||
{
|
||||
"path": "/home/worsch/.cache/helm/repository/gitea-12.5.0.tgz",
|
||||
"sha256": "2d6f35f3d1ff0cfcab0f22142d20a1d6405fa974a79d7a18000904d233131823"
|
||||
}
|
||||
],
|
||||
"packet_input_sha256": {
|
||||
"client-public-build.yaml": "942d642a40bf76f11d09336fa6e90f861b5fccffa7ba6143bdcbfa8a1498bb6b",
|
||||
"forgejo-values.postimage.yaml": "210e6e450ad9fbe7e25ad7ec8ae5d4e9bf9bbc9e7f65d52ac670a66a9dd79d06",
|
||||
"net-kingdom-client.postimage.yaml": "3210f662cf65d54e912969ad03767053baf5c867402fc5f24b7017fb3b82d8e1",
|
||||
"execution-steps.json": "98cb80a42e22315a7e846418f5157091c2e246ed3eb86e26dc46a3d5bec69ee6",
|
||||
"deployment-preconditions.json": "940594b48dfc024ed3eb83f86f051ee539b2ad90a18aa088cfa73483618e529e"
|
||||
},
|
||||
"owner_source_publication": {
|
||||
"railiance-apps": {
|
||||
"base_revision": "2fdc198b2bb8e6d4ad50768c22cf99328828eb4f",
|
||||
"path": "helm/forgejo-values.yaml",
|
||||
"base_sha256": "64ee8cbac800d9c5743ccdba47eb019064d23afdbc2d92c2568c7eafb107c18f",
|
||||
"postimage": "forgejo-values.postimage.yaml"
|
||||
},
|
||||
"net-kingdom": {
|
||||
"base_revision": "b3f31d9f26b98a3b92690e0be34a205e49fa3956",
|
||||
"path": "sso-mfa/k8s/keycape/client-profiles/forgejo-public-build.yaml",
|
||||
"precondition": "absent",
|
||||
"postimage": "net-kingdom-client.postimage.yaml"
|
||||
},
|
||||
"method": "Existing file-scoped publication helper, ordinary hooks and consistency/projection; preserve unrelated index/worktree states."
|
||||
},
|
||||
"preconditions": [
|
||||
"Revalidate all source and packet hashes.",
|
||||
"Revalidate deployment UID, generation, images and pod-template hashes from deployment-preconditions.json immediately before mutation.",
|
||||
"KeyCape live/proposed config must pass the reproduced be37738f checker; registry entry remains absent and UID/resourceVersion exact.",
|
||||
"Forgejo authentication-source inventory must remain empty before creation; native local settings remain auto-registration false, linking login and expected ROOT_URL.",
|
||||
"Read node resource requests and retain Recreate, one replica each, same images/resources. Current CPU requests3605m/4000m; no additional pod requested.",
|
||||
"Run in the founder-attended window; no unattended schedule or later generic grant."
|
||||
],
|
||||
"execution": "Exactly the four apply commands in execution-steps.json, with Ready and discovery/readback between phases; retain both changed and unchanged failures.",
|
||||
"verification": [
|
||||
"KeyCape same image Ready, no startup restart loop, discovery200 and authorization start accepts the exact forgejo client/callback with MFA still required.",
|
||||
"Forgejo same image Ready; one netkingdom source; local sign-in still available; redirect has exact HTTPS callback, issuer, scopes, state and S256 challenge.",
|
||||
"Compare pod templates after stripping only the approved restart annotation; preserve all scanner hook/template/guard and other configuration.",
|
||||
"Record actual source ID and registry before/after UID/resourceVersion. Do not claim browser login from readiness.",
|
||||
"Then T43 actual founder account linking/sign-in and T44 actual coding-agent Git/authenticated API trials; no trial impersonation."
|
||||
],
|
||||
"rollback": "execution-steps.json: created-only source removal before human linking, exact public registry removal, up to one additional restart per changed deployment. Preserve unrelated resources. Uncertainty/drift/new links stops automatic undo for owner reconciliation.",
|
||||
"restart_budget": {
|
||||
"normal": {
|
||||
"keycape": 1,
|
||||
"forgejo": 1
|
||||
},
|
||||
"additional_only_if_rollback": {
|
||||
"keycape": 1,
|
||||
"forgejo": 1
|
||||
},
|
||||
"readiness_timeout_seconds_each": 180,
|
||||
"impact": "Each single-replica Recreate briefly interrupts its service and in-flight sign-in. Stop at a failed phase; no continuing to consumer if issuer fails."
|
||||
},
|
||||
"cost_estimate_eur": 0,
|
||||
"cost_basis": "No purchase, extra deployment or resource allocation; existing infrastructure only. Agent/model/tool spend is outside this estimate.",
|
||||
"risk_estimate_eur": 300,
|
||||
"risk_basis": "Founder-reviewed planning estimate: up to three hours recovery at EUR100/hour; temporary build-mode identity-profile exposure is unpriced and requires explicit human acceptance rather than being declared covered by that number. No customer production approval.",
|
||||
"native_changes_already_executed": false,
|
||||
"ordinary_confidential_case_status_changed": false
|
||||
}
|
||||
|
|
@ -0,0 +1 @@
|
|||
fcea2cd32b8ee2634d2ebeb9ef4b659b6b85f8a4878413ed96e5d06cb09892c3 proposal.json
|
||||
|
|
@ -0,0 +1,86 @@
|
|||
{
|
||||
"exit_code": 1,
|
||||
"seconds": 1.018,
|
||||
"argv": [
|
||||
"bwrap",
|
||||
"--unshare-net",
|
||||
"--unshare-pid",
|
||||
"--die-with-parent",
|
||||
"--new-session",
|
||||
"--ro-bind",
|
||||
"/usr",
|
||||
"/usr",
|
||||
"--ro-bind",
|
||||
"/lib",
|
||||
"/lib",
|
||||
"--ro-bind",
|
||||
"/lib64",
|
||||
"/lib64",
|
||||
"--ro-bind",
|
||||
"/home/worsch/go/pkg/mod",
|
||||
"/home/worsch/go/pkg/mod",
|
||||
"--ro-bind",
|
||||
"/tmp/custodian-forgejo-successor-attempt2/modules",
|
||||
"/tmp/custodian-forgejo-successor-attempt2/modules",
|
||||
"--symlink",
|
||||
"usr/bin",
|
||||
"/bin",
|
||||
"--proc",
|
||||
"/proc",
|
||||
"--dev",
|
||||
"/dev",
|
||||
"--tmpfs",
|
||||
"/tmp",
|
||||
"--bind",
|
||||
"/tmp/custodian-forgejo-public-pkce-r1",
|
||||
"/work",
|
||||
"--ro-bind",
|
||||
"/tmp/custodian-forgejo-successor/go",
|
||||
"/toolchain",
|
||||
"--chdir",
|
||||
"/work/source/src",
|
||||
"--clearenv",
|
||||
"--setenv",
|
||||
"PATH",
|
||||
"/toolchain/bin:/usr/bin:/bin",
|
||||
"--setenv",
|
||||
"GOTOOLCHAIN",
|
||||
"local",
|
||||
"--setenv",
|
||||
"GOPROXY",
|
||||
"off",
|
||||
"--setenv",
|
||||
"GOSUMDB",
|
||||
"off",
|
||||
"--setenv",
|
||||
"GOMODCACHE",
|
||||
"/work/modules",
|
||||
"--setenv",
|
||||
"GOCACHE",
|
||||
"/work/gocache",
|
||||
"--setenv",
|
||||
"GOMAXPROCS",
|
||||
"4",
|
||||
"--setenv",
|
||||
"CGO_ENABLED",
|
||||
"0",
|
||||
"--setenv",
|
||||
"PUBLIC_PKCE_REPORT",
|
||||
"/work/public-pkce-report.json",
|
||||
"--",
|
||||
"go",
|
||||
"test",
|
||||
"-mod=mod",
|
||||
"-p",
|
||||
"4",
|
||||
"-json",
|
||||
"-count=1",
|
||||
"-run",
|
||||
"^TestUpstreamForgejoPublicPKCE$",
|
||||
"./internal/server/oidc"
|
||||
],
|
||||
"real_credentials": false,
|
||||
"network": "isolated loopback only",
|
||||
"live_changes": false,
|
||||
"profile_approved": false
|
||||
}
|
||||
|
|
@ -0,0 +1,7 @@
|
|||
go: downloading github.com/markbates/goth v1.80.0
|
||||
{"ImportPath":"github.com/markbates/goth/providers/openidConnect","Action":"build-output","Output":"# keycape/internal/server/oidc\n"}
|
||||
{"ImportPath":"github.com/markbates/goth/providers/openidConnect","Action":"build-output","Output":"internal/server/oidc/forgejo_public_interop_test.go:19:2: mkdir /work/modules/cache/download/github.com/markbates: file exists\n"}
|
||||
{"ImportPath":"github.com/markbates/goth/providers/openidConnect","Action":"build-fail"}
|
||||
{"Time":"2026-10-18T17:04:14.412495465Z","Action":"start","Package":"keycape/internal/server/oidc"}
|
||||
{"Time":"2026-10-18T17:04:14.412578559Z","Action":"output","Package":"keycape/internal/server/oidc","Output":"FAIL\tkeycape/internal/server/oidc [setup failed]\n"}
|
||||
{"Time":"2026-10-18T17:04:14.412620525Z","Action":"fail","Package":"keycape/internal/server/oidc","Elapsed":0,"FailedBuild":"github.com/markbates/goth/providers/openidConnect"}
|
||||
|
|
@ -0,0 +1,86 @@
|
|||
{
|
||||
"exit_code": 1,
|
||||
"seconds": 0.266,
|
||||
"argv": [
|
||||
"bwrap",
|
||||
"--unshare-net",
|
||||
"--unshare-pid",
|
||||
"--die-with-parent",
|
||||
"--new-session",
|
||||
"--ro-bind",
|
||||
"/usr",
|
||||
"/usr",
|
||||
"--ro-bind",
|
||||
"/lib",
|
||||
"/lib",
|
||||
"--ro-bind",
|
||||
"/lib64",
|
||||
"/lib64",
|
||||
"--ro-bind",
|
||||
"/home/worsch/go/pkg/mod",
|
||||
"/home/worsch/go/pkg/mod",
|
||||
"--symlink",
|
||||
"usr/bin",
|
||||
"/bin",
|
||||
"--proc",
|
||||
"/proc",
|
||||
"--dev",
|
||||
"/dev",
|
||||
"--tmpfs",
|
||||
"/tmp",
|
||||
"--ro-bind",
|
||||
"/tmp/custodian-forgejo-successor-attempt2/modules",
|
||||
"/tmp/custodian-forgejo-successor-attempt2/modules",
|
||||
"--bind",
|
||||
"/tmp/custodian-forgejo-public-pkce-r1",
|
||||
"/work",
|
||||
"--ro-bind",
|
||||
"/tmp/custodian-forgejo-successor/go",
|
||||
"/toolchain",
|
||||
"--chdir",
|
||||
"/work/source/src",
|
||||
"--clearenv",
|
||||
"--setenv",
|
||||
"PATH",
|
||||
"/toolchain/bin:/usr/bin:/bin",
|
||||
"--setenv",
|
||||
"GOTOOLCHAIN",
|
||||
"local",
|
||||
"--setenv",
|
||||
"GOPROXY",
|
||||
"off",
|
||||
"--setenv",
|
||||
"GOSUMDB",
|
||||
"off",
|
||||
"--setenv",
|
||||
"GOMODCACHE",
|
||||
"/work/modules",
|
||||
"--setenv",
|
||||
"GOCACHE",
|
||||
"/work/gocache",
|
||||
"--setenv",
|
||||
"GOMAXPROCS",
|
||||
"4",
|
||||
"--setenv",
|
||||
"CGO_ENABLED",
|
||||
"0",
|
||||
"--setenv",
|
||||
"PUBLIC_PKCE_REPORT",
|
||||
"/work/public-pkce-report.json",
|
||||
"--",
|
||||
"go",
|
||||
"test",
|
||||
"-mod=mod",
|
||||
"-p",
|
||||
"4",
|
||||
"-json",
|
||||
"-count=1",
|
||||
"-run",
|
||||
"^TestUpstreamForgejoPublicPKCE$",
|
||||
"./internal/server/oidc"
|
||||
],
|
||||
"real_credentials": false,
|
||||
"network": "isolated loopback only",
|
||||
"live_changes": false,
|
||||
"profile_approved": false
|
||||
}
|
||||
|
|
@ -0,0 +1,7 @@
|
|||
go: downloading golang.org/x/oauth2 v0.28.0
|
||||
{"ImportPath":"golang.org/x/oauth2","Action":"build-output","Output":"# keycape/internal/server/oidc\n"}
|
||||
{"ImportPath":"golang.org/x/oauth2","Action":"build-output","Output":"/work/modules/github.com/markbates/goth@v1.80.0/provider.go:8:2: open /work/modules/cache/download/golang.org/x/oauth2/@v/v0.28.0.lock: read-only file system\n"}
|
||||
{"ImportPath":"golang.org/x/oauth2","Action":"build-fail"}
|
||||
{"Time":"2026-10-18T17:04:41.667384334Z","Action":"start","Package":"keycape/internal/server/oidc"}
|
||||
{"Time":"2026-10-18T17:04:41.667420944Z","Action":"output","Package":"keycape/internal/server/oidc","Output":"FAIL\tkeycape/internal/server/oidc [setup failed]\n"}
|
||||
{"Time":"2026-10-18T17:04:41.667446255Z","Action":"fail","Package":"keycape/internal/server/oidc","Elapsed":0,"FailedBuild":"golang.org/x/oauth2"}
|
||||
|
|
@ -0,0 +1,86 @@
|
|||
{
|
||||
"exit_code": 0,
|
||||
"seconds": 19.159,
|
||||
"argv": [
|
||||
"bwrap",
|
||||
"--unshare-net",
|
||||
"--unshare-pid",
|
||||
"--die-with-parent",
|
||||
"--new-session",
|
||||
"--ro-bind",
|
||||
"/usr",
|
||||
"/usr",
|
||||
"--ro-bind",
|
||||
"/lib",
|
||||
"/lib",
|
||||
"--ro-bind",
|
||||
"/lib64",
|
||||
"/lib64",
|
||||
"--ro-bind",
|
||||
"/home/worsch/go/pkg/mod",
|
||||
"/home/worsch/go/pkg/mod",
|
||||
"--symlink",
|
||||
"usr/bin",
|
||||
"/bin",
|
||||
"--proc",
|
||||
"/proc",
|
||||
"--dev",
|
||||
"/dev",
|
||||
"--tmpfs",
|
||||
"/tmp",
|
||||
"--ro-bind",
|
||||
"/tmp/custodian-forgejo-successor-attempt2/modules",
|
||||
"/tmp/custodian-forgejo-successor-attempt2/modules",
|
||||
"--bind",
|
||||
"/tmp/custodian-forgejo-public-pkce-r1",
|
||||
"/work",
|
||||
"--ro-bind",
|
||||
"/tmp/custodian-forgejo-successor/go",
|
||||
"/toolchain",
|
||||
"--chdir",
|
||||
"/work/source/src",
|
||||
"--clearenv",
|
||||
"--setenv",
|
||||
"PATH",
|
||||
"/toolchain/bin:/usr/bin:/bin",
|
||||
"--setenv",
|
||||
"GOTOOLCHAIN",
|
||||
"local",
|
||||
"--setenv",
|
||||
"GOPROXY",
|
||||
"off",
|
||||
"--setenv",
|
||||
"GOSUMDB",
|
||||
"off",
|
||||
"--setenv",
|
||||
"GOMODCACHE",
|
||||
"/work/modules",
|
||||
"--setenv",
|
||||
"GOCACHE",
|
||||
"/work/gocache",
|
||||
"--setenv",
|
||||
"GOMAXPROCS",
|
||||
"4",
|
||||
"--setenv",
|
||||
"CGO_ENABLED",
|
||||
"0",
|
||||
"--setenv",
|
||||
"PUBLIC_PKCE_REPORT",
|
||||
"/work/public-pkce-report.json",
|
||||
"--",
|
||||
"go",
|
||||
"test",
|
||||
"-mod=mod",
|
||||
"-p",
|
||||
"4",
|
||||
"-json",
|
||||
"-count=1",
|
||||
"-run",
|
||||
"^TestUpstreamForgejoPublicPKCE$",
|
||||
"./internal/server/oidc"
|
||||
],
|
||||
"real_credentials": false,
|
||||
"network": "isolated loopback only",
|
||||
"live_changes": false,
|
||||
"profile_approved": false
|
||||
}
|
||||
|
|
@ -0,0 +1,8 @@
|
|||
{"Time":"2026-10-18T17:05:47.587707851Z","Action":"start","Package":"keycape/internal/server/oidc"}
|
||||
{"Time":"2026-10-18T17:05:47.593659497Z","Action":"run","Package":"keycape/internal/server/oidc","Test":"TestUpstreamForgejoPublicPKCE"}
|
||||
{"Time":"2026-10-18T17:05:47.593752356Z","Action":"output","Package":"keycape/internal/server/oidc","Test":"TestUpstreamForgejoPublicPKCE","Output":"=== RUN TestUpstreamForgejoPublicPKCE\n"}
|
||||
{"Time":"2026-10-18T17:05:47.770524191Z","Action":"output","Package":"keycape/internal/server/oidc","Test":"TestUpstreamForgejoPublicPKCE","Output":"--- PASS: TestUpstreamForgejoPublicPKCE (0.18s)\n"}
|
||||
{"Time":"2026-10-18T17:05:47.770589125Z","Action":"pass","Package":"keycape/internal/server/oidc","Test":"TestUpstreamForgejoPublicPKCE","Elapsed":0.18}
|
||||
{"Time":"2026-10-18T17:05:47.770608259Z","Action":"output","Package":"keycape/internal/server/oidc","Output":"PASS\n"}
|
||||
{"Time":"2026-10-18T17:05:47.770911599Z","Action":"output","Package":"keycape/internal/server/oidc","Output":"ok \tkeycape/internal/server/oidc\t0.182s\n"}
|
||||
{"Time":"2026-10-18T17:05:47.770938327Z","Action":"pass","Package":"keycape/internal/server/oidc","Elapsed":0.183}
|
||||
|
|
@ -0,0 +1,9 @@
|
|||
{
|
||||
"issuer_source_revision": "e2c5a947829568baf837543a6708fd0c3f6a863b",
|
||||
"issuer_go_mod_sha256": "66e219373666294d37395082700f6e62fa9a55b02bc996655883b07d04436b14",
|
||||
"upstream_archive_sha256": "1ddd8c926ca07d40436897971458ace7dcf1d3496a0813ab9e0d869edef2365c",
|
||||
"goth_source": "/tmp/custodian-forgejo-successor-attempt2/modules/github.com/markbates/goth@v1.80.0",
|
||||
"test_source_sha256": "8968c617d83bb041f0c2a4c251bf1efc88db45036f51c6eb55c5058bdb662aed",
|
||||
"candidate_profile": "Public PKCE build-mode candidate only; NOT approved; original confidential target unchanged",
|
||||
"live_changes": false
|
||||
}
|
||||
|
|
@ -0,0 +1,14 @@
|
|||
{
|
||||
"actual_https_discovery_token_and_userinfo": true,
|
||||
"code_replay_refused": true,
|
||||
"identity_matches": true,
|
||||
"live_changes": false,
|
||||
"native_signin_or_account_linking": false,
|
||||
"provider": "github.com/markbates/goth v1.80.0 (upstream Forgejo 11.0.3)",
|
||||
"public_client_no_secret": true,
|
||||
"status": "passed",
|
||||
"synthetic_human_mfa_session": true,
|
||||
"token_requests": 4,
|
||||
"userinfo_requests": 1,
|
||||
"wrong_pkce_refused": true
|
||||
}
|
||||
|
|
@ -0,0 +1,15 @@
|
|||
# PROPOSED, NOT APPLIED. Temporary public-PKCE development profile.
|
||||
# The confidential-client and full security acceptance target remains WP-82.
|
||||
gitea:
|
||||
oauth:
|
||||
- name: netkingdom
|
||||
provider: openidConnect
|
||||
key: forgejo
|
||||
secret: ""
|
||||
autoDiscoverUrl: https://kc.coulomb.social/.well-known/openid-configuration
|
||||
scopes: openid,profile,email,groups
|
||||
config:
|
||||
oauth2_client:
|
||||
ENABLE_AUTO_REGISTRATION: false
|
||||
ACCOUNT_LINKING: login
|
||||
OPENID_CONNECT_SCOPES: openid profile email groups
|
||||
|
|
@ -0,0 +1,30 @@
|
|||
{
|
||||
"before": {
|
||||
"resource_version": "75372162",
|
||||
"uid": "2e94519d-1550-41c7-9701-2efe47fe1fd3"
|
||||
},
|
||||
"change_needed": true,
|
||||
"changed": false,
|
||||
"check_config": {
|
||||
"live": {
|
||||
"clients": 13,
|
||||
"errors": [],
|
||||
"valid": true
|
||||
},
|
||||
"proposed": {
|
||||
"clients": 14,
|
||||
"errors": [],
|
||||
"valid": true
|
||||
}
|
||||
},
|
||||
"client_id": "forgejo",
|
||||
"client_type": "public",
|
||||
"deployment_env_bound": null,
|
||||
"mode": "dry-run",
|
||||
"operation": "add",
|
||||
"other_secret_data_unchanged": true,
|
||||
"schema": "netkingdom.keycape-client.v1",
|
||||
"target": "sso/keycape-config",
|
||||
"unrelated_config_bytes_preserved": true,
|
||||
"values_emitted": false
|
||||
}
|
||||
|
|
@ -0,0 +1,28 @@
|
|||
{
|
||||
"before": {
|
||||
"resource_version": "75372162",
|
||||
"uid": "2e94519d-1550-41c7-9701-2efe47fe1fd3"
|
||||
},
|
||||
"change_needed": true,
|
||||
"changed": false,
|
||||
"check_config": {
|
||||
"live": {
|
||||
"clients": 13,
|
||||
"errors": [],
|
||||
"valid": true
|
||||
},
|
||||
"proposed": {
|
||||
"clients": 14,
|
||||
"errors": [],
|
||||
"valid": true
|
||||
}
|
||||
},
|
||||
"client_id": "forgejo",
|
||||
"client_type": "public",
|
||||
"deployment_env_bound": null,
|
||||
"mode": "inspect",
|
||||
"operation": "add",
|
||||
"schema": "netkingdom.keycape-client.v1",
|
||||
"target": "sso/keycape-config",
|
||||
"values_emitted": false
|
||||
}
|
||||
|
|
@ -0,0 +1,92 @@
|
|||
package oidc_test
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/rand"
|
||||
"crypto/rsa"
|
||||
"crypto/sha256"
|
||||
"encoding/base64"
|
||||
"encoding/json"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"net/url"
|
||||
"os"
|
||||
"strings"
|
||||
"sync/atomic"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/markbates/goth/providers/openidConnect"
|
||||
"keycape/internal/domain"
|
||||
"keycape/internal/server/oidc"
|
||||
"keycape/internal/server/telemetry"
|
||||
)
|
||||
|
||||
type fixtureDirectory struct{ user domain.User }
|
||||
|
||||
func (d fixtureDirectory) LookupUser(context.Context, string) (*domain.User, error) { u := d.user; return &u, nil }
|
||||
func (d fixtureDirectory) LookupGroups(context.Context, string) ([]domain.Group, error) { return nil, nil }
|
||||
func (d fixtureDirectory) ListUsers(context.Context) ([]domain.User, error) { return []domain.User{d.user}, nil }
|
||||
func (d fixtureDirectory) ValidatePassword(context.Context, string, string) (bool, error) { return false, nil }
|
||||
|
||||
// Source compatibility proof only: actual producer token/UserInfo handlers and
|
||||
// the exact upstream Forgejo Goth version. Human/MFA authorization is a synthetic
|
||||
// session seed; this cannot count as deployed sign-in or account linking.
|
||||
func TestUpstreamForgejoPublicPKCE(t *testing.T) {
|
||||
key, err := rsa.GenerateKey(rand.Reader, 2048)
|
||||
if err != nil { t.Fatal("fixture signing key failed") }
|
||||
directory := fixtureDirectory{domain.User{ID: "uid=fixture,ou=people,dc=invalid", Username: "fixture", DisplayName: "Synthetic fixture", Email: "fixture@example.invalid", Enabled: true, Tenant: "tenant:fixture", Groups: []string{"forge-admins"}}}
|
||||
client := &domain.Client{ClientID: "forgejo", ClientType: "public", RedirectURIs: []string{"https://forgejo.invalid/user/oauth2/netkingdom/callback"}, AllowedScopes: []string{"openid", "profile", "email", "groups"}, GrantTypes: []string{"authorization_code"}}
|
||||
sessions := oidc.NewSessionStore()
|
||||
mux := http.NewServeMux()
|
||||
server := httptest.NewTLSServer(mux)
|
||||
defer server.Close()
|
||||
clients := map[string]*domain.Client{client.ClientID: client}
|
||||
tokens := &oidc.TokenHandler{ClientConfig: clients, Sessions: sessions, Users: directory, SigningKey: key, Issuer: server.URL, TokenLifetime: time.Minute, Emitter: telemetry.NoopEmitter{}}
|
||||
userinfo := &oidc.UserinfoHandler{Users: directory, SigningKey: &key.PublicKey, Issuer: server.URL, ClientConfig: clients, Emitter: telemetry.NoopEmitter{}}
|
||||
var tokenCalls, userinfoCalls atomic.Int64
|
||||
var secretPresented atomic.Bool
|
||||
mux.HandleFunc("/token", func(w http.ResponseWriter, r *http.Request) {
|
||||
tokenCalls.Add(1)
|
||||
_ = r.ParseForm()
|
||||
_, secret, _ := r.BasicAuth()
|
||||
if secret != "" || r.FormValue("client_secret") != "" { secretPresented.Store(true) }
|
||||
tokens.ServeHTTP(w, r)
|
||||
})
|
||||
mux.HandleFunc("/userinfo", func(w http.ResponseWriter, r *http.Request) { userinfoCalls.Add(1); userinfo.ServeHTTP(w, r) })
|
||||
mux.HandleFunc("/.well-known/openid-configuration", func(w http.ResponseWriter, r *http.Request) {
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
_ = json.NewEncoder(w).Encode(map[string]any{"issuer": server.URL, "authorization_endpoint": server.URL+"/authorize", "token_endpoint": server.URL+"/token", "userinfo_endpoint": server.URL+"/userinfo"})
|
||||
})
|
||||
previous := http.DefaultClient
|
||||
http.DefaultClient = server.Client()
|
||||
defer func(){ http.DefaultClient = previous }()
|
||||
provider, err := openidConnect.New(client.ClientID, "", client.RedirectURIs[0], server.URL+"/.well-known/openid-configuration", client.AllowedScopes...)
|
||||
if err != nil { t.Fatal("upstream provider discovery failed") }
|
||||
provider.HTTPClient = server.Client()
|
||||
session, err := provider.BeginAuth("synthetic-state")
|
||||
if err != nil { t.Fatal("upstream authorization URL failed") }
|
||||
authURL, _ := session.GetAuthURL()
|
||||
parsed, _ := url.Parse(authURL)
|
||||
if parsed.Query().Get("client_id") != client.ClientID || parsed.Query().Get("redirect_uri") != client.RedirectURIs[0] || parsed.Query().Get("state") != "synthetic-state" { t.Fatal("upstream authorization binding differs") }
|
||||
verifier := strings.Repeat("v", 43)
|
||||
digest := sha256.Sum256([]byte(verifier))
|
||||
issue := func() string {
|
||||
return sessions.Create(&oidc.PKCESession{ClientID: client.ClientID, Username: directory.user.Username, RedirectURI: client.RedirectURIs[0], PKCEChallengeMethod: "S256", PKCEChallenge: base64.RawURLEncoding.EncodeToString(digest[:]), Scopes: client.AllowedScopes, MFAVerified: true, AuthTime: time.Now().Add(-time.Minute), ExpiresAt: time.Now().Add(time.Minute)})
|
||||
}
|
||||
goodCode := issue()
|
||||
_, err = session.Authorize(provider, url.Values{"code": {goodCode}, "code_verifier": {verifier}})
|
||||
if err != nil { t.Fatal("public PKCE exchange failed") }
|
||||
identity, err := provider.FetchUser(session)
|
||||
if err != nil || identity.UserID != directory.user.ID || identity.NickName != directory.user.Username || identity.Email != directory.user.Email || identity.AccessToken == "" || identity.IDToken == "" { t.Fatal("upstream user identity did not match") }
|
||||
if secretPresented.Load() { t.Fatal("public client unexpectedly presented a secret") }
|
||||
badSession, _ := provider.BeginAuth("synthetic-state-bad")
|
||||
_, err = badSession.Authorize(provider, url.Values{"code": {issue()}, "code_verifier": {strings.Repeat("x",43)}})
|
||||
if err == nil { t.Fatal("wrong public PKCE verifier accepted") }
|
||||
replay, _ := provider.BeginAuth("synthetic-state-replay")
|
||||
_, err = replay.Authorize(provider, url.Values{"code": {goodCode}, "code_verifier": {verifier}})
|
||||
if err == nil { t.Fatal("used authorization code accepted") }
|
||||
report := map[string]any{"status":"passed", "provider":"github.com/markbates/goth v1.80.0 (upstream Forgejo 11.0.3)", "actual_https_discovery_token_and_userinfo":true, "public_client_no_secret":true, "identity_matches":true, "wrong_pkce_refused":true, "code_replay_refused":true, "token_requests":tokenCalls.Load(), "userinfo_requests":userinfoCalls.Load(), "synthetic_human_mfa_session":true, "native_signin_or_account_linking":false, "live_changes":false}
|
||||
raw, _ := json.MarshalIndent(report, "", " ")
|
||||
if err := os.WriteFile(os.Getenv("PUBLIC_PKCE_REPORT"), append(raw,'\n'), 0600); err != nil { t.Fatal("public report write failed") }
|
||||
}
|
||||
|
|
@ -0,0 +1,27 @@
|
|||
--- a/helm/forgejo-values.yaml
|
||||
+++ b/helm/forgejo-values.yaml
|
||||
@@ -84,6 +84,13 @@
|
||||
terminationMessagePolicy: File
|
||||
|
||||
gitea:
|
||||
+ oauth:
|
||||
+ - name: netkingdom
|
||||
+ provider: openidConnect
|
||||
+ key: forgejo
|
||||
+ secret: ""
|
||||
+ autoDiscoverUrl: https://kc.coulomb.social/.well-known/openid-configuration
|
||||
+ scopes: openid,profile,email,groups
|
||||
admin:
|
||||
username: forgejo_admin
|
||||
email: admin@coulomb.social
|
||||
@@ -98,6 +105,10 @@
|
||||
name: forgejo-mailer
|
||||
key: MAILER_PASSWD
|
||||
config:
|
||||
+ oauth2_client:
|
||||
+ ENABLE_AUTO_REGISTRATION: false
|
||||
+ ACCOUNT_LINKING: login
|
||||
+ OPENID_CONNECT_SCOPES: openid profile email groups
|
||||
database:
|
||||
DB_TYPE: postgres
|
||||
HOST: forgejo-db-rw.databases.svc.cluster.local:5432
|
||||
|
|
@ -0,0 +1,35 @@
|
|||
"""Reproduce the already owner-reviewed checker; no runtime inputs are read."""
|
||||
from pathlib import Path
|
||||
import hashlib
|
||||
import io
|
||||
import json
|
||||
import subprocess
|
||||
import tarfile
|
||||
import time
|
||||
|
||||
packet = Path(__file__).resolve().parent
|
||||
work = Path('/tmp/custodian-forgejo-public-checker-r1')
|
||||
work.mkdir(exist_ok=False)
|
||||
revision = '3c71226a1a7b8a2bdb710e9c497e1a1f7f8a3987'
|
||||
archive = subprocess.check_output(['git','-C','/home/worsch/key-cape','archive',revision,'src'])
|
||||
assert hashlib.sha256(archive).hexdigest() == '770a15933e6c45d4b94beb37d4322fd4ae1436d8193dfb7f5655bbc851f3fc73'
|
||||
with tarfile.open(fileobj=io.BytesIO(archive)) as tar:
|
||||
tar.extractall(work, filter='data')
|
||||
toolchain = Path('/home/worsch/go/pkg/mod/golang.org/toolchain@v0.0.1-go1.23.0.linux-amd64')
|
||||
env = {'PATH':str(toolchain/'bin')+':/usr/bin:/bin','TMPDIR':str(work),
|
||||
'GOTOOLCHAIN':'local','GOPROXY':'off','GOSUMDB':'off',
|
||||
'GOMODCACHE':'/home/worsch/go/pkg/mod','GOCACHE':str(work/'gocache'),
|
||||
'CGO_ENABLED':'0','GOOS':'linux','GOARCH':'amd64','GOAMD64':'v1',
|
||||
'GOFLAGS':'-mod=readonly'}
|
||||
command = [str(toolchain/'bin/go'),'build','-trimpath','-buildvcs=false','-o',str(work/'keycape'),'./cmd/keycape']
|
||||
start = time.monotonic()
|
||||
result = subprocess.run(command, cwd=work/'src', env=env, capture_output=True, timeout=240)
|
||||
(packet/'checker-build.log').write_bytes(result.stdout+result.stderr)
|
||||
assert result.returncode == 0, 'checker build failed; contained log retained'
|
||||
digest = hashlib.sha256((work/'keycape').read_bytes()).hexdigest()
|
||||
assert digest == 'be37738fe1c08259fb4f9ca0daf0f9301bfffc237e5bb38435525dfbf11ccff5'
|
||||
receipt = {'verified':True,'artifact':str(work/'keycape'),'sha256':digest,
|
||||
'source_revision':revision,'byte_identical_to_existing_owner_review':True,
|
||||
'seconds':round(time.monotonic()-start,3),'real_runtime_configuration_access':False}
|
||||
(packet/'checker-reproduction.json').write_text(json.dumps(receipt,indent=2)+'\n')
|
||||
print(json.dumps(receipt))
|
||||
|
|
@ -0,0 +1,17 @@
|
|||
{
|
||||
"before": {
|
||||
"resource_version": "75372162",
|
||||
"uid": "2e94519d-1550-41c7-9701-2efe47fe1fd3"
|
||||
},
|
||||
"change_needed": true,
|
||||
"changed": false,
|
||||
"check_config": null,
|
||||
"client_id": "forgejo",
|
||||
"client_type": "confidential",
|
||||
"deployment_env_bound": false,
|
||||
"mode": "inspect",
|
||||
"operation": "add",
|
||||
"schema": "netkingdom.keycape-client.v1",
|
||||
"target": "sso/keycape-config",
|
||||
"values_emitted": false
|
||||
}
|
||||
|
|
@ -0,0 +1,12 @@
|
|||
{
|
||||
"proposal_id": "CUST80-FORGEJO-PUBLIC-BUILD-20261018",
|
||||
"original_revision": 1,
|
||||
"original_sha256": "61eff8b488674e75510133fe118de86d895be04c033e109c3597c03ed2ecf30e",
|
||||
"original_local_receipt": "/tmp/custodian-forgejo-public-proposal-r1/proposal.json",
|
||||
"current_revision": 2,
|
||||
"current_sha256": "fcea2cd32b8ee2634d2ebeb9ef4b659b6b85f8a4878413ed96e5d06cb09892c3",
|
||||
"reason": "The unchanged scanner classified two public source SHA-256 hashes as generic API keys when filenames were used as JSON keys. Represent identical source pins as typed path/sha256 records. No profile, command, target, source hash, verification or rollback changed.",
|
||||
"original_bytes_reconstruction": "Load proposal.json preserving order; set revision to1; convert source_pins records to an ordered path:sha256 mapping; serialize json.dumps(indent=2) plus newline. Assert original_sha256. The exact original bytes are reconstructible from committed public data.",
|
||||
"scanner_rules_or_exemptions_changed": false,
|
||||
"native_effects": false
|
||||
}
|
||||
|
|
@ -0,0 +1,41 @@
|
|||
"""Run the source-only upstream-provider/KeyCape compatibility check offline."""
|
||||
from pathlib import Path
|
||||
import argparse
|
||||
import json
|
||||
import subprocess
|
||||
import time
|
||||
|
||||
packet = Path(__file__).resolve().parent
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument('--attempt', required=True, type=int)
|
||||
args = parser.parse_args()
|
||||
stem = packet / f'protocol-attempt{args.attempt}'
|
||||
work = Path('/tmp/custodian-forgejo-public-pkce-r1')
|
||||
cache_a = '/home/worsch/go/pkg/mod'
|
||||
cache_b = '/tmp/custodian-forgejo-successor-attempt2/modules'
|
||||
argv = ['bwrap', '--unshare-net', '--unshare-pid', '--die-with-parent', '--new-session']
|
||||
for path in ['/usr', '/lib', '/lib64', cache_a]:
|
||||
argv += ['--ro-bind', path, path]
|
||||
argv += ['--symlink', 'usr/bin', '/bin', '--proc', '/proc', '--dev', '/dev',
|
||||
'--tmpfs', '/tmp', '--ro-bind', cache_b, cache_b,
|
||||
'--bind', str(work), '/work',
|
||||
'--ro-bind', '/tmp/custodian-forgejo-successor/go', '/toolchain',
|
||||
'--chdir', '/work/source/src', '--clearenv']
|
||||
for key, value in {'PATH':'/toolchain/bin:/usr/bin:/bin', 'GOTOOLCHAIN':'local',
|
||||
'GOPROXY':'off', 'GOSUMDB':'off', 'GOMODCACHE':'/work/modules',
|
||||
'GOCACHE':'/work/gocache', 'GOMAXPROCS':'4', 'CGO_ENABLED':'0',
|
||||
'PUBLIC_PKCE_REPORT':'/work/public-pkce-report.json'}.items():
|
||||
argv += ['--setenv', key, value]
|
||||
argv += ['--', 'go', 'test', '-mod=mod', '-p', '4', '-json', '-count=1',
|
||||
'-run', '^TestUpstreamForgejoPublicPKCE$', './internal/server/oidc']
|
||||
start = time.monotonic()
|
||||
with stem.with_suffix('.jsonl').open('xb') as output:
|
||||
result = subprocess.run(argv, stdout=output, stderr=subprocess.STDOUT, timeout=240)
|
||||
receipt = {'exit_code':result.returncode, 'seconds':round(time.monotonic()-start, 3),
|
||||
'argv':argv, 'real_credentials':False, 'network':'isolated loopback only',
|
||||
'live_changes':False, 'profile_approved':False}
|
||||
stem.with_suffix('.json').write_text(json.dumps(receipt, indent=2)+'\n')
|
||||
if result.returncode == 0:
|
||||
(packet/'protocol-result.json').write_bytes((work/'public-pkce-report.json').read_bytes())
|
||||
print(json.dumps({key:receipt[key] for key in ['exit_code','seconds','live_changes','profile_approved']}))
|
||||
raise SystemExit(result.returncode)
|
||||
|
|
@ -0,0 +1,39 @@
|
|||
{
|
||||
"image": "code.forgejo.org/forgejo/forgejo@sha256:41649dff5311664097d29b4d80620403f0b27e7c0eee0b8af79b132a37f3807b",
|
||||
"profile_approved": false,
|
||||
"production_mutation": false,
|
||||
"database": "disposable local SQLite only",
|
||||
"commands": [
|
||||
{
|
||||
"name": "migrate",
|
||||
"exit_code": 0
|
||||
},
|
||||
{
|
||||
"name": "add-oauth",
|
||||
"exit_code": 0
|
||||
}
|
||||
],
|
||||
"netkingdom_option_visible": true,
|
||||
"local_signin_form_preserved": true,
|
||||
"authorization_start": {
|
||||
"http_status": 307,
|
||||
"issuer_host": "kc.coulomb.social",
|
||||
"path": "/authorize",
|
||||
"client_id": [
|
||||
"forgejo"
|
||||
],
|
||||
"callback": [
|
||||
"http://127.0.0.1:18092/user/oauth2/netkingdom/callback"
|
||||
],
|
||||
"scope": [
|
||||
"openid profile email groups"
|
||||
],
|
||||
"pkce_method": [
|
||||
"S256"
|
||||
],
|
||||
"pkce_challenge_present": true,
|
||||
"state_present": true
|
||||
},
|
||||
"passed": true,
|
||||
"local_container_stopped": true
|
||||
}
|
||||
|
|
@ -32,3 +32,4 @@ Add a row whenever you loosen, shortcut or leave unarmed a control. Remove nothi
|
|||
| 23 | Traefik HTTPS upload read budget | Activated 2026-10-13 under the founder-approved publication fix: shared websecure readTimeout raised from 60s to bounded 600s. Traefik generation 2 Ready; five previously failing Forgejo OCI blobs completed in 85–183s, exact tested image digest published. Evidence: railiance-cluster RCLUSTER-WP-0016 and rapp-playwright/docs/evidence/2026-10-13-publication.md. TLS and registry authentication stay enforced. | At production close-down review the 600s body-read budget and slow-upload exposure; retain a measured artifact-upload budget or isolate the registry entry point. Do not disable timeouts. | railiance-cluster / railiance-forge | 2026-10-13 |
|
||||
| 24 | Metered spend settlement (rein-aharness spend envelopes) | Opt-in per envelope since 2026-10-14 (rein-aharness `c7dceee`, `c04d1c6`; owner decision `a93619a9`): `settlement: observed-usage` charges a completed provider request its gateway-metered cost (provider-reported tokens at admitted rates) instead of its full hold, and a run max(metered requests, reported/attested cost). Unfinished requests keep full hold; overrun still freezes the envelope; `full-hold` stays the default. First use: memo `infd-20261014-b01` (3 attempts, EUR 10). Provider-reported usage is trusted until billing is compared. | Compare each observed-usage envelope's charges with provider billing before renewal; decide whether observed-usage stays allowed or envelopes return to full-hold. | rein-aharness, owner | 2026-10-14 |
|
||||
| 25 | WP-80 functional delivery before full security acceptance | Founder decision `9663d4ca-3793-4ad7-9dc1-954001d43596` prioritizes working development flows and permits later hardening. Actual Playwright human/agent commands pass through the existing authenticated workstation port-forward; this is a supervised build profile, without independent per-caller identity acceptance. No credentials distributed, new target enabled or installed control disabled. Nineteen full checkpoints and six remaining acceptance scopes transfer to live CUST-WP-0082; ledger statuses and the full target stay unchanged. [Scope and evidence](changes/netkingdom-target-2026-10-18/wp80-build-mode-r1/README.md). | Promote/revalidate relevant hardening before customer production, target expansion or broader unattended authority. Verify the full current-identity, per-caller transport/isolation, lifecycle, recovery and audit criteria in CUST-WP-0082; close all 16 target gates and reconcile all 116 ledger entries. No automatic production approval or blanket formal requirement exception. | Custodian coordinates; existing KeyCape / Platform / NetKingdom / Forge / Apps / custody and audit owners implement | 2026-10-18 |
|
||||
| 26 | Proposed temporary Forgejo public-PKCE build profile | Not activated. Proposal `CUST80-FORGEJO-PUBLIC-BUILD-20261018` r2, SHA `fcea2cd32b8ee2634d2ebeb9ef4b659b6b85f8a4878413ed96e5d06cb09892c3`, awaits actual founder decision `ad88edeb-7c7c-486f-a695-1a7abae5b20c` and independent NetKingdom/Apps/KeyCape reviews. Candidate uses the unchanged Forgejo release, exact callback, S256 and required MFA, preserving local sign-in and existing account permissions. It intentionally defers the accepted confidential-client secret/delivery/full case lifecycle; no value is generated or distributed and no current control is disabled by preparation. [Packet](changes/netkingdom-target-2026-10-18/forgejo-good-case-preparation-r1/README.md). | If activated, complete the confidential-client registration and issuer/consumer custody path plus original full security acceptance under CUST-WP-0082-T20 and existing owners when promoted; revalidate before customer production or broader unattended authority. Record actual approval/execution and preserve rollback/unknown outcomes. | Custodian coordinates; NetKingdom, railiance-apps, key-cape; Platform owns later confidential custody | 2026-10-18 |
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue