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}