code wiki / _hdl_build / nx_gen_gateway_gate.nx
nx_gen_gateway_gate.nx source
↩ module page · 262 lines · 16886 B
1// nx_gen_gateway_gate.nx -- R4: the OPAQUE /gen gateway wired to the gen orchestrator, proven
2// end-to-end over real loopback sockets. Parent binds 3 listening sockets (gateway/orchestrator/
3// worker) then forks: a mock GPU worker, the orchestrator (go_serve_conn), and the gateway
4// (gw_serve_conn, backend=orchestrator, family realm nishi_site_admin, small argon). Then as the
5// HTTP client it proves:
6// (1) GET /gen/ with NO session -> 401 (never a public byte; the cardinal rule)
7// (2) POST /gen/auth/register (handle=elderwesto) -> 200 + recovery mnemonic
8// (3) POST /gen/auth/login -> 200 + session token
9// (4) GET /gen/ WITH the token -> 200 + the Elder AI gen UI (proxied from the orchestrator)
10// (5) POST /gen/api/generate WITH the token -> 200 + CIDs (gateway -> orchestrator -> worker)
11// license_tier: ORIGINAL expect_exit: 0
12import "nx_gen_gateway.nx" // gw_serve_conn + GGW_* + helpers
13import "nx_gen_orchestrator.nx" // go_serve_conn
14import "nx_opaque_login.nx" // olg_ctx_setup_ttl + NxAuthContext
15import "nx_hr_entitle.nx" // he_ent_put + he_has_access_realm (the /gen authz)
16import "nx_hr_admin.nx" // hra_enroll + HRA_LVL_OWNER (enroll the test owner in the HR store)
17import "nx_syscalls.nx"
18
19import "nx_connect.nx" // bounded connect: a raw sys_connect hangs ~127s on a black-holed host
20func ig(s: *u8) -> i64 { var n: i64=0; while s[n]!=(0 as u8){n=n+1} sys_write(1,s,n); return 0 }
21func ig_n(v: i64) -> i64 { var m: i64=v; if m<0{ig("-" as *u8);m=0-m} let t:*u8=sys_mmap(24); var k:i64=0; if m==0{t[0]=48 as u8;k=1} while m>0{t[k]=(48+(m%10)) as u8;m=m/10;k=k+1} var i:i64=0; let o:*u8=sys_mmap(24); while i<k{o[i]=t[k-1-i];i=i+1} sys_write(1,o,k); return 0 }
22func ig_row(id: *u8, ok: i64, pass: *i64) -> i64 { ig(" " as *u8); ig(id); ig(": " as *u8); if ok==1 { ig("OK\n" as *u8); pass[0]=pass[0]+1 } else { ig("FAIL\n" as *u8) } return 0 }
23func ig_cat(dst: *u8, off: i64, s: *u8) -> i64 { var o: i64=off; var i: i64=0; while s[i]!=(0 as u8){dst[o]=s[i]; o=o+1; i=i+1} return o }
24func ig_catb(dst: *u8, off: i64, src: *u8, n: i64) -> i64 { var o: i64=off; var i: i64=0; while i<n {dst[o]=src[i]; o=o+1; i=i+1} return o }
25func ig_itoa(dst: *u8, off: i64, v: i64) -> i64 { let t: *u8=sys_mmap(28); var m: i64=v; var k: i64=0; if m==0{t[0]=48 as u8;k=1} while m>0{t[k]=(48+(m%10)) as u8;m=m/10;k=k+1} var o: i64=off; var q: i64=k-1; while q>=0{dst[o]=t[q];o=o+1;q=q-1} return o }
26func ig_find(buf: *u8, n: i64, needle: *u8) -> i64 { var nl: i64=0; while needle[nl]!=(0 as u8){nl=nl+1} if nl==0 {return 0} var i: i64=0; while i+nl<=n { var j: i64=0; var ok: i64=1; while j<nl { if (buf[i+j]&0xff)!=(needle[j]&0xff){ok=0;j=nl} else {j=j+1} } if ok==1 {return i} i=i+1 } return 0-1 }
27func ig_has(buf: *u8, n: i64, needle: *u8) -> i64 { if ig_find(buf,n,needle)>=0 {return 1} return 0 }
28func ig_nap(ms: i64) -> i64 { let ts: *i64 = sys_mmap(16) as *i64; ts[0]=0; ts[1]=ms*1000000; __syscall(35, ts as i64, 0, 0, 0, 0, 0); return 0 }
29func ig_truncate(path: *u8) -> i64 { let fd: i64 = sys_openat_wr(path, 0x1a4); if fd>=0 {sys_close(fd)} return 0 }
30func ig_freshns(prefix: *u8) -> i64 {
31 let mfn: *u8 = sys_mmap(256); var mo: i64=0
32 while (prefix[mo]&0xff)!=0 { mfn[mo]=prefix[mo]; mo=mo+1 }
33 let suf: *u8 = "manifest.txt" as *u8; var so: i64=0
34 while (suf[so]&0xff)!=0 { mfn[mo+so]=suf[so]; so=so+1 }
35 mfn[mo+so]=0 as u8
36 let fd: i64 = sys_openat_wr(mfn, 0x1a4); if fd>=0 {sys_close(fd)}
37 return 0
38}
39
40func ig_basepng(b: *u8) -> i64 {
41 b[0]=137 as u8; b[1]=80 as u8; b[2]=78 as u8; b[3]=71 as u8
42 b[4]=13 as u8; b[5]=10 as u8; b[6]=26 as u8; b[7]=10 as u8
43 b[8]=0 as u8; b[9]=0 as u8; b[10]=0 as u8; b[11]=14 as u8
44 b[12]=73 as u8; b[13]=72 as u8; b[14]=68 as u8; b[15]=82 as u8
45 b[16]=0 as u8; b[17]=0 as u8; b[18]=0 as u8; b[19]=0 as u8; b[20]=1 as u8
46 b[21]=0 as u8; b[22]=0 as u8; b[23]=0 as u8; b[24]=1 as u8; b[25]=8 as u8
47 b[26]=6 as u8; b[27]=0 as u8; b[28]=0 as u8; b[29]=0 as u8
48 b[30]=115 as u8; b[31]=22 as u8; b[32]=206 as u8; b[33]=242 as u8
49 b[34]=0 as u8; b[35]=0 as u8; b[36]=0 as u8; b[37]=0 as u8
50 b[38]=73 as u8; b[39]=69 as u8; b[40]=78 as u8; b[41]=68 as u8
51 b[42]=174 as u8; b[43]=66 as u8; b[44]=96 as u8; b[45]=130 as u8
52 return 46
53}
54func ig_enc(v: i64) -> i64 { let x: i64=v&0x3f; if x<26 {return 65+x} if x<52 {return 97+(x-26)} if x<62 {return 48+(x-52)} if x==62 {return 43} return 47 }
55func ig_b64encode(inp: *u8, n: i64, out: *u8) -> i64 {
56 var i: i64 = 0; var o: i64 = 0
57 while i + 3 <= n {
58 let b0: i64=inp[i]&0xff; let b1: i64=inp[i+1]&0xff; let b2: i64=inp[i+2]&0xff
59 out[o]=ig_enc(b0>>2) as u8; out[o+1]=ig_enc(((b0<<4)|(b1>>4))&0x3f) as u8; out[o+2]=ig_enc(((b1<<2)|(b2>>6))&0x3f) as u8; out[o+3]=ig_enc(b2&0x3f) as u8
60 o=o+4; i=i+3
61 }
62 let rem: i64 = n - i
63 if rem == 1 { let b0: i64=inp[i]&0xff; out[o]=ig_enc(b0>>2) as u8; out[o+1]=ig_enc((b0<<4)&0x3f) as u8; out[o+2]=61 as u8; out[o+3]=61 as u8; o=o+4 }
64 if rem == 2 { let b0: i64=inp[i]&0xff; let b1: i64=inp[i+1]&0xff; out[o]=ig_enc(b0>>2) as u8; out[o+1]=ig_enc(((b0<<4)|(b1>>4))&0x3f) as u8; out[o+2]=ig_enc((b1<<2)&0x3f) as u8; out[o+3]=61 as u8; o=o+4 }
65 return o
66}
67
68func ig_listen(port: i64) -> i64 {
69 let lfd: i64 = sys_socket(2,1,0); if lfd<0 {return 0-1}
70 let optv: *u8 = sys_mmap(4); optv[0]=1 as u8; sys_setsockopt(lfd,1,2,optv,4)
71 let addr: *u8 = sys_mmap(16)
72 addr[0]=2 as u8; addr[1]=0 as u8; addr[2]=((port>>8)&0xff) as u8; addr[3]=(port&0xff) as u8
73 addr[4]=127 as u8; addr[5]=0 as u8; addr[6]=0 as u8; addr[7]=1 as u8
74 var zi: i64=8; while zi<16 {addr[zi]=0 as u8; zi=zi+1}
75 if sys_bind(lfd, addr, 16) < 0 { return 0-1 }
76 if sys_listen(lfd, 16) < 0 { return 0-1 }
77 return lfd
78}
79// connect 127.0.0.1:port (retry), send raw, read until close.
80func ig_send(port: i64, raw: *u8, rawlen: i64, resp: *u8, cap: i64) -> i64 {
81 var tries: i64 = 0
82 while tries < 300 {
83 let fd: i64 = sys_socket(2,1,0)
84 if fd >= 0 {
85 let tv: *u8 = sys_mmap(16); tv[0]=15 as u8; var tz: i64=1; while tz<16 {tv[tz]=0 as u8; tz=tz+1}
86 sys_setsockopt(fd, 1, 20, tv, 16)
87 let addr: *u8 = sys_mmap(16)
88 addr[0]=2 as u8; addr[1]=0 as u8; addr[2]=((port>>8)&0xff) as u8; addr[3]=(port&0xff) as u8
89 addr[4]=127 as u8; addr[5]=0 as u8; addr[6]=0 as u8; addr[7]=1 as u8
90 var z: i64=8; while z<16 {addr[z]=0 as u8; z=z+1}
91 if nx_connect_bounded(fd, addr, 16, NX_CONN_DEFAULT_MS) == 0 {
92 sys_write(fd, raw, rawlen)
93 var rn: i64 = 0; var g: i64 = 1
94 while g==1 { let r: i64 = sys_read(fd, (resp as i64+rn) as *u8, cap-rn); if r<=0 {g=0} else {rn=rn+r; if rn>=cap {g=0}} }
95 sys_close(fd)
96 return rn
97 }
98 sys_close(fd)
99 }
100 ig_nap(15)
101 tries = tries + 1
102 }
103 return 0 - 1
104}
105
106func main() -> i64 {
107 let pass: *i64 = sys_mmap(8) as *i64; pass[0]=0
108 ig("=== NX-GEN-GATEWAY GATE (OPAQUE /gen gateway -> orchestrator -> worker, real loopback) ===\n" as *u8)
109 let png: *u8 = sys_mmap(256); let plen: i64 = ig_basepng(png)
110 let b64: *u8 = sys_mmap(512); let b64len: i64 = ig_b64encode(png, plen, b64)
111
112 let WPORT: i64 = 39301
113 let OPORT: i64 = 39302
114 let GPORT: i64 = 39303
115
116 ig_freshns("/tmp/gengw-" as *u8); ig_truncate("/tmp/gengw_cids.tsv" as *u8)
117 ig_truncate("/tmp/gengw_keys.bin" as *u8); ig_truncate("/tmp/gengw_store.log" as *u8)
118 // test authz (OWNER-ONLY -- no "*" bypass): empty entitlement store + enroll ONLY "elderwesto" as OWNER in the HR
119 // store. This exercises the EXACT live path the gateway uses: hex-encode the whoami uid -> hra_is_superadmin(hr,cidhex).
120 // Proves the enrolled owner -> 200, AND a registered-but-UNENROLLED user -> 403 (the precise bug elderwesto hit live).
121 ig_freshns("/tmp/gengw_ent-" as *u8); ig_freshns("/tmp/gengw_hr-" as *u8)
122 let cido: *u8 = sys_mmap(96)
123 hra_enroll("/tmp/gengw_hr-" as *u8, "nishi_site_admin" as *u8, 16, "elderwesto" as *u8, 10, HRA_LVL_OWNER, "nishi" as *u8, 1000, "system" as *u8, cido)
124
125 // orchestrator config: worker on loopback WPORT
126 let cfg: *i64 = sys_mmap(8*16) as *i64
127 cfg[0]=127; cfg[1]=0; cfg[2]=0; cfg[3]=1; cfg[4]=WPORT
128 let sp_store: *u8 = "/tmp/gengw-" as *u8
129 let sp_blob: *u8 = "/tmp/gengwblob-" as *u8
130 let sp_side: *u8 = "/tmp/gengw_cids.tsv" as *u8
131 let sp_host: *u8 = "laptop" as *u8
132 cfg[5]=sp_store as i64; cfg[6]=sp_blob as i64; cfg[7]=sp_side as i64; cfg[8]=sp_host as i64
133
134 // OPAQUE auth context (small argon, family realm) -- set up BEFORE fork so children inherit it.
135 let ctx: *NxAuthContext = sys_mmap(256) as *NxAuthContext
136 if olg_ctx_setup_ttl(ctx, "/tmp/gengw_keys.bin" as *u8, "/tmp/gengw_store.log" as *u8, "nishi_site_admin" as *u8, 16, "Nishi Family" as *u8, 12, 3600, 512, 3, 1) != 0 {
137 ig("CTX-INIT-FAIL\n" as *u8); sys_exit(1); return 1
138 }
139
140 let wfd: i64 = ig_listen(WPORT); if wfd<0 { ig("FAIL worker listen\n" as *u8); sys_exit(1); return 1 }
141 let ofd: i64 = ig_listen(OPORT); if ofd<0 { ig("FAIL orch listen\n" as *u8); sys_exit(1); return 1 }
142 let gfd: i64 = ig_listen(GPORT); if gfd<0 { ig("FAIL gw listen\n" as *u8); sys_exit(1); return 1 }
143
144 // mock GPU worker (serve 1)
145 let pidW: i64 = sys_fork()
146 if pidW == 0 {
147 sys_close(ofd); sys_close(gfd)
148 let c: i64 = sys_accept(wfd)
149 if c >= 0 {
150 let rb: *u8 = sys_mmap(16384); sys_read(c, rb, 16384)
151 let bod: *u8 = sys_mmap(4096); var bo: i64 = 0
152 bo = ig_cat(bod, bo, "{\"data\":[{\"b64_json\":\"" as *u8); bo = ig_catb(bod, bo, b64, b64len); bo = ig_cat(bod, bo, "\"}]}" as *u8)
153 let resp: *u8 = sys_mmap(8192); var o: i64 = 0
154 o = ig_cat(resp, o, "HTTP/1.1 200 OK\r\nContent-Type: application/json\r\nConnection: close\r\nContent-Length: " as *u8)
155 o = ig_itoa(resp, o, bo); o = ig_cat(resp, o, "\r\n\r\n" as *u8); o = ig_catb(resp, o, bod, bo)
156 sys_write(c, resp, o); sys_close(c)
157 }
158 sys_exit(0)
159 }
160 // orchestrator (serve 2: proxied UI + proxied generate)
161 let pidO: i64 = sys_fork()
162 if pidO == 0 {
163 sys_close(wfd); sys_close(gfd)
164 var i: i64 = 0
165 while i < 2 { let c: i64 = sys_accept(ofd); if c>=0 { go_serve_conn(c, cfg); sys_close(c) } i=i+1 }
166 sys_exit(0)
167 }
168 // gateway (serve 5: 401, register, login, proxied UI, proxied generate)
169 let pidG: i64 = sys_fork()
170 if pidG == 0 {
171 sys_close(wfd); sys_close(ofd)
172 var i: i64 = 0
173 while i < 8 { let c: i64 = sys_accept(gfd); if c>=0 { gw_serve_conn(c, ctx, 1, OPORT, 3600, "/tmp/gengw_hr-" as *u8, "/tmp/gengw_ent-" as *u8); sys_close(c) } i=i+1 }
174 sys_exit(0)
175 }
176 sys_close(wfd); sys_close(ofd); sys_close(gfd)
177
178 let raw: *u8 = sys_mmap(8192)
179 let resp: *u8 = sys_mmap(262144)
180
181 // (1) no-session GET /gen/ -> 401
182 var rl: i64 = ig_cat(raw, 0, "GET /gen/ HTTP/1.0\r\nConnection: close\r\n\r\n" as *u8)
183 let n1: i64 = ig_send(GPORT, raw, rl, resp, 262144)
184 let unauth_401: i64 = (ig_has(resp, n1, "401" as *u8)==1) as i64
185
186 // (2) register elderwesto
187 let rb: *u8 = "handle=elderwesto&pw=foxtest12" as *u8
188 var rbl: i64 = 0; while rb[rbl]!=(0 as u8){rbl=rbl+1}
189 rl = ig_cat(raw, 0, "POST /gen/auth/register HTTP/1.0\r\nContent-Type: application/x-www-form-urlencoded\r\nConnection: close\r\nContent-Length: " as *u8)
190 rl = ig_itoa(raw, rl, rbl); rl = ig_cat(raw, rl, "\r\n\r\n" as *u8); rl = ig_catb(raw, rl, rb, rbl)
191 let n2: i64 = ig_send(GPORT, raw, rl, resp, 262144)
192 let reg_ok: i64 = ((ig_has(resp, n2, "200 OK" as *u8)==1) as i64) & ((ig_has(resp, n2, "mnemonic" as *u8)==1) as i64)
193
194 // (3) login -> token
195 rl = ig_cat(raw, 0, "POST /gen/auth/login HTTP/1.0\r\nContent-Type: application/x-www-form-urlencoded\r\nConnection: close\r\nContent-Length: " as *u8)
196 rl = ig_itoa(raw, rl, rbl); rl = ig_cat(raw, rl, "\r\n\r\n" as *u8); rl = ig_catb(raw, rl, rb, rbl)
197 let n3: i64 = ig_send(GPORT, raw, rl, resp, 262144)
198 let login_ok: i64 = ((ig_has(resp, n3, "200 OK" as *u8)==1) as i64) & ((ig_has(resp, n3, "token" as *u8)==1) as i64)
199 // extract token between "token":" and the next "
200 let tok: *u8 = sys_mmap(1024); var tlen: i64 = 0
201 let tp: i64 = ig_find(resp, n3, "\"token\":\"" as *u8)
202 if tp >= 0 {
203 var i: i64 = tp + 9; var s: i64 = 0
204 while s==0 { if i>=n3 {s=1} else { if (resp[i]&0xff)==34 {s=1} else { tok[tlen]=resp[i]; tlen=tlen+1; i=i+1 } } }
205 }
206 tok[tlen]=0 as u8
207
208 // (4) authed GET /gen/ -> proxied UI
209 rl = ig_cat(raw, 0, "GET /gen/ HTTP/1.0\r\nX-Nishi-Session: " as *u8); rl = ig_catb(raw, rl, tok, tlen)
210 rl = ig_cat(raw, rl, "\r\nConnection: close\r\n\r\n" as *u8)
211 let n4: i64 = ig_send(GPORT, raw, rl, resp, 262144)
212 let proxy_ui: i64 = ((ig_has(resp, n4, "200 OK" as *u8)==1) as i64) & ((ig_has(resp, n4, "Elder AI" as *u8)==1) as i64)
213
214 // (5) authed POST /gen/api/generate -> proxied full chain
215 let gb: *u8 = "{\"prompt\":\"a fox\",\"width\":256,\"height\":256,\"steps\":8,\"cfg\":\"1.0\",\"seed\":7,\"count\":1,\"sampler\":\"euler\"}" as *u8
216 var gbl: i64 = 0; while gb[gbl]!=(0 as u8){gbl=gbl+1}
217 rl = ig_cat(raw, 0, "POST /gen/api/generate HTTP/1.0\r\nX-Nishi-Session: " as *u8); rl = ig_catb(raw, rl, tok, tlen)
218 rl = ig_cat(raw, rl, "\r\nContent-Type: application/json\r\nConnection: close\r\nContent-Length: " as *u8)
219 rl = ig_itoa(raw, rl, gbl); rl = ig_cat(raw, rl, "\r\n\r\n" as *u8); rl = ig_catb(raw, rl, gb, gbl)
220 let n5: i64 = ig_send(GPORT, raw, rl, resp, 262144)
221 let proxy_gen: i64 = ((ig_has(resp, n5, "200 OK" as *u8)==1) as i64) & ((ig_has(resp, n5, "\"cids\":[\"" as *u8)==1) as i64)
222
223 // (6) register a SECOND user "intruder" -- a valid account but NOT enrolled in the HR store
224 let ib: *u8 = "handle=intruder&pw=foxtest12" as *u8
225 var ibl: i64 = 0; while ib[ibl]!=(0 as u8){ibl=ibl+1}
226 rl = ig_cat(raw, 0, "POST /gen/auth/register HTTP/1.0\r\nContent-Type: application/x-www-form-urlencoded\r\nConnection: close\r\nContent-Length: " as *u8)
227 rl = ig_itoa(raw, rl, ibl); rl = ig_cat(raw, rl, "\r\n\r\n" as *u8); rl = ig_catb(raw, rl, ib, ibl)
228 let n6: i64 = ig_send(GPORT, raw, rl, resp, 262144)
229 // (7) login intruder -> token2
230 rl = ig_cat(raw, 0, "POST /gen/auth/login HTTP/1.0\r\nContent-Type: application/x-www-form-urlencoded\r\nConnection: close\r\nContent-Length: " as *u8)
231 rl = ig_itoa(raw, rl, ibl); rl = ig_cat(raw, rl, "\r\n\r\n" as *u8); rl = ig_catb(raw, rl, ib, ibl)
232 let n7: i64 = ig_send(GPORT, raw, rl, resp, 262144)
233 let tok2: *u8 = sys_mmap(1024); var tlen2: i64 = 0
234 let tp2: i64 = ig_find(resp, n7, "\"token\":\"" as *u8)
235 if tp2 >= 0 {
236 var i2: i64 = tp2 + 9; var s2: i64 = 0
237 while s2==0 { if i2>=n7 {s2=1} else { if (resp[i2]&0xff)==34 {s2=1} else { tok2[tlen2]=resp[i2]; tlen2=tlen2+1; i2=i2+1 } } }
238 }
239 tok2[tlen2]=0 as u8
240 // (8) authed GET /gen/ as the NON-enrolled intruder -> 403 (owner-only; the superadmin gate denies)
241 rl = ig_cat(raw, 0, "GET /gen/ HTTP/1.0\r\nX-Nishi-Session: " as *u8); rl = ig_catb(raw, rl, tok2, tlen2)
242 rl = ig_cat(raw, rl, "\r\nConnection: close\r\n\r\n" as *u8)
243 let n8: i64 = ig_send(GPORT, raw, rl, resp, 262144)
244 let intruder_403: i64 = (ig_has(resp, n8, "403" as *u8)==1) as i64
245
246 __syscall(129, pidW, 9, 0, 0, 0, 0) // rv64 kill=129 -- raw x86 62 is an RV64 KEY the backend translated to lseek(8), so these SIGKILLs never happened (debt idx 2277)
247 __syscall(129, pidO, 9, 0, 0, 0, 0)
248 __syscall(129, pidG, 9, 0, 0, 0, 0)
249 let st: *i64 = sys_mmap(8) as *i64; var rr: i64 = 0; while rr < 30 { sys_wait4(0 - 1, st, 1); rr = rr + 1 }
250
251 ig(" bytes: 401="); ig_n(n1); ig(" reg="); ig_n(n2); ig(" login="); ig_n(n3); ig(" ui="); ig_n(n4); ig(" gen="); ig_n(n5); ig(" intr="); ig_n(n8); ig("\n" as *u8)
252 ig_row("no-session GET /gen/ -> 401 (cardinal: never a public byte)" as *u8, unauth_401, pass)
253 ig_row("POST /gen/auth/register -> 200 + recovery mnemonic (OPAQUE, family realm)" as *u8, reg_ok, pass)
254 ig_row("POST /gen/auth/login -> 200 + session token" as *u8, login_ok, pass)
255 ig_row("authed GET /gen/ as ENROLLED OWNER -> 200 + Elder AI UI (the superadmin path; proxied)" as *u8, proxy_ui, pass)
256 ig_row("authed POST /gen/api/generate (owner) -> 200 + CIDs (gateway->orchestrator->worker)" as *u8, proxy_gen, pass)
257 ig_row("authed GET /gen/ as NON-enrolled user -> 403 (owner-only; the superadmin gate denies)" as *u8, intruder_403, pass)
258
259 ig("GEN-GATEWAY-GATE rows=6 pass="); ig_n(pass[0])
260 if pass[0]==6 { ig(" verdict=GREEN\n" as *u8); sys_exit(0); return 0 }
261 ig(" verdict=RED\n" as *u8); sys_exit(1); return 1
262}